Interprocess Communications

消息传递

管道

匿名管道随进程的创建而建立,随进程的结束而销毁

管道是基于Linux & Unix的操作系统的核心概念之一。管道允许您以一种非常优雅的方式 | 将命令链接在一起,将一个程序的输出传递为另一个程序的输入。

1
ls -la | sort | less

bash 的执行过程

  1. bash 创建2个管道,一个从 lssort,一个从 sortless
  2. bash fork 三个子进程分别用于每个命令
  3. 子进程1 ls 设置 stdoutpipe A 的写端
  4. 子进程2 sort 设置 stdinpipe A 的读端,设置 stdoutpipe B 的写端
  5. 子进程3 less 设置 stdinpipe B 的读端

Linux has a VFS (virtual file system) module called pipefs, that gets mounted in kernel space during boot. pipefs is mounted alongside the root file system (/), not in it (pipe’s root is pipe: ). pipefs is stored using an in-memory file system.

pipefs cannot be directly examined by the user unlike most file systems. The entry point to pipefs is the pipe syscall. The pipe syscall is used by shells and other programs to implement piping, and just creates a new file in pipefs, returning two file descriptors (one for the read end, opening using O_RDONLY, and one for the write end, opened using O_WRONLY).

impl
pipe

使用 fork 创建子进程,创建的子进程会复制父进程的文件描述符,这样就做到了两个进程各有两个 fd[0]fd[1],两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信。

父子进程间的单向数据流(半双工管道)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <unistd.h>
int main(int argc, char **argv)
{
int pipe_fd[2];
pipe(pipe_fd);

pid_t childpid = fork();
if(childpid == 0)
{
close(pipe_fd[1]);
//close(pipe_fd[0]);
// TODO
exit(0);
}
close(pipe_fd[0]);
//close(pipe_fd[1]);

waitpid(childpid,null,0);
}

父子进程间的双向数据流的两个管道(全双工管道,有两个半双工管道构成)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <unistd.h>
int main(int argc, char **argv)
{
int pipe_fd1[2],pipe_fd2[2];
pipe(pipe_fd1);
pipe(pipe_fd2);

pid_t childpid = fork();
if(childpid == 0)
{
close(pipe_fd1[1]);
close(pipe_fd2[0]);
//close(pipe_fd1[0]);
//close(pipe_fd2[1]);
//TODO
exit(0);
}
close(pipe_fd1[0]);
close(pipe_fd2[1]);
//close(pipe_fd1[1]);
//close(pipe_fd2[0]);
waitpid(childpid,null,0);
}

FIFO

名字空间

当两个或多个无亲缘关系的进程使用某种类型的IPC对象来彼此交换信息时,该IPC对象必须有一个某种形式的 名字(name)标识符(identifier) ,这样其中一个进程(往往是服务器)可以创建该IPC对象,其余进程则可以指定同一个IPC对象

管道没有名字,因此它们的最大劣势就是智能用于有一个共同祖先进程的各个进程之间。而 FIFO(指代first in, first out) 类似于管道(单向数据流),且有一个文件路径名与之关联从而实现无亲缘关系进程访问同一个FIFO,所以也被称为 命名管道(named pipe)

Any FIFO is much like a pipe: rather than owning disk blocks in the filesystems, an opened FIFO is associated with a kernel buffer that temporarily stores the data exchanged by two or more processes. Thanks to the disk inode, however, a FIFO can be accessed by any process, since the FIFO filename is included in the system’s directory tree. FIFO inodes appear on the system directory tree rather than on the pipefs special filesystem.

impl

Allocates a pipe_inode_info object (the actual pipe) for providing buffer space when fifo_open(…)

impl

通过 mkfifo 命令来创建并指定管道名字

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
mkfifo myFifo
//Example: write first
int main()
{
char * myfifo = "/tmp/myfifo";

// Creating the named file(FIFO)
// mkfifo(<pathname>, <permission>)
mkfifo(myfifo, 0666);

char arr1[80], arr2[80];
while (1)
{
// Open FIFO for write only
fd = open(myfifo, O_WRONLY);

// Take an input arr2ing from user.
// 80 is maximum length
fgets(arr2, 80, stdin);

// Write the input arr2ing on FIFO
// and close it
write(fd, arr2, strlen(arr2)+1);
close(fd);

// Open FIFO for Read only
fd = open(myfifo, O_RDONLY);

// Read from FIFO
read(fd, arr1, sizeof(arr1));

// Print the read message
printf("User2: %s\n", arr1);
close(fd);
}
return 0;
}

消息队列

随内核的持续性

消息队列可认为时一个消息链表,有足够写权限的线程可往队列中放置消息,有足够读权限的线程可从队列中取走消息。

每个消息都具有如下属性:

  • 一个无符号整数优先级(Posix) / 一个长整数类型(System V)
  • 消息的数据部分长度
  • 数据本身

System V 消息队列

impl
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
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
// structure for message queue
struct msg_buffer
{
long msg_type;
char msg[100];
} message;

void send()
{
key_t my_key;
int msg_id;
my_key = ftok("progfile", 65); //create unique key
msg_id = msgget(my_key, 0666 | IPC_CREAT); //create message queue and return id
message.msg_type = 1;
printf("Write Message : ");
fgets(message.msg, 100, stdin);
msgsnd(msg_id, &message, sizeof(message), 0); //send message
printf("Sent message is : %s \n", message.msg);
}

void receive()
{
key_t my_key;
int msg_id;
my_key = ftok("progfile", 65); //create unique key
msg_id = msgget(my_key, 0666 | IPC_CREAT); //create message queue and return id
msgrcv(msg_id, &message, sizeof(message), 1, 0); //used to receive message
// display the message
printf("Received Message is : %s \n", message.msg);
msgctl(msg_id, IPC_RMID, NULL); //destroy the message queue
return 0;
}

Posix 消息队列

POSIX message queues implement priority-ordered messages. Each message written by a sender process is associated with an integer number which is interpreted as message priority; messages with a higher number are considered higher in priority.

impl
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
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <mqueue.h>

#define MAX_SIZE 1024
#define QUEUE_NAME "/test"

void createAndReceive()
{
mqd_t mq;
struct mq_attr attr;
char buffer[MAX_SIZE + 1];

/* initialize the queue attributes */
attr.mq_flags = 0;
attr.mq_maxmsg = 10;
attr.mq_msgsize = MAX_SIZE;
attr.mq_curmsgs = 0;

/* create the message queue */
mq = mq_open(QUEUE_NAME, O_CREAT | O_RDONLY, 0644, &attr);

ssize_t bytes_read;
/* receive the message */
bytes_read = mq_receive(mq, buffer, MAX_SIZE, NULL);

mq_close(mq);
mq_unlink(QUEUE_NAME);
}

void send()
{
mqd_t mq;
char buffer[MAX_SIZE];

/* open the mail queue */
mq = mq_open(QUEUE_NAME, O_WRONLY);

memset(buffer, 0, MAX_SIZE);
fgets(buffer, MAX_SIZE, stdin);

mq_send(mq, buffer, MAX_SIZE, 0);
mq_close(mq);
}

区别

  • 对Posix消息队列的读总是返回最高优先级的最早消息,对SystemV消息队列的读则可以返回任意优先级的消息
  • 当往一个空队列放置一个消息时,Posix消息队列允许产生一个信号或启动一个线程,SystemV消息队列则不提供类似机制

参考

Unix 网络编程 卷2: 进程间通信

Slide - Linux Kernel Implementation of Pipes FIFOs

进程间通信