Linux进程间通信完整总结

一、管道通信

管道的特点

  • 1.管道是一个字节流
  • 2.管道是单向的
  • 3.管道的容量是有限的
  • 4.管道一次写入数量最大为PIPE_BUF。多进程同时写入时,如果超过了这个数量,可能会发生数据混合。
  • 5.当管道中不存在数据时,read操作会阻塞(如果管道写端已经关闭,read操作会返回0)

1.标准流管道

  • 1. 打开关闭标准流管道

  • 打开操作
    使用popen函数创建。一般用于父子进程间的通信。
    在这里插入图片描述

    • 输入:1.shell指令 command,指定打开的程序, 2.管道流的打开方法,指定为w或r
    • 返回: 1.成功:标准输入输出流, 2.失败 NULL
    • popen
      popen()函数1.创建了一个管道,2.然后创建了一个子进程来执行shell,3.而shell又创建了一个子进程来执行command字符。mode参数示一个字符串,它确定调用进程是从管道中读取数据mode 为r,还是将数据写入管道中,mode是w。
      mode的取值,确定了所执行的命令的标准输出连接到管道的写入端还是将其标准输入连接到管道的读取端。
  • 关闭操作
    - 随机管道流式FILE*类型,但关闭不能使用fclose(),必须使用 pclose()函数
    - pclose的输出流,默认是块缓冲模式
    - pclose执行后,进程会进入阻塞状态,直到popen开启的进程结束。
    - 成功时,pclose返回新进程的退出状态,失败时返回-1

  • 2.数据的读写

  • 在调用进程中,管道流的读写使用fread()/fwrite()进行

  • 在开启的新进程中,管道流的读写使用scanf()和printf()进行

  • 3.使用流程

//1.创建标准流管道,将其设置为只读模式
FILE* pipe = popen("./hello","r");
//2.从pipe中读取数据
char msg[100] = {0};
int ret = fread(msg,1,100,pipe);
//3.关闭流
pclose(pipe);

//hello.c向pipe中写入数据的方法
printf("hello,world");

2.无名管道PIPE

  • 特点:
    1. 只能在亲缘关系进程间进行通信(父子或兄弟)
    2. 半双工
    3. 无名管道是特殊的文件,可以用read\write读写,只能在内存中
  • 创建方法
    无名管道是利用文件描述符来创建管道文件,创建成功后,会分别传回管道文件的读端和写端的文件描述符。因此,只可以用于共享文件描述符的进程之间的通信。
    在这里插入图片描述一般使用第二种形式比较多一些;
    • 传入参数 int数组,大小为2,用于接收返回的管道文件描述符,pipefd[0]为读端,pipedf[1]为写端
    • 返回 正常返回0,错误返回-1.
    • 使用第三种形式时,如果flags指定0,和第二种一样。
      flags 可以指定为
      O_NONBLOCK 设定为非阻塞模式。
      O_DIRECT 设定为包传输模式,每次read/write会向管道中读取/写入一个数据包。(连续发送数据时不会粘在一起)
  • 读取和写入操作
    正常使用read/write对文件描述符进行操作即可
  • 关闭操作
    使用close操作来关闭管道的文件描述符即可。
    需要注意的是,
    • 管道的写端先关闭时,读端会返回0,并不会报错,此时根据读端返回的数值进行管道的关闭即可。
    • 管道的读端先关闭,则写端进程会收到到SIGPIPE信号,进程异常终止;如果写端处理了SIGPIPE信号,则会返回一个负值,表示管道读端已经关闭。
  • 使用流程
//1.创建无名管道文件
int fdp[2] = {-1};
int ret = pipe(fdp);
if(-1 == ret) {
	perror("pipe");
	exit(-1)
}
//2.创建子进程
char msg[100]={0};
ret = fork();
if(ret){
	if(ret = -1) exit(-1);
	//3.关闭父进程写端,使用读端读取子进程的数据
	close(fd[1]);
	//4.读取数据
	read(fd[0],msg,100);
	printf("msg:%s\n",msg);
	wait(NULL);
	//5.关闭读端
	close(fd[0]);
}else {
	//3.关闭子进程读端,使用写端
	close(fd[0]);
	//4.写入数据
	write(fd[1]."hello,world",11);
	//5.关闭写端
	close(fd[1]);
	return ;
}
	

