Linux编程-进程间通信
2026/8/22 9:57:22 网站建设 项目流程

进程有独立地址空间,不能直接访问对方内存,IPC 就是实现不同进程之间数据交换、同步协作的机制。

IPC 分类

  1. 传统 IPC:管道、命名管道、信号
  2. System V IPC:共享内存、消息队列、信号量
  3. POSIX IPC:posix 消息队列、posix 信号量、posix 共享内存
  4. Socket:支持本机 + 跨网络进程通信

进程通信的原理:进程空间 相互独立 每个进程拥有独立的4G内存空间,但是内核空间共享
所有进程间通信方式,本质都是借助操作系统内核来完成的

匿名管道 pipe

  1. pipe()系统调用,内核在内核缓冲区开辟一块环形缓冲区(内存,不在磁盘)。
  2. 创建两个文件描述符:fd[0]读端fd[1]写端,这两个 fd 属于进程。
  3. fork()之后子进程复制文件描述符,父子进程指向同一个内核缓冲区。
  4. 只有文件描述符,只能用于有血缘关系进程通信(父子、兄弟)半双工:一端读、一端写。
int pipe(int pipefd[2]); @pipefd --- 用来存放管道两端的文件描述符 pipefd[0] ---管道读端 pipefd[1] ---管道写端 返回值: 成功 0 失败 -1 && errno被设置

管道的大小为64KB字节,也是65536个字节。

管道读写规则 读 端 写 端 存在 存在 ---写管道,如果管道为空,此时可以写,但是管道满,写操作阻塞 存在 存在 ---读管道,如果管道中有数据,则读取,没有数据,此时读操作会阻塞 存在 不存在 ---读管道,如果管道中有数据,则读取,没有数据,此时读操作直接返回 不存在 存在 ---写管道,管道破裂

练习:创建一个子进程,实现父子进程间的任意信息

  1. pipe(fd)创建无名管道,实现父子进程通信;
  2. fork()创建子进程,父子进程各自持有管道两个 fd;
  3. 父进程:关闭读端,读取键盘输入,把数据写到管道;输入quit就关闭写端,等待子进程结束;
  4. 子进程:关闭写端,循环读管道数据,打印到屏幕;收到quit直接退出;
  5. 注意:管道是半双工,父子必须关闭自己不用的那一端。
#include<stdio.h> #include<unistd.h> #include<string.h> #include <sys/types.h> #include <sys/wait.h> int main(int argc, const char *argv[]) { int fd[2]; // 管道文件描述符数组,fd[0]读端,fd[1]写端 // 创建无名管道,成功返回0,失败返回-1 if(pipe(fd) < 0) { perror("pipe fail"); // 打印管道创建失败原因 return -1; } // 打印管道读写两端的文件描述符 printf("pipe success\nfd[0]=%d\tfd[1]=%d\n",fd[0],fd[1]); // 创建子进程,父进程返回子进程pid,子进程返回0,出错返回-1 pid_t pid = fork(); if(pid == -1) { perror("fork fail"); // 创建子进程失败打印错误信息 return -1; } // ==========父进程分支========== if(pid > 0) { close(fd[0]); // 父进程只负责写管道,关闭管道读端 while(1) // 循环读取键盘输入,写入管道 { char buf[20] = {0}; // 从标准输入(键盘,文件描述符0)读取用户输入 int ret = read(0,buf,sizeof(buf)); // 将读到的数据写入管道写端,传给子进程 write(fd[1],buf,ret); // 判断输入是否为quit,退出程序 if(strncmp(buf,"quit",4) == 0) { close(fd[1]); // 关闭管道写端 wait(NULL); // 阻塞等待回收子进程资源,防止僵尸进程 return 0; } } } // ==========子进程分支========== if(pid == 0) { close(fd[1]); // 子进程只负责读管道,关闭管道写端 while(1) // 循环从管道读取父进程发来的数据 { char buf[20] = {0}; // 从管道读端读取父进程发送的数据 int ret = read(fd[0],buf,sizeof(buf)); // 如果读到quit,子进程关闭读端退出 if(strncmp(buf,"quit",4) == 0) { close(fd[0]); return 0; } // 将读到的数据输出到标准输出(屏幕,文件描述符1) write(1,buf,ret); } } return 0; }

练习:父子进程的双向通信

  • 有名管道 FIFO 半双工,一根管道只能单向传输;双向通信必须使用两根 FIFO
    • a2b:A 写 → B 读
    • b2a:B 写 → A 读
  • 每个程序内部fork(),父子进程分工:一个负责键盘输入写管道,另一个阻塞读管道接收对方消息。
  • mkfifo重复创建会报错,判断errno != EEXIST避免管道已存在时报错。
  • unlink()删除磁盘上的管道文件,避免残留。

a进程代码:

