进程通信 [ fork() 管道( pipe() ) FIFO ]

来源:互联网 发布:巨人网络涨停 编辑:程序博客网 时间:2024/06/14 02:05

X86/Debian Linux/gcc


1 进程通信手段

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcessCommunication)。


Figure1.进程通信机制

进程间通信必须通过内核提供的通道,而且必须有一种办法在进程中标识内核提供的某个通道


(1) 进程通信手段总结(抄)

  • 父进程通过fork可以将打开文件的描述符传递给子进程
  • 子进程结束时,父进程调用wait可以得到子进程的终止信息
  • 几个进程可以在文件系统中读写某个共享文件,也可以通过给文件加锁来实现进程间同步
  • 进程之间互发信号,一般使用SIGUSR1SIGUSR2实现用户自定义功能
  • 管道
  • 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]);向这个文件读写数据其实是在读写内核缓冲区。


开辟了管道之后实现两个进程间的通信,一般是用下列步骤:

Figure2.管道用于进程通信步骤

(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 MAXPPSIZE80#defineMSG"Hello, world\n"int main(void){intn;intfd[2];pid_tpid;charpp_content[MAXPPSIZE];if (pipe(fd) < 0) {perror("pipe");exit(1);}if ( (pid = fork() ) < 0 ) {perror("fork");exit(1);}if (pid > 0) {//In parent progresswrite(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 progresswrite(fd[1], MSG, strlen(MSG) );wait(NULL);} else {//In child progressn= read(fd[0], pp_content, MAXPPSIZE);write(STDOUT_FILENO, pp_content, n);}

根据fork创建子进程的机制,父子进程同时拥有管道的读、写端描述符。程序运行结果如下:

Hello, world

这个Hello world是子进程读出来并输出来的。


[3] 父进程写,子进程读,子进程读

将父进程调用fork()后的一段代码改为:

if (pid > 0) {//In parent progresswrite(fd[1], MSG, strlen(MSG) );wait(NULL);n= read(fd[0], pp_content, MAXPPSIZE);write(STDOUT_FILENO, pp_content, n);} else {//In child progressn= read(fd[0], pp_content, MAXPPSIZE);write(STDOUT_FILENO, pp_content, n);//Read againprintf("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 progresswrite(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 progressprintf("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 progresswrite(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>#defineFIFO_FILE"fifo"#defineMSG"Hello, world\n"#define MAX_CT20int main(void){int fd;pid_tpid;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 {//Childint 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]
LCNote Over.
0 0