每个操作系统都有自己的进程间通信的方式,不过大都类似,这里主要讨论Linux下的几种进程间通信方式。
管道
大多数操作系统的管道是半双工的,某些系统有全双工管道。管道只能在具有公共祖先的两个进程中使用,通常有一个进程创建,另一个进程被fork出来,此时这两个进程就可以使用该管道。
在命令行中会以|
的形式将两边程序进行管道通信,例如
在程序中,管道可以通过pipe
函数进行创建
1 2 3 4 5
| #include <unistd.h>
int pipe(int fd[2]);
|
为了不混淆两个进程谁来读或谁来写,一般会创建一个管道,fork之后,父进程关闭一端,子进程关闭另一端,这样就形成了一个单向流动的管道。
具体用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include <unistd.h> #include <wait.h>
#include <cstdio>
int main() { int fd1[2]; int fd2[2]; pipe(fd1); pipe(fd2); pid_t pid = fork(); if (pid == 0) { close(fd1[1]); close(fd2[0]);
char s1[] = "Hello World"; write(fd2[1], s1, sizeof(s1));
sleep(1); char s2[50]; read(fd1[0], s2, 20); printf("from father to son : %s\n", s2); } else { close(fd1[0]); close(fd2[1]);
char s1[] = "dlroW olleH"; write(fd1[1], s1, sizeof(s1));
sleep(1); char s2[50]; read(fd2[0], s2, 20); printf("from son to father : %s\n", s2);
wait(&pid); } return 0; }
|
FIFO
FIFO又被称为有名管道,未命名管道只能用在两个相关的进程,而FIFO可以使两个不相关的进程也能交换数据。
创建FIFO如同创建文件,该路径名称也存在于文件系统中
1 2 3 4 5 6
| #include <sys/stat.h>
int mkfifo(const char *path, mode_t mode); int mkfifoat(int fd, const char *path, mode_t mode);
|
FIFO有两种用途:
- shell命令使用FIFO将数据从一条管道传送到另一条时,无需创建中间临时文件。
- 客户端和服务端应用程序中,FIFO作为汇聚点,再客户端和服务端之间传递数据。
具体用法
send.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h>
#include <cstdio>
int main() { mkfifo("a.txt", 0777); int fd = open("a.txt", O_WRONLY); char s[] = "Hello World"; write(fd, s, sizeof(s)); printf("send.cpp send a message...\n"); return 0; }
|
recv.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h>
#include <cstdio>
int main() { mkfifo("a.txt", 0777); int fd = open("a.txt", O_RDONLY); while (true) { char s[20]; int ret = read(fd, s, sizeof(s)); if (ret > 0) { printf("recv recieve a message: %s\n", s); break; } } return 0; }
|
消息队列
消息队列是消息的链接表,存储在内核中。每个消息包含一个正的长整型的字段、一个非负的长度以及实际字节数。不一定需要按照先进先出的顺序取消息,可以按照消息的类型字段取消息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include <sys/ipc.h> #include <sys/msg.h> #include <sys/types.h>
int msgget(key_t key, int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
|
具体用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include <sys/ipc.h> #include <sys/msg.h> #include <sys/types.h> #include <unistd.h> #include <wait.h>
#include <cstdio> #include <cstdlib> #include <cstring>
#define MAX_LEN 128
struct MessageType { long type; char content[MAX_LEN]; };
int main() { int msgid = msgget(1001, 0666 | IPC_CREAT);
pid_t pid = fork(); if (pid == 0) { sleep(1); system("ipcs -q"); MessageType recv_msg; msgrcv(msgid, &recv_msg, sizeof(recv_msg.content), 0, 0); printf("child recieve message: %s\n", recv_msg.content); } else { MessageType send_msg; send_msg.type = 1; memset(send_msg.content, 0, sizeof(send_msg.content)); strcpy(send_msg.content, "Hello World"); msgsnd(msgid, &send_msg, strlen(send_msg.content), 0); wait(&pid); } return 0; }
|
信号量
它是一个计数器,用于为多个进程提供对共享数据对象的访问。
为了获取共享资源,进程需要执行以下操作:
- 测试控制该资源的信号量。
- 如果该信号量为正,则可以使用该进程资源,同时进程会将信号量减1,表示它使用了一个资源。
- 否则如果该信号量为0,则进入休眠状态,直到信号量大于0。进程被唤醒后返回第一步。
当进程不再使用资源时,该信号量加1,如果有进程正在休眠等待此信号,则唤醒他们。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include <sys/ipc.h> #include <sys/sem.h> #include <sys/types.h>
int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, size_t nsops);
int semctl(int semid, int semnum, int cmd, ...);
|
共享内存
共享内存允许两个或多个进程共享一个给定的存储区,因为数据不需要来回复制,所以是最快的一种IPC。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
|
shm和mmap
mmap是在磁盘上创建一个文件,在进程的地址空间和文件建立映射;shm没有创建文件,每个进程都会映射到同一块物理内存。