#include<stdio.h> #include<unistd.h> #include<string.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/stat.h> #include<stdlib.h> #include<errno.h> #include<fcntl.h> int main(int argc, const char *argv[]) { // 创建有名管道a2b,a写b读,存在则忽略报错 if(mkfifo("a2b",0666) < 0 && errno != EEXIST) { perror("mkfifo fail"); return -1; } // 创建有名管道b2a,b写a读 if(mkfifo("b2a",0666) < 0 && errno != EEXIST) { perror("mkfifo fail"); return -1; } printf("mkfifo success\n"); // fd_w:打开a2b管道,只写,a向b发送消息 int fd_w = open("a2b",O_WRONLY); if(fd_w < 0) { perror("open a2b fail"); unlink("a2b"); unlink("b2a"); return -1; } // fd_r:打开b2a管道,只读,a读取b发来的消息 int fd_r = open("b2a",O_RDONLY); if(fd_r < 0) { perror("open b2a fail"); unlink("a2b"); unlink("b2a"); return -1; } // fork创建子进程,父子分工:一个写、一个读 pid_t pid = fork(); if(pid < 0) { perror("fork fail"); close(fd_w); close(fd_r); unlink("a2b"); unlink("b2a"); return -1; } if(pid > 0) { // 父进程:键盘输入,写入a2b管道发给b进程 while(1) { printf("a to b:"); char buf[20] = {0}; int ret = read(0,buf,sizeof(buf)); // 读取键盘输入 write(fd_w,buf,ret); // 写入a2b管道 if(strncmp(buf,"quit",4) == 0) // quit退出 break; } } if(pid == 0) { // 子进程:读取b2a管道,接收b进程发来的数据 while(1) { char buf[20] = {0}; int ret = read(fd_r,buf,sizeof(buf)); // 读b2a管道 if(strncmp(buf,"quit",4) == 0) // 收到quit结束 break; printf("b to a:%s\n",buf); // 打印b发送的内容 } } // 关闭fd,删除有名管道文件 close(fd_w); close(fd_r); unlink("a2b"); unlink("b2a"); return 0; }

b进程代码:

#include<stdio.h> #include<unistd.h> #include<string.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/stat.h> #include<stdlib.h> #include<errno.h> #include<fcntl.h> int main(int argc, const char *argv[]) { // 创建有名管道a2b:a进程写,b进程读;EEXIST代表管道已存在,不报错 if(mkfifo("a2b",0666) < 0 && errno != EEXIST) { perror("mkfifo fail"); return -1; } // 创建有名管道b2a:b进程写,a进程读;实现双向通信需要两根FIFO if(mkfifo("b2a",0666) < 0 && errno != EEXIST) { perror("mkfifo fail"); return -1; } printf("mkfifo success\n"); // fd_w:打开a2b管道,只读,b进程从a2b读取a进程发来的数据 int fd_w = open("a2b",O_RDONLY); //打开b从a读取的FIFO if(fd_w < 0) { perror("open fail"); unlink("a2b"); // 删除管道文件 unlink("b2a"); return -1; } // fd_r:打开b2a管道,只写,b进程往b2a写入数据发给a进程 int fd_r = open("b2a",O_WRONLY); //打开b向a写入的FIFO if(fd_r < 0) { perror("open fail"); unlink("a2b"); unlink("b2a"); return -1; } // 创建子进程,一个进程负责读管道,一个负责键盘输入写管道 pid_t pid = fork(); if(pid < 0) { perror("fork fail"); close(fd_w); close(fd_r); unlink("a2b"); unlink("b2a"); return -1; } if(pid > 0) { // 父进程:负责读a2b管道,接收a进程发送过来的消息 while(1) { char buf[20] = {0}; int ret = read(fd_w,buf,sizeof(buf)); // 从a2b管道读取a发来数据 if(strncmp(buf,"quit",4) == 0) // 读到quit跳出循环结束通信 break; printf("a to b:%s\n",buf); // 打印a进程发来的内容 } } if(pid == 0) { // 子进程:读取键盘输入,通过b2a管道发送给a进程 while(1) { char buf[20] = {0}; printf("b to a:"); int ret = read(0,buf,sizeof(buf)); // 读取键盘输入 write(fd_r,buf,ret); // 将输入写入b2a管道发给a if(strncmp(buf,"quit",4) == 0) // 输入quit退出循环 break; } } // 关闭管道文件描述符,删除磁盘上的FIFO管道文件 close(fd_w); close(fd_r); unlink("a2b"); unlink("b2a"); return 0; }

有名管道FIFO

有名管道 FIFO:文件系统中存在对应的管道文件,内核缓冲区和匿名管道一致,区别在于拥有文件名、可被任意进程打开,支持无亲缘关系进程间通信。可以使用open、read、write、close等命令,进行打开关闭、读写操作,但是不能使用lseek进行定位

有名管道的创建:

int mkfifo(const char *pathname, mode_t mode); 参数: @pathname 管道文件名 @mode 文件权限 权限与open时,加入了creat后输入的权限参数方式相同 返回值: 成功 0 失败 -1 && errno被设置

有名管道进行通信时,需要进行打开操作 open,如果只有一端 以 只读或只写的方式打开 ,此时程序会阻塞在open操作,必须等待 另一端 以只写或只读的相对方式打开,阻塞才会解除,此时也表示有名管道文件 读端和写端都打开了

练习:创建有名管道,实现两个进程之间的单向通信,一个进程从终端读取数据,通过管道发送到另一个进程,另一个进程接收数据并打印出来。

  1. 终端 A 运行 ./fifo_write,终端 B 运行 ./fifo_read 两端都打印 mkfifo success,然后都卡在 open,直到两端都启动
  2. 在写端(A)键盘输入:hello回车
    1. 写端 read (0) 获取 hello,write 写入管道
    2. 读端 read 拿到 hello,打印read data:hello
  3. 在写端输入:quit回车
    1. 写端将 quit 写入管道,检测到 quit,break,close (fd),程序结束
    2. 读端读到 quit,break,unlink 删除 fifo 文件,close (fd),程序结束

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

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

立即咨询