Linux_系统文件I/O详解
系统文件I/O
在学习c语言阶段时,我们就已经学习过了对文件的操作,但大多数同学的c文件的操作,并不理解,甚至说压根就不明白文件操作的底层含义,如果学习文件操作还只是停留在语言层面上,是很难对文件有一个比较深刻的了解!
我们知道C程序会默认打开三个输入输出流:stdin,stdout,stderr,它们分别代表键盘,显示器,显示器.
文件的相关操作最终一定是访问计算机的硬件:显示器,键盘,文件(磁盘),而OS又是硬件的管理者,由此我们得知,所有语言上对"文件"的操作,都必须贯穿OS!
系统调用接口
1.open:
pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
返回值:
成功:新打开的文件描述符(下文会详解)
失败:-1
PS: mode是用来设置,用户权限的,可自行设置用户,组和其它对文件的权限,如果不写的话,文件被创建出时,其权限是随机的,所以一般情况下用户需要填入三者的权限
1 #include<stdio.h>
2 #include<string.h>
3 #include<unistd.h>
4 #include<fcntl.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7 int main()
8 {
9 int fd =open("./log.txt",O_WRONLY|O_CREAT,0644);
10 if(fd<0)
11 {
12 perror("OPEN FILED\n");
13 return 1;
14 }
15 const char* msg="Hello File\n";
16 write(fd,msg,strlen(msg));
17 close(fd);
18
19
20 return 0;
21 }
文件描述符fd
在上文我们用到open函数时,看到open的返回值是int类型的整数,但是其具体有什么含义呢?
1 #include<stdio.h>
2 #include<string.h>
3 #include<unistd.h>
4 #include<fcntl.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7 int main()
8 {
9 int fd1 = open("./log1.txt",O_WRONLY|O_CREAT,0644);
10 int fd2 = open("./log2.txt",O_WRONLY|O_CREAT,0644);
11 int fd3 = open("./log3.txt",O_WRONLY|O_CREAT,0644);
12 int fd4 = open("./log4.txt",O_WRONLY|O_CREAT,0644);
13 int fd5 = open("./log5.txt",O_WRONLY|O_CREAT,0644);
14 int fd6 = open("./log6.txt",O_WRONLY|O_CREAT,0644);
15 int fd7 = open("./log7.txt",O_WRONLY|O_CREAT,0644);
16 printf("%d\n",fd1);
17 printf("%d\n",fd2);
18 printf("%d\n",fd3);
19 printf("%d\n",fd4);
20 printf("%d\n",fd5);
21 printf("%d\n",fd6);
22 printf("%d\n",fd7);
23 return 0;
24 }
此时我们发现当我们创建多个文件时,返回的fd值是从3开始顺序增加的,我们稍加思考一下会发现这与我们在c语言学习过的数组的下标是一样的.
文件描述符fd就是从0开始的整数,当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述文件.于是有了file结构体,表示一个打开的文件对象.而进程执行open系统调用时,必须要让进程和文件关联起来,所以每个进程内部都有一个指针*files,指向一张表files_struct,该表最重要的一部分就是包含一个指针数组,每个元素都指向打开文件的指针!所以本质上文件描述符就是数组的下标也就是说拿到文件描述符,就能找到对应的文件
文件描述符fd的分配规则:
文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
重定向
每个文件描述符都是一个内核中文件描述信息数组的下标,对应有一个文件的描述信息用于操作文件,而重定向就是在不改变所操作的文件描述符的情况下,通过改变描述符对应的文件描述信息进而实现改变所操作的文件
1 #include<stdio.h>
2 #include<string.h>
3 #include<unistd.h>
4 #include<fcntl.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7 int main()
8 {
9 close(1);
10 int fd = open("./log1.txt",O_WRONLY|O_CREAT,0644);
11 printf("%d\n",fd);
12 printf("hello world\n");
13 printf("hello world\n");
14 printf("hello world\n");
15 printf("hello world\n");
16 printf("hello world\n");
17 printf("hello world\n");
18 return 0;
19 }
运行后我们发现,明明我们在代码里写了打印语句,但是显示器上却没有显示?
此时我们看到明明应该打印在显示器里的语句,全部被输入到了log1.txt文件中,而且文件描述符fd也变成了1,这种现象就叫做重定向
那么重定向的本质是什么?
在上文中我们说过文件描述符的分配规则是在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。当我们close(1)时,1号文件已经被关闭了,那么最小的没用被使用的下标就是1.
dup2 系统调用
int dup2(int oldfd, int newfd);
函数功能为将newfd描述符重定向到oldfd描述符,相当于重定向完毕后都是操作oldfd所操作的文件
但是在过程中如果newfd本身已经有对应打开的文件信息,则会先关闭文件后再重定向(否则会资源泄露)
1 #include<stdio.h>
2 #include<string.h>
3 #include<unistd.h>
4 #include<fcntl.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7 int main()
8 {
9 int fd = open("./log1.txt",O_WRONLY|O_CREAT,0644);
10 dup2(fd,1);
11 printf("%d\n",fd);
12 printf("hello dup2\n");
13 printf("hello dup2\n");
14 printf("hello dup2\n");
15 printf("hello dup2\n");
16 printf("hello dup2\n");
17 printf("hello dup2\n");
18 return 0;
19 }
缓冲区及刷新策略
我们先来看一段之前写的代码
1 #include<stdio.h>
2 #include<string.h>
3 #include<unistd.h>
4 #include<fcntl.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7 int main()
8 {
9 close(1);
10 int fd = open("./log1.txt",O_WRONLY|O_CREAT,0644);
11 const char* msg="hello File\n";
12 write(fd,msg,strlen(msg));
13 printf("%d\n",fd);
14 printf("hello dup2\n");
15 printf("hello dup2\n");
16 printf("hello dup2\n");
17 printf("hello dup2\n");
18 printf("hello dup2\n");
19 return 0;
20 }
依旧是重定向到log1.txt文件中
但是我们发现代码写的有点问题,我们既然打开了文件,但是最后程序退出前我们并没有关闭,当我们加入close(fd)关闭文件时
log1.txt里的内容就只剩下了一行,这是什么原因?
用户->OS:
刷新策略:
1.立即刷新(不缓冲)
2.行刷新(行缓冲'\n'),比如,显示器打印
3缓冲区满了,才刷新(全缓冲),比如,磁盘往文件里写入
现在就可以回答为什么log.txt里只有一行,因为,当我们close(fd)时文件被关闭,而此时printf打印的语句全部都存在c缓冲区中,而想要将c缓冲区里的内容刷新到OS缓冲区需要系统调用接口,而此时fd已经被我们关闭了,压根就找不到文件,而write本身就是系统调用接口,它会直接刷新到OS缓冲区,所以不受影响
理解文件系统
我们知道电脑上的硬盘空间是非常大的,目前正常的电脑一般都有512GB,这对操作系统而言,管理这么大的空间是成本非常高的,然而操作系统为了方便管理硬盘空间,会将硬盘进行分区管理,所谓文件系统就是操作系统管理硬盘的一种软件分层方式
Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。例如mke2fs的-b选项可以设定block大小为1024、2048或4096字节。而上图中启动块(Boot Block)的大小是确定的
Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。
超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了
GDT,Group Descriptor Table:块组描述符,描述块组属性信息
块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用
inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用
i节点表:存放文件属性 如 文件大小,所有者,最近修改时间等
数据区:存放文件内容
inode
在Linux中inode是一个结构体,其中包含了
- inode 编号
- 用来识别文件类型,以及用于 stat C 函数的模式信息
- 文件的链接数目
- 属主的ID (UID)
- 属主的组 ID (GID)
- 文件的大小
- 文件所使用的磁盘块的实际数目
- 最近一次修改的时间
- 最近一次访问的时间
- 最近一次更改的时间
inode编号
操作系统在查找文件的时候,是不会通过文件名来判断该文件是否为要找的文件,而是通过inode编号找到文件inode信息,通过inode信息找到对应的数据块
理解软硬链接
软连接:就是给文件创建一个快捷方式
硬链接:本质不是一个独立的文件,而是一个文件名和inode编号的映射关系,因为自己没有独立的inode编号