目录
一、文件基础
二、常见的C语言文件接口
三、系统文件接口
四、理解语言与系统文件操作的关系
五、如何理解一切皆文件
六、文件标识符再理解
一、文件基础
一个空文件,也会占用磁盘空间,这是因为文件不仅仅有存放在里面的内容,还有属性,比如文件名。创建时间,文件权限等。
文件 = 内容 + 属性。我们对文件的操作,无非是对内容或者对属性的操作。我们知道,=文件是存储在磁盘中的,如果我们打开文件,需要将文件加载到内存中,这样CPU才可以对文件进行处理。打开文件的本质,就是将文件加载到内存。
操作系统在运行过程中,可能会打开很多文件,因此操作系统要对打开的文件进行管理。管理的本质操作就是先描述在组织。因此在内核中一定存在已打开文件的结构体,他们依次用链表存储起来,操作系统对文件的管理转为对链表的增删查改。
今天我们着重学系的就是进程与被打开文件的关系。
二、常见的C语言文件接口
- fopen() —— 打开文件;
- FILE * fopen ( const char * filename, const char * mode );
- fclose() —— 关闭文件;
- int fclose ( FILE * stream );
C语言写入函数
C语言读取函数
使用fputs写入函数测试下。
其中“w”权限是写入,没有文件就先创建文件,在写入前会先清空文件内容。在linux中还有一个方法可以进行w写入,就是输出重定向。>
还有“a”,追加方式,他不会在写入前清空文件内容,而是在文件内容末尾继续追加新内容。linux中的追加重定向为 >>
重定向这里先了解一下,后续会继续讲解。
三、系统文件接口
我们知道,打开文件是要将文件加载到内存中,我们之前的操作是使用进程打开文件,进程是没有这么大的权利的,他肯定是调用了操作系统给我们的系统接口。虽然刚刚我们没有调用系统接口,用的是C语言给我们的接口,但其实,C语言打开文件的接口,底层是封装了系统调用接口的!!
系统接口open,第一个参数为路径+文件名,第二个参数是以什么样的方式打开,第三个参数对已创建的文件可以不填,对未创建的文件表示对文件设置什么权限。创建成功返回值为文件描述符file descriptor(简称fd),创建失败返回值为-1。
主要方式如下
- O_RDONLY:只读模式
- O_WRONLY:只写模式
- O_RDWR:可读可写
- O_APPEND 表示追加,如果原来文件里面有内容,则这次写入会写在文件的最末尾。
- O_CREAT 表示如果指定文件不存在,则创建这个文件
- O_EXCL 表示如果要创建的文件已存在,则出错,同时返回 -1,并且修改 errno 的值。
- O_TRUNC 表示截断,如果文件存在,并且以只写、读写方式打开,则将其长度截断为0。
这些方式是通过位图来实现的,比如O_RDONLY为00000001,O_WRONLY为00000010,O_RDWR为00000100,如此类推,需要使用指定的方式,我们直接进行或运算就好。
运行结果如下,我们创建了log.txt文件,但权限为664,而不是我们代码中的0666,这是因为我们系统权限掩码为0002的原因,0666 &(~0002)= 0664。
写数据的系统接口为write,第一个参数代表往那个文件中写入(是open打开文件获取的文件描述符fd),第二个参数需要写入的字符串,第三个参数为写入的字节数。返回值为实际写入了多少字节的数据。
写入代码
成功写入
我们使用的为O_WRONLY | O_CREAT,只写的方式,没有文件就创建。因此如果再进行写入,并不会将文件内容清空,而是进行覆盖。
如下将str进行修改,再执行命令,查看log.txt的内容,发现good覆盖上了hell 。
如果我们打开方式 | O_TRUNC,就会在打开前先清空文件。
如果我们打开方式 | O_APPEND,会从文件结尾处开始写入,这是追加(不清空文件)。
四、理解语言与系统文件操作的关系
经过前面的学习,我们知道C语言文件的处理底层是封装了系统调用接口的,但是C语言fopen返回的是FILE* 指针,而系统open返回的是int类型整数fd,后续也是通过fd来对指定文件做处理的。
那么fd存放的内容是什么呢?我们通过如下代码打印出来看。
发现fd从 3 开始,是连续的小整数。 为什么不从0开始呢,0 1 2这三个去哪里了。如下
进程在运行的时候,默认是把这三小只打开的。但是这三小只不是硬件嘛,怎么跟文件扯上关系了,因为Linux下,一切皆文件。(后面会讲)
我们是通过进程对文件进行处理,那么进程的task_struct里面存在一个files_struct指针指向文件描述符表,里面有很多数据,其中有一个struct file* fd_array[]的数组指针,他指向被打开的文件结构体,这个索引就是文件描述符。如下图所示
但我们查看C标准库,发现这三个变量类型为FILE* 。
File是C语言提供的结构体类型,fopen的底层是open,那么File结构体里面必定封装了文件描述符。我们通过如下代码也能印证一番
操作系统默认将stdin stdout stderr 打开,就是为了让程序员方便进行输入输出代码。就可以直接scanf 和 printf进行输入和显示。我们C语言第一个头文件基本都是stdio.h,stdio就是标准输入输出,C++第一个头文件 iostream,输入输出流。
五、如何理解一切皆文件
操作系统要管理硬件,也是先描述在组织,会将各种硬件使用结构体组织起来,不同的硬件,读写的方法肯定是不一样的,但是struct file里的接口read和write函数指针会指向对应硬件的读写。这样从统一的视角去看待不同硬件。我们也就理解了为何文件先会打开0 1 2这三小只。
六、文件标识符再理解
我们知道了0 1 2号文件标识符分别为stdin,stdout,stderr,也知道了linux一切皆文件,那么我们尝试下使用 read与write + 文件标识符 进行读写来代替(scanf和printf)。
read函数第一个参数fd为文件标识符,第二个参数为读取的数据保存到buf缓冲区中,第三个参数为读取的字节数,返回值为带符号整数(实际读取字节数,失败返回-1)。
write函数第一个参数fd也为文件标识符,第二个参数为从buf缓冲区中写入数据到文件,第三个参数为写入的字节数,返回值为带符号整数(实际写入字节数,失败返回-1)
代码如下,从0(stdin)中读取输入到buffer,从buffer写入数据到1(stdout)中。
成功输出我王慕霸没有开挂!!!!
我们知道,打开文件fd是从3开始,按顺序依次累加。如果我们先关闭某一个或几个fd文件,那么新打开的文件还是从3开始吗?
使用下面代码测试下
发现是从索引0开始,往后依次查找空位进行放入,比如fd1找到0不存在,就放入到0位置,fd2发现0 1 2都存在,3不存在,就放入3位置。
如果你关掉了1,虽然fd也会放入1位置,但是不会打印,因为1号为标准输出,关闭还打印个啥。