目录
1.为什么需要进程间通信?
2.常见的进程间通信方式:
3.管道:
匿名管道
(1)管道符号
(2)管道的本质
(3)管道的接口
(4)从PCB的角度理解管道
(5)管道的特性
(6)阻塞:当调用pipe创建出来的读写两端的文件描述符的属性,默认都是阻塞属性
(7)设置非阻塞特性
(8)非阻塞属性
扩展知识:
命名管道:
1.创建命名管道
命名管道的使用:
共享内存:
1.共享内存的原理
2.共享内存的接口
2.1创建或者获取共享内存的接口
2.2将共享内存的附加到进程虚拟地址空间
2.3分离
2.4操作共享内存接口
3.共享内存的特性:
3.1生命周期跟随操作系统
3.2共享内存是覆盖写的方式,读的时候是访问地址,没有将数据读走(注意:管道是字节流(直接将数据从管道中读走))
3.3共享内存的删除特性
ipcs命令 & ipcrm命令
消息队列
1.消息队列的原理
2.消息队列的接口
创建消息队列
发送消息
接收消息
操做消息队列的接口
每一个进程的数据都是存储在物理内存之中的,进程通过各自的进程虚拟地址空间进行访问,访问的时候,通过各自的页表的映射关系,访问到物理内存。从进程的角度看,每个进程都认为自己有独立的4G的空间,至于物理内存当中如何存储,页表如何映射,进程是不清楚的。这也造成了进程的独立性:
好处:让每个进程在运行的时候都是独立运行的,数据不会窜。
坏处:如果两个进程之间需要数据交换,那么由于进程独立性,就没有那么方便了
所以:进程间通信本质上是进程和进程之间交换数据的手段。
管道/共享内存/消息队列/信号量/信号/网络,其中网络是最大的,使用最广泛的进程间通信方式。
ps aux | grep xxx
管道在内核当中是一块缓冲区,供进程进行读写,交换数据。
》1理解参数的含义:
int pipe(int pipefd[2]); //创建匿名管道,本质上就是在内核创建出来一个缓冲区
参数:参数为出参,也就是pipe函数进行填充的,调用者进行使用
pipefd是数组,有两个元素
pipefd[0]:管道的读端
pipefd[1]:管道的写端
返回值:
0:创建成功
-1:创建失败
代码验证:
通过创建管道并且保持管道存在的时候,我们可以看到上面这种情况。
那么要让不同的进程进行通过匿名管道进行交换数据(进程间通信),进程应该具备什么样的条件呢?
不同的进程,要用同一个匿名管道进行通信,则进程需要拥有该管道两端的文件描述符。
代码实现父子进程的进程间通信:
如上图代码所示:我们先创建管道,再创建子进程,那么子进程就会拷贝父进程的PCB(管道是内核上的,所以不会被拷贝)但是父进程会拷贝管道的读写两端。 如下图所示:
pipe_size:通过ulimit -a 可以查得管道的大小为512*8=4096字节
原子性:一个操作要么不间断的全部被执行,要么一个也没有执行:非黑即白
管道这里的理解:即读/写操作在同一时刻只有一个进程在操作,保证进程在操作的时候,读写操作不会被其它进程打扰阻塞:读写两端的文件描述符初始的属性为阻塞属性。
当write一直调用写,读不去读,则写满之后管道会阻塞
当read一直进行读,当管道内部被读完之后,则read会阻塞
int fcntl(int fd,int cmd,.../*arg*/);
参数:fd:待要操作的文件描述符
cmd:告知fcntl函数要做什么操作
F_GETFL:获取文件描述符的属性信息
F_SETFL:设置文件描述符的属性信息,设置的属性放到可变参数列表当中
返回值:
F_GETFL:返回文件描述符的属性信息
F_SETFL:0:设置成功
-1:设置失败
设置非阻塞属性的时候,宏的名字为O_NONBLOCK
eg:第一步:先获取文件描述符原来的属性
int flag=fcntl(fd[0],F_GETFL);
第二步:设置文件描述符新的属性的时候,不要忘记将原来的属性也带上
新的属性 = 老的属性 | 增加的属性
fcntl(fd[0],F_SETFL,flag | O_NONBLOCK);
读设置为非阻塞属性 | 写设置为非阻塞属性 | ||
写不关闭 | 写关闭 | 读不关闭 | 读关闭 |
一直读,读端调用read函数之后,返回值-1,errno设置为EAGAIN | 一直读,读端read函数返回0,表示什么都没有读到 | 一直写,当把管道写满之后,则再调用write,就会返回-1 | 一只写,写端调用write进行写的时候,就会发生崩溃。(本质原因是因为写端进程,收到了SIGPIPE信号,导致写进程发生了崩溃) |
读设置为非阻塞代码验证:
写设置为非阻塞代码验证:
系统接口当中,文件方式打开的宏,在内核当中使用的方式是位图
eg:O_RDONLY,O_CREAT,O_NONBLACK
O_RDONLY,O_WRONLY,O_RDWR,O_CREAT,O_TRUNC,O_APPAND,O_NONBLOCK本质上操作系统内核都是采用位图的方式继续使用。
1.1mkfifo命令
1.2函数创建
mdfifo函数
int mkfifo(const char *pathname,mode_t mode);
参数:pathname:要创建的命名管道的路径以及文件名
mode_t:命名管道的权限,八进制数组(0664)
返回值:成功:0
失败:1
1.3特性
支持不同进程进行进程间通信,不依赖亲缘性了。
1.4代码验证
先创建命名管道:
int shmget(key_t key, size_t size,int shmflg);
参数:key:共享内存标识符
size:共享内存的大小
shmflg:获取/创建共享内存时,传递的属性信息
IPC_CREAT:如果共享内存不存在则创建
IPC_EXCL | IPC_CREAT:如果获取的共享内存存在,则函数报错
如果获取的共享内存不存在,则创建
本质上:该组合是要获取重新创建的共享内存
shmflag按位或上权限,权限是8进制数字
返回值:成功:返回共享内存的操作句柄,失败:-1
void *shmat(int shmid, const void *shmaddr,int shmflag);
参数:shmid:共享内存的操作句柄
shmaddr:将共享内存附加到共享区当中的哪一个地址上。一般让操作系统自己分配,传递NULL;
shmflag:以什么权限将共享内存附加到进程中
SHM_RDONLY:只读
0:可读可写
返回值:成功:返回附加的虚拟地址
失败:NULL;
int shmdt(const void *shmadrr);
参数:shmaddr:shmat的返回值
返回值:成功:0
失败:-1
int shmctl(int shmid, int cmd,struct ds *buf);
参数:shmid:共享内存的操作句柄
cmd:告诉shmctl函数需要完成什么功能
IPC_SET:设置共享内存属性信息
IPC_START:获取共享内存的属性信息
IPC_RMID:删除共享内存,第三个参数为NULL
buf:共享内存的数据结构buf
此时是连接的进程不为0,则删除共享内存的时候就有如下现象:
当我们把连接的进程结束的时候它的共享内存就会直接被释放:
这里画一幅图来理解:注意:只要满足先进先出特性的数据结构都可以称之为队列
int msgget(key_t key, int msgflg);
参数:key:消息队列的标识符
msgflg:创建的标志,例如:IPC_CREAT
IPC_CREAT:如果不存在,则创建
按位或(|)权限(8进制的数字就可以了)
返回值:成功:返回队列ID
失败:返回-1,并设置erron
int msgsnd(int msqid,const void *msgp, size_t msgsz, int msgflg);
参数:msgid:消息队列的ID
msgp:指向msgbuf的指针,用来指定发送的消息
操作系统为该函数发送的消息定义了发送格式,只是定义了一部分,另外一部分还需要程序员自己定义。
msgsz:要发送消息的长度
msgflg:创建标记,如果指定IPC_NOWAIT,失败会立刻返回
0:阻塞发送
IPC_NOWAIT:非阻塞放松
返回值:
成功返回 0
失败返回-1,并设置erron
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
参数:msgid:消息队列的ID
msgp:指向msgbuf的指针,用来接收消息
msgsz:要接收消息的长度
注意:参数msgsz指定由msgp参数指向的结构的成员mtext的最大大小(以字节为单位),msgtyp也有三种方式
msgtyp:接受消息的方式
1.msgtyp = 0:读取队列中的第一条消息
2.msgtyp > 0:读取队列中类型为msgtyp的第一条消息(从消息的发射我们就可以看出,队列中消息的类型是用一个大于0的整数表示的)。如果在msgflg中指定了MSG_EXCEPT,将读取类型不等于msgtyp的队列中的第一条消息。
3.msgtyp < 0:读取消息队列中最小类型小于或等于msgtyp绝对值的第一条消息
msgflg:创建标记,如果指定IPC_NOWAIT,获取失败会立刻返回
返回值:成功返回实际读取消息的字节数
失败返回-1,并设置erron
int msgctl(int msgqid,int cmd,struct msqid_ds *buf);
参数:msqid:消息队列ID
cmd:控制命令,
例如:IPC_RMID,删除命令
IPC_STAT,获取状态
buf:存储消息队列的相关消息的buf
返回值:成功根据不同的cmd有不同的返回值
失败返回-1,并设置erron
代码测试:我们循环发送了两轮类型从1到10的消息