大家好呀,我是残念,希望在你看完之后,能对你有所帮助,有什么不足请指正!共同学习交流哦!
本文由:残念ing原创CSDN首发,如需要转载请通知
个人主页:残念ing-CSDN博客,欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
📣系列专栏:[残念ing 的【Linux】系列专栏——CSDN博客]
目录
前置预备知识回忆
根据之前的学习我们知道:
1 文件=内容+属性
2 访问文件之前,都必须先打开它(fopen)!因为文件没有被打开时是在磁盘上的,(当文件执行到fopen后文件才会被打开,访问文件的时候其实是进程在访问的,而进程是在内存上的,通过cpu来执行的,因为cpu可以直接读写内存)根据冯诺依曼体系,cpu不能直接访问磁盘。打开的本质就是将文件(内容或者属性)加载到内存中!
我们思考一个问题,一个进程可以打开多个文件,多个进程也是一样的,我们知道这些进程都是要被操作系统管理(什么时候加载,加载到哪里,状态是什么…)那么OS也要管理加载到内存中的文件。那要如何管理文件呢? 先描述,在组织
猜测:在内核中,文件=文件的内核数据结构+文件内容
其实我们研究打开的文件,是在研究:进程和文件的关系。
fopen的一些参数
x:如果文件不存在,就会新建,但是如果文件存在且里面有内容,会先把文件清空
a:追加文件内容
进程默认会打开三个输入输出流
stdin:标准输入—键盘
stdout:标准输出—显示器
stderr:标准错误—显示器
我们在之前所用到的C文件接口,底层一定要封装文件类系统调用
学习目标
1 什么是宏
2 open
3 文件写入与二进制写入的区别
4 IO的基本过程
5 根据这些知识简单的实现一下stdio.h
1. 什么是宏
宏就是只有一个比特位为1
2 open
注意:如果我们需要新建文件时往往采用三参数的open,如果打开文件就只读,写,但是文件已经存在了,一般使用两参数的open,如果硬是要用两参数open,那么创建的文件的权限就会乱码。
return:文件描述符
O_RDNLY:打开文件
O_CREAT:不存在就创建
O_WRONLY:以写的方式打开文件
O_TRUNC:有数据就给他清空
O_APPEND:追加
我们设置的是0666权限为什么会出现0664权限呢?
因为系统中会有一个umask(0002)会去掉oser的写权限
利用umask函数就可以改回来了
补充:系统中的umask是没有变的,如果你在进程启动的时候自己手动设置了umask权限,那么就会采用就近原则来直接使用用户的umask
2.1 关于open的返回值
关闭文件
对打开的文件进行读
对打开的文件进行写
以上这些的结论:C库函数所使用的这些(fopen、fclose、fread、fwrite)都是封装了系统调用(open、close、read、write)的接口
int fd1 =open("log.txt",O_WRONLY |O_CREAT |O_TRUNC,0666);//本质与w一样
int fd1 =open("log.txt",O_WRONLY |O_CREAT |O_APPEND,0666);//本质与r一样
2.2 关于fd的问题
在系统层面上,fd是访问文件的唯一方式
2.3 linux下一切皆是文件吗??
这些键盘、显示器、网卡等硬件,根据冯诺依曼定理都叫做外设,这些外设中的属性类别是一样的,值有可能不相同,但是要知道它们的IO方法一定是不同的。
通过以上图还有分析我们可以知道:所谓一切皆文件,其实就是通过函数指针来屏蔽底层硬件操作上的差异
3 文本写入和二进制写入
我们来看一下当运行下面的代码时,会按我们的预期一样打出我们的 12345 吗?
我们看到了没有达到我们想要的效果,其实显示器也可以叫做字符设备,当我们把代码改了后(先让snprintf将a先写入到字符数组中,然后再调用write将字符数组写到显示器上)
那又为什么我们都有write了还要printf、fprintf等这些接口呢?其实就是因为我们在很多情况下我们需要进行把我们内存级别的二进制数据转换为我们的字符串风格,然后通过read打印到我们的显示器上,这一整个过程就是格式化的过程。
所谓上面这些操作,就是为了:
1 方便用户操作
2 提高语音的可移植性
总结:文本写入和二进制写入是没有区别的,在系统层面上只有二进制写入,因为read 在系统调用的参数是(void*)所谓的格式是用户层自己维护的。所以文本写入和二进制写入由用户决定。
4 IO的基本过程——文件内核缓冲区——重定向
4.1 文件内核缓冲区
进程打开文件,需要给文件分配新的fd,fd的分配是有规则的,一般都是先分配最小的,没有被使用的fd
4.2 重定向
重定向原理:更改文件描述表特点下标里面的内容(文件对象的地址),更改后就可以自动完成重定向,但是在这过程中,上层是毫不知情的。
重定向也有自己的接口,我们可以通过这个接口来进行重定向。
4.2.1 输出重定向
4.2.2 追加重定向
4.2.3 输入重定向
程序替换会不会影响重定向呢?
答案是会的
其实在调用系统调用时,也是有成本的(时间或者空间)
4.3 补充(用户级缓冲区)
在用户级层面上的缓冲区中,存在几种人为可以控制的刷新方式
1 显示器文件–行刷新(\n)
2 普通文件,缓冲区打满再刷新
3 不缓冲
只要将数据从用户级缓冲区拷贝到文件的内核缓冲区里,我们就任务把当前我们对应的文件已经写给了操作系统。只要交给了操作系统,那么就跟客户无关了。
内核缓冲区中的数据,刷新到外设的方式是操作系统自己决定的。除非出现特殊情况,当文件关闭时,就会强制刷新。当一个进程在退出的时候,会自动刷新自己的缓冲区(所有的FILE对象内部,包括stdin、stdout、stderr)
如果我们想强制的把数据刷新到外设系统也为我们提供了相应的接口函数
根据上面知识的了解,我们来看一个程序
我们看到重定向后
运行的代码结果:
我们惊奇的发现只有C语言的库函数被打印了两次,而系统调用只被打了一次。
出现这样结构的原因是:虽然printf等后面都跟了\n,但是我们在这之前做了重定向,重定向后缓冲区的行刷新变成了普通文件刷新(没有满,没有刷新),就一直在缓冲区中,而系统调用的时候就直接写到write里面了,而缓冲区里面还有内容时,调用了fork后,子进程进行显时拷贝,父子各自结束各自调用自己的刷新(彼此之间互不影响),这就是为什么C库函数会在重定向文件中打印两次的原因。
5 简单的实现一下stdio.h
接下来我们来根据上面所有的理解来自己实现一下stdio.h
我们来简单的运用一下
运行结束后查看log.txt文件
当我们把 \n 去掉后
运行时我们看到数据并没有写给操作系统,没有写到内核级缓冲区里,写到了FILE结构当中,在语音层充当语音层缓冲区,当进程结束后才统一刷新到内核级缓冲区。
当我们调用系统调用,写到操作系统的内部
接口作用:将内核中的文件数据强制刷新到存储设备上
版权归原作者 残念ing 所有, 如有侵权,请联系我们删除。