Linux 进程间通信的方式可以分为三大类:
- 管道
- 信号
- IPC,又可分为三类
- 共享内存
- 消息队列
- 信号量
管道
管道是 Linux 内核中的数据结构,类似于队列,管道中的特性如下:
- 数据只能读取一次,读取后消失,不可再次读
- 半双工通信,两端都可以用来写数据,但一端开始写数据的话,另一端只能用来读
- 无数据时,读会阻塞;数据写满时,会写阻塞
用过命令行的人,对管道应该不陌生。例如,查看 bash 进程状态:
ps aux | grep bash
上面命令创建了一个匿名管道,将前面命令的输出作为后面命令的输入。
匿名管道
通过 pipe 命令可以创建匿名管道。只能用于有血缘关系的进程之间通信。
#include <unistd.h>
int pipe(int pipefd[2]);
因为需要返回读端和写端两个文件描述符,所以通过数组参数来实现。其中 pipefd[0]
是读端,pipefd[1]
是写端。成功返回 0,失败返回 -1。
示例 注意,必须在 fork 子进程之前创建匿名管道:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
char readBuf[10];
int fd[2];
int pid;
int ret = pipe(fd);
pid = fork();
if (pid < 0) {
printf("fork error\n");
return -1;
}
if (pid == 0) {
read(fd[0], readBuf, 10);
printf("readBuf is:%s\n", readBuf);
close(fd[0]);
} else {
int status;
sleep(3);
write(fd[1], "hello linux\n", 10);
close(fd[1]);
waitpid(pid, &status, 0);
}
return 0;
}
有名管道
如果想用管道实现无血缘关系的进程间通信,必须使用有名管道。创建有名管道时,会在磁盘上创建管道文件,文件类型是 p
。
Linux 中一切皆文件,常见的文件类型有:
-
常规文件
d
目录文件
p
管道文件
l
连接文件
s
套接字文件
b
block device 块设备文件,如硬盘。可以以块为单位进行随机访问
c
character device 字符设备文件,如键盘。可以以字符为单位进行线性访问
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
mkfifo 会在文件系统中创建管道文件,任何进程打开这个文件后通过读写操作就可以实现进程间通信。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
int ret = mkfifo("myfifo", 0777);
if (ret == -1) {
perror("mkfifo");
exit(EXIT_FAILURE);
}
sleep(4);
int fd = open("./myfifo", O_RDWR);
write(fd, "hello linux\n", 10);
close(fd);
return 0;
}
信号
发送信号
Linux 中有 3 个函数用来发送信号:
- kill:可以向指定进程发送指定信号
- raise:向进程自身发送信号
- alarm:向进程自身发送 SIGALRM 信号
Linux 中,如果想杀死某个进程,只需要执行命令 kill -9 pid
。其中,-9 表示发送 9 号信号(SIGKILL 结束进程),pid 表示要操作的进程的 ID。Linux 中向进程发送信号的系统调用跟这个命令类似:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
接收信号
Linux 中有 3 中方式接收信号:
- while 循环
- sleep 一段时间
- pause 休眠,等待被信号唤醒
处理信号
如果不想用系统默认的信号处理方式,可以用 signal 函数绑定自己的函数来处理信号。