X86/Debian Linux/gcc
1 进程通信手段
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcessCommunication)。
Figure1.进程通信机制
进程间通信必须通过内核提供的通道,而且必须有一种办法在进程中标识内核提供的某个通道。
(1) 进程通信手段总结(抄)
- 父进程通过fork可以将打开文件的描述符传递给子进程
- 子进程结束时,父进程调用wait可以得到子进程的终止信息
- 几个进程可以在文件系统中读写某个共享文件,也可以通过给文件加锁来实现进程间同步
- 进程之间互发信号,一般使用SIGUSR1和SIGUSR2实现用户自定义功能
- 管道
- FIFO
- mmap函数,几个进程可以映射同一内存区
- SYS V IPC,以前的SYSV UNIX系统实现的IPC机制,包括消息队列、信号量和共享内存,现在已经基本废弃
- UNIX Domain Socket,目前最广泛使用的IPC机制
此笔记练习管道和FIFO。
(1) 管道
管道是一种最基本的IPC机制,由pipe函数创建:
int pipe(int filedes[2]); |
调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。
开辟了管道之后实现两个进程间的通信,一般是用下列步骤:
(2) FIFO和UNIX Domain Socket
文件系统中的路径名是全局的,各进程都可以访问,因此可以用文件系统中的路径名来标识一个IPC通道。
用mkfifo 来创建一个FIFO
mkfifo fifo ls -l fifo prw-r--r-- 1 lly lly 0 Aug 10 16:10 fifo |
FIFO文件在磁盘上没有数据块,仅用来标识内核中的一条通道,各进程可以打开这个文件进行read/write,实际上是在读写内核通道(根本原因在于这个file结构体所指向的read、write函数和常规文件不一样),这样就实现了进程间通信。
UNIXDomain Socket和FIFO的原理类似,也需要一个特殊的socket文件来标识内核中的通道。这些文件在磁盘上也没有数据块。
2 利用管道实现进程通信
(1) PIPE(2) LinuxProgrammer’s Manual
[1] 头文件及原型
#include <unistd.h> int pipe(int filedes[2]); |
[2] 功能简述
进程通信的一种手段,见1.1。
[3] 返回值
pipe函数调用成功返回0,调用失败返回-1。
(2) pipe()用于进程通信的一些情况
[1] 父进程写父进程读
/* Filename: pipe.c
* Brife: Create one pipe to be used communication beteween two progress
* Author: One fish
* Date: 2014.8.10 Sunday
*/
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define MAXPPSIZE 80
#define MSG "Hello, world\n"
int main(void)
{
int n;
int fd[2];
pid_t pid;
char pp_content[MAXPPSIZE];
if (pipe(fd) < 0) {
perror("pipe");
exit(1);
}
if ( (pid = fork() ) < 0 ) {
perror("fork");
exit(1);
}
if (pid > 0) { //In parent progress
write(fd[1], MSG, strlen(MSG) );
n = read(fd[0], pp_content, MAXPPSIZE);
write(STDOUT_FILENO, pp_content, n);
} else { //In child progress
;
}
return 0;
}
- ssize_t read(int fd, void *buf, size_t count);尝试着从文件描述符fd哪里读到count字节保存到缓冲区buf内。当函数执行成功时,函数返回所读到数据的字节数m(读到文件末尾则返回0),并且使文件的位置向前移动m个位置。如果m < count并不一定代表着错误,遇到以下情况时m 会小于count:只有m个可读字节(可能是到了文件末尾),也有可能正在读一个管道,或者在读终端,或者read()被一个信号打断。如果函数执行错误则返回-1。
- 父进程往管道的写端写入数据,然后再从管道的读端将数据读出来。
程序运行结果如下:
Hello, world |
[2] 父进程写,子进程读
将父进程调用fork()后的一段代码改为:
if (pid > 0) { //In parent progress
write(fd[1], MSG, strlen(MSG) );
wait(NULL);
} else { //In child progress
n = read(fd[0], pp_content, MAXPPSIZE);
write(STDOUT_FILENO, pp_content, n);
}
根据fork创建子进程的机制,父子进程同时拥有管道的读、写端描述符。程序运行结果如下:
Hello, world |
这个Hello world是子进程读出来并输出来的。
[3] 父进程写,子进程读,子进程读
将父进程调用fork()后的一段代码改为:
if (pid > 0) { //In parent progress
write(fd[1], MSG, strlen(MSG) );
wait(NULL);
n = read(fd[0], pp_content, MAXPPSIZE);
write(STDOUT_FILENO, pp_content, n);
} else { //In child progress
n = read(fd[0], pp_content, MAXPPSIZE);
write(STDOUT_FILENO, pp_content, n);
//Read again
printf("Child read again\n");
n = read(fd[0], pp_content, MAXPPSIZE);
write(STDOUT_FILENO, pp_content, n);
}
程序运行结果如下:
Hello, world Child read again
|
如果是在Linux字符界面下,可以看到光标在最后一行闪动,说明子进程阻塞。管道读端将数据读完后,再次从管道读端read就会遭遇阻塞。
[4] 父进程写子进程读,子进程写父进程读
将父进程调用fork()后的一段代码改为:
if (pid > 0) { //In parent progress
write(fd[1], MSG, strlen(MSG) );
wait(NULL);
printf("After child, read pipe's data which child write in:\n");
n = read(fd[0], pp_content, MAXPPSIZE);
write(STDOUT_FILENO, pp_content, n);
} else { //In child progress
printf("Read pipe's data which parent write in:\n");
n = read(fd[0], pp_content, MAXPPSIZE);
write(STDOUT_FILENO, pp_content, n);
//Child progress write data for parent progress
write(fd[1], MSG, strlen(MSG));
}
程序运行结果如下:
Read pipe's data which parent write in: Hello, world After child, read pipe's data which child write in: Hello, world |
可见,在这个例子里面,父子进程至少可以用管道实现一个轮回的相互通信。
3 利用FIFO实现进程通信
(1) 创建FIFO文件
用mkfifo 来创建一个FIFO
mkfifo fifo ls -l fifo prw-r--r-- 1 lly lly 0 Aug 10 16:10 fifo |
(2) FIFO进程通信代码
/*Filename: fifo.c
*Brife: Two progress communicate by fifo
*Author: One fish
*Date: 2014.8.10 Sunday
*/
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FIFO_FILE "fifo"
#define MSG "Hello, world\n"
#define MAX_CT 20
int main(void)
{
int fd;
pid_t pid;
fd = open(FIFO_FILE, O_RDWR);
if (-1 == fd) {
perror("open");
exit(1);
}
write(fd, "HW\n", 3);
if ( ( pid = fork() ) < 0 ) {
perror("fork");
exit(1);
}
if (pid > 0) { //Parent
write(fd, MSG, strlen(MSG));
wait(NULL);
} else { //Child
int n;
char buf[MAX_CT];
n = read(fd, buf, MAX_CT);
write(STDOUT_FILENO, buf, n);
}
return 0;
}
程序运行结果:
HW Hello, world |
在父进程中写write(fd, "HW\n",3);语句主要是为了测试如果子进程先运行的情况。手动多运行了几次都是以上运行结果(虽然很可能是碰巧)。
[2014.8.10– 15.05 --- 2014.8.10– 17.15]