Linux----基础IO
文章目录
复习C语言io知识
1 |
|
C 程序默认会打开3个输出流,stdin,stdout,stderr
stdin对应键盘,stdout对应显示器,stderr对应显示器
fputs(msg,stdout);//直接向显示器去写入
stdout是向显示器去输出
将原本应该显示到显示屏的内容,显示到了文件里面
本质是指把stdout的内容重定向到文件中
把原本应该打印在文件里面的打印在了显示器里面
fputs向一般文件或者硬件设备都能写入
磁盘也是硬件
同理
c++:cin,cout cerr
c语言的一切操作实际上都是在向硬件写入(所有语言上对“文件”的操作都要贯穿操作系统)
即最终都是访问硬件,
用户行为–>语言,程序,lib–>OS–>驱动–>硬件但是操作系统不相信任何人 ,所以访问操作系统是需要系统调用接口的
所以几乎所有的语言fopen,fclose,fread等等的底层一定是使用OS提供的系统调用
学习文件的系统调用接口
离OS更近,更能了解
文件打开
1 |
|
pathname就是要打开的路径名,flags就是我们打开的方式,mode就是打开的权限信息,
会返回一个文件描述符,int类型
flag是整数,:传递标志位,
int:32个bit位,一个bit,就代表一个标志,就代表一个标志位
0000 0000,如以最后一个标志位为0还是1,代表是读还是写
,以第一个标志位,代表是否创建文件
可以传多个,还快
1
if(O_WRONLY&flag)
判断结果,所以O_WRONLY O_rdnly O_CREAT
这些都是只有一个比特位为1的数据,而且都不重复#define O_WRONLY 0x1
#define O_RDONLY 0x2
等等如果想要得到两个以上的功能,我们直接|就可以了
这就是通过比特位的方式传多组标记的做法
文件关闭
1 |
|
我们没有输入第三个参数(权限参数),那么加入不存在这个文件,那么产生的文件的权限是乱的
文件描述符
1 |
|
1 |
|
我们发现是从3开始连续
文件描述符那么0,1,2去那里了呢
0:标准输入,键盘
1:标准输出,显示器
2:标准错误,显示器
0 1 2 3 4 5 6 7
我们可以联想到一个数组的下标
open的返回值是OS系统给我们的
文件与进程
所有的文件操作,表现上都是进程执行对应的函数
进程对文件的操作
- 要操作文件必须先打开文件
- 打开文件的本质:将文件相关的属性信息加载到内存
- 系统中会存在大量的进程,进程可以打开多个文件,系统中存在更多的打开的文件
那么OS 要把打开的文件管理起来,
(先描述再组织)
- 如果一个文件没有被打开,没有被创建,那么这个文件就在磁盘上,同理一个进程没有被打开,这个进程也在磁盘上面
- 如果创建了空文件(内容),要不要占磁盘空间呢,但是还有文件的属性,也是数据,,所以也是要占据磁盘空间,
磁盘文件=文件内容+文件的属性
对文件的操作:
- 对文件内容进行操作
- 对文件属性进行操作
管理的思路
先描述再组织
1 |
|
在进程里面有
struct task_struct
{
struct files_struct* fs;//地址,指向的就是其对应的内容
}
1 |
|
因为array是一个指针数组,所以可以用对应的下标找到对应的地址0,1,2
相当于fd_array[0]就指向了一个文件
所以0,1,2分别被标准输入,标准输出,标准错误文件给占用
每次生成一个文件,再内存里面就要形成一个struct file结构,在把地址填入到array下标处
而我们再write和read的时候,都要传入fd
- 执行write和read调用的是进程,而进程就能通过自己的PCB 找到对应的fs指针,找到files_struct里面根据文件描述符找到对应的文件,进行相关操作
fd
本质就是内核中进程和文件关联的数组的下标
一切皆文件
一切皆为文件
每一个硬件都有其对应的write和read的方法
虚拟文件,可以类比于多态,使用函数指针,就用指针调用对应的函数,这些函数调用这些硬件对应的方法,
多态就是实现一切皆()的高级方法
如我们在上层调用read/write的时候,就指向了对应的fd,再指向其对应的方法
1 |
|
我们直接向标准输出去书出
直接从键盘输入
1 |
|
文件描述符的分配规则
1 |
|
分配规则
每次给新文件分配的fd,是从fd_array中找到最小的,没有被使用的,作为新的fd
1 |
|
我们把1的标准输出给关了,
没有显示到显示屏中,而是显示到了文件中
这就是输出重定向
原因:我们把1的文件描述符给关了,所以现在给g.txt里面的fd就是1,
而printf里面对应的文件的fd一定是1,所以现在g.txt的fd为=1,就写到了g.txt
而printf和fprintf里面的都是有相关的fd,因为操作系统最大,
write
功能:向文件描述符写入,在buf的用户缓冲区里面,期望写入cout个字节
return:返回的是实际向文件里面写入多少个字节的内容
1 |
|
我们用write写入了5个hello,
read
功能:从文件描述符中读取指定内容,一次读取到的内容,都放到用户层缓冲区中,每次读取count个字节
return:如果count是我们期望读多少个字节,返回就是我们实际读取多少个字节
1 |
|
重定向
输出重定向
1 |
|
我们可以理解为把echo的1关掉,在把log打开,再把所有内容打印到里面
追加重定向
1 |
|
只在open的时候把append选项加上去
输入重定向
就是把原来从键盘里面读入的东西,现在从一个文件里面读
1 |
|
验证文件描述符
1 |
|
dup2
newfd是oldfd的一份拷贝,数组内容的拷贝,指针的拷贝
所以全部变成old,
输出重定向
1 |
|
shell中的重定向
echo “hello” > file.c
fork->child->dup2(fd,1)->exec(“echo,“echo”)
fork创建之后 子进程也有fd,而且文件描述符和父进程都一样
但是打开的那些文件不会新建,因为我们是在创建进程,
如果父进程打开了标准输入,输出,错误,子进程也会继承下去
因为bash是所有进程的父进程,而bash打开了标准输入,输出,错误,所以所有的子进程也都继承下去了
缓冲区
标准输出和标准错误
1 |
|
我们发现只有标准输出重定向到了文件里面,但是标准错误仍然打印在屏幕里面了
因为重定向只有fd=1的被弄进去,而fd=2不会被弄进去
1 |
|
把标准输出和标准错误都重定向进去
1 |
|
./redir >log.txt 2>&1
先执行前面第一条语句,此时把fd=1原本指向显示器改为指向一个特定文件,而fd=2的文件原本指向了显示屏,把1里面的内容拷贝到2里面,所以2也指向1指向的文件
缓存区
1 |
|
第一次可以显示出fprintf
close后,第二次不能输出这些内容
1 |
|
把fd关闭之后就没有显示内容了
c语言本身也
我们曾经说的缓冲区都是用户级缓冲区,都是语言层面
printf是向stdout写入(FILE*)–》(struct file)
我们使用printf和fprintf,我们并没有写到OS里面,而是写到c语言缓冲区,把c语言的缓存区刷新到操作系统,
我们在特定的情况下才会把数据刷新到内核缓冲区,
- 遇到\n的时候,会刷新到显示器上面,
- 进程退出的时候,会刷新FILE 内部的数据到OS,没有进程退出的时候数据还会在C缓冲区里面,
用户——>OS
刷新策略
- 立即刷新(不缓冲)
- 行刷新(行缓冲\n):如显示器打印,
- 全缓冲区满了才刷新(也就是不会溢出),比如,往磁盘文件里面写入
OS—> 硬件,也是同样使用的
如果发生了重定向,
显示器 --> log.txt
原本是行刷新(行缓冲),现在就变成了全缓冲
1 |
|
对于这个代码
如果不close的话,所有的信息都打印到了文件当中
没有close的话,所有的消息都直接输出到了c语言缓冲区里面,然后因为没有close掉fd,这批数据就会在进程退出的时候,数据就会刷新到内核,
把close放开,文件里面什么都没有,
所有的消息,刷新变成了全缓冲,有可能并没有被写满,说明可能没有立即被刷新到文件里头,而进程退出之前调用了close,就把文件描述符给关了,进程退出的时候数据还在缓冲区里面,来不及刷新到内核当中,所以文件里面就没有看到对应的内容
如果把close(1)关掉,那么printf把数据都刷新到用户缓冲区里头,行刷新,立马刷新了
如果在close之前
fflush(stdout),强制刷新缓冲区里面,就可以了写到文件里面了,
FILE*里面是有包含缓冲区的
1 |
|
我们发现我们即使close(1),仍然能够打印在显示屏上,因为三行刷新,有\n就刷新了
我们把内容重定向到文件里面,
发现只有write的1被写进去了
这是因为我们close(1),当重定向的时候原本要写到1里面的内容,显示到文件中,就从行刷新,变成了全缓冲,不立即刷新,而write是系统调用没有经过c语言缓冲区,就可以打印到文件里面,
1 |
|
我们如果2往显示器上打印,大家都正常,如果往文件里面打印,c接口会重复,系统接口不受影响
刷新策略变了,
写入文件里面的时候,就变成了全缓冲,数据就先写到了c语言缓冲区里面(不是操作系统提供的),
fork之后发生了写时拷贝,父进程写到了缓冲区里面,子进程也刷新到了缓冲区里面,当副进程退出的时候,就把数据i刷新出去,子进程也要刷新,所以因为写实拷贝的问题,出现了重复刷新
而如果在fork之前就把数据全部fflush出去的话,fork之后就不会发生写实拷贝,因为缓冲区里面没有数据了,可是write没有打印两个,