系统章节-----进程间通信

目录

1.为什么需要进程间通信? 

2.常见的进程间通信方式:

3.管道:

匿名管道

(1)管道符号

(2)管道的本质

 (3)管道的接口

  (4)从PCB的角度理解管道

 (5)管道的特性

扫描二维码关注公众号,回复: 14449671 查看本文章

(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.消息队列的接口

创建消息队列

发送消息

接收消息

操做消息队列的接口


1.为什么需要进程间通信? 

每一个进程的数据都是存储在物理内存之中的,进程通过各自的进程虚拟地址空间进行访问,访问的时候,通过各自的页表的映射关系,访问到物理内存。从进程的角度看,每个进程都认为自己有独立的4G的空间,至于物理内存当中如何存储,页表如何映射,进程是不清楚的。这也造成了进程的独立性:

好处:让每个进程在运行的时候都是独立运行的,数据不会窜。

坏处:如果两个进程之间需要数据交换,那么由于进程独立性,就没有那么方便了

所以:进程间通信本质上是进程和进程之间交换数据的手段。

2.常见的进程间通信方式:

管道/共享内存/消息队列/信号量/信号/网络,其中网络是最大的,使用最广泛的进程间通信方式。

3.管道:

匿名管道

(1)管道符号

ps aux | grep xxx

(2)管道的本质

管道在内核当中是一块缓冲区,供进程进行读写,交换数据。

 (3)管道的接口

》1理解参数的含义:

int pipe(int pipefd[2]);  //创建匿名管道,本质上就是在内核创建出来一个缓冲区

参数:参数为出参,也就是pipe函数进行填充的,调用者进行使用

pipefd是数组,有两个元素

pipefd[0]:管道的读端

pipefd[1]:管道的写端

返回值:

0:创建成功

-1:创建失败

代码验证:

 通过创建管道并且保持管道存在的时候,我们可以看到上面这种情况。

  (4)从PCB的角度理解管道

 那么要让不同的进程进行通过匿名管道进行交换数据(进程间通信),进程应该具备什么样的条件呢?

不同的进程,要用同一个匿名管道进行通信,则进程需要拥有该管道两端的文件描述符。

代码实现父子进程的进程间通信:

如上图代码所示:我们先创建管道,再创建子进程,那么子进程就会拷贝父进程的PCB(管道是内核上的,所以不会被拷贝)但是父进程会拷贝管道的读写两端。 如下图所示:

 (5)管道的特性

  • 半双工:数据只能从管道的写端流向读端,并不支持双向通信。
  • 没有标识符;只有具有亲缘关系的进程进行进程间通信。(父进程先创建管道-->父进程再创建子进程:为了保证子进程的文件描述符表当中有读写两端的文件描述符)
  • 管道的生命周期跟随进程:进程退出之后,管道也就随之销毁了。
  • 管道的大小为64K:无脑的向管道当中写,不进行读。看看什么时候把管道写满,写满占了多少字节呢?
  • 管道提供字节流服务:从读端进行读的时候,是将数据从管道当中读走了(读端可以自定义读多少字节)
  • 原子性:当读写的数据小于pipe_size 的时候,管道保持原子性。

pipe_size:通过ulimit -a 可以查得管道的大小为512*8=4096字节

 原子性:一个操作要么不间断的全部被执行,要么一个也没有执行:非黑即白

管道这里的理解:即读/写操作在同一时刻只有一个进程在操作,保证进程在操作的时候,读写操作不会被其它进程打扰阻塞:读写两端的文件描述符初始的属性为阻塞属性。

(6)阻塞:当调用pipe创建出来的读写两端的文件描述符的属性,默认都是阻塞属性

当write一直调用写,读不去读,则写满之后管道会阻塞

当read一直进行读,当管道内部被读完之后,则read会阻塞

(7)设置非阻塞特性

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);

(8)非阻塞属性

读设置为非阻塞属性 写设置为非阻塞属性
写不关闭 写关闭 读不关闭 读关闭
一直读,读端调用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.创建命名管道

1.1mkfifo命令

