- 本人的LeetCode账号:魔术师的徒弟,欢迎关注获取每日一题题解,快来一起刷题呀~
- 本人Gitee账号:路由器,欢迎关注获取博客内容源码。
一、C语言文件操作回顾
1 利用fputs写文件内容
C语言利用文件指针与fputs完成文件写入:
2 利用fgets读文件内容
3 fopen打开方式"a":追加
二、系统文件IO调用接口的引入
1 stdin stdout stderr
C语言会默认为我们打开三个输入输出流:stdin、stdout、stderr,从类型是FILE*的角度来看,C语言好像把他们当文件来处理。
stdin对应的叫做键盘,stdout对应的硬件是显示器,stderr对应的硬件是显示器,我们最终一定是向硬件写入的。
利用fputs向标准输出写入:
我们也可以利用输出重定向把标准输出的内容从标准输出写到文件中:
这里引入输出重定向的原因想说明stderr虽然绑定的也是显示器,但是它无法被输出重定向。
所以输出重定向的含义是把stdout的内容重定向到别的地方,stderr的内容无法被这样输出重定向,如果要重定向stderr和stdout,命令行操作为&>和&>>.
2 cout cin cerr
C++中也有三个对象表示这三个输入输出流:cout、cin、cerr。
一般大部分语言都会提供这三个输出输出流。
所以这说明了fputs向一般文件或硬件设备都能写入,磁盘明明是硬件,但是也可以写入,这体现了Linux的一切皆文件。
3 OS与文件读写的关系引入
所有的读写,最终访问的都是硬件:显示器、键盘、磁盘(普通文件),而OS是硬件的管理者,所以所有的语言对“文件”的操作,都必须贯穿OS,而操作系统不相信任何人,访问操作系统必须通过系统调用。
所以结论就是几乎所有语言封装的文件的读写操作,如fopen、fclose、fread、fwrite等等函数的底层一定需要使用OS提供的系统调用。
三、文件IO的系统调用接口
1 open和close以及它们的测试
先用man 2 open看一下:
int open(const char* pathname, int flags, mode_t mode);
第一个参数pathname表示文件的路径与名字,第二个参数flags表示状态(只读、只写、创建),第三个参数mode表示创建文件的权限信息。
flag的参数宏:
返回值的情况:返回一个文件描述符,出错则返回-1.
接着看看close,参数是一个文件描述符。
权限的讨论
为了看看权限的作用,我们进行一个简单的测试:利用open和close如果文件存在就只写打开 不存在就创建:
发现创建出了一个权限奇形怪状的文件,每次创建后权限还都不一样:

