Linux系统编程03

不带缓冲区的文件IO

利用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成功读取的字符数;=0EOF读到文件终止符; -1 报错
注意:
(1)count应当是申请内存的大小
(2)read前先清空buf

写文件
向文件中写入东西有两种方式,第一种是使用strlen,第二种是使用sizeof两种的区别就在于strlen写入方式是以字符串的方式进行写入,打开文件可以很方便让人阅读;而sizeof方式存入文件中是以数据流,ASCLL码的方式进行存储,不方便阅读,但是读取写入效率高
在这里插入图片描述

使用strlen向文件中写入数据
在这里插入图片描述
可正常查看
在这里插入图片描述
使用sizeof向文件中写东西
在这里插入图片描述
存储的内容无法看懂
在这里插入图片描述
但是我们只要使用对应的sizeof读取方法就可以成功将数据读出
在这里插入图片描述
在这里插入图片描述

实现cp命令

这里是引用
在这里插入图片描述
可以使用vimdiff比较两个文件是否相同
在这里插入图片描述

性能问题

buf越大越好,减少状态切换的次数,上面我们进行拷贝的时候需要从用户态节切换内核态,再内核态进行拷贝之后再切换出用户态,如果buf容量过小,会导致频繁切换状态,导致效率变低
同时我们还可以使用文件流进行辅助传输,我们现在用户态将文件传入到用户态的文件流中,如果文件流满再将文件流数据传入内核态进行拷贝
使用文件流
(1)优势:零碎的写入,少量的系统调用
(2)劣势:拷贝次数更多

文件的截断

ftruncate可以截断文件,可以固定文件的大小

在这里插入图片描述
其返回值为-1表示截断失败,返回值为0表示截断成功
传入参数length为截断的长度,会从文件头开始截断
在这里插入图片描述
在这里插入图片描述
当我们更改截断长度,将长度加长由30变成40
在这里插入图片描述
其文件成都也变成40
在这里插入图片描述
我们发现它为我们再文件末尾补了一些数据
在这里插入图片描述
使用:%!xxd命令可以查看得出再末尾补了0
在这里插入图片描述

大到小,截断末尾
小到大,末尾补0
文件空洞:一个文件显示有很大的内存空间,但是其真正使用分配到的磁盘空间很少,而很大一部分容量并没有分配磁盘空间,此时就会造成文件空洞

内存映射mmap

内存映射机制:在用户态空间分配一片空间,该内存直接和外设建立映射,使用*/[]进行读写内存 <—等价于—>读写文件
建立映射的限制:
(1)文件大小固定,不能改变(ftruncate)
(2)只能是磁盘文件
(3)建立映射之前先open
mmap函数
在这里插入图片描述
mmap会自动分匹配内存,如果第一个参数*addrNULL那么空间就会分配在堆上,否则内存空间就会分配到我们传入的指针上,申请空间使用完毕之后munmap进行空间释放
length长度要固定
prot保护文件,指定文件读写权限
在这里插入图片描述
flags映射的属性,直接填写MAP_SHARED
由于我们现在所写的程序时一个一个的进程,我们建立映射了之后,我们的进程就和外面的磁盘建立了映射;一个程序可以看到磁盘,另一个进程也可以看到磁盘,因为进程之间是共享磁盘数据的,那如果可以共享磁盘数据,那我们就也可以实现他们共享这一块映射区,MAP_SHARED的含义就是他们之间也可以共享映射区
在这里插入图片描述
fd表示映射的文件
offset直接写0
在这里插入图片描述
在这里插入图片描述

lseek

内核态和用户态都有文件缓冲区,但是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
dup2newfdoldfd指向同一个文件对象,如果newfd已经有指向,就会自动close
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

有名管道(named pipe/FIFO)

是进程间通信机制在文件系统的映射
通信传输方式分为:
单工 A—>B
半双工 A—>B ,B—>A不能同时进行通信
全双工 A<—>B

单工通信

在这里插入图片描述
在这里插入图片描述
现在我门面使用指令创建了一条管道,管道不能持久存储数据,只能缓存数据
用系统调用操作管道
在这里插入图片描述
我们启动了写进程,发现进程阻塞了,是因为我们没有开启读进程,因此管道会发生堵塞,堵塞是在open时发生的
在这里插入图片描述
当我们打开另一个管道进程,两个文件开始通过pipe进行读取数据
在这里插入图片描述

半双工通信

在这里插入图片描述
我们可以看到这样会导致chat1在等待chat2的写,chat2在等chat1的读,导致有限的资源被占用,而出现死锁,因此我们可以将源代码中O_RDONLYO_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,程序便会发生永久的阻塞,不会执行读操作也不会执行写操作
在这里插入图片描述
刚刚开始的时候一读一写
在这里插入图片描述
后面随着读区域占满变成两读一写
在这里插入图片描述

select实现原理

fd_set的本质是一个位图
其有1024bit,如果有对应的文件描述符需要添加监听,那么就会把相应下标的位图数据从0改为1
在这里插入图片描述
**调用select **

fd_set是定义在用户态的栈或栈帧里,rdset会从用户态拷贝一份发哦内核态,作为监听集合;每当我们的硬件产生响应,内核会检测到,这时候内核就会区检查 监听集合,同时监听集合也有一个参数nfds其存储这监听集合中最大的文件描述符的位置加1,因此这样就不用区循环查找整个1024大小的集合,只需要监听0 ~ nfds-1即可
当内核在集合中找到对应的文件描述符,如果找到就会将对应的改为1,这时候就变成就绪集合,此刻就绪集合传输给用户态
监听和就绪混在一起,使用的都是一个结构体,只是在不同的时期变成不同的涵义
这样做的劣势
(1)监听和就绪混合在一起,每一次select之前要把监听重新设置
(2)文件数量固定,不能更多,是固定的1024
(3)大量用户态和内核态的拷贝
(4)最严重的问题:寻找就绪文件浪费了大量时间,不管就绪还是没就绪都要轮询。解决方案:我们只将就绪的文件返回给用户态,而不是将所有的监听集合的就绪状态返回给用户

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值