当前位置: 首页 > news >正文

操作系统(进程通信)

一、进程间通信介绍

什么是进程间通信:

进程间通信(Interprocess communication 也叫IPC):指两个或多个进程之间进行数据交互的过程。

为什么进程之间需要通信:

由于进程采用的是虚拟空间+用户态/内核态机制,所以就导致进程与进程之间、进程与内核之间是互相独立的,如果想让多个进程协同工作解决复杂问题,就需要进程之间进行通信。

那类型的问题需要多进程协同工作:
进程的运行机制:

从人类的感知来讲,所有进程是并行执行,但实际情况是所以进程轮流使用CPU,只是轮转的速度比较快,人类感受不到,CPU的一个内核就是一个运算单位,可以供一个进行使用(这也是CPU厂商不断追求CPU内核的数量原因),CPU内核越多,同时执行的进程就越多。

CPU密集型问题:

主要特点是需要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力,负责运算的进程越多,竞争得到的执行时间就越长。但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,是一种损人不利已的做法。所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。

IO密集型问题:

主要涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度,因为创建进程也需要相关的资源,比如代码段、栈、文件描述符表、环境变量表。

所以处理CPU密集型程序时可以选择多进程实现,有效的利用多核提升效率;而IO密集型的由于99%的时间都花在IO上,花在CPU上的时间很少,所以多线程也能提高很大效率。

二、简单的进程间通信

命令行参数:

情况1:在终端执行程序时,给子进程传递命令行参数。

情况2:使用vfork+exec创建进程时,给子进程传递命令行参数。

只能在父子进程之间,在父进行创建子进程时使用,只能由父进行传递给子进程,单向通信,并且只能传递一些简单的字符串数据。

环境变量表:

情况1:使用fork创建子进程时,子进程会拷贝一份父进程的环境变量表。

情况2:使用vfork+exec创建进程时,给子进程传递一份父进程的环境变量表,子进程拿到环境表后会进行拷贝。

只能在父子进程之间,父进行创建子进程时使用,只能由父进行传递给子进程,单向通信,并且只能传递一些简单的字符串数据。

信号:

情况1:使用kill向指定的进程发送信号进行通信。

情况2:使用sigqueue向指定的进程发送信号,也可以附带一些简单的数据。

可以在任意进程之间进行通信,但也只能是互相告知某某事件发生了,即使用能使用sigqueue带一些数据,但只有能传递一个int类型整数,只有通过fork创建的子进程才能传递内存地址。

文件:

情况1:使用文件+信号,让任意两个进程之间传递大量数据,但要各自控制好位置指针,协调好读写时间。

情况2:使用文件+文件锁,也可以让任意两个进程之间传递大量数据,但文件锁的操作比较麻烦,也有可能陷入死锁的情况。

单纯的使用文件进行通信,无法协调读取和写入的时间,空间造成文件内的数据混乱,所以必须配合使用信号或文件锁。

三、传统的进程间通信——管道通信

  • 管道是UNIX系统中最古老的进程间通信方式,是一种特殊文件读写机制

  • 当进程从管道文件中读取数据时,如果管道中没有数据则进程会进入阻塞状态,直到有数据读取出来才返回,因此不需要借助信号、文件锁来协调读写时间

  • 管道中的数据一旦读取完毕就会消失,因此也不需要管理文件的位置指针,所以使用管道文件比普通文件的进程间通信要方便很多

  • 古老的好处是所有系统都支持,早期的管道文件是半双工,现在有些系统支持管道文件的全双工,现在绝大多数情况已经不使用管道来通信了

有名管道:
  • 在文件系统中创建出一个实体的有文件名的管道文件,然后通过系统I/O的相关API来进行相关操作

