目录
10、为什么文件描述符的属性信息需要用按位或的方式进行设置?(*****)
一、为什么需要进行进程通信呢?
1.1进程具有独立性
每一个进程的数据都是存储在物理内存当中的,进程通过自己的虚拟地址空间去进行访问,访问的时候通过各自的页表的映射关系,访问到物理内存当中去
从进程的角度看,每个进程都认为首己拥有4G(32位下)的空间,至 于物理内存当中属于如何存储,页 表如何映射,进程是不清楚。 这 也造就了进程的独立行。
1.2进程独立性的优缺点:
优点:让每个进程运行的时候都是独立进行的,数据不会窜
缺点:如果两个进程想要进行数据交换时,由于进程的独立性,就不是那么的方便了
所以: 进程问通信本质上是进程和进程之间交换数据的手段。
1.3进程通信的手段:
管道,共享内存,消息队列,信号量,网络
其中网络是最大的进程间通信方式
二、匿名管道
1.1管道符号
ps aux | grep 文件名
1.2管道的本质
管道在内核里是一块缓冲区,供进程进行读写、数据交换
1.3管道的接口
- 1.3.1理解参数的含义 程序猿通过该函数创建一个匿名管道
- int pipe ( int pipefd [2] );
- 参数:
- 参数为出参,也就是pipe fd【0】【1】的值是p ipe函数进行填充的,调用者进行使用
- pipefd是数组,有两个元素
- pipefd[0] :管道的读端
- pipefd[1] :管道的写端
为什么pipefd[0]和pipefd[1]能称为 管道的读写两端?
因为这两个元素保存的内容是文件描述符
- 返回值:
- 0 : 创建成功
- -1 : 创建失败
1.4从PCB角度理解管道
1.5代码验证
1.5.1验证pipe函数
1、验证pipe函数是出参
可以发现,调用pipe函数之前,数组元素的值是随机的,调用之后,值就变位了3和4
2、验证fd[0],fd[1]保存的是文件描述符、
我们可以让程序一直不退出,在去查看/proc/【pid】/fd下面去查看是不算多了对应的文件描述符
可以发现,验证成功
3、利用write和read来进行验证读端和写端
可以发现,验证成功
4、父子进程进行通信
父子进程要通过匿名管道进行通信,核心点在于父子进程都要能够读写通道,即父子进程都要有管道读写两端的文件描述符
因为子进程是要拷贝父进程的PCB的,所以父子之间进行通信,一定是先创建管道再创建子进程的,否则子进程就没有管道的读写两端的文件描述符
代码实现:
目标:
实现父子进程的通信
做法:
1、父进程创建管道
2、父进程创建子进程
3、父进程在他的代码逻辑中写
4、子进程在他的代码逻辑中读
代码如下:
可以发现,每次运行,子进程都能读出来父进程写入的内容
那么就要疑问了:
原因:
当管道当中没有数据的时候,子进程调用的read函数去管道当中进行读的时候,会阻塞,直到管道当中有内容,read函数读回来之后,read才返回
怎么验证:
pstack 子进程进程号,查看子进程在干什么
我们让一进入父进程就sleep(1000),再去看看子进程调用read函数的状态是不是阻塞的
我们可以看到确实是阻塞的状态
三、匿名管道的特性
1.半双工
数据只能从写端流向读端,单向通信
2.只能支持有亲缘关系的进程通信
没有标识符,其他进程是没有办法通过标识符找到这个匿名管道对应的缓冲区,只能具有亲缘性关系的进程进行进程间通信:因为其他进程没有管道的读写两端的文件描述符,所以这就要求父进程先创建管道再创建子进程
3、管道的生命周期是跟随进程的
进程退出了,管道在内核当中就被销毁了
4、管道的大小是64K
怎么验证:
如果没有人读,疯狂往管道里面写,会发生什么呢?
运行一下:
1、我们可以看到最终写65536字节写满了也就是64kb
2、write往管道中写的时候阻塞掉了
这时候使用pastack去查看
结论:当管道写满之后,再调用write函数往管道去写的时候,write会阻塞
5、管道提供字节流服务
1、先后两次写入管道的数据之间没有间隔
2、数据是被读走的,而并非拷贝走的
6、pipe_size
pipe_size 的大小是4096,并非是管道的大小(pipe_buffer : 64k)
什么是原子性?
原子性这个词是诞生于1980年代,那个时候世界上最小的物质是原子,所以起名为原子性
原子性:意味着不可以分割,已经是最小的单位了
原子性:在计算机里面表示非黑即白,不存在中间状态,一个操作要么执行到底,要么就不执行
7、阻塞属性
读写两端的文件描述符初始的属性为阻塞属性
- 当write一直调用写,读端不去读, 则写满之 后write会阻寒
同理,当read一直进行读,当管道的内 部被读完之后,则read会阻寨
8.、非阻塞属性
第一件事情:设置非阻塞属性
第二件事情:写代码验证非阻塞属性,给管道读写两端带来的特性
- int fcntl(int fd,int cmd, ··· /*arg*/ );
- 参数:
- fd:待要操作的文件描述符 (fd[0] , fd[1])
- cmd:告知fcntl函数做什么操作
- F_GETFL:获取文件描述符的属性信息
-
通过返回值获取管道读端的属性信息:
int flag =fcntl(fd[0],F_GETFL)
-
F_SETFL:设置文件描述符的属性信息,设置新的属性放到可变参数列表当中
- 返回值:
- F_GETFL:返回文件描述符的属性信息
- F_SETFL:
- 0:设置成功
- -1: 设置失败
代码:
我们先来获取文件描述符的属性信息
我们分别获取fd [0]和fd[1]的文件描述符的属性,发现值分别是0和1
我们再来看看设置非阻塞属性后的文件描述符的属性,和之前的进行对比一下
可以发现,设置了非阻塞属性以后,文件描述符的属性信息变为了2048
9、文件描述符数值的含义
1、O_NONBLOCK的值是多少
我们可以去操作系统中查找O_NONBLOCK的定义
我们可以看到,O_NONBLOCK的定义转化为10进制数的值正好是2048
2、fd[0]的属性信息为啥是0呢?
3、fd[1]的属性信息为啥是1呢?
fd[1]这个文件描述符的属性为
10、为什么文件描述符的属性信息需要用按位或的方式进行设置?(*****)
结论: 文件描述符的属性信息,再操作系统内核当中用比特位来进行表示
系统接口中,文件打开方式的宏,在内核当中的使用方式是位图
O_RDONLY O_CREAT O_NONBLOCK
为什么操作系统中大量使用位图?
1、位操作起来快速
2、节省空间
11、代码验证非阻塞属性
一、读设置成为非阻塞属性 fd[0]为非阻塞
1、写不关闭,一直读,读端调用read函数之后,返回值为-1,errno设置为EAGATN
进程可正常操作fd[1],可以正常操作写端
这时候执行一下,发现资源不可用
资源不可用是是因为管道当中没有内容可读
2、写关闭,一直读,读端read函数返回0,表示什么也没有读到
这时候彻底关闭写端,再执行一下代码
二、写设置成为非阻塞属性 fd[1]为非阻塞
1、读不关闭,一只写,当把管道写满之后,在调用write,就会返回-1
2、读关闭
四、命名管道
创建命名管道
1、mkfifo命令:
结论:
1、数据还是存储在内核的缓冲区当中的
2、管道文件的作用是为了让不同的进程可以找到这块缓冲区
2.函数创建
mkfifo函数:(first inpit first output) 先进先出
- int mkfifo(const char *pathname, mode_ t mode ) ;
- 参数:
- pathname ;要创建的命名管道文件的路径,以及文件名
- mode_ t :命名管道文件的权限,八进制数组 (0664)
- 返回值:
- 成功: 0
- 失败: -1
命名管道的特性:
支持不同的进程进行通信,不依赖亲缘性了
因为不同的进程可以通过管道文件去找到管道(操作系统内核的缓冲区)
代码验证:
我们分别写两个进程 一个write,一个read,让这两个进程进行通信
写好代码,运行一下:
我们可以看到,read确实是在一直读取管道的的数据