目录
一、引言
1、进程间通信的概念
进程间通信(Inter-Process Communication,IPC)是指多个进程之间传递数据或信号的机制。由于进程拥有独立的地址空间,操作系统需要提供特定的方法以实现数据共享或同步。
2、常见的进程间通信方式
-
管道(Pipe):管道是一种半双工的通信方式,数据只能单向流动。通常用于具有亲缘关系的进程间通信,例如父子进程。可分为:匿名管道(通过
pipe()
系统调用创建,仅限父子进程使用)和命名管道(FIFO)(通过mkfifo
命令或系统调用创建,允许无亲缘关系的进程通信)。 -
消息队列(Message Queue):消息队列是由内核维护的链表结构,允许进程发送或接收特定格式的消息。与管道不同,消息队列支持多对多通信,且消息可以指定类型(包括发送消息:
msgsnd()和
接收消息:msgrcv())。
-
共享内存(Shared Memory):共享内存是最快的IPC方式,多个进程映射同一块物理内存到各自的地址空间,直接读写数据。创建共享内存:
shmget();
关联到进程空间:shmat();
解除关联:shmdt()
-
信号量(Semaphore):信号量用于进程间同步,避免资源竞争。它是一个计数器,控制对共享资源的访问。创建信号量:
semget();
操作信号量:semop()
-
套接字(Socket):套接字可用于不同主机或同一主机的进程间通信,支持全双工通信。创建套接字:
socket();
绑定地址:bind();
监听连接:listen()
3、注意事项
- 共享内存需配合信号量或其他同步机制,避免数据竞争。
- 管道和消息队列有大小限制,需考虑数据量。
- 信号是异步的,处理逻辑应尽量简单以避免竞态条件。
二、Linux管道
管道可以用来在两个进程之间传递数据,如: ps -ef | grep “bash”, 其中‘|’就是管道,其作用就是将 ps 命令的结果写入管道文件,然后 grep 再从管道文件中读出该数据进行过滤。
1、有名管道
有名管道可以在任意两个进程之间通信,但需要在同一个管道内。
注:如果管道空,读阻塞;如果管道满,写阻塞。
单次管道通信
有名管道的创建:
#include <sys/types.h>
#include <sys/stat.h>
//filename 是管道名 mode 是创建的文件访问权限
int mkfifo(const char *filename, mode_t mode);
- a.c代码写入数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <fcntl.h>
int main(){
int fdw = open("fifo",O_WRONLY);//只写打开管道,可能阻塞
if(-1==fdw){
printf("open fifo failed\n");
exit(1);
}
printf("fdw=%d\n",fdw);
char buff[128] = {0};// 创建一个字符数组用于存储输入的数据
fgets(buff, 128, stdin);// 从标准输入读取一行数据
write(fdw, buff, strlen(buff) - 1);//向管道写数据
close(fdw);//关闭管道
exit(0);正常退出程序
}
- b.c读程序代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <fcntl.h>
int main(){
int fdr = open("fifo",O_RDONLY);//只读打开管道,可能阻塞
if(-1==fdr){
exit(1);
}
printf("fdr=%d\n",fdr);
char buff[128] = {0};
int n= read(fdr,buff,127);
if(n>0){
printf("read:%s\n",buff);
}
close(fdr);//关闭管道
exit(0);
}
运行结果:
注:
必须打开两个窗口,一个执行写程序,另一个执行读程序
写入的数据会通过命名管道传输到读端程序,并被打印出来。
多次管道通信
a.c代码写入数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <fcntl.h>
int main(){
int fdw = open("fifo",O_WRONLY);//只写打开管道,可能阻塞
if(-1==fdw){
printf("open fifo failed\n");
exit(1);
}
printf("fdw=%d\n",fdw);
while(1){
char buff[128] = {0};
fgets(buff, 128, stdin);
if(strncmp(buff,"end",3)==0){
break;
}
write(fdw, buff, strlen(buff) - 1);//向管道写数据
}
close(fdw);//关闭管道
exit(0);
}
b.c读取程序代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <fcntl.h>
int main(){
int fdr = open("fifo",O_RDONLY);//只读打开管道,可能阻塞
if(-1==fdr){
exit(1);
}
printf("fdr=%d\n",fdr);
while(1){
char buff[128] = {0};
int n= read(fdr,buff,127);
if(n==0){
break;
}
printf("read:%s\n",buff);
}
close(fdr);//关闭管道
exit(0);
}
运行结果
通过while循环就会一直通信
-
读端可以不断地从管道中读取数据,直到满足某个条件(如读取到特定结束标志)。
-
写端可以不断地向管道中写入数据,直到满足某个条件(如用户输入结束标志)
直至在写端./a中写入end才会关闭,终止程序。
管道的写端关闭,读端read返回值为0;管道的读端关闭,写端写数据
2、 无名管道
无名管道的创建pipe
#include <unistd.h>
/*
pipe()成功返回 0,失败返回-1
fds[0]是管道读端的描述符
fds[1]是管道写端的描述符
*/
int pipe(int fds[2]);
无名管道代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
int main(){
int fds[2];//fds[0]读端,fds[1]写端描述符
if(pipe(fds)==-1){//创建无名管道并打开
printf("创建管道失败\n");
exit(1);
}
pid_t pid = fork();
if(pid==-1){
exit(1);
}
if( pid == 0 ) {
close(fds[1]);
char buff[128] = {0};
read(fds[0], buff, 127);//子进程读
printf("child read: %s\n", buff);
close(fds[0]);
}
else {
close(fds[0]);
write(fds[1], "hello", 5);//父进程写
close(fds[1]);
}
exit(0);
}
三、管道的面试题目
- 无论有名还是无名,写入管道的数据都在内存中。
- 管道是一种半双工通信方式(通信方式有单工、半双工、全双工)。
- 有名和无名管道的区别:有名可以在任意进程间使用,而无名主要在父子进程间。