1.2函数创建

mdfifo函数

int mkfifo(const char *pathname,mode_t mode);

参数:pathname:要创建的命名管道的路径以及文件名

           mode_t:命名管道的权限,八进制数组(0664)

返回值:成功:0

               失败:1

1.3特性

支持不同进程进行进程间通信,不依赖亲缘性了。

1.4代码验证

先创建命名管道:

命名管道的使用:

 共享内存:

1.共享内存的原理

  • 在物理内存当中开辟一段空间
  • 不同的进程通过页表将物理内存空间映射到自己的进程虚拟地址空间当中
  • 不同的进程通过操作自己的进程虚拟地址空间当中的虚拟地址,来操作共享内存

2.共享内存的接口

2.1创建或者获取共享内存的接口

int shmget(key_t key, size_t size,int shmflg);

参数:key:共享内存标识符

           size:共享内存的大小

           shmflg:获取/创建共享内存时,传递的属性信息

                IPC_CREAT:如果共享内存不存在则创建

                IPC_EXCL | IPC_CREAT:如果获取的共享内存存在,则函数报错

                                                            如果获取的共享内存不存在,则创建

                本质上:该组合是要获取重新创建的共享内存

                shmflag按位或上权限,权限是8进制数字

返回值:成功:返回共享内存的操作句柄,失败:-1

2.2将共享内存的附加到进程虚拟地址空间

void *shmat(int shmid, const void *shmaddr,int shmflag);

参数:shmid:共享内存的操作句柄

           shmaddr:将共享内存附加到共享区当中的哪一个地址上。一般让操作系统自己分配,传递NULL;

        shmflag:以什么权限将共享内存附加到进程中

                SHM_RDONLY:只读

                0:可读可写

返回值:成功:返回附加的虚拟地址

               失败:NULL;

2.3分离

int shmdt(const void *shmadrr);

参数:shmaddr:shmat的返回值

返回值:成功:0

               失败:-1

2.4操作共享内存接口

int shmctl(int shmid, int cmd,struct ds *buf);

参数:shmid:共享内存的操作句柄

           cmd:告诉shmctl函数需要完成什么功能

                IPC_SET:设置共享内存属性信息

                IPC_START:获取共享内存的属性信息

                IPC_RMID:删除共享内存,第三个参数为NULL

           buf:共享内存的数据结构buf

3.共享内存的特性:

3.1生命周期跟随操作系统

3.2共享内存是覆盖写的方式,读的时候是访问地址,没有将数据读走(注意:管道是字节流(直接将数据从管道中读走))

3.3共享内存的删除特性

ipcs命令 & ipcrm命令

  • 一旦共享内存被删除掉之后,共享内存的物理内存当中的空间被销毁了
  • 如果删共享内存的时候,共享内存附加的进程数量为0,则内核当中描述该共享内存的结构体也被释放了。
  • 如果删除共享内存的时候,共享内存附加的进程数量为不为0,则会将该共享内存的key,变成0x00000000.表示当前共享内存不能被其他进程所附加,共享内存的状态会被设置为destory.附加的进程一旦全部退出之后,该共享内存在内核的结构体会被操作系统释放掉。

此时是连接的进程不为0,则删除共享内存的时候就有如下现象: 

  

 当我们把连接的进程结束的时候它的共享内存就会直接被释放:

消息队列

1.消息队列的原理

  • msgqueue采用链表来实现消息队列,该链表是由系统内核维护的。
  • 系统中可能有很多msgqueue,每个MQ用消息队列描述符(消息队列ID:qid)来区分,qid是唯一的,用来区分不同的MQ。
  • 在进行进程通信时,一个进程将消息添加到MQ的尾端,另一个进程从消息队列中取消息(不一定以先进先出来取消息,也可以按照消息类型字段取信息)这样就实现了进程间通信。

这里画一幅图来理解:注意:只要满足先进先出特性的数据结构都可以称之为队列
 

 2.消息队列的接口

创建消息队列

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的消息

猜你喜欢

转载自blog.csdn.net/qq_57822158/article/details/123445705