由于这部分的知识很多很多,分成两回
目录
1.文件描述符
文件描述符
1.文件描述符
首先我们看一下几个小问题
1.你真的理解文件原理和操作了吗?
这不是语言的问题,而是操作系统的问题
2.是不是只有C/C++有文件操作?
其他语言python,Java,go...也有文件操作,但是他们的文件操作方法不一样
那如何理解这个现象?有没有统一的角度看待所有语言的文件操作?
理解要学完本文,当然是有统一角度——操作系统角度(看完本文,你自然理解这个问题)
3.操作文件的时候第一件事情都是打开文件,打开文件是在做什么?如何理解?
本文的重点内容
4.文件=内容+属性,针对文件的操作时对内容的操作还是对属性的操作?
对内容+属性的操作
5.文件没被操作时,文件一般在什么位置?磁盘
6.当我们操作文件时,文件需要在哪里?
内存(为什么?因为冯诺依曼体系告诉我们的)
7.当我们对文件操作,文件需要被提前加载到内存,被加载的到底是内容还是属性?
至少要有属性
8.是不是只有你一个人在load?
不是,内存中一定有大量不同的文件属性
9.打开文件的本质:
将需要的文件属性加载到内存,OS内部一定会同时存在大量被打开的文件,那么操作系统要不要管理这些被打开的 文件?
当然,先构建在内存中的文件结构体 struct XXX{} 被打开的文件属性被load到文件结构体中
a.每一个被打开的文件都需要在OS内部对应的文件对象的struct结构体,可以将所有的结构体(其实这种结构体叫struct file)用某种数据结构连接起来,在OS内部,对被打开的文件进行管理 就 转换成对链表的增删查改
上面一段话简而言之:
被打开的文件,OS要为其创建对应的内核数据结构——struct file
struct file
{
//文件的各种属性
//各个file结构体之间的链接关系
}
10.文件其实可以分成两类:磁盘文件,被打开的文件(内存文件)
11.文件被打开,是谁在打开?
OS
是谁让OS打开?用户(进程为代表的)
12.我们之前所有的文件操作都是进程和被打开文件的关系
也就是struct task_struct 和struct file的关系
复习一下C语言的文件操作:
- 打开文件的方式
w:默认写方式打开,文件不存在则新建,如果只打开文件内容自动被清空,每次写入都会从最开始写
a:不会清空文件,而是每次从文件尾追加(也就是append)
- 复习几个C库函数
查三号手册 得
参数列表里面的...是可变参数列表,即参数酌情写
printf //默认向显示器打印
fprintf //
sprintf //向字符串打印
snprintf //和sprintf函数不同的是,他会限制写入str字符串的字符数量为size
1 #include <stdio.h>
2
3 #define LOG "log.txt"
4 int main()
5 {
6 const char * msg="hello wrt";
7 printf("%s\n",msg);
8 FILE * fp=fopen(LOG,"w");
9 fprintf(fp,"%s",msg);
10 char buf1[124]={0};
11 sprintf(buf1,"%s",msg);
12 printf("%s\n",buf1);
13 char buf2[124];
14 snprintf(buf2,4,"%s",msg);
15 printf("%s\n",buf2);
16 return 0;
17 }
结果是
很奇怪最后这怎么就三个字符输出
并且最后一次我们以为写入4个字符,但是只有 size - 1 个字符被写入缓冲区,最后一个字符为字符串结尾的空字符 \0
- fgets()
fegts函数把文件流内容写入到字符串s
这个函数遇到EOF(文件末)或者\n , 或者读到size-1个字符就停止读取
会自动在size个字符处填上\0
如果成功,该函数返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。
如果发生错误,返回一个空指针
- fputs()
把字符串s写入到文件流
该函数返回一个非负值,如果发生错误则返回 EOF(也就是0)
- fread
刚才我们在语言方面简单看了一下对文件的一些基本操作函数,现在看几个操作系统层面的系统调用
- open
该函数的返回值是被打开文件的文件描述符
浅浅看一下flags是什么
手册里还有更多,真的是头都大了
其实这些大写的flags都是宏,他是OS让用户给自己传递的标志位
表面上他看起来是整数,有32位bit,我们可以用一个bit表示一个标志位,所以一个int最多可以传递32个标志位
简单看一下这个原理
我定义了三个宏,他们在十六进制下二进制位刚好是错开的
重点体会这种位图可以传递信息的思想
所以,flags的参数是一堆宏,他的二进制位不重叠,一次可以传多个宏
但是发现用open打开的log.txt权限是乱码(根本没有s权限)
因为用open带上写选项(写的flags)之后,需要对写完之后的文件权限修改
也就是
之前学习过八进制语法:
八进制语法. chmod命令可以使用八进制数来指定权限
这里的mode_t就是输入对应的八进制数即可更改文件的权限
这里0表示接下来的数值是八进制表示
rwx:4+2+1=7
666表示文件的权限都设置成rw-
但是此时的文件权限还是原来的样子
是因为umask掩码,那么怎么可以在我自己的进程短暂改变掩码?
我自己的进程有个掩码,系统的掩码,到底这个进程的掩码听谁的?
就近原则,现在我在自己程序设置掩码,肯定是我自己的比系统的更亲
此时设置成功
再来看一下这个open函数的返回值是多少?
记住这个3~~~~~~~~~~~~~~~~~~~~~~~~~~~
- close
与open对应的就是close()
这个fd是文件描述符~~~究竟他是什么我们后面惠更清晰的讲解,刚才open的返回值就是fd=3
那么输入3给close,他就能找到对应的被打开的文件,从而关闭他
是不是感觉很神奇,fd就像密码一样
- write
把buf中n个字节内容写入到fd对应的文件中
最后返回实际写入的字符数,如果写入失败返回-1并且设置errno变量
如果成功返回值>=0
简单演示一下
要不要+1是考虑要不要加上\0
此时没+1
很明显是正确的
为什么不加?
因为字符串以\0结尾是C语言的规定不是文件的规定!!!!!
- read
把文件中的内容读到buf中,读取count字节的内容
最后的返回值还是实际读到的字节数,暂时我们无法按行读取只能整体读
在使用系统接口进行IO的时候一定要注意\0的问题
用之前的log.txt里面有hello\n
那么语言和我们今天讲的接口有什么关系?
库函数的底层调用:系统调用
小总结:
fopen,fclose,fwrite/fputs,fread/fgets ——库函数
open,close,write,read——系统调用
他们的关系式库函数封装了系统调用!!!!!!!!!!!
文件描述符
fd(file descriptor)
任何一个进程启动的时候默认会打开当前进程的三个文件
标准输入——键盘文件
标准输出——显示器文件
标准错误——设备文件——显示器文件
结果
说好的标准输出和标准错误都会向显示器打印,但是他们本质不一样
还记不记得我们之前在open的时候返回值fd=3
那么为什么是3,不是0 ,1,2
并且还发现
这些被打开文件的fd居然是连续的!!!!!!!
这让人不禁想起数组
因为我觉得这些被打开文件肯定也是有人帮我维护的,维护就需要一定的数据结构,那我猜测这种数据结构就是数组!
真的 是这样!
因为0 1 2已经被操作系统占用了。所以只能从3开始
文件描述符:本质是数组下标
是什么数组的下标?
当进程要打开一个文件时:
OS在内存中创建一个struct_file对象(为了把磁盘中的对应文件加载到内存)
找到是哪个进程要打开文件,用这个进程的struct files_struct* files找到struct files_struct 进而找到内部fd_array[]数组,从0下标向下遍历到一个没被占用的下标(这里很重要,星号标记*)
0 1 2默认被系统占用,找到3(假设)把创建好的struct_file对象的地址填入下标为3的数组中,向应用层返回文件描述符3!!!!
所以上层只要拿到3就可以像密码一样打开后面的很多函数(系统调用) ,但是作为用户他并不知道OS为他做了什么~
我们所谓的IO类(read,write函数)本质就是拷贝函数——用户空间和内核空间进行数据的来回拷贝
具体过程如下
那么为什么要用fd_argv[ ]里面放file* 的方法做映射?
实现模块解耦
如何理解一切皆文件?
下回分说~