文章目录
- 基础IO要讲的知识点介绍
- fd周边问题
基础IO要讲的知识点介绍
1.复习一下C语言的接口
2.直接使用系统接口
3.分析系统接口的细节,引入fd(文件描述符)
4.fd的周边问题(fd的理解、fd和file的关系、fd分配规则、fd重定向…)
fd周边问题
我们前面学了fd,那么fd到底是什么?
进行访问文件必须先打开文件,那么一个进程可以打开多个文件吗?可以的!
一般而言进程 : 打开的文件 = 1 : n的关系
。
文件要被访问,前提是加载到内存中,才能被直接访问!
因为进程 : 打开的文件 = 1 : n的关系,那么如果多个进程都打开自己的文件呢?
系统中就会存在大量被打开的文件!
所以OS要不要把如此多的打开文件管理起来?要
如何管理?先描述,再组织!
在内核中如何看待打开的文件?
OS内部为了管理每一个被打开的文件,所以为每个打开的文件构建了struct file。
创建struct file的对象充当被打开的文件,如果有很多呢?
再用双链表组织起来
进程和文件的对应关系如下图:
再具体点
所以fd本质就是一个数组的下标的。
文件分两类:
1.被打开的文件(内存文件)
2.没有被打开的文件(磁盘文件)
而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。**每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!**所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。
fopen和fwrite的底层调用逻辑
fopen->open->fd(用fd封装FILE文件)->FILE->*FILE**(返回给)->fopen
fwrite->FILE*->fd->write->write(fd,.....)->进程自己执行OS内部的write方法->能找到进程的task_struct->*files->file_struct->fd_array[fd]->struct_file->内存文件被找到->开始操作
理解了什么是fd接下来就要知道fd的分配规则到底是什么。
我们看到关闭0号文件,它占用的就是0号描述符。
所以fd的分配规则:最小的,没有被占用的文件描述符
输出重定向
运行结果
我们内容确实达到显示器上了,但是如果我们关闭1,显示器也就关闭了,这内容会打向哪里呢?
我们看到内容确实没打到显示器上。
那么打印到了哪里呢?
打印到了log.txt中,这就是输出重定向。
原理:
而接下来向1中写的内容不会写到stdout中只会写到log.txt中,这就是重定向的原理。
重定向本质:在OS内更改fd对应的内容指向!
输入重定向的演示:
#include <stdio.h>2 #include <string.h>3 #include <unistd.h>4 #include <sys/types.h>5 #include <sys/stat.h>6 #include <fcntl.h>7 8 int main()9 {10 //默认打开0 1 2 关闭0号文件描述符11 close(0);12 int fd = open("log.txt",O_RDONLY,0666);13 if(fd < 0)14 {15 perror("open");16 }17 else18 {19 char buffer[64];20 while(fgets(buffer,sizeof(buffer),stdin) != NULL)21 {22 23 fprintf(stdout,buffer,NULL); 24 }close(fd);39 }40 return 0;41 }
运行代码
我们看到本来应该从stdin读取的内容变成从log.txt里面读取。
追加重定向:
我们只要把输出重定向的O_TRUNC改成O_APPEND即可。
这里大家自己更加,我便不演示了。
我们之前都是自己手动关闭0,1的,这样太挫了,下面就有个函数帮助我们。
问题:
dup2拷贝是在拷贝什么?拷贝的是文件描述符数组里面的指针。
谁是谁的拷贝?
是把new拷给old?
还是把old拷给new?
看英文可知是把:oldfd拷贝给newfd。所以最后newfd里面的指针和oldfd一样
理解:
输出重定向更改
运行代码
追加重定向更改
运行代码
输入重定向这里就不更改了,逻辑一样大家自行更改即可。
如何理解一切皆文件呢?->Linux的设计哲学->体现在OS的软件设计层面!
LinuxC语言写的!如何用C语言实现面向对象,甚至是运行时多态呢?->struct类但是我们知道struct类是没有成员函数的那么如何实现呢?函数指针!
底层不同的硬件,一定对应的是不同的操作方法!但是上面的设备都是外设,所以每一个设备都核心访问函数都可以是read,write的I/O操作。
所有设备都可以有自己的read和write,但是代码的实现一定是不一样的。
所以在struct file的这层往上就没有任何硬件的区别了
看待所有文件的方式,都统一成为了struct file->所以就有了Linux下一切皆文件的说法。
接下来就是缓冲区的理解:
1.什么是缓冲区?就是一段内存空间
(这个空间谁提供?用户?语言?OS?)
2.为什么要有缓冲区?
故事:
小明在云南大学想给远在北京邮电的小华送一些书过去。
送书有2种方法:
1.自己骑车送过去(写透模式(WT) 成本高,花时间多,慢)
2.快递发送点
这是写回模式(WB) 成本低,花时间少,快
那么顺丰是立马发送吗?
不是,是快递累积到一定数量后再发送
在这个故事里的顺丰就是典型的缓冲区的意义:目的是体改整机的效率
类比:
顺丰:缓冲区
小明:用户
小华:磁盘
书: 数据
缓冲区主要是为了提高用户的响应速度。
缓冲区刷新策略:
顺丰发送策略对应缓冲区的刷新策略:
1.立即发送(立即刷新)
2.架子上的快递一行一行发送的行发送(行缓冲)
3.快递架子满了在发送(全缓冲)
特殊情况:
1.用户强制刷新(fflush)
2.进程退出
缓冲策略 = 一般加特殊
那么缓冲区到底谁提供的?
C?OS?
写一段代码:
运行代码
我们发现把内容打印到stdout的内容重定向到log.txt中,log.txt中
C语言提供的接口打印了两次
OS提供的接口打印了一次
接来下我们把fork注释掉看一下,运行结果
这里说明一个问题,这个现象绝对个fork有关。
那么为什么OS提供的接口打印一次,C提供的接口打印两次下次再说。