UNIX IPC

UNIX IPC概览

Unix设计风格都秉承着“做单件事并做好”的方法,因此,提供了众多的IPC方法,并用其来连接小型进程,Unix操作系统提倡把程序分解成更简单的子进程,并专注考虑这些子进程间的接口

这得益于Unix上平价的进程生成和简单的进程控制,Unix家族操作系统倾向于减轻fork的开销,特别是Linux,使得进程的生成比其他操作系统的线程生成还要快

实际上,在shell编程中,就无时无刻都在鼓励Unix程序员从多个协作进程的角度思考问题,最为简单的例子就是

cat file | less

尽管将程序划分为协作进程可以降低全局的复杂度,但是随之而来的,是我们必须更多地关注在进程间传递消息和命令的协议设计,但这都是后话,首先,先看看Unix下的IPC工具

IPC 工具

  • 管道
  • 套接字
  • 消息队列
  • 信号量
  • 共享内存

管道

管道是Unix上最古老的IPC方式,一个管道就是一个字节流,所谓“流”,便代表“无边界”,因此从管道中读取的数据大小是任意的,而不是固定的,在管道中也无法进行随机读取

管道中数据的传输是单向的,一端用于读,一端用于写

在管道中,只要确保写入不超过PIPE_BUF字节,那么这个写操作就是原子的,可以确保写入的数据不会发生混杂

在管道中,容量是有限的,如果容量耗尽,那么写操作就会发生阻塞,直到读操作将一部分取出

关于pipe的简单用例如下

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
#include <unistd.h> //for pipe, fork, read, write
#include <string>
#include <iostream>

int main()
{
int myPipe[2]; //0:read, 1:write
pipe(myPipe);
pid_t pid;

pid = fork();
if (pid == 0)
{
//sub process
close(myPipe[1]);
char msg[100];
bzero(msg, 100);
int n = read(myPipe[0], msg, 100);
std::cout << "the size of data :" << n << std::endl;
std::cout << msg << std::endl;
exit(0);
}
close(myPipe[0]);
std::string msg = "hello,world";
write(myPipe[1], msg.c_str(), msg.size());
waitpid(pid, NULL, 0);
return 0;
}

System V 消息队列

消息队列允许进程以消息的形式交换数据,在ftok中,如果使用相同的path name,那么就会创建出相同的key,这样的话,使用msgget所获得的消息队列也会相同

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>

#include <string>
#include <cstring>
#include <iostream>
#include <unistd.h>
// 用于创建一个唯一的key
// 两个进程如果使用同一个路径进行ftok,会得到同一个msqid
#define MSG_FILE "/etc/passwd"
int main()
{
int msqid;
key_t key;
// 获取key值
if((key = ftok(MSG_FILE,'z')) < 0)
{
perror("ftok error");
exit(1);
}
// 打印key值
printf("Message Queue - Server key is: %d.\n", key);

// 创建消息队列
if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
{
perror("msgget error");
exit(1);
}
// 打印消息队列ID
printf("My msqid is: %d.\n", msqid);


char msg[100];
std::string s;
pid_t pid;
pid = fork();

//子进程,不断接收数据
if (pid == 0)
{
char recv[100];
while(strcmp(recv, "exit")) {
memset(recv, 0, 100);
msgrcv(msqid, recv, sizeof(recv), 0, 0);
std::cout << "sub process recv:" << recv << std::endl;
}
exit(0);
}


while(s != "exit")
{
std::getline(std::cin, s);
memset(msg, 0, 100);
strcpy(msg, s.c_str());
msgsnd(msqid, msg, sizeof(msg), 0);
}
//msgrcv(msqid, recv, sizeof(recv), 0, 0);
msgctl(msqid, IPC_RMID, nullptr);
//std::cout << recv << std::endl;
return 0;
}

System V 信号量

信号量和前面介绍的IPC方式不同,并不是用来传递数据的,而是用来同步进程的

内核会将所有试图把信号量的值降到0以下的操作堵塞

如果信号量的值不为0,那么内核会将所有等待信号量的值等于0的进程堵塞

如果信号量不为N(N>0),那么内核会将所有等待信号量的值等于N的进程堵塞

System V 共享内存

共享内存允许两个或者多个进程共享物理内存中的同一块区域,这种IPC机制不需要内核介入,所有需要做的就是将数据复制进共享内存中,由于没有了内核的介入,这种IPC比消息队列,管道等都要快

POSIX 消息队列

POSIX的消息队列是引用计数的,只有所有使用该队列的进程都关闭了队列之后,才会对队列进行标记以删除

POSIX的消息队列存在优先级,所有消息都是通过优先级排序排队的

POSIX消息队列提供了一个特性允许队列中的一条信息可用时异步地通知进程

POSIX共享内存

POSIX共享内存能够让无关进程共享一个映射区域而不需创建一个相应的映射文件,一些Unix实现将共享对象名创建为文件系统上一个特殊位置处的文件,这些文件会在系统关闭后丢失