3.命名管道FIFO

  • 存在实际的物理文件

  • 可用于不相关进程之间的通信

  • 一般需要配合以进程间同步措施

  • 1.创建方法在这里插入图片描述

  • 传入 1.需要创建的管道的路径 2.管道的读写执行权限

  • 返回 成功:0 失败:-1

  • 2.删除方法
    在这里插入图片描述

  • 3.打开关闭和读写方法
    属于正常的文件类型。创建成功后使用文件描述符操作即可

//创建
int ret = mkfifo("./1.pipe,0600);
//打开(以读写模式打开)
int fd = open("./1.pipe",O_RDWR);
//写入
ret = write(fd,"hello",5);
//读取
char tmp[10] = {0};
ret = read(fd,tmp,10);
//关闭
close(fd);

二、system V的三种方法

0.SYSTEM V 工具的通性

  • SYSTEM V 相当于在系统内核中抽象出了一个仓库,将进程之间需要进行交互的信息全部存储在里面。根据不同的交互要求,分为了三大类,每一类有不同的存储方式和存储结构。存取数据都要向内核申请。
  • 三个工具的每一个实例均有一个唯一的识别码key,通过调用相应的函数,并指定key,可以创建一个工具,或查找一个工具是否存在,同时返回对该工具实例进行操作的句柄。 key是一个整数类型,使用相同的key才能访问同一块内核区域、进行通信。key可以是任意指定的整数值,也可以通过IPC_PRIVATE创建(仅对父子进程间生效),或通过ftok()函数创建。
  • 对工具实例整体的控制,使用***ctl()函数完成,
  • 对各工具实例,依据其特点不同进行的操作,指定了不同的接口函数。
  • 通过ftok()获得key值
    在这里插入图片描述
    + 传入:1.任意文件路径 2.项目代码,可以任意指定
    + 返回值: 成功时返回key值,失败时返回-1

1.共享内存

  • SYSTEM V中的共享内存,本质上是一块特殊的内存,进程将需要共享的信息放到该共享内存区域中。所有需要访问该区域的进程,都需要把共享区域映射到本进程的地址空间中。因此各进程只需要对本进程内映射的地址进行简单的读写操作,即可获得共享数据。
  • 进程对象对共享内存的访问,通过Key值和创建共享内存时设置的权限进行控制。

1.使用shmget获取共享内存

在这里插入图片描述

  • shmget用于打开或创建一段共享内存,该共享内存由key值唯一确定。
  • 传入:1.key值 2.需要开辟的内存空间大小(字节)3.获取共享内存的模式
  • 返回: 成功时返回共享内存的识别号(唯一标志着共享内存) 失败时返回-1
  • shmflg 的常用选项
    IPC_CREAT 创建IPC工具(共享内存),此时需要制定共享内存的读写权限,如 | 0600
    IPC_EXCL 和IPC_CREAT联合使用,如果共享内存已存在,则返回错误

2.建立映射关系

在这里插入图片描述

  • 传入:1.共享内存识别码shmid 2.本进程需要映射的地址shmaddr,一般填NULL,由程序自动分配,也可以自己指定, 3.建立映射的模式,一般填0即可
    shmflg可设定为:
    SHM_EXEC 设定内存端的内容为可执行,调用进程必须有执行的权限
    SHM_RDONLY 设定映射内存段为只读。
  • 返回值:正常返回映射后的地址 错误返回(void)-1*
    注意点:由于返回值是一个void*类型,得到值后需要进行类型转换。对运行的结果进行检查是也需要进行检查

3.解除映射关系

在这里插入图片描述
传入:需要解除映射的地址
返回:成功返回0,失败返回-1.

4.删除共享内存

在这里插入图片描述
删除共享内存的机制为引用计数机制,如果删除时仍有内存没有接触映射状态,则共享内存不会立即删除,等待所有进程均解除了和共享内存的映射后,才会进行解除

  • 传入:1.共享内存标识符shmid 2.控制指令,删除的控制指令为IPC_RMID, 3.共享内存的数据结构(删除操作,直接填NULL即可)
  • 返回:成功时返回0,失败时返回-1.
    另外当cmd指令为 IPC_STAT,可以获得当前共享内存的信息,存放到buf中。(映射时间,pid等等)
    在这里插入图片描述### 5.使用流程
//0.获取key值
key_t key = ftok("./makefile",0);
//1.创建共享内存
int shmid = shmget(key,100,IPC_CREATE|0600);
//2.建立共享内存映射
char* ch = (char*)shmat(shmid,NULL,0);
//3.共享内存的读写
//直接将其视作正常的char*进行读写即可
//写入(仅作演示)
strcpy(ch,"hello,world");
sprintf(ch,"%d\t hello\n",15);
//读取(仅作演示)
int i = 0;
char msg[100]={0};
strcpy(msg,ch);
sscanf(ch,"%d\t",&i);
//4.解除映射
shmdt(ch);
//5.删除共享内存
shmctl(shmid,IPC_RMID,NULL);

2.hugepage的设定方法

2.信号量

1.信号量的基本概念

  • 信号量用于同步进程的动作。信号量的常见用途是同步对一块共享内存的访问,以防止出现一个进程在访问共享内存的同时另一个进程更新这块内存的情况。
  • 信号量是一个由系统内核维护的整数,其值被限制为大于或等于0。在一个信号量上执行隔着操作(即系统调用),包括:
    1. 将信号量设置成一个绝对值
    2. 在信号量当前值的基础上加上一个整数
    3. 在信号量当前值的基础上减去一个整数
    4. 等待信号量的值为0。
      以上操作的后两个,可能会造成调用进程的堵塞
  • 当减小一个信号量的值时,内核会将所有试图将信号量值降低到0以下的操作阻塞。类似的,如果信号量的当前值不为0,那么等待信号量为0的调用操作也会阻塞。不管是什么情况,调用进程会一直阻塞到其他一些进程进程将信号量的值修改为一个运行这些操作继续向前的值,届时,系统将会唤醒被阻塞的进程。

2.信号量的创建步骤

  • 1.使用semget()函数创建或打开一个信号量集合
    在这里插入图片描述

    • 传入:1.key值 2.需要创建的信号量的个数,3.IPC工具的相关设置 IPC_CREATE
    • 返回:1.成功返回信号量的标识符semid 2.失败返回-1
  • 2.使用semctl()的SETVAL或SETALL 操作初始化集合中的所有信号量值,只有一个进程需要完成此操作**,同时,可以使用GETVAL\GETALL来获取信号量的值。semctl可以有三个或四个参数,执行初始化操作时,必须用到第四个参数,其类型为联合体union semun
    在这里插入图片描述

  • 传入:1.信号量标识符 semid 2.信号量序号 semnum(从0开始)3.操作指令 4.union semun,存储需要保存或传入的值
  • 返回:成功返回0,失败返回-1
    semun的结构体表示
    在这里插入图片描述其中,对单个信号量的值进行操作时,传入一个整数即可。对全部的信号量进行操作时,需要传入一个unsigned short类型的数组(信号量的值永远不为负数)。
  • 使用SETALL和GETALL时,第二个参数semnum会被忽略。而使用GETVAL和SETVAL时则必须从填写

  • 3.使用semop()操作, 操作信号量值。使用信号量的进程通常会使用这些操作来表示一种共享资源的获取和释放,semop操作需要借助结构体sembuf来实现。
    在这里插入图片描述

  • 传入:1.信号量的标识符semid,2.信号量的操作集合sops (注意是指针类型) 3.需要执行的操作数

  • 返回: 成功返回0,失败返回-1.
    sembuf结构体的定义:

struct sembuf{
	unsigned short sem_num;	//指定需要操作的信号量
	short 			sem_op;	//指定在该信号量上进行的操作,
	short 			sem_flg;//指定操作的属性
};

sem_op 是一个相对数值,是指该次操作在原信号量上加的数值。
sem_flag可以指定为:
0 默认属性
IPC_NOWAIT 设置为非阻塞模式
SEM_UNDO 进程结束后,将信号量恢复到之前的状态。

  • 4.当所有进程都不在需要使用信号量集之后,使用semctl()的IPC_RMID操作删除这个信号量集合只有一个进程需要完成此操作

3.操作步骤

//1.key值获取
key_t key = ftok("./makefile",2);
//2.创建一个含有两个信号的信号量工具
int semid = semget(key,2,IPC_CREAT|0600);
//3.对信号量集合进行初始化
unsigned short a[2] = {1,0};
int ret = semctl(semid,0,SETALL,a);
//获取初始化之后的数值
unsigned short b[2] ={0};
ret = semctl(semid,0,GETALL,b);
//4.创建信号量操作集合
struct sembuf ops[2];
ops[0].sem_num = 0;
ops[0].sem_op = -1;
ops[0].sem_flg = 0;
ops[1].sem_num = 0;
ops[1].sem_op = 1;
ops[1].sem_flg = 0;
//5.执行信号量操作
semop(semid,&ops[0],1);
printf("hello,world\n");
semop(semid,&ops[1],1);
//6.输出信号量
ret = semctl(semid,0,IPC_RMID);

3.消息队列

1.消息队列的概念和特点

消息队列是用于在在进程间进行通信的工具,它允许进程间以消息的形式交换数据。

  • 用来引用消息的句柄是一个由msgget()函数调用返回的句柄。
  • 通过消息队列进行的通信是面向消息的。消息是以一个特定的结构体单位msgbug来进行写入和读取的。
  • 除了数据以外,msgbuf中还有一个long类型来表示消息的类型,从消息队列中读取消息既可以按照先入先出的顺序,也可以根据类型来读取消息。

2.消息队列的使用步骤

1.创建或打开一个消息队列

msgget系统调用会首先在所有既有的消息队列中搜索与指定Ke值相同的队列,如果找到了,则返回该实例的标识符(在msgflg同时使用IPC_CREAT和IPC_EXCL会返回错误)。如果没有,且指定了IPC_CREAT,那么就会创建一个新队列,并返回该队列的标识符。
在这里插入图片描述

  • 输入:1.标识符 key(可以指定为IPC_PRIVATE用于父子进程间通信) 2.创建消息队列的msgflg
  • 返回:1.成功返回消息队列的标识符semid 2.失败-1
    +创建时使用的msgflg :
    1. IPC_CREAT
    2. IPC_EXCEL
    3. 此外,如果是创建消息队列时,还需要额外加上该消息队列实例对不同用户的权限,比如 | 0600。否则会导致访问权限错误

2.发送和接收消息

分别使用msgsnd和msgrcv函数向队列中写入和读取消息。
使用这两个函数时必须确保调用进程拥有相应的权限。

  • 消息队列使用结构体msgbuf传递消息,msgbuf的结构体定义如下,其第一个参数表示消息的类型,消息队列实例依据信息类型的不同,将不同的的信息归档。第二个参数则是需要传递的消息的字符串数组,由于默认大小是1,因此,使用此结构体时,需要重新定义,将其设置为符合要求的大小。
    在这里插入图片描述

  • 写入和读取函数
    在这里插入图片描述

  • 写入 msgsnd

在这里插入图片描述

  1. 传入参数:1.消息队列标识符 msgid 2.消息的数据结构的指针 msgp 3.写入的数据大小 msgsz
    4 .写入操作的msgflg
  2. 返回值 1.成功0 2.失败 -1
  3. 当写入操作超过队列最大容量时,写入操作会阻塞,直到有读取操作从队列中取出一定的消息。如果在msgflg值指定了IPC_NOWAIT,那么此时写入操作会直接失败。注意点写入操作的msgtype不能指定为0
  • 读取 msgrcv
  • 在这里插入图片描述

1.传入参数1. 消息队列的标识符 msqid 2.指定msgbuf的指针msgp,3.指定读取数据大小 msgsz,4.指定读取的消息类型 msgtyp 5.读取操作的msgflg
2.返回值 1.成功 返回实际从消息队列中读取的数据数量 2.失败 -1
4. - 如果msgtype 指定为0,表示从所有队列中接收消息,队列中的第一个消息会被接收。
-如果msgtype 大于0,则表示从指定的消息队列中获取第一个消息
-如果msgtype小于0,则表示从类型值最小、或绝对值等于msgtype的队列中获取第一个消息
5. 如果msgsz中设置的大小小于要读取的消息的大小,那么读取操作会失败并返回-1,除非在msgflg中指定MSG_NOERROR,这样读取操作用以msgsz的大小将消息截断、存储到msgp的mtext中,并将超过的部分丢弃。
6. 当消息队列中没有消息是,读取操作会阻塞,此时可以通过在msgflg中指定IPC_NOWAIT,令其直接返回
7. 在msgflg中指定MSG_CPOY,可以获取消息,但是不将其从队列中剔除
8. 在msgflg中指定MSG_EXCEPT ,读取除msgtype之外的第一个消息

3.消息队列的控制操作

通过msgctl 系统调用实现对消息队列的控制
在这里插入图片描述

  • 传入:1.消息队列标识符 msqid 2.操作令 cmd 3,消息队列数据结构的指针 buf

  • 传出:根据 设置的指令的不同,返回不同的数值;

  • msqid_ds数据结构:
    在这里插入图片描述

  • 常用的指令cmd:

  1. IPC_RMID 删除消息队列此时只需要指定msqid即可,成功返回0,失败返回-1;
  2. MSG_STAT 通过buf返回消息队列的状态,成功返回0,失败返回-1

使用步骤

//1.指定key值
key_t key = ftok("./makefile",0);

//2.使用key向内核申请创建新的信息队列
int msgid = msgget(key,IPC_CREAT|0600);

//3.定义发送和接收信息的结构体
	//3.1重新定义需要使用的msgbuf结构体
typedef struct msgbuf{
	long mtype;
	char mtext[100];
}mbuf_t,pmbuf_t;
	//3.2定义和初始化
mbuf_t msg1,msg2;//msg1用于发送,msg2用于接收
memset(&msg1,0,sizeof(mbuf_t));
memset(&msg2,0,sizeof(mbuf_t));
msg1.mtype= 1;//发送的消息类型不能指定为0,否则会报错

//4.发送数据
strcpy("hello,world",msg1.mtext);
int ret = msgsnd(msgid,&msg1,strlen(msg1.mtext),0);

//5.接收数据
//接收类型指定为0,直接读取第一条消息复制到msg2,同时使用了非阻塞模式
ret = msgrcv(msgid,&msg2,100,0,IPC_NOWAIT);

//6.关闭消息队列
ret = msgctl(msgid,IPC_RMID,0);


三、Linux系统信号

1.信号概念

  1. 信号是进程在运行的过程中,由自身或由进程外部发送过来的消息(事件),对进程的一种通知机制。
  2. 信号产生后,会被传递给一个进程进程也会采取某些措施来响应进程,。在产生和到达的期间,信号处于等待状态pending。
    3.信号到达后,会根据信号编号的不同执行以下不同的操作:
    在这里插入图片描述
  • 终止(杀死)进程
  • 忽略信号,内核将该信号丢弃,信号对进程没有任何影响(进程永远都不知道曾经出现过该信号)
  • 产生核心转储文件
  • 停止,暂停进程的执行
  • 继续,恢复进程的执行

2.信号的分类

  • 信号分为两大类:
    第一组用于内核向进程通知事件。称为标准信号,其编号范围为0~31。标准信号由进程请求,由内核发给对应的进程。
    第二类 信号由实时信号组成。

标准信号

在这里插入图片描述

3.发送信号的发送

shell kill

命令格式:
在这里插入图片描述

  • kill 默认使用 -4 SIGTERM信号来终结进程
  • kill -l 可以显示所有信号

系统调用 kill

函数声明
在这里插入图片描述
传入 :1.pid编号 2.需要发送的信号即可
返回值:成功返回0,失败返回-1

  • pid 的表示方法
    • pid >0 向指定pid进程发送指定信号
    • pid = 0 向本进程组的所有进程发送指令信号
    • pid = -1 调用进程向所有有权限的进程发送指定信号
    • pid < -1 向pid绝对值的进程组发送信号
  • 0号信号,可以用来探测进程是否仍然存在,或者当前进程对指定进程是否拥有权限

4.信号处理器程序

  • 信号处理器程序,是当指定的信号传递给进程时会调用的一个函数。
    • 调用信号处理器程序,可能会随时打断主程序流程;内核代表进程来调用处理器程序,当处理器返回时,主程序会在处理器打断的位置恢复执行。
  • SIGKILL 的处理器程序不能被重新定义

1.signal函数

在这里插入图片描述
输入:1.信号编号signum 2.需要执行的信号函数sighandler
返回:1.正常结束:之前的信号处理函数 2.错误:SIG_ERR

2.sigaction函数

sigaciton通过调用使用了struct sigaction的指针,来完成信号传递后的各种操作。相比于signal函数,灵活性更高,可以把signal函数看做sigaction函数的一个真子集。

  • sigaction 的结构体定义如下:
    在这里插入图片描述

    • sigaction结构体的用途:

    • 1 .sa_handler和sa_sigcation 函数用于接收信号后的处理。默认使用的是sa_handler函数,如果需要使用sa_sigcation函数,则需要在sa_flag中增加SA_SIGINFO,此操作会向信号处理函数中传入siginfo_t*类型的参数对象;因此必须使用sa_sigaction,

    • 2 .sa_mask的类型是一个信号量编号的集合的结构体,向这个结构体中增加/删除成员需要使用一组特定的函数:
      在这里插入图片描述

    • 3 .sa_flags
      在这里插入图片描述

    • 4.sa_restorer 函数,这个函数现在没有规定具体用途,可以先忽略

  • sigaction函数的声明
    在这里插入图片描述
    输入:1.信号编号signum 2.信号处理相关信息的结构体的指针 act 3.用于保存之前的数据处理信息的结构体 oldcat(默认可以为NULL)
    输出:1.正常结束:0 2.异常 -1

  • 使用sigaction函数

  • 1 . 定义sigaction结构体

  • 2 .给sigactionj 结构体赋值

  • 3.确定需要使用的flag模式

  • 4.调用
    调用步骤

#1.定义处理函数
void sigfunc(int num,siginfo_t* info,void *  useless){
	printf("hello,world\n");
}
//2.定义sigaction结构体
struct sigaction  siga;
memset(&siga,0,sizeof(struct sigaction));
siga.sa_sigation = sigfunc;
siga.flg = SA_SIGINFO|SA_SIGNODEFER;//传入siginfo信息并且如果函数调用时发生了其他信号,则立即中断进入
//3.调用sigaction函数
int ret = sigaction(SIGINT,&siga,NULL);

5.信号的阻塞操作

#include <signal.h>

0.操作的基本单位 sigset_t

sigset_t 实际是一个位图的结构,是一个二进制的数组
对sigset_t 的操作如下:
在这里插入图片描述另外,如果在#include<signal.h>前加上 #define _GNU_SOURCE ,
则可以使用以下函数
在这里插入图片描述

1.使用sigaction函数

  • 使用sigaction函数阻塞信号,主要是利用传入参数struct sigaction* act中的sa_mask成员。
  • 其生效范围是sigaction执行信号处理函数的时候。
    在此信号处理函数之外的信号不会被阻塞
//屏蔽SIGINT信号
struct sigaction siga;
sigemptyset(siga.mask);
sigaddset(&siga.mask,SIGINT);

2.使用sigprocmask函数

  • 使用sigprocmask系统调用。需要实现定义好sigset。之后调用sigprocmask进行屏蔽操作。

  • sigprocmask修改的是进程结构体task_struct中关于屏蔽信号的信息,所以其有效范围是直到进程结束。

  • 函数声明

  • 在这里插入图片描述传入:1.sigprocmask操作指令 how 2.需要操作的集合set 3.修改的集合oldset

  • 返回值: 0正常结束 -1错误

  • 其中,how可以指定以下几个值

    • SIG_BLOCK 将set集合添加到现有的屏蔽集合中
    • SIG_UNBLOCK 将set集合从现有的屏蔽集合中删除
    • SIG_SETMASK 将屏蔽集合重置为set集合
  • 使用步骤:

//1.定义需要屏蔽的信号集合
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
//2.调用sigprocmask
sigprocmask(SIG_BLOCK,&set,NULL);
//3.使用结束后可以解除屏蔽
sigprocmask(SIG_UNBLOCK,&set,NULL);

四、POSIX 的三种方法

和SYSTEM V类似,接口不一样,后面补充

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页