赞
踩
利用
open
打开文件
pathname
:文件路径
flags
:存储文件的信息,如读写权限,标定如果打开不存在的文件需要报错还是直接创建文件,因此虽然这里的flags
是一个int类型的数据,但是我们要将其看作为一个32位的状态标记,每个属性某些位是1,其余是0,多个独立属性共存,使用按位或运算|
其返回值为文件描述符,如果返回值为负数,则为报错
flags
的选择
O_RDONLY
,O_WRONLY
,O_REWR
这三个选项只能三选一,三个属性是彼此互斥的
O_CREAT
如果flags中存在这个状态,那么我们就需要调用含有三个参数的open函数,并且最后一个参数为创建你的文件的初始权限
open打开文件例子
文件中不存在文件时报错,有文件之后打印输出此文件再内核态中的文件描述符
为3,因为0,1,2都已经被文件打开,关闭,创建指令使用了,所以其文件描述符
为3
使用三个参数的open例子
拥有O_CREAT
指令就可以创建文件,但是创建文件设置的默认权限会受到文件掩码的影响,导致我们创建出的文件权限和预设的不一致
O_EXCL
如果文件存在就不会再创建文件,open就会失败
O_TRUNC
清空文件内容
fopen
底层实质是调用了open
读写文件
读文件
*buf
是一个不固定类型的指针,他是一个传入传出参数,其原理就是read函数将文件描述符fd
传入内核态,内核态指针找到对应的文件对象,然后再将文件对象放入到用户态指针*buf
指向的区域
文本文件底层是ASCLL的序列,以字符形式读写,好处:可以直接看懂文本内容
count
是读取文字的上限
(优先)
二进制文件底层不是ASCLL的序列的文件,怎么写就怎么读,好处:所占的空间小
read返回值:>0
成功读取的字符数;=0
EOF读到文件终止符;-1
报错
注意:
(1)count应当是申请内存的大小
(2)read前先清空buf写文件
向文件中写入东西有两种方式,第一种是使用strlen
,第二种是使用sizeof
两种的区别就在于strlen
写入方式是以字符串的方式进行写入,打开文件可以很方便让人阅读;而sizeof
方式存入文件中是以数据流,ASCLL码的方式进行存储,不方便阅读,但是读取写入效率高
使用
strlen
向文件中写入数据
可正常查看
使用sizeof
向文件中写东西
存储的内容无法看懂
但是我们只要使用对应的sizeof
读取方法就可以成功将数据读出
可以使用vimdiff
比较两个文件是否相同
buf越大越好,减少状态切换的次数,上面我们进行拷贝的时候需要从用户态节切换内核态,再内核态进行拷贝之后再切换出用户态,如果buf容量过小,会导致频繁切换状态,导致效率变低
同时我们还可以使用文件流进行辅助传输,我们现在用户态将文件传入到用户态的文件流中,如果文件流满再将文件流数据传入内核态进行拷贝
使用文件流
(1)优势:零碎的写入,少量的系统调用
(2)劣势:拷贝次数更多
ftruncate
可以截断文件,可以固定文件的大小
其返回值为-1
表示截断失败,返回值为0
表示截断成功
传入参数length
为截断的长度,会从文件头开始截断
当我们更改截断长度,将长度加长由30变成40
其文件成都也变成40
我们发现它为我们再文件末尾补了一些数据
使用:%!xxd
命令可以查看得出再末尾补了0
大到小,截断末尾
小到大,末尾补0
文件空洞:一个文件显示有很大的内存空间,但是其真正使用分配到的磁盘空间很少,而很大一部分容量并没有分配磁盘空间,此时就会造成文件空洞
内存映射机制:在用户态空间分配一片空间,该内存直接和外设建立映射,使用
*
/[]
进行读写内存 <—等价于—>读写文件
建立映射的限制:
(1)文件大小固定,不能改变(ftruncate)
(2)只能是磁盘文件
(3)建立映射之前先open
mmap函数
mmap会自动分匹配内存,如果第一个参数*addr
为NULL
那么空间就会分配在堆上,否则内存空间就会分配到我们传入的指针上,申请空间使用完毕之后munmap
进行空间释放
length
长度要固定
prot
保护文件,指定文件读写权限
flags
映射的属性,直接填写MAP_SHARED
由于我们现在所写的程序时一个一个的进程,我们建立映射了之后,我们的进程就和外面的磁盘建立了映射;一个程序可以看到磁盘,另一个进程也可以看到磁盘,因为进程之间是共享磁盘数据的,那如果可以共享磁盘数据,那我们就也可以实现他们共享这一块映射区,MAP_SHARED
的含义就是他们之间也可以共享映射区
fd
表示映射的文件
offset
直接写0
内核态和用户态都有文件缓冲区,但是
fseek
作用的是用户态文件缓冲区的ptr
,而lseek
作用的是内核态文件缓冲区的ptr
相当于直接修改磁盘
lseek
也会产生文件空洞,因为其指针直接修改磁盘中的存储空间
文件描述符0代表
stdin
,1代表stdout
,2代表的是stderr
printf
对应的fd是1
实现输出重定向,如果我们先close(1)
将一号文件描述符关闭,在open一个文件,那么输出流就不会在向显示屏幕打印输出,而是将是输出流重定向到文件上,会将输出的信息输出在文件中
我们有另外的需求,我们已经把文件打开了,还没有将1号文件描述符关闭,但是我们还想要实现重定向,让1号文件描述符不指向屏幕,想让新的文件描述符fd
和1号文件描述符都指向新的fd
文件描述符
文件描述符的复制可以实现这个需求
(1)数值上不同
(2)偏移量共享
dup
选择一个最小可用的fd
,和oldfd
指向同一个文件对象
dup
可以让两个文件描述符指向同一个文件对象,内核里面的偏移量也是共享的,也就是说调用两个fd
进行写操作,都是在后面拼接,不会覆盖
当其中一个文件描述符关闭文件,关闭和内核中内存的链接,那么内存中的空间也还是不会释放,因为还有其他的文件描述符在连接,其实在内核中对于每个文件对象有一个专门的计数器,用来存储有多少个文件描述符指向自己,当指向自身的文件描述符增加或减少,相应数值也增加或减少,当这个数值减到0时,就会释放内存,这个计数器我们叫做引用计数
dup2
dup2
让newfd
和oldfd
指向同一个文件对象,如果newfd
已经有指向,就会自动close
是进程间通信机制在文件系统的映射
通信传输方式分为:
单工 A—>B
半双工 A—>B ,B—>A不能同时进行通信
全双工 A<—>B单工通信
现在我门面使用指令创建了一条管道,管道不能持久存储数据,只能缓存数据
用系统调用操作管道
我们启动了写进程,发现进程阻塞了,是因为我们没有开启读进程,因此管道会发生堵塞,堵塞是在open时发生的
当我们打开另一个管道进程,两个文件开始通过pipe进行读取数据
半双工通信
我们可以看到这样会导致chat1
在等待chat2
的写,chat2
在等chat1
的读,导致有限的资源被占用,而出现死锁,因此我们可以将源代码中O_RDONLY
和O_WRONLY
两个创建读写操作的顺序调换位置就可以实现两个进程之间使用两个管道进行读写
半双工通信实现两个进程相互通信
这样的半双工通信我们可以看到,它实现很简单,并且实现两个进程之间通信也只能实现对面回一句我接收一句在发送我的信息,但是我们像一次可以发送多条,这样子就需要借助IO多路复用技术来实现全双工通信
IO多路复用思路
使用一个助手帮我们循环的来轮询是否有进程发送的消息,如果有消息我们直接通过助手可以看,不必要我们一直在等待某个进程管道发送的信息
select
fd_set
"小助手"监听集合
使用select帮助我们实现管道通信的监听实现步骤
(1)创建fd_set
(2)设置合适的监听a.FD_ZERO
清空b.FD_SET
加入监听
(3)调用select函数,会让进程阻塞
(4)当监听的fd中,有任何一个就绪,则select就绪
(5)轮流询问(轮询)所有监听的fd,是否就绪,使用FD_ISSET
进行轮询
从上面我们可以看出我们可以接收对方的多条数据,我们也可以重复发送很多条数据给其他进程
聊天的关闭
上面的例子当我们需要关闭管道,停止聊天时,我们会发现另一端会在一直循环输出内容,另一端就会一直处于死循环;
写端先关闭
,读端read会读到EOF,然后读端就会就绪,进行轮询
读端先关闭
写端继续write会导致进程崩溃,收到一个SIGPIPE信号
下面我们对关闭聊天进行改进,我们使用ctrl+d
会用stdin
向控制台输出一个结束字符串
select的超时
timeout
是一个传入传出参数,每次循环开始时,要设置timeout
使用select
的返回值来区分超时导致的就绪
一个管道至少存在两个文件对象,一个
read
文件对象,一个write
对象,
程序在输出到16的时候就停止了,触发了写阻塞,如果想要实现写操作持续下去,就需要将管道里的数据读取出来,才能继续向管道里写数据
write
为空写就绪read
为空读就绪
read和write都有可能发生阻塞
如果读的速度没有写的快,会导致读写永久阻塞,避免永久阻塞------>使用select监听读和写使用select监听读和写
我们可以看到管道的大小为
4096
,select认为写就绪的条件写缓冲区为空,当我们满足写就绪是,我们要往管道里写4097大小的数据,这时候写到4096管道就已经满了,就无法往管道里面写数据,因此就会发生阻塞
将23行写操作write(fdw,buf,4096)
改成4097
,程序便会发生永久的阻塞,不会执行读操作也不会执行写操作
刚刚开始的时候一读一写
后面随着读区域占满变成两读一写
fd_set
的本质是一个位图
其有1024bit,如果有对应的文件描述符需要添加监听,那么就会把相应下标的位图数据从0
改为1
**调用select **
fd_set是定义在用户态的栈或栈帧里,rdset会从用户态拷贝一份发哦内核态,作为监听集合;每当我们的硬件产生响应,内核会检测到,这时候内核就会区检查 监听集合,同时监听集合也有一个参数nfds
其存储这监听集合中最大的文件描述符的位置加1,因此这样就不用区循环查找整个1024大小的集合,只需要监听0 ~ nfds-1
即可
当内核在集合中找到对应的文件描述符,如果找到就会将对应的改为1,这时候就变成就绪集合,此刻就绪集合传输给用户态
监听和就绪混在一起,使用的都是一个结构体,只是在不同的时期变成不同的涵义
这样做的劣势
(1)监听和就绪混合在一起,每一次select之前要把监听重新设置
(2)文件数量固定,不能更多,是固定的1024
个
(3)大量用户态和内核态的拷贝
(4)最严重的问题:
寻找就绪文件浪费了大量时间,不管就绪还是没就绪都要轮询。解决方案:我们只将就绪的文件返回给用户态,而不是将所有的监听集合的就绪状态返回给用户
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。