☰
Linux学习日记:进程通信(IPC)机制
2026/9/29 5:44:08 网站建设 项目流程

进程间通信上(IPC)

总共五种,分别是:管道、信号量、消息队列、共享内存、套接字

1.管道

管道又分为命名管道和匿名管道

作用:比方说我现在想在a进程与b进程之间通信,希望在a进程写入数据,b进程读取数据,这时候就可以使用管道。那去使用.txt文件不是一样可以做到吗?a进程在.txt文件写入内容,b进程在.txt文件读取内容。是的,使用纯文本文档一样可以做到,但是管道的效率远超文件(因为纯文本文档的写入与读取需要使用I/O设备,而写入管道的数据会存放在内存中)。

命名管道:

创建命名管道:使用mkfifo+文件名

(删除方法与其他文件也一致)

使用示例:

a.c文件→

b.c文件→

很简单的两串代码,主要还是open()/close()和write()/read()函数能否熟练使用,那么,此时只编译运行a.c:

发现程序直接阻塞住了(甚至连fd的值都没有打印出来)

同理只运行b.c也是阻塞状态:

只有当两个进程同时运行时:

此时fd的值才能正常打出,此时给a进程写入数据→

此时两个进程同时结束,可见通信最少必须在两个进程之间完成,同时管道也是最基础的IPC机制

若是改成循环(即进程间多次发生通信)

a.c b.c

主要注意循环终止条件!当管道空时,读端会阻塞;管道满时,写段会阻塞。写段先关闭,读段read()返回值==0;读段先关闭,写段write()会触发信号SIGPIPE(13)程序退出

管道文件不占用内存,只是一个接口,实际上的内容写在内存开辟的一块空间上

匿名管道:

匿名管道主要用于父子进程之间的通信,它不需要先创建文件,而是直接在代码中通过函数创建匿名管道:

pipe()函数创建匿名管道,成功返回0,失败返回-1;参数为数组,其中pipefd[0] 代表读端(读权限)。pipefd[1]代表写端(写权限),以下为示例:

若是在父子进程中使用:

#include <unistd.h> int main() { int fd[2]; pipe(fd); // 创建管道,fd[0]读端,fd[1]写端 if (fork() == 0) { // 子进程 close(fd[0]); // 关闭读端 write(fd[1], "Hello", 6); } else { // 父进程 close(fd[1]); // 关闭写端 char buf[10]; read(fd[0], buf, 6); printf("Received: %s\n", buf); } }

总结:管道是一个半双工通信(什么是半双工?即两个进程只能由一个读取数据,一个写入数据,但是对哪边进行读取或写入不做要求,而手机通信是全双工通信,收音机是单工通信)。命名管道可以在任意两个进程间通信,匿名管道只能在父子进程之间通信,管道本质上是内核维护的内存缓冲区。

2.信号量

信号量是一种用于进程/线程同步的机制,它不仅仅用于进程间通信,更核心的作用是控制多个进程/线程对共享资源的访问。

  • 信号量的值:0,1,2,3都可以,需要多少信号量由程序员自己来决定
  • p操作(等待):对信号量减一,若信号量的值为0,则阻塞
  • v操作(释放):若有进程此时在等待,则唤醒等待进程,否则信号量+1

想象一个停车场:

  • 信号量的值= 停车场的空位数量。

  • P操作(等待) = 一辆车进入停车场。空位减一。如果没有空位,车子就必须等待。

  • V操作(发送) = 一辆车离开停车场。空位加一。如果有车在等待,就放一辆车进来。

临界资源:同一时刻只允许一个进程访问的资源

临界区:访问临界资源的代码段

信号量函数:

#include <sys/sem.h> int semget(key_t key, int nsems, int semflg);

参数key_t key,唯一唯一标识信号量集的键值。(可以理解为同一个门牌号,只要门牌号一样就是同一间房间,不需要特定的门牌号)。

参数int nsems,信号量个数,想定义几个信号量就定义几个。

参数int semflg,权限标志和创建选项的组合,权限位与文件权限类似(0600),创建选项:

  • IPC_CREAT:如果不存在则创建,如果已存在则忽略

  • IPC_EXCL:与IPC_CREAT一起使用,如果已存在则失败

返回值:创建成功返回信号量集的标识符(非负整数),失败返回-1

int semctl(int semid, int semnum, int cmd, ...);

参数int semid,由函数semget()返回的信号量集

参数int semnum,信号量在集合中的索引(类似数组下标,例如创建了两个信号量,则第一个信号量索引为0,第二个信号量索引为1,以此类推)

参数int cmd,要执行的控制命令。

  • 常用命令:

    • IPC_RMID:立即删除信号量集

    • SETVAL:设置单个信号量的值

    • GETVAL:获取单个信号量的值

    • SETALL:设置所有信号量的值

    • GETALL:获取所有信号量的值

    • IPC_STAT:获取信号量集的状态信息

第四个参数...,可变参数,通过自己定义的联合体union semun来输入

union semun { int val; // SETVAL 用的值 struct semid_ds *buf; // IPC_STAT, IPC_SET 用的缓冲区 unsigned short *array; // SETALL, GETALL 用的数组 struct seminfo *__buf; // IPC_INFO 用的缓冲区(Linux特有) };
int semop(int semid, struct sembuf *sops, size_t nsops);

参数int semid,由函数semget()返回的信号量集

参数struct sembuf* sops,指向操作数组的指针(其实就是指向一个结构体的指针,不知道为啥非要这样说...)

参数size_t nsops,操作数组的操作个数

sembuf结构体:

struct sembuf { unsigned short sem_num; // 信号量在集合中的索引 short sem_op; // 操作类型 short sem_flg; // 操作标志 };

结构体参数unsigned short sem_num,信号量在集合中的索引(同上,类似数组下标)

结构体参数short sem_op,赋值-1为p操作,1为v操作

结构体参数short sem_flg,通常填写SEM_UNDO(进程退出时自动撤销操作,防止死锁)或0(默认阻塞行为)

此时存在两个进程text和tmp,代码如下

tmp.c text.c

此时同时运行这两个程序(放在后台可一起运行)→

会发现这两个进程同时混在一起运行,无法做到一个进程运行完一次循环另一个进程再去运行。

通过对上面三个函数进行封装得到自己的信号量函数:

sem_init()

sem_p()

sem_v()

sem_destroy()

此时再对text进程与tmp进程使用信号量进行控制:

tmp.c text.c

运行结果:

总结:信号量是一个功能强大的同步原语,其核心是一个受保护的整型变量和两个原子操作(P 和 V)。它通过控制进程的"等待"和"唤醒"来协调对共享资源的访问。

个人小结:管道这里倒是通俗易懂,信号量的接口实在是太多了...真的就是只能理解理解原理了,联合体需要自己编写,结构体需要记,还有各种特殊参数QAQ。真的不好记,进程间通信上课还因为前一天晚上熬夜导致没有好好听课,现在补回放只感觉路漫漫其修远兮啊。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询