赞
踩
目录
在 Linux 中,有一句经典的话叫做:一切皆文件。这句话是站在内核的角度说的,因为在内核中所有的设备 (除了网络接口) 都一律使用 Linux 独有的虚拟文件系统 (VFS) 来管理。这样做的最终目的,是将各种不同的设备用“文件”这个概念加以封装和屏蔽,简化应用层编程的难度。文件,是 Linux 系统最重要的两个抽象概念之一 (另一个是进程) 。
在 Linux 中,文件总共被分成了 7 种,他们分别是:
1,普通文件 (regular) :存在于外部存储器中,用于存储普通数据。
2, 目录文件 (directory) :用于存放目录项,是文件系统管理的重要文件类型。 3,管道文件 (pipe) :一种用于进程间通信的特殊文件,也称为命名管道 FIFO。 4,套接字文件 (socket) :一种用于网络间通信的特殊文件。
5,链接文件 (link) :用于间接访问另外一个目标文件,相当于 Windows 快捷方式。 6,字符设备文件 (character) :字符设备在应用层的访问接口。
7,块设备文件 (block) :块设备在应用层的访问接口。
下面是一张全家福
注意到,每个文件信息的最左边一栏,是各种文件类型的缩写,从上到下依 次是:
b (block) 块设备文件
c (character) 字符设备文件
d (directory) 目录文件
l (link) 链接文件 (软链接)
p (pipe) 管道文件 (命名管道)
- (regular) 普通文件
s (socket) 套接字文件 (Unix 域/本地域套接字)
其中,块设备文件和字符设备文件,是 Linux 系统中块设备和字符设备的访问节点,在 内核中注册了某一个设备文件之后,还必须在/dev/下为这个设备创建一个对应的节点文件 (网络接口设备除外) ,作为访问这个设备的入口。目录文件用来存放目录项,是实现文件 系统管理的最重要的手段。链接文件指的是软链接,是一种用来指向别的文件的特殊文件, 其作用类似于 Windows 中的快捷方式,但他有更加有用的功能,比如库文件的版本管理。 普通文件指的是外部存储器中的文件,比如二进制文件和文本文件。套接字文件指的是本机 内进程间通信用的 Unix 域套接字,或称本地域套接字。
对一个文件的操作有两种不同的方式,既可以使用由操作系统直接提供的编程接口 (API),即系统调用,也可以使用由标准 C 库提供的标准 IO 函数,他们的关系如图 4-3 所 示。
图 4-3 标准 IO 和系统 IO 的位置
在 Linux 操作系统中,应用程序的一切行为都依赖于这个操作系统,但是操作系统的内 部函数应用层的程序是不能直接访问的,因此操作系统 OS 提供了大约四五百个接口函数, 叫做“系统调用接口”,好让应用程序通过他们使用内核提供的各种服务,上图中用红色标 注的那一层,就是这所谓的系统调用接口,这几百个函数是非常精炼的 (Windows 系统的 接口函数有数千个) ,他们以功能的简洁单一为美,以健壮稳定为美,但考虑到用户可能需 要用到更加丰富的功能,因此就开发了很多库,其中最重要的也是应用程序必备的库就是标 准 C 库,库里面的很多函数实际上都是对系统调用函数的进一步封装而已,用个比喻来讲 就是:OS 的系统调用接口类似于菜市场,只提供最原始的肉菜,而库的函数接口相当于饭 馆,对肉菜进行了加工,提供风味各异品种丰富的更方便食用的佳肴。
在几百个 Linux 系统调用中,有一组函数是专门针对文件操作的,比如打开文件、关闭 文件、读写文件等,这些系统调用接口就被称为“系统 IO”,相应地,在几千个标准 C 库 函数中,有一组函数也是专门针对文件操作的,被称为“标准 IO”,他们是工作在不同层 次,但都是为应用程序服务的函数接口。
要对一个文件进行操作,首先必须“打开”他,打开两个字之所以加上双引号,是因为 这是代码级别的含义,并非图形界面上所理解的“双击打开”一个文件,代码中打开一个文件意味着获得了这个文件的访问句柄 (即 file descriptor,文件描述符 fd) ,同时规定了之后访问这个文件的限制条件
我们使用以下系统 IO 函数来打开一个文件:
功能 | 打开一个指定的文件并获得文件描述符,或者创建一个新文件 | ||
头文件 | #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> | ||
原型 | int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); | ||
参数 | pathname:即将要打开的文件 | ||
flags | O_RDONLY:只读方式打开文件 | 这三个参数互斥 | |
O_WRONLY:只写方式打开文件 | |||
O_RDWR:读写方式打开文件 | |||
O_CREAT:如果文件不存在,则创建该文件。 | |||
O_EXCL:如果使用 O_CREAT 选项且文件存在,则返回错误消息。 | |||
O_NOCTTY:如果文件为终端,那么终端不可以作为调用 open()系统调 用的那个进程的控制终端。 | |||
O_TRUNC:如文件已经存在,则删除文件中原有数据。 | |||
O_APPEND:以追加方式打开文件。 | |||
mode | 如果文件被新建,指定其权限为 mode (八进制表示法) | ||
返回值 | 成功 | 大于等于 0 的整数 (即文件描述符) | |
失败 | - 1 | ||
备注 | 无 |
表 4-1 函数 open( )的接口规范
使用系统调用 open( )需要注意的问题有:
1,flags 的各种取值可以用位或的方式叠加起来,比如创建文件的时候需要满足这样的 选项:读写方式打开,不存在要新建,如果存在了则清空他。那么此时指定的 flags 的取值 应该是:O_RDWR | O_CREAT | O_TRUNC。
2,mode 是八进制权限,比如 0644,或者 0755 等。
3,它可以用来打开普通文件、块设备文件、字符设备文件、链接文件和管道文件,但 只能用来创建普通文件,每一种特殊文件的创建都有其特定的其他函数。
4,其返回值就是一个代表这个文件的描述符,是一个非负整数。这个整数将作为以后任何系统 IO 函数对其操作的句柄,或称入口
以下的系统 IO 函数用来关闭一个文件
功能 | 关闭文件并释放相应资源 | |
头文件 | #include <unistd.h> | |
原型 | int close(int fd); | |
参数 | fd:即将要关闭的文件的描述符 | |
返回值 | 成功 | 0 |
失败 | - 1 | |
备注 | 重复关闭一个已经关闭了的文件或者尚未打开的文件是安全的。 |
表 4-2 函数 close( )的接口规范
系统调用 close( )相对来讲简单得多,只需要提供已打开的文件描述即可。一般来讲, 当我们使用完一个文件之后,需要及时对其进行关闭,以防止内核为继续维护它而付出不必 要的代价。下面是一个简单的使用了这两个函数的示例代码:
- #include <stdio.h>
- #include <sys/stat.h>
- #include <sys/types.h>
- #include <fcntl.h>
-
- int main(void)
- {
- int fd = open("a.txt", O_CREAT|O_TRUNC|O_WRONLY, 0644);
- printf("fd: %d\n", fd);
-
- close(fd);
- return 0;
- }
代码中第 8 行使用 open( )打开了一个文件a.txt,打开模式是只读,并且存在就打开不 存在就创建,从程序的执行结果来看,获得的文件描述符 fd 等于 3,这是因为 0、1 和 2 三 个描述符在程序一开始运行时就已经被默认打开了,他们分别代表了标准输入、标准输出和 标 准 出 错 ,事 实 上 ,在代码 中 我 们 经 常 使 用 STDIN_FILENO 、 STDOUT_FILENO 和 STDERR_FILENO 来替代 0、1 和 2。
图 4-4 文件描述符与文件
上面的示意图表示,每一个被打开的文件 (键盘、显示器都是文件) 都会有一个非负的 描述符来对应他们,一个文件还可以被重复打开多次,每打开一次也都会有一个描述符对应, 并且可以有不同的模式。
那么,这个所谓的文件描述符究竟是什么玩意儿呢?其实他是一个数组的下标值,在 4.1 节中提到过,在内核中打开的文件是用 file 结构体来表示的,每一个结构体都会有一个 指针来指向他们,这些指针被统一存放在一个叫做 fd_array 的数组当中,而这个数组被存 放在一个叫做 files_struct 的结构体中,该结构体是进程控制块 task_struct 的重要组成部分。 他们的关系如图 4-5 所示
图 4-5 文件描述符的本质
接下来是文件的读写接口:
功能 | 从指定文件中读取数据 | |
头文件 | #include <unistd.h> | |
原型 | ssize_t read(int fd, void *buf, size_t count); | |
参数 | fd:从文件fd 中读数据 | |
buf:指向存放读到的数据的缓冲区 | ||
count:想要从文件 fd 中读取的字节数 | ||
返回值 | 成功 | 实际读到的字节数 |
失败 | - 1 | |
备注 | 实际读到的字节数小于等于 count |
功能 | 将数据写入指定的文件 | |
头文件 | #include <unistd h> | |
原型 | ssize_t write(int fd, const void *buf, size_t count); | |
参数 | fd:将数据写入到文件 fd 中 | |
buf:指向即将要写入的数据 | ||
count:要写入的字节数 | ||
返回值 | 成功 | 实际写入的字节数 |
失败 | - 1 | |
备注 | 实际写入的字节数小于等于 count |
表 4-3 函数 read( )和 write( )的接口规范
下面通过一个示例展示他们的用法,这是示例实现一个简单的功能:将指定的一个文件 的内容拷贝到另一个指定的文件中去, 目前暂时只支持普通文件的拷贝。代码如下:
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <unistd.h>
-
- define SIZE 1024
-
- int main(int argc, char **argv)
- {
- int fd_from, fd_to;
-
- if(argc != 3) 15 {
- printf("Uage: %s <src> <dst>", argv[0]);
- exit(1);
- }
- // 以只读方式打开源文件,以只写方式打开目标文件
- fd_from = open(argv[1], O_RDONLY);
- fd_to = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC, 0644); 22
- char buf[SIZE];
- char *p;
- int nread, nwrite;
-
- while(1)
- {
- nread = read(fd_from, buf, SIZE); 30
- if(nread == 0)
- break;
- write(fd_to, buf, nread);
- }
-
- close(fd_from);
- close(fd_to); 39
- return 0;
- }

