目录
文件类别
文件操作
系统 IO
头文件
打开文件
关闭文件
文件描述符
读写
例子 拷贝文件
偏移量
其他接口
mmap()映射
文件类别
- 普通文件(regular):存在于外部存储器中,用于存储普通数据。
- 目录文件(directory):用于存放目录项,是文件系统管理的重要文件类型。
- 管道文件(pipe):一种用于进程间通信的特殊文件,也称为命名管道 FIFO。
- 套接字文件(socket):一种用于网络间通信的特殊文件。
- 链接文件(link):用于间接访问另外一个目标文件,相当于 Windows 快捷方式。
- 字符设备文件(character):字符设备在应用层的访问接口。
- 块设备文件(block):块设备在应用层的访问接口。
文件信息的最左边一栏是各种文件类型的缩写
- b(block)块设备文件
- c(character)字符设备文件
- d(directory)目录文件
- l(link)链接文件(软链接)
- p(pipe)管道文件(命名管道)
- -(regular)普通文件
- s(socket)套接字文件
- 块设备文件和字符设备文件,是 Linux 系统中块设备和字符设备的访问节点, 在内核中注册了某一个设备文件之后,还必须在/dev/下为这个设备创建一个对应的节点文 件(网络接口设备除外),作为访问这个设备的入口
- 目录文件用来存放目录项,是实现文 件系统管理的最重要的手段。
- 链接文件指的是软链接,是一种用来指向别的文件的特殊文件, 其作用类似于 Windows 中的快捷方式,但他有更加有用的功能,比如库文件的版本管理。
- 普通文件指的是外部存储器中的文件,比如二进制文件和文本文件。
- 套接字文件指的是本机内进程间通信用的 Unix 域套接字,或称本地域套接字。
文件操作
对一个文件的操作有两种不同的方式,既可以使用由操作系统直接提供的编程接口 (API),即系统调用,也可以使用由标准 C 库提供的标准 IO 函数
在几百个 Linux 系统调用中,有一组函数是专门针对文件操作的,比如打开文件、关 闭文件、读写文件等,这些系统调用接口就被称为“系统 IO”,相应地,在几千个标准 C 库函数中,有一组函数也是专门针对文件操作的,被称为“标准 IO”,他们是工作在不同 层次,但都是为应用程序服务的函数接口
系统 IO
头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
打开文件
注意
- flags 的各种取值可以用位或的方式叠加起来,比如创建文件的时候需要满足这样 的选项:读写方式打开,不存在要新建,如果存在了则清空他。那么此时指定的 flags 的取 值应该是:O_RDWR | O_CREAT | O_TRUNC。
- mode 是八进制权限,比如 0644,或者 0755 等。
- 它可以用来打开普通文件、块设备文件、字符设备文件、链接文件和管道文件,但 只能用来创建普通文件,每一种特殊文件的创建都有其特定的其他函数。
- 其返回值就是一个代表这个文件的描述符,是一个非负整数。这个整数将作为以后任何系统 IO 函数对其操作的句柄,或称入口
关闭文件
文件描述符
是一个数组的下标值,在内核中打开的文件是用 file 结构体来表示的,每一个结构体都会有一个 指针来指向他们,这些指针被统一存放在一个叫做 fd_array 的数组当中,而这个数组被存 放在一个叫做 files_struct 的结构体中,该结构体是进程控制块 task_struct 的重要组成 部分
- task_struct 被称为进程控制块,是程序运行时在 内核中的实现形式
- 结构体 file{}是内核管理文件操作的最重要的数据之一,里面存放了对该文件的访问模 式、文件位置偏移量等重要信息。在操作文件之前,open( )参数中指定的模式将被记录在 该结构体中,在操作文件之时,文件相关的控制数据也一并在此统一管理
读写
- 实际的读写字节数要通过返回值来判断
- 当实际的读写字节数小于 count 时,有以下几种情形
- 读操作时,文件剩余可读字节数不足 count
- 读写操作期间,进程收到异步信号
- 读写操作同时对 f_pos 起作用。也就是说,不管是读还是写,文件的位置偏移量(即 内核中的 f_pos)都会加上实际读写的字节数,不断地往后偏移
例子 拷贝文件
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <errno.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7
8 #define SIZE 1024
9
10 int main(int argc, char **argv)
11 {
12 int fd_from, fd_to;
13
14 if(argc != 3)
15 {
16 printf("Uage: %s <src> <dst>", argv[0]);
17 exit(1);
18 }
19 // 以只读方式打开源文件,以只写方式打开目标文件
20 fd_from = open(argv[1], O_RDONLY);
21 fd_to = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC, 0644);
22
23 char buf[SIZE];
24 char *p;
25 int nread, nwrite;
26
27 while(1)
28 {
29 nread = read(fd_from, buf, SIZE);
30
31 if(nread == 0)
32 break;
33
34 write(fd_to, buf, nread);
35 }
36
37 close(fd_from);
38 close(fd_to);
39
40 return 0;
41 }
偏移量
例子 可以实现一个进程拷贝前半段,另一个进程拷贝后半段
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdbool.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <strings.h>
7 #include <errno.h>
8
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <fcntl.h>
12
13 int main(int argc, char **argv)
14 {
15 if(argc != 3) // 输入两个参数,用法类似于 Shell 命令:cp file1 file2
16 {
17 printf("Usage: %s <src> <dst>\n", argv[0]);
18 exit(1);
19 }
20
21 // 创建一个子进程(详细讲解参见第五章)
22 pid_t a = fork();
23
24 // 父子进程都打开源文件和目标文件
25 int fd1 = open(argv[1], O_RDONLY);
26 int fd2 = open(argv[2], O_CREAT|O_RDWR|O_TRUNC, 0644);
27 if(fd1 == -1 || fd2 == -1)
28 {
29 perror("open()");
30 exit(1);
31 }
32
33 int size = lseek(fd1, 0, SEEK_END); // 获得文件大小
34 if(a == 0) // 在子进程中,将位置偏移量调整到中间位置(形成空洞)
35 {
36 lseek(fd1, size/2, SEEK_SET);
37 lseek(fd2, size/2, SEEK_SET);
38 }
39 else if(a > 0) // 在父进程中,将文件位置偏移量调整到文件开头处
40 {
41 lseek(fd1, 0, SEEK_SET);
42 }
43
44 char buf[100];
45 int nread;
46
47 while(1)
48 {
49 bzero(buf, 100);
50 nread = read(fd1,bug,100);
51 if(nread==0)
52 break;
53
54 if(a > 0)
55 {
56 // 在父进程中,查看当前偏移量是否已经到达中间位置
57 int n;
58 n = lseek(fd1, 0, SEEK_CUR) - size/2;
59 if(n >= 0) // 到达甚至已经超过中间位置
60 {
61 write(fd2, buf, n); // 写入未超过中间位置的字节
62 exit(0); // 然后退出
63 }
64 }
65
66 write(fd2, buf, nread);
67 }
68
69 close(fd1);
70 close(fd2);
71
72 return 0;
73 }
其他接口
调整文件为异步工 作模式
int on = 1;
ioctl(fd, FIOASYNC, &on);//后面的变参也由前面的 request 命令字决定
等同于
fcntl(fd, F_SETFL, O_ASYNC);
fcntl( )而言,其第二个参数命令字 cmd 有很多
mmap()映射
该函数在进程的虚拟内存空间 中映射出一块内存区域,用以对应指定的一个文件,该内存区域上的数据跟对应的文件的数 据是一一对应的,并在一开始的时候用文件的内容来初始化这片内存
例子 使用 mmap( )实现类似 Shell 命令 cat 的功能:将一个普通文件的内容显示到屏幕上
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <errno.h>
5
6 #include <sys/stat.h>
7 #include <sys/mman.h>
8 #include <sys/types.h>
9 #include <fcntl.h>
10
11 int main(int argc, char **argv)
12 {
13 if(argc != 2)
14 {
15 printf("Usage: %s <filename>\n", argv[0]);
16 exit(1);
17 }
18
19 // 以只读方式打开一个普通文件
20 int fd = open(argv[1], O_RDONLY);
21
22 // 申请一块大小为 1024 字节的映射内存,并将之与文件 fd 相关联
23 char *p = mmap(NULL, 1024, PROT_READ, 24 MAP_PRIVATE, fd, 0);
25
26 // 将该映射内存的内容打印出来(即其相关联文件 fd 的内容)
27 printf("%s\n", p);
28
29 return 0;
30 }