这说明使用open系统调用创建文件时,如果你的文件没有告知权限,权限就会是混乱的,我们必须提前告知其权限。
以权限的八进制方案补上:
所以我们使用fopen("./log.txt", "w")创建并写文件时,文件就是正常权限,从来也没关心过创建文件的权限,这就是C语言对系统调用进行了一层封装。
flag的参数
这种宏的方式是给操作系统传多个标志位的常见方法:
flags是一个int类型,32位,它是以一个bit表示一个标志位,这样同时可以传多个标志位,且使用位运算控制,速度比较快。
所以我们的|就可以把对应的位置成1,然后操作系统内部再通过&来判断对应的标志位是否启用。
所以这些宏O_WRONLY、O_CREAT、O_RDONLY都是只有一个比特位为1的数据,它们不会重复。
所以要一次向系统调用传递两个标志位,只要把它们按位或起来就行。
我们去/usr/include/bits/fcntl-linux.h中找系统中的宏来验证一下我们的说法:
2 返回值—文件描述符
我们把前面的程序的返回值打印出来看看:
为啥是3呢,我们再创建一批临时文件看看它们的文件描述符:
这个文件描述符是连续的,那为啥从3开始打,0 1 2去哪了呢?
实际上,文件描述符0 1 2对应的分别是标准输入、标准输出、标准错误。
而C语言中默认也会打开三个输入输出流:stdin、stdout、stderr,语言和系统之间一定是有某种对应关系的。
从0开始的一组数,会联想到数组下标!
3 操作系统组织管理加载到内存中的文件的引出
所有的文件操作,表现上都是进程执行相应的函数,也就是说文件操作就是进程对文件的操作,而要操作文件必须先打开文件,本质就是将文件相关的属性信息,加载到内存。
而操作系统中有大量的进程,每个进程都可以打开很多文件,进程与文件之间的关系应该是1对n的。
那么系统中就可能存在很多的打开的文件。
那么OS一定需要把打开的文件在内存中管理起来!如何管理?和管理进程一样,先描述再组织。
而我们学习的是Linux系统,在内核中有一个数据结构struct file,里头维护着打开文件的相关属性与组织这些数据结构的链接属性。
4 write—向文件中写内容的系统调用接口
返回值表示我向文件中成功写入了多少字节。
5 read 读文件内容的系统调用
一个简单的测试:
四、文件描述符
1 文件描述符的本质
我们打开这么文件,为啥文件描述符是从3开始的呢?0 1 2哪去了呢?
当我们的程序运行起来后,变成了进程,默认情况下,OS会帮助我们的进程打开三个输入输出流:
- 0:标准输入,设备是键盘;
- 1:标准输出,设备是显示器;
- 2:标准错误,设备是显示器;
这和C/C++会默认帮我们打开标准输入流stdin、标准输出流stdout、标准错误流stderr很像。
0 1 2 …,这个返回值很像数组,而open的返回值是OS给我们的,那这个数组如果存在一定是在操作系统那里,而我们知道进程打开文件都是一个进程可以打开多个文件,而打开文件是操作系统帮我们打开的,所以操作系统一定会打开更多的文件,所以操作系统一定要对打开的文件做管理!
一个文件尚未被打开时,它就在磁盘上静静地放着,那如果我们创建一个空文件,它是否要在磁盘上占空间呢?
文件除了它的内容,还有各种属性,如权限、文件名、修改时间等等,这些属性都是数据。
所以一个空文件指的是内容是空的,但是其属性一样会占空间。
所以一个磁盘文件,其内容就包括文件内容+文件属性。
所以对文件的操作就只有两种:对内容的修改(fput等)和对文件属性的修改(chmod等)。
回到主题,os来描述文件,肯定也要先描述后组织,我们先来关心描述:
也就是一个struct file,回想进程中task_struct放的东西,不就是一些进程的属性吗,所以struct file中放的一定也是文件相关的属性信息。
那么这些文件的结构也按双链表组织给os管理,那么哪些是我们进程的呢?
所以OS还给每个进程的task_struct中给了一个struct file* ps,它会指向一个数组struct file* fd_array[],这个数组会指向一个个文件的task_struct,这样进程和文件就关联起来了。
而数组不是有一个一个的下标吗,所以对每个进程来说,这个下标就是文件描述符,所以进程通过一个一个文件描述符找到对应的文件。
所以系统调用read和write通过文件描述符,先通过task_struct中的struct files* ps找到数组,然后利用文件描述符找到数组中对应的struct file*,进而找到那个文件进行操作。
所以文件描述符本质是进程和文件关联用的数组struct file* fd_array[]数组的下标,调用过程如图:
2 进一步理解一切皆文件
网卡、键盘、显示器、磁盘等硬件,他们一定都要提供read和write方法,它们一定要和内存进行IO。
有的设备不能写,有的设备不能读,那么把对应的接口设为空就可以了,不管怎么说,不同的设备在底层来讲,读写方法一定是不一样的。
那为啥说一切皆文件呢,大家这些外设对应的读写方法不是不一样吗?
这是因为Linux中有虚拟文件系统vfs,不管你是什么样的外设,我都为你创建一个结构体,然后通过双链表组织这些结构。
然后利用多态的思想,在write和read中定义上一些函数指针,以保证它们能够实现对应不同硬件的读写方法(类似C++的虚函数表)。
但是从顶层来看,看这些struct file都是一样的,我要读就调用read方法,要写就调用write方法,我并不关心其到底是什么文件,也就是说,从顶层来看,它们

本文详细探讨了Linux系统中的文件操作,包括C语言的fopen、fputs、fgets、fopen的a追加模式以及系统调用接口open、close、write、read。介绍了文件描述符的概念,其分配规则以及与进程的关系。此外,讲解了文件系统的结构,如inode、磁盘布局、文件时间戳等,并讨论了软链接和硬链接的区别。最后,概述了静态库和动态库的制作与使用,以及如何在程序中链接和查找这些库。
最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