上述代码中的第 27 - 35 行是关键,程序循环地从 fd_from 中读取数据,放置到 buf 中, 然后将数据写入fd_to 中去。当 read( )返回 0 时代表已经读完,退出循环并且关闭两个文 件描述符。
在读写文件的时候有个偏移量的概念,即当前读写的位置,这个位置可 以获取,也可以人为调整,用到的系统 IO 接口如下
功能 | 调整文件位置偏移量 | ||
头文件 | #include <sys/types.h> #include <unistd.h> | ||
原型 | off_t lseek(int fd, off_t offset, int whence); | ||
参数 | fd:要调整位置偏移量的文件的描述符 | ||
offset:新位置偏移量相对基准点的偏移 | |||
whence:基准点 | SEEK_SET:文件开头处 | ||
SEEK_CUR:当前位置 | |||
SEEK_END:文件末尾处 | |||
返回值 | 成功 | 新文件位置偏移量 | |
失败 | - 1 | ||
备注 | 无 | ||
表 4-3 函数 lseek( )的接口规范
注意,lseek( )只对普通文件凑效,特殊文件是无法调整偏移量的。下面通过一个示例 来使用这个接口:创建一个空洞文件。
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdbool.h>
- #include <unistd.h>
- #include <string.h>
- #include <strings.h>
- #include <errno.h>
- #include <sys/stat.h>
- #include <sys/types.h>
- #include <fcntl.h>
-
- int main(int argc, char **argv)
- {
- int fd = open("file", O_RDWR|O_CREAT|O_TRUNC, 0644); 16
- write(fd, "abc", 3); // 写入”abc”
- lseek(fd, 100, SEEK_CUR); // 定位到 100 个字节之后
- write(fd, "xyz", 3); // 写入”xyz”
-
- close(fd);
- return 0;
- }

注意上述代码的第 17 - 19 行,首先写入”abc”三个字符,然后直接将文件位置偏移量人 为调整到“当前位置往后 100 个字节”处,然后再写入”xyz”三个字符,这时文件file 中就 包含了一个大小为 100 个字节的空洞,头尾分别有三个字节,整个文件的大小为 106 个字 节。
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。