第七节 进程间的通信

发布时间:2026/8/5 9:14:30
第七节 进程间的通信 一、管道管道|-----所有管道的特性①管道是单向的数据只能从左到右;②管道会创建子进程执行命令③管道中的命令时并行执行的④管道可以串联形成管道链。管道可以用来在两个进程之间传递数据如 ps -ef | grep “bash”, 其中‘|’就是管道其作用就是将 ps 命令的结果写入管道文件然后 grep 再从管道文件中该数据进行过滤。1有名管道通过mkfifo命令创建管道形成管道名专属于有名管道的特性①有可见的文件名存在于文件系统中②可以被任意进程访问需要权限;③数据传输遵循先进先出的原则④本质上时内存中的特殊文件不占用磁盘空间⑤关闭所有引用它的进程后管道文件任然存在需要手动删除。测试一读端持续监听管道cat管道文件注意持续读取whiel true;do cat 管道文件done写端多次写入内容cat管道文件需要开两个终端第一个终端Cat fifo管道文件开启写端在屏幕上写第二个终端接收第一个终端的信息测试二在文件读取和写入读文件使用read函数读文件注意read的第三个参数不能是初始化的strlen写文件使用write函数将数据写入到管道文件中#includestdio.h #includestdlib.h #includestring.h #includeunistd.h #includefcntl.h void main() { int fdwopen(fifo,O_WRONLY);//只读的方式打开 if(fdw-1) exit(1); printf(input:); char buff[128]; fgets(buff,128,stdin); write(fdw,buff,strlen(buff)); close(fdw); exit(0); }#includestdio.h #includestdlib.h #includestring.h #includeunistd.h #includefcntl.h void main() { int fdropen(fifo,O_RDONLY); if(fdr-1) exit(1); char arr[128]; read(fdr,arr,sizeof(arr)); printf(%s\n,arr); close(fdr); exit(0); }注意两个进程通信成功但是使用很多次管道文件fifo ,但是它的内存还是0不占用空间2无名管道无名称仅通过文件描述访问无名管道主要应用于父子进程间的通信。无名管道的特性①仅支持亲缘进程父子进程、兄弟进程②创建方式pipe () 系统调用③生命周期随进程的结束而销毁。测试无名管道的创建#includestdio.h #includestdlib.h #includestring.h #includeunistd.h #includefcntl.h int main() { int fd[2];//用来存储读端和写端的值 pid_t pid;//需要fork() char buff[128];//读端缓冲区 char*messagehello from parent;//写端发送的数据 ///1.创建无名管道 if(pipe(fd)-1) { printf(pipe err!\n); exit(1); } //2.创建子进程继承管道文件描述符 if((pidfork())-1) { printf(fork err!\n); exit(1); } //3.区分父子进程设置通信方向 if(pid0) { close(fd[0]);//关闭读端 write(fd[1],message,strlen(message)); printf(Parent finish!\n); close(fd[1]);//关闭写端 } else { close(fd[1]);//关闭写端 int read_lenread(fd[0],buff,sizeof(buff)-1); if(read_len0) printf(%s\n,buff); close(fd[0]); } exit(0); }3总结区别有名管道无名管道关系有名存在文件系统中不在系统中生命周期随文件系统的销毁而销毁随进程的销毁而销毁进程关系任意进程父子进程、兄弟进程创建方式mkfifopipe注意事项避免管道破裂读端已关闭的情况下写端还在输出数据会触发SIGPIPE信号终止写端进程。数据无边界管道是字节流数据若按照信息的格式一条一条发送需要自定义信息格式长度数据避免多读少读。明确知道发送数据的长度。缓冲区的堵塞问题读空写满会堵塞进程所以需要fcntl()设置属性进行处理。资源泄露问题进程退出提前关闭管道描述符会导致缓冲区中数据丢失。二、进程间通信的方式PIC单工通信单向性确定方向后固定不能相互传输数据双工通信双向性同一时刻两方可以相互传输数据不同时刻也可以传半双工通信双向性同一时刻只能一方给另一方传输数据例如管道进程间通信 的方式简称PIC管道、共享内存、消息队列、信号量、套接字1共享内存共享内存为多个进程之间共享和传递数据提供了一种有效的方式。①shmget 创建 / 获取一块共享内存段函数类型int shmget(key_t key,size_t size,int shmflg);函数返回值-1--表示共享内存创建失败如果是正数表示该段的标识符表示创建成功。shmget函数创建一块共享内存空间key表示内存段的唯一标识size表示创建的共享内存空间的大小创建新共享空间时该参数填正整数获取已有共享空间时该参数可以填0shmflg标志位用来控制函数行为创建标志IPC_CREAT如果不存在则创建IPC_EXCL与IPC_CREAT配合使用如果key已经创建则报错并设置错误原因给errno(linux系统中全体变量)保证全新创建。权限控制需要指定权限位0777 0666 八进制ipcs可以查看当前系统中进程间通信资源测试#includestdio.h #includestdlib.h #includeunistd.h #includestring.h #includepwd.h #includesys/wait.h #includesignal.h #includesys/ipc.h #includesys/shm.h int main() { int shmidshmget((key_t)1234,128,IPC_CREAT|IPC_EXCL|0600); if(shmid-1) { printf(shmget err!\n); exit(1); } return 0; }②shmat将共享内存段挂载映射到调用进程的虚拟地址空间void* shmat(int shm_id,const void*shm_addr,int shmflg);shmat函数作用将一个已存在的共享内存段映射到地址空间中shm_id共享内存段的标识符通常是shmget函数的返回值shm_addr指定共享内存段要附加到进程地址空间的哪个地址。NULL让系统内存自动选择一块合适的未使用的地址来附加到共享内存段非NULL填具体位置系统会将共享内存段附加到指定位置如果指定位置不能使用则报错附加失败。shmflg控制附加操作的控制位0默认权限根据shmget创建时的权限进行续写SHM_RDONY:以只读的方式附加内存段如果该进程尝试写入则引入发段错误SHM_EXEC允许在共享内存段上执行代码测试share.c#includestdio.h #includestdlib.h #includeunistd.h #includestring.h #includepwd.h #includesys/wait.h #includesignal.h #includesys/ipc.h #includesys/shm.h int main() { int shmidshmget((key_t)1234,128,IPC_CREAT|0600);//获取该共享内存的内存段的标识符 if(shmid-1) { printf(shmget err!\n); exit(1); } char*s(void*)shmat(shmid,NULL,0);//映射函数 if(s(char*)-1) { printf(shmat err!\n); exit(1); } strcpy(s,hello);//写入一个数据s 字符串hello return 0; }share1.c#includestdio.h #includestdlib.h #includeunistd.h #includestring.h #includepwd.h #includesys/wait.h #includesignal.h #includesys/ipc.h #includesys/shm.h int main() { int shmidshmget((key_t)1234,128,IPC_CREAT|0600);//获取该共享内存的内存段的标识符 if(shmid-1) { printf(shmget err!\n); exit(1); } char*s(void*)shmat(shmid,NULL,0);//映射函数 if(s(char*)-1) { printf(shmat err!\n); exit(1); } //strcpy(s,hello);//写入一个数据s 字符串hello printf(%s\n,s); return 0; }③练习共享内存设置一个进程为 读取共享内存上的数值持续读取另外一个进程可以持续写入。在两个终端中使用全双工双向性#includestdio.h #includestdlib.h #includeunistd.h #includestring.h #includepwd.h #includesys/wait.h #includesignal.h #includesys/ipc.h #includesys/shm.h int main() { int shmidshmget((key_t)1234,128,IPC_CREAT|0600); char buff[128]; if(shmid-1) { printf(shmget err!\n); exit(1); } char*s(void*)shmat(shmid,NULL,0); if(s(char*)-1) { printf(shmat err!\n); exit(1); } while(1) { fgets(buff,128,stdin); strcpy(s,buff); if(strncmp(s,end,3)0) break; } }#includestdio.h #includestdlib.h #includeunistd.h #includestring.h #includesys/ipc.h #includesys/shm.h int main() { // key、内存大小 和写端完全一致 int shmid shmget((key_t)1234, 128, IPC_CREAT|0600); if(shmid -1) { printf(shmget err!\n); exit(1); } char *s (void*)shmat(shmid, NULL, 0); if(s (char*)-1) { printf(shmat err!\n); exit(1); } while(1) { // 读取共享内存数据 printf(读到%s, s); // 检测到 end 退出 if(strncmp(s,end,3)0) break; usleep(200000); // 轻微休眠避免疯狂循环占用CPU } // 解除映射 shmdt(s); return 0; }2信号量信号量是操作系统用于进程间与互斥的关键机制解决多个进程对共享资源的竞争问题避免数据冲突或不一致。原理通过一个计数器对共享资源的访问。信号量的本质是一个受保护整数变量其值反映了共享资源的可用数量只能通过特定的原子操作不可被中断的操作修改。原子操作对信号量的修改加、减避免多进程并发修改导致数据不一致。堵塞机制当信号量的值为0时试图获取信号量的进程会被诸塞直到有其他进程释放资源。信号量的类型①二值信号量值只能时0和1用于实现互斥类似锁一次只允许一个进程运行②计数信号量值可以时任意非负数同于控制对多个相同资源的并发访问。假设信号量为3最大可支持3个进程哦同时运行。信号量可以为4也可以但是不要同时运行就行P、V 原语信号量的行为由两个基本操作定义称为“原语”确保原子性P操作将信号量值减一若结果继续执行如果结果0进阻塞放入信号量的等待的队列拿取资源。V操作将信号量加一若结果0进程可继续执行如果结果0从等待队列中唤醒一个进程使其继续执行。临界资源同一时刻只允许一个进程访问的资源临界区访问临界资源的代码段A.int semget(key_t key, int nsems, int semflg)semget()用于获取信号量集的系统调用key用于表示信号量集的唯一性确保不同进程能通过相同的Key访问同一信号量集nsems信号量的数量要创建的信号量集中包含的信号量个数若存在创建新信号量集则nsems必须大于0若是获取已存在的信号量集则nsems可以设置为0semflg:(标志位)控制信号量集的创建/获取行为。IPC_CREAT若对应的信号量不存在则创建新集若已存在则会返回0IPC_EXCL与IPC_CREAT组合使用若信号量集已存在则创建失败。确保获取新资源B.int semctl(int semid, int semnum, int cmd, ...)semctl用于控制操作信号量集不同操作命令需要传入不同类型的数据所以用联合体共用一块内存。semid信号量集的标识符semget()返回指定要操作的信号量集。semnum信号量在集中的索引 从0开始指定要操作的单个信号量。若对整个信号量集进程操作IPC_RMID,则此参数忽略。cmd要执行的控制命令决定semct的行为SETVAL设置semnum指定的信号量的初始值需要配合第四个参数传递CETVAL获取semnum指定的信号量的当前值返回值为该信号量的值IPC_RMID删除整个信号量集所有进程都无法访问IPC_STAT获取信号量集的状态信息存入semid_ds的结构体创建时间权限IPC_SET设置信号量集的属性需要通过semid_ds的结构传递新属性可变参数可选根据cmd参数可能需要传递一个union semnum类型的参数需要自定义//union semun是semctl()系统调用专用联合体用来给 semctl 传递参数。union semun{int val ;//用于SETVAL命令设置信号量值struct semid_ds *buf;//IPC-STAT或者IPC_SET状态信息unsigned short*array;//GETALL或者SETALL批量操作struct seminfo*_buff;//用于IPC_INFO系统限制信息}C.int semop(int semid, struct sembuf *sops, size_t nsops)semop用于执行信号量的操作semid信号量集的标识sops指向struct sembu的结构体操作struct sembuff{unsigned short sem_num;//信号量在集中的索引short sem_op;//操作类型1 还是-1short sem_flg;//操作系统一般使用 SEM_UNION确保进程异常退出时系统撤销对信号量的修改避免永久堵塞}nsops数组中nsops元素的数量要执行的操作数量D.额外补充 lseek(int fd,off_t offset,int whence)lseek()该函数可以改变文件中读写指针的位置fd表示要操作的文件offset偏移量字节whence起始位置SEEK_SET表示从文件开头开始SEEK_CUK表示从当前指针位置开始SEEK_END表示从文件结尾开始3消息队列消息队列的原理是一种异步通信中间件生产者把消息发送到队列消费者从队列取消息处理实现服务解耦、异步、削峰。核心模型生产者 → 队列 → 消费者。核心角色①生产者 Producer产生消息发送给消息队列②BrokerMQ 服务端存储消息管理队列、转发消息③队列 Queue消息的存储容器先进先出 FIFO④消费者 Consumer从队列拉 / 接收消息执行业务逻辑核心工作原理a.生产者不直接调用消费者把消息投递到 Broker 的队列b.Broker 把消息持久化保存到队列等待消费c.消费者主动拉取pull或者 Broker 推送push消息给消费者d.消费者处理完成后发送ACK 确认告诉 MQ消息处理完毕e.MQ 收到 ACK才删除这条消息没收到 ACK消息会重新投递。三大核心能力原理解耦服务 A 不用直接调用服务 BA 发消息到 MQ 就返回B 自己消费消息。A、B 可以独立上线、修改互不依赖。异步A 不需要等待 B 处理完成发完消息立刻返回提升接口响应速度。例如下单后发短信、生成订单日志不阻塞下单主流程。削峰填谷流量突增时大量消息先堆积在 MQ 队列消费者按自己的处理速度慢慢消费避免下游服务被瞬间大流量打垮。注意出现问题消息丢失、消息重复消费、消息积压A.intmsgget(key_t key,intmsqflg)key_t key IPC 键值用来唯一标识一个消息队列 可以用ftok()生成 key特殊值IPC_PRIVATE每次调用都创建全新私有消息队列。msgget()创建或者获取一个消息队列msgget()成功返回消息队列 ID失败返回-1msqflgIPC_CREAT不存在则创建存在直接打开 IPC_EXCL必须和IPC_CREAT一起用如果队列已存在直接报错返回‑1防止重复创建。B.intmsgsnd(intmsqid,constvoid*msqp,size_tmsqsz,intmsqflg)函数作用向 System V 消息队列发送一条消息把用户空间的消息复制到内核的消息队列中。int msqid消息队列 ID来自msgget()返回值。const void *msqp指向自定义消息结构体的指针。size_t msqszmtext 有效数据的字节大小不包含 mtype (long) 的大小。int msqflg标志0默认阻塞。队列满时调用进程阻塞等待直到队列有空位。 IPC_NOWAIT非阻塞。队列满不等待直接返回‑1errnoEAGAIN。#include stdio.h #include string.h #include sys/types.h #include sys/ipc.h #include sys/msg.h struct msgbuf { long mtype; char mtext[128]; }; int main(void) { key_t key ftok(./test.c,100); //生成一个System V IPC 的 key 键值用于消息队列、共享内存、信号量给msgget()、 //shmget()、semget()使用。 //不同进程传入同一个 pathname 和 proj_id就会得到完全相同的 key_t 值以此实现进程间定位同一个 //IPC 对象。 //返回值成功返回 key_t 键失败返回‑1。 int msgid msgget(key, IPC_CREAT | 0666); struct msgbuf buf; //读取类型1的消息 msgrcv(msgid, buf, sizeof(buf.mtext), 1, 0); printf(读到消息%s\n, buf.mtext); msgctl(msgid, IPC_RMID, NULL); //删除消息队列 return 0; }C.ssize_t msgrcv(intmsqid,void*msgp,size_tmsqsz,longmsqtyp,intmsqflg)函数作用从 System V 消息队列中读取一条消息将内核中的消息拷贝到用户缓冲区读取成功后该消息会从内核消息队列中移除。msqid消息队列 ID来自msgget()返回值。msgp接收缓冲区指针指向struct msgbuf结构体。struct msgbuf{ long mtype; //消息类型 char mtext[128]; //消息数据 };msqszmtext缓冲区大小只算数据部分不包含 long mtype。 如果队列里消息的数据长度 msqsz消息会被截断。msqtyp按类型选择接收消息核心msqtyp 0读取队列第一条消息不管什么类型msqtyp 0读取mtype 等于该值的消息msqtyp 0读取队列中mtype 小于等于 abs (msqtyp)的最小类型消息。msqflg标志0默认阻塞没有匹配消息时进程阻塞等待消息到来。IPC_NOWAIT非阻塞没有匹配消息直接返回‑1errnoENOMSG。 MSG_NOERROR消息数据超过 msqsz 时自动截断不报错。不加这个超长消息会报错返回‑1。D.intmsgctl(intmsqid,intcmd,structmsqid_ds *buf)msgctl()控制消息队列msgctl()成功返回 0失败返回-1msqid消息队列 idmsgget()返回值。cmd命令决定要做什么操作三个最常用IPC_RMID删除消息队列内核立刻销毁队列第三个参数buf传NULL。IPC_STAT获取消息队列状态信息把队列信息存入buf指向的struct msqid_ds结构体 。IPC_SET修改消息队列的权限、所有者等属性从buf读取设置的值。