使用函数创建:
#include <sys/types.h>
#include <sys/stat.h>
​
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道文件
pathname:管道文件的名字
mode:管道文件的权限
返回值:成功返回0 失败-1
使用命令创建:
mkfifo <file>
管道单向通信的编程模型:
进程A      ->         进程B
创建有名管道      
打开管道              打开管道
写数据                读数据
关闭管道              关闭管道
删除管道                
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
​
int main(int argc,const char* argv[])
{//  创建管道文件if(mkfifo("fifo",0644)){perror("mkfifo");return -1;}
​//  打开管道int fd = open("fifo",O_WRONLY);if(0 > fd){perror("open");unlink("fifo");return -1;}
​char buf[256] = {};
​for(;;){printf(">>>");//  使用封装好的my_gets()gets(buf);//  写文件 发送数据write(fd,buf,strlen(buf));//  检查是否quitif(0 == strcmp(buf,"quit")){printf("通信结束\n");usleep(1000);break;}}//  关闭管道close(fd);
​//  删除管道unlink("fifo");
}
#include <stdio.h>                        
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
​
int main(int argc,const char* argv[])
{//  打开管道int fd = open("fifo",O_RDONLY);if(0 > fd){perror("open");return -1;}
​char buf[256] = {};
​//  读数据for(;;){read(fd,buf,sizeof(buf));printf("read:%s\n",buf);
​if(0 == strcmp(buf,"quit")){printf("通信结束\n");break;}}
​//  关闭管道close(fd);
}
​
​
匿名管道:
  • 只在内核中创建的管道文件对象并返回该对象的文件描述符,然后使用系统IO进行相关操作,匿名管道文件不会在文件系统中显示,在创建时不需要提供路径,也不会占用磁盘空间,只是使用内核空间来临时存储数据,当关闭文件描述符后会自动回收

  • 注意:只适合fork创建的有关系的父子进程之间进行通信

相关API:
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建出匿名管道文件对象,并返回该对象的文件描述符
pipefd:输出型参数,用于存储文件描述符的数组,其中pipefd[0]   用于读操作pipefd[1]   用于写操作
​
使用步骤:1、调用该函数在内核中创建出管道文件,并获取到该文件的两个文件描述符2、通过fork创建出子进程,子进程可以直接拷贝父进程的pipefd描述符3、写数据的进程要关闭读端,读数据的进程要关闭写端4、发送完毕后,父子进程分别关闭文件描述符
编程模型:
    父进程     ->      子进程创建匿名管道           创建子进程         拷贝一对fd关闭读端(fd[0])    关闭写端(fd[1])写数据(fd[1])      读数据(fd[0])关闭写              关闭读
#include <stdio.h>          
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
​
int main(int argc,const char* argv[])
{//  创建匿名管道int fd[2] = {};if(pipe(fd)){perror("pipe");return -1;}
​//  创建子进程if(fork()){//  父进程  负责写 关闭读close(fd[0]);char buf[256] = {};for(;;){printf("父进程>>>");gets(buf);write(fd[1],buf,strlen(buf));if(0 == strncmp(buf,"quit",4)){printf("通信结束\n");break;}usleep(1000);}usleep(1000);close(fd[1]);usleep(1000);}else{//  子进程  负责读 关闭写close(fd[1]);char buf[256] ={};
​for(;;){read(fd[0],buf,sizeof(buf));printf("read:%s\n",buf);if(0 == strncmp(buf,"quit",4)){printf("父进程要结束通信\n");break;}}close(fd[0]);}
}                                                                    

四、XSI机制的进程间通信

1、XSI介绍:
什么是XSI:

X/Open国际联盟有限公司是一个欧洲基金会,它的建立是为了向UNIX环境提供标准,XSI是X/Open System Interface的缩写,也就是X/Open设计的系统接口。

X/Open的主要的目标是促进对UNIX系统、接口、网络和应用的开放式系统协议的制定。它还促进在不同的UNIX环境之间的应用程序的互操作性,以及支持对电气电子工程师协会对UNIX的可移植操作系统接口规范。

IPC对象:
  • 它用于进程间通信的XSI-IPC内核对象,类似于匿名管道、文件内核对象一样,通过使用XSI方式进行进程间通信时,系统会在内核中创建出一个XSI-IPC内核对象,让通信的进程能够共同访问该内核对象

  • XSI-IPC对象只存在于内核空间,不会在文件系统中显示,该对象需要通过IPC键值来创建和获取

IPC键值:
  • 用于创建\获取 IPC对象的凭证,是一个无符号的整数,相当于IPC对象的名字,类似于文件名

  • 在创建IPC对象时,需要创建者提供一个IPC键值,有点类似给文件取名字,但是所有已经存在的IPC对象都在同一个作用域下,因此所有进程都可以访问到,所有有很大重名的风险,所以不建议创建者自己瞎想一个IPC键值去创建IPC对象,而应该使用操作系统提供的一个自动生成IPC键值的API

