- C文件 IO 相关操作
- 系统调用接口
- 文件描述符 fd
- 重定向
- dup2系统调用
- 文件系统
- 动态库和静态库
有关c语言的IO函数
fopen、fread、fwrite、f等等
我们先来看一个简单的例子:打开一个文件
- fopen函数的两个参数,第一个参数为文件路径,如果我们不加路径,只写文件名则指当前路径 —cwd
什么是当前路径呢?本质是进程运行时所处的路径
我们可以通过命令的形式看一看进程究竟在什么路径下
我们知道除了 axj aux 这样的查看进程命令,还可以通过proc文件夹 来查看进程
我们可以清晰的看到,有一个exe 指 可执行文件所处的路径,cwd 指进程运行时的路径
- 第二个参数为打开方式,总的来说,有b则是打开二进制文件,有+则一定是可读可写,其中r+为打开w+为新建有b为二进制
这个小小的例子就是打开一个用来写入的文件,要注意操作完要记着关闭文件,接下来我们尝试操作文件
fputs 将字符写入流中
fgets,将流中的内容写到缓冲区中,其中第一个参数为缓冲区,第二个参数则是我们预期输入缓冲区的大小
至此我们完成了大致的c语言文件操作,这里有几个细节
FILE* 是我们打开文件时的返回值,这里的文件类型可以是普通文本文件、二进制文件、设备文件等
还需要注意的是 ,进程运行时,会自动打开三个流文件,分别叫做标准输入、标准输出、标准错误,他们都在头文件中被声明,分别对应的设备为键盘、显示器、显示器。
因此,我们就可以通过fgets和fputs 来模拟printf和scanf
第三点:a和w分别为追加写入和覆盖写入,也就是从文末开始写 与 从文件开头开始写
以上就是大致c语言相关的io操作 ,当然还有fseek、ftell、frewind等等函数,我们这里不深入探究。
系统调用接口
我们上文中所使用的C语言IO函数,也就是c语言的库函数,它通过对系统调用接口的封装来让程序员轻易使用,接下来我们就聊聊更加底层的系统调用接口。
我们先来看看如何打开文件,叫做 open函数
它的参数有三个,第一个为我们所要打开的文件路径和文件名,这个与fopen相同
第二个参数为标志位参数,可以有O_RDonLY , O_WRonLY , O_RDWR 分别对应了只读 、只写、 可读可写。
除此之外,还有其他的标志位,比如 O_CREAT 我们可以发现 我们的系统调用接口与fopen有一些不同,fopen在文件不存在的情况下会自动创建,而open不可以,你需要写标志位O_CREAT 来增加这一功能。否则会打开失败,返回-1
第三个参数为我们创建文件时的权限值,所以如果文件已经存在 ,就不需要第三个参数了。
在此,我们来对第二个参数的标志位进行深入的了解:
我们可以认为有32个不同的标志位,因为我们在传参时,理论上可以传32bit位,标志位的本质就是一位为1,其余31位全部为0,来分别标志不同的状态。这也就解释了我们在进行标志时用 或 (|)这个运算符来处理。
这样比特位传递参数的方式就给我们许多的选线。
最后一个参数也有一些小细节,我们一定要输入四位的权限 ,第一位我们默认为0就好了,否则我们仅仅输入 666这样的码是无法完成权限的赋值的。
其中在赋值权限时,还要注意 umask这样的掩码,我们可以在打开文件前将掩码赋为0。
我们就可以轻易的赋值权限了。
最后我们来谈谈open的返回值,让我们意想不到的是它为int ,而我们fopen的返回值为FILE * 。
open的返回值:成功 返回file descriptor – 文件描述符 ,失败则返回 -1
我们可以打开多个文件,发现他们的fd值是从3开始的连续整数。
有两个问题:1.为什么没有0、1、2
2.为什么它是连续的,很容易让人想到数组
我们回答第一个问题,我们还记得之前说过进程运行时会自动打开3个流,其中0就是 stdin的 fd, 1就是stdout 的 fd, 2 就是 stderr 的 fd。所以我们在打开第四个文件时,其返回值fd会为3 。我们可以想到,这三个流也是被打开的文件,所有文件都会在打开时有一个fd值。我们所谓的文件描述符就是数组下标,之后打开的文件会依次被赋予fd值。当然这是大致的了解。
我们现在就来看看文件描述符的底层逻辑:
我们的文件可以分为两种:1.磁盘文件 2.内存文件
磁盘文件:
就是我们保存在磁盘中的文件,这个文件会在进程调用它的时候进行加载到内存的操作,类似于我们的程序替换这样的加载器。这个文件的组成为:内容+属性,属性比如创建时间、文件大小、所属组、修改日期等等这样的信息 , 也称作 元信息 ,而内容就是我们打开所看到的内容。
内存文件:
当我们进程需要一个打开或是操作一个文件的时候,我们操作系统会为这个文件生成一个struct file 这样的文件描述结构体,其中大多包含了文件的元信息。一个个这样的结构体会形成双链表,让我们管理。在这之前,在进程创建的时候,还会生成一个struct files_struct 这样的结构体,这个结构体被pcb内的指针所指向,用来管理文件。其中这个结构体中有一个部分为一个指针数组,struct file* fd array[32] ,这里的每一个指针就会指向我们文件的struct file。而这里的数组下标就是我们所知道的文件描述符。其中这个文件描述符所在的数组是可以扩展的。所以我们每打开一个文件,都会有文件描述符指针指向它,并且它的指向是有特性的:也就是依次从小到大指。
我们都知道,一个系统中会有无数已经被打开的文件,所以我们通过这样的方式对文件进行管理。
当然这些结构体都处在内存中。
如果我们需要对一个文件进行操作,不是一下将磁盘中的全部文件拷贝到我们的内存中,而是通过缓冲区,延后式的加载到内存中让我们操作。
比如我们打开一个log.txt文件,并被赋予了3号fd , 我们现在read(3,xx,yy),此时执行到read这一步时才会通过我们的文件描述信息struct file 将文件加载到内存中。
此时如果我们close (1)这个stdout文件,那么我们在打开文件时,log.txt会被分配到 1号fd。
以上就是我们文件描述符的底层实现,它的分配规则就是从最小但是没有被使用的fd开始分配。
重定向的本质:就是修改文件描述符fd下标所对应的 struct file * 的内容。
比如原本 1 号fd 指向 stdout ,而现在让其指向了 log .txt ,这就叫做重定向