#include <sys/types.h>
#include <sys/ipc.h>
​
key_t ftok(const char *pathname, int proj_id);
功能:根据项目路劲和项目编号 自动生成一个ipc键值
pathname:建议提供当前项目的路径
proj_id:项目编号
返回值:会根据项目路径和编号来计算出一个IPC键值,但是只是根据字符串内容来计算,不会检查路径是否为假
注意:使用时,建议提供当前的正确的工作路径,以及不同的项目编号,就可以获得不同的IPC键值,通过不同的IPC键值来创建不同的IPC对象
但是获取时,需要拿到相同的IPC对象,因此需要拿相同的IPC键值,因此提供相同路径和编号即可获取
IPC描述符:
  • 类似于文件描述符,是一个非负整数,它是IPC内核对象的给用户空间来访问的凭证

显示IPC对象命令:
ipcs -m  #查看共享内存IPC对象   memory
ipcs -q  #查看消息队列IPC对象   queue
ipcs -s  #查看信号量IPC对象     sem
ipcs -a  #查看所有的IPC对象           
删除IPC对象命令:
ipcrm -m  id    #删除共享内存IPC对象
ipcrm -q  id    #删除消息队列IPC对象
ipcrm -s  id    #删除信号量IPC对象

2、共享内存:
基本原理:
  • 在内核中开辟一块内存,可以让其它进程的虚拟地址与这块内存进行映射,从而达到让多个进程共享一块内存的目的,当一个进程向这块内存写数据时,其它进程就都可以读取到,这就达到了通信的目的

  • 优点:这种通信方式不存在数据的复制,是最快的进程间通信方式

  • 缺点:需要考虑同步访问的问题(用信号)

使用方式:

当一个进程向共享内存写入数据时,内核不会通知其他进程,进程从共享内存读取数据时,也无法分辨是否是通信的对方新写入的数据,为解决该问题,有以下方式:

  • 读写:一个进程只负责写,另一个只负责读,这要负责读的进程使用的是最新即可,是一种单向通信

  • 轮询:配合定时器或者闹钟,每隔一段时间就读取一次

  • 中断:配合信号,进程只要往共享内存中写入数据,就给对方发送一个约定好的信号,其它进程如果已经读取完共享内存也可以发送一个约定好的信号

相关API:
#include <sys/ipc.h>
#include <sys/shm.h>
​
int shmget(key_t key, size_t size, int shmflg);
功能:创建\获取一个共享内存的IPC对象
key:IPC键值,类似于文件名
size:共享内存的字节数,建议取内存页字节数的整数倍,默认1页=4096字节如果是想要创建,则必须指定size的大小如果是获取已有的共享内存,这size可取0即可
shmflg:0       -表示该参数无效,获取共享内存,size也无效了IPC_CREAT   -创建共享内存IPC_EXCL    -如果已经存在同一个IPC对象,返回失败mode        -共享内存的权限,当创建共享内存时必须加上例如:IPC_CREAT|0644
返回值:IPC描述符,失败-1
​
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:加载共享内存(把进程中的虚拟地址与内核中的共享内存建立映射关系)
shmid:IPC描述符,要映射哪条共享内存
shmaddr:想要映射的虚拟地址,可以给NULL由操作系统自动分配
shmflg:0               - 以读写方式映射共享内存SHM_RDONLY      - 以只读方式映射共享内存
返回值:映射成功后的虚拟内存首地址,失败返回-1int shmdt(const void *shmaddr);
功能:取消虚拟内存与共享内存的映射,也叫卸载共享内存    
shmaddr:要取消映射的虚拟内存地址int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:销毁共享内存、获取共享内存属性、设置共享内存的属性
shmid:IPC描述符
cmd:IPC_SET     -设置共享内存的属性  buf输入型参数,只有uid、gid、mode可设置IPC_STAT    -获取共享内存属性   buf输出型参数IPC_RMID    -删除共享内存 buf给NULL即可,-删除并非真正直接删除,而是对共享内存的使用计数-1,如果该计数被-1为0后,意味着系统中没有任何的进程映射这块共享内存,才会从内核中真正销毁它struct shmid_ds {struct ipc_perm shm_perm;    //  属主和权限信息size_t          shm_segsz;   //  共享内存的大小time_t          shm_atime;   //  最后映射时间(加载)time_t          shm_dtime;   //  最后取消映射的时间(卸载)time_t          shm_ctime;   //  最后内存内容修改时间pid_t           shm_cpid;    //  创建者pidpid_t           shm_lpid;    //  最后加载、卸载者pidshmatt_t        shm_nattch;  //  当前映射的进程数的计数器...
};
​
struct ipc_perm {key_t          __key;    //  IPC键值uid_t          uid;      //  属主idgid_t          gid;      //  属主组iduid_t          cuid;     //  创建者idgid_t          cgid;     //  创建者组idunsigned short mode;     //  权限unsigned short __seq;    //  序列号
};
​
编程模型:
    进程A             进程B创建共享内存        获取共享内存映射共享内存        映射共享内存写数据并通知对方    接收到通知后读取数据接收到通知后读取数据 写数据并通知对方取消映射           取消映射删除共享内存
//  shmA.c
​
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
​
static int shmid;
static char* shm;
​
//  接收到对方发送信号 才读取数据
void sigread(int num)
{printf("\nread:%s\n",shm);if(0 == strncmp(shm,"quit",4)){   printf("对方不想鸟你了\n");
​//  取消映射if(shmdt(shm)){perror("shmdt");exit(EXIT_FAILURE);}
​//  删除共享内存if(shmctl(shmid,IPC_RMID,NULL)){perror("shmctl");exit(EXIT_FAILURE);}      //  必须结束进程 return没有用exit(EXIT_SUCCESS);}   
}
int main(int argc,const char* argv[])
{signal(SIGRTMIN,sigread);printf("我是进程%u\n",getpid());
​//  创建共享内存shmid = shmget(ftok(".",110),4096,IPC_CREAT|0644);if(0 > shmid){perror("shmget");return EXIT_FAILURE;}
​//  映射共享内存shm = shmat(shmid,NULL,0);if((void*)-1 == shm){perror("shmat");return EXIT_FAILURE;}
​//  获取对方的进程idpid_t pid = 0;printf("请输入与我通信的进程ID:");scanf("%u",&pid);
​//  写入数据并通知对方for(;;){printf(">>>");scanf("%s",shm);
​//  发送信号kill(pid,SIGRTMIN);if(0 == strncmp(shm,"quit",4)){printf("你终止通信了\n");break;}}
​//  取消映射if(shmdt(shm)){perror("shmdt");return EXIT_FAILURE;}//  删除共享内存if(shmctl(shmid,IPC_RMID,NULL)){perror("shmctl");return EXIT_FAILURE;}return EXIT_SUCCESS;
}
​
​
​
3、消息队列:
基本原理:
  • 由系统内核维护的一个链式队列,每个节点称为一条消息,每条消息由消息类型、数据、长度信息组成

  • 和管道类似,可以双向通信,并且从中读取一个消息后,会自动出队,而且不是按照顺序读取,是按照消息类型读取

相关API:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
​
int msgget(key_t key, int msgflg);
功能:创建\获取消息队列
key:IPC键值
msgflg:0           -获取,不存在则失败IPC_CREAT   -创建,不存在则创建,存在会获取,除非IPC_EXCL    -排斥,已存在则创建失败mode        -消息队列权限,创建时必给
返回值:IPC描述符,失败-1int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列发送消息包
msqid:IPC描述符
msgp:提供一个包含有消息类型和消息内容的消息包内存块,参考格式如下:struct msgbuf {long mtype;       // 消息类型char mtext[1];    // 消息内容,也可以使用柔性数组};
msgsz:提供消息包中消息内容的字节数,不包含消息类型的字节数
msgflg:0       -如果当消息队列中没有空闲空间,该函数会一直阻塞IPC_NOWAIT  -当消息队列中没有空闲空间,不会阻塞,返回-1
返回值:成功0 失败-1
​
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:从消息读取消息包
msqid:IPC描述符
msgp:接收消息包的结构体首地址,也是由消息类型+消息内容组成,输出型参数
msgsz:消息包的字节数,如果实际消息包的大小>msgsz,会立即返回-1
msgtyp:要接收的消息类型0   -获取消息队列中的第一条消息>0  -获取指定消息类型的消息<0  -获取消息队列中,消息类型小于或等于abs(msgtyp)的消息,如果有多个,则接收最小值的
msgflg:0           阻塞等待IPC_NOWAIT  如果消息队列中没有该消息类型的消息,直接返回MSG_EXCEPT  获取消息队列中第一个不等于msgtyp的消息,msgtyp>0MSG_NOERROR 如果包含它,则当消息包的大小>msgsz时,不会报错,并接收msgsz字节
返回值:成功接收到的消息的字节数
​
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:销毁消息队列、获取消息队列属性、设置消息队列属性
shmid:IPC描述符
cmd:IPC_SET     -设置共享内存的属性  buf输入型参数,只有uid、gid、mode可设置IPC_STAT    -获取共享内存属性   buf输出型参数IPC_RMID    -删除共享内存 buf给NULL即可
​
struct msqid_ds {struct ipc_perm msg_perm;     // 与shmctl的一致time_t          msg_stime;    /* Time of last msgsnd(2) */time_t          msg_rtime;    /* Time of last msgrcv(2) */time_t          msg_ctime;    // 最后属性修改时间unsigned long   __msg_cbytes; // 消息队列中的字节数msgqnum_t       msg_qnum;     // 消息队列中的消息数msglen_t        msg_qbytes;   // 消息队列运行的最大字节数pid_t           msg_lspid;    /* PID of last msgsnd(2) */pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
};
​
编程模型:
        进程A             进程B创建消息队列        获取消息队列发送消息(type:a)    接收消息(type:a)接收消息(type:b)    发送消息(type:b) 销毁消息队列      

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Spring 中的InitializingBean
  • C语言实现数据结构之队列
  • 基于GeoTools使用JavaFx进行矢量数据可视化实战
  • NoSQL之Redis配置与优化
  • 36集网剧《天降神医朱丹溪》电影《百草园里杏花香》在义乌启动
  • Java 并发编程:ReentrantLock 锁与 AQS
  • 【机器人学】6-5.六自由度机器人运动学参数辨识-逆运动学迭代解【附MATLAB代码】
  • 响应式Web设计的发展与特点
  • 【rz sz】Centos/Linux 如何快捷的上传下载文件到系统当中?
  • 白骑士的Matlab教学基础篇 1.4 函数与脚本
  • LeetCode Hot100 LRU缓存
  • Npm使用教程(详细讲解)
  • 算法打卡 Day19(二叉树)-平衡二叉树 + 二叉树的所有路径 + 左叶子之和 + 完全二叉树的节点个数
  • 【学习笔记】:Maven初级
  • 2024rk(案例三)
  • SegmentFault for Android 3.0 发布
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • Angular数据绑定机制
  • Apache Pulsar 2.1 重磅发布
  • Cumulo 的 ClojureScript 模块已经成型
  • echarts花样作死的坑
  • extract-text-webpack-plugin用法
  • gcc介绍及安装
  • laravel with 查询列表限制条数
  • Linux下的乱码问题
  • mysql innodb 索引使用指南
  • Python实现BT种子转化为磁力链接【实战】
  • Quartz初级教程
  • Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)
  • SAP云平台里Global Account和Sub Account的关系
  • 每天一个设计模式之命令模式
  • 漂亮刷新控件-iOS
  • 扑朔迷离的属性和特性【彻底弄清】
  • 算法系列——算法入门之递归分而治之思想的实现
  • 问题之ssh中Host key verification failed的解决
  • 新手搭建网站的主要流程
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • ​​​​​​​​​​​​​​Γ函数
  • ​【经验分享】微机原理、指令判断、判断指令是否正确判断指令是否正确​
  • ​flutter 代码混淆
  • ​secrets --- 生成管理密码的安全随机数​
  • #1015 : KMP算法
  • (HAL)STM32F103C6T8——软件模拟I2C驱动0.96寸OLED屏幕
  • (二)原生js案例之数码时钟计时
  • (六)DockerCompose安装与配置
  • (面试必看!)锁策略
  • (学习日记)2024.02.29:UCOSIII第二节
  • (最简单,详细,直接上手)uniapp/vue中英文多语言切换
  • ***linux下安装xampp,XAMPP目录结构(阿里云安装xampp)
  • .net 7 上传文件踩坑
  • .NET 服务 ServiceController
  • @Conditional注解详解
  • @ModelAttribute 注解
  • [ CTF ] WriteUp-2022年春秋杯网络安全联赛-冬季赛