进程间通信(IPC)是指能在两个进程间进行数据交换的机制。现代OS都对进程有保护机制,因此两个进程不能直接交换数据,必须通过一定机制来完成。
  IPC的机制的作用:
  (1)一个软件也能更容易跟第三方软件或内核进行配合的集成,或移植.如管道,在shell 下执行 ps –aux | grep bash。
  (2)简化软件结构, 可以把一个软件划分多个进程或线程,通过IPC,集成在一起工作.如消息队列。
  (3)让操作系统各个模块交换数据,包括内核与应用程序机制。
  (4)提供进程之间或同一进程之间多线程的同步机制,如信号量。

1、管道

  管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。

  数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
  管道的创建:int pipe(int fd[2]) ;
  管道的读写:管道文件也是一种文件,用write,read

即可完成读写。管道两端可分别用描述字fd[0]以及fd[1]来描述,需要注意的是,管道的两端是固定了任务的。即一端只能用于读,由描述字fd[0]表示,称其为管道读端;另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。

  管道的关闭:管道文件也是一种文件,因此用close关闭即可。
  管道的局限:(1)只支持单向数据流;
(2)只能用于具有亲缘关系的进程之间; (3)没有名字;
(4)管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小);
(5)管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等。

现在使用管道实现进程的同步,父进程读取子进程输入的数据、子进程读取父进程恢复的数据。实现TELL_WAIT、TELL_PARENT、TELL_CHILD、TELL_PARENT及WAIT_CHILD函数。程序如下:

 1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <sys/types.h>
6
7 static int fd1[2],fd2[2];
8
9 void TELL_WAIT()
10 {
11 pipe(fd1);
12 pipe(fd2);
13 }
14
15 void TELL_PARENT(pid_t pid)
16 {
17 write(fd2[1],"c",1);
18 }
19 void WAIT_PARENT(void)
20 {
21 char c;
22 read(fd1[0],&c,1);
23 if(c!='p')
24 {
25 printf("WAIT_PARENT: Incorretc data");
26 exit(0);
27 }
28 else
29 printf("Read from parent.\n");
30 }
31 void TELL_CHILD(pid_t pid)
32 {
33 write(fd1[1],"p",1);
34 }
35 void WAIT_CHILD()
36 {
37 char c;
38 read(fd2[0],&c,1);
39 if(c!='c')
40 {
41 printf("WAIT_CHILD: Incorretc data");
42 exit(0);
43 }
44 else
45 printf("Read from child.\n");
46 }
47
48 int main()
49 {
50 pid_t pid;
51 TELL_WAIT();
52 pid =fork();
53 if(pid == -1)
54 {
55 perror("fork() error");
56 exit(-1);
57 }
58 if(pid == 0)
59 {
60 printf("child process exec.\n");
61 WAIT_PARENT();
62 TELL_PARENT(getppid());
63 }
64 else
65 {
66 printf("Parent process exec.\n");
67 TELL_CHILD(pid);
68 WAIT_CHILD();
69
70 }
71 exit(0);
72 }

程序执行结果如下:

popen和pclose函数

  常见的操作时创建一个管道连接到另外一个进程,然后读取其输出或向其输入端发送数据。popen和pcolse函数实现的操作是:创建一个管道,调用fork产生一个子进程,关闭管道的不使用端,执行一个shell以运行命令,然后等待命令终止。函数原型如下:

FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);

函数popen先执行fork,然后调用exec执行cmdstring,并且返回一个标准I/O文件指针。如果type是“r”,则文件指针连接到cmdstring的标准输出,如果type是“w”,则文件指针连接到cmdstring的标准输入。popen特别适用于构造简单的过滤程序,它变换运行命令的输入或输出。写一个程序,将标准输入复制到标准输出,复制的时候将所有的大写字母变换为小写字母,程序分为两部分,转换程序如下:

 1 #include <stdio.h>
2 #include <ctype.h>
3 #include <stdlib.h>
4 int main()
5 {
6 int c;
7 while((c = getchar()) != EOF)
8 {
9 if(isupper(c))
10 c= tolower(c);
11 if(putchar(c) == EOF)
12 printf("output error");
13 if(c=='\n')
14 fflush(stdout);
15 }
16 exit(0);
17 }

将可执行文件保存为change。输入输出程序如下:

 1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <errno.h>
6
7 #define MAXLINE 1024
8
9 int main()
10 {
11 char line[MAXLINE];
12 FILE *fpin;
13 if((fpin = popen(".//change","r")) == NULL)
14 {
15 perror("popen() error");
16 exit(-1);
17 }
18 for(; ;)
19 {
20 fputs("prompt> ",stdout);
21 fflush(stdout);
22 if(fgets(line,MAXLINE,fpin) == NULL)
23 break;
24 if(fputs(line,stdout) == EOF)
25 {
26 perror("fputs error to pipe");
27 }
28 }
29 if(pclose(fpin) == -1)
30 {
31 perror("pclose() error");
32 exit(-1);
33 }
34 putchar('\n');
35 exit(0);
36 }

程序执行结果如下

  协同进程:当一个进程产生某个过滤程序的输入,同时又读取该过滤程序的输出。popen只提供链接到另一个进程的标准输入或标准输出的一个单向管道,对于协同进程,则连接到另一个进程的两个单向管道,一个接到标准输入,一个接标准输出。写个程序展示一下协同进程,程序从标准输入读入两个整数,调用程序计算它们的和,然后将结果输出到标准输出。过滤程序即求和程序如下:

 1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <fcntl.h>
5 #include <unistd.h>
6 #define MAXLINE 1024
7
8 int main()
9 {
10 int n,int1,int2;
11 char line[MAXLINE];
12 while((n=read(STDIN_FILENO,line,MAXLINE)) > 0)
13 {
14 line[n] = '\0';
15 if(sscanf(line,"%d%d",&int1,&int2) == 2)
16 {
17 sprintf(line,"%d\n",int1+int2);
18 n = strlen(line);
19 if(write(STDOUT_FILENO,line,n) != n)
20 {
21 perror("write() error");
22 exit(-1);
23 }
24 }
25 else if(write(STDOUT_FILENO,"invalid arg\n",13) != 13)
26 {
27 perror("write() error");
28 exit(-1);
29 }
30 }
31 exit(0);
32 }

编译执行保存为可执行文件为add。

协同程序如下:

 1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <signal.h>
6 #include <string.h>
7 #define MAXLINE 1024
8
9 static void sig_pipe(int);
10
11 int main()
12 {
13 int n,fd1[2],fd2[2];
14 pid_t pid;
15 char line[MAXLINE];
16
17 if(signal(SIGPIPE,sig_pipe) ==SIG_ERR)
18 {
19 perror("signal() error");
20 exit(-1);
21 }
22 if(pipe(fd1) == -1||pipe(fd2) == -1)
23 {
24 perror("pipe() error");
25 exit(-1);
26 }
27 if((pid =fork()) == -1)
28 {
29 perror("fork() error");
30 exit(-1);
31 }
32 if(pid == 0)
33 {
34 close(fd1[1]);
35 close(fd2[0]);
36 if(fd1[0] != STDIN_FILENO)
37 if(dup2(fd1[0],STDIN_FILENO) != STDIN_FILENO)
38 {
39 perror("dup2 error in stdin");
40 close(fd1[0]);
41 exit(-1);
42 };
43 if(fd2[1] != STDOUT_FILENO)
44 if(dup2(fd2[1],STDOUT_FILENO) != STDOUT_FILENO)
45 {
46 perror("dup2 error in stdout");
47 close(fd2[1]);
48 exit(-1);
49 };
50 if(execl(".//add","add",(char *)0) == -1)
51 {
52 perror("execl() error");
53 exit(-1);
54 }
55 }
56 else
57 {
58 close(fd1[0]);
59 close(fd2[1]);
60 printf("Enter two number: ");
61 while(fgets(line,MAXLINE,stdin) != NULL)
62 {
63 n = strlen(line);
64 if(write(fd1[1],line,n) != n)
65 {
66 perror("write errot to pipe");
67 exit(-1);
68 }
69 if((n=read(fd2[0],line,MAXLINE)) ==-1)
70 {
71 perror("read error to pipe");
72 exit(-1);
73 }
74 if(n== 0)
75 {
76 printf("child close pipe.\n");
77 break;
78 }
79 line[n] = '\0';
80 printf("The result is: ");
81 fputs(line,stdout);
82 }
83 }
84 }
85
86 static void sig_pipe(int signo)
87 {
88 printf("SIGPIPE caught\n");
89 exit(1);
90 }

程序执行结果如下:

2、FIFO

  FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信。FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。

命名管道的命名管道创建:int mkfifo(const char * pathname, mode_t mode) 。
命名管道的打开:命名管道比管道多了一个打开操作:open ,在open时,用O_NONBLOCK 标志表示非阻塞模式,如fd=open(“/tmp/fifo”,O_RDONLY|O_NONBLOCK,0)。
命名管道的读入:read 读取管道数据,读取分为阻塞和非阻塞模式,阻塞模式下,如果没有数据被入,进程会在read处停下来.直到有新数据被写入,或管道被关闭,才会继续。
命名管道的写入:write 写入管道数据,PIPE_BUF表示一次触发管道读操作最大长度.如果每次写入数据长于PIPE_BUF ,write将会多次触发read 操作。
命名管道的关闭:管道文件也是一种文件,因此用close关闭即可。

FIFO的两种用途:

(1)FIFO有shell命令使用以便将数据从一条管道线传送到另一条,为此无需创建中间临时文件。

(2)FIFO用于客户进程—服务器进程应用程序中,以在客户进程和服务器进程之间传递数据。

3、XSI IPC

 消息队列、信号量、共享存储区相似的特征如下:具有标识符和键,标识符是IPC对象的内部名,每个IPC对象都与一个键相关联,创建IPC结构需要指定一个键,键的数据类型为key_t。每个IPC都设置了权限结构。

  优点及缺点:IPC结构是在系统范围内起作用,没有访问计数。在文件系统中没有名字,不使用文件描述符,不能对它们使用多路转接I/O函数。优点:可靠、流是受控的,面向记录、可以用非先进先出方式处理。

  消息队列(Messge
Queue):消息队列是消息的链接表,包括Posix消息队列SystemV消息队列。它克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新消息;对消息队列有读权限的进程则可以从消息队列中读取消息。
  信号量(Semaphore):主要作为进程之间以及同一进程的不同线程之间的同步和互斥手段。

  共享内存(Shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。
 
建议:要学会使用管道和FIFO,因为大量应用程序中仍可以有效地使用这两种技术,在新的应用程序中,要尽可能避免使用消息队列及信号量,应当考虑全双工管道和记录锁。

Unix环境高级编程(十六)进程间通信的更多相关文章

  1. (十二) 一起学 Unix 环境高级编程 (APUE) 之 进程间通信(IPC)

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  2. Unix环境高级编程(十八)高级进程间通信

    本章主要介绍了基于STREAM的管道和UNIX域套接字,这些IPC可以在进程间传送打开文件描述符.服务进程可以使用它们的打开文件描述符与指定的名字相关联,客户进程可以使用这些名字与服务器进程通信. 1 ...

  3. Unix环境高级编程(十五)高级I/O

    1.非阻塞I/O 对低速设备的I/O操作可能会使进程永久阻塞,这类系统调用主要有如下情况:(1)如果数据并不存在,则读文件可能会使调用者永远阻塞(例如读管道.终端设备和网络设备).(2)如果数据不能立 ...

  4. Unix环境高级编程(十二)线程控制

    本章介绍了一个进程中多个线程之间如何保持数据的似有性及进程的系统调用如何与线程进行交互. 1.线程限制: Single Unix定义了一线线程操作的限制,和其他的限制一样,可以通过sysconf来查询 ...

  5. Unix环境高级编程(十)信号续

    1.signal函数 Unix系统的信号机制最简单的接口是signal函数,函数原型如下: #include <signal.h> typedef void (*sighandler_t) ...

  6. unix环境高级编程第六章笔记

    口令文件 阴影口令 组文件 附属组ID 登录账户记录 系统标识 口令文件<\h2> /etc/passwd文件是UNIX安全的关键文件之一.该文件用于用户登录时校验用户的口令,文件中每行的 ...

  7. Unix环境高级编程(十九)终端I/O

    终端I/O应用很广泛,用于终端.计算机之间的直接连线.调制解调器以及打印机等等.终端I/O有两种不同的工作模式: (1)规范模式输入处理:终端输入以行为单位进行处理,对于每个读要求,终端驱动程序最多返 ...

  8. Unix环境高级编程(十四)守护进程实现时间服务器

    守护进程是在后台运行不受终端控制的进程(如输入.输出等),一般的网络服务都是以守护进程的方式运行.守护进程脱离终端的主要原因有两点:(1)用来启动守护进程的终端在启动守护进程之后,需要执行其他任务.( ...

  9. (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

随机推荐

  1. BNU Concentric Rings

    http://www.bnuoj.com/bnuoj/problem_show.php?pid=16030 Concentric Rings   There are several different ...

  2. Python基础语法学习整理

    1.基础 r’  ‘:原始字符串 pow.round是内建函数 2.序列通用操作: 索引:d[] 分片:[:] 相加:d+[] 乘法:[1,2]*3 成员判断:in 可用函数:len  max  mi ...

  3. python input 与raw_input函数的区别

    转自:http://blog.csdn.net/sruru/article/details/7790436 以前没有深入考虑过raw_input与input函数的区别,所以一直比较困惑,今天测试之后, ...

  4. QR分解与最小二乘

    主要内容: 1.QR分解定义 2.QR分解求法 3.QR分解与最小二乘 4.Matlab实现   一.QR分解 R分解法是三种将矩阵分解的方式之一.这种方式,把矩阵分解成一个正交矩阵与一个上三角矩阵的 ...

  5. Linux/Unix分配进程ID的方法以及源代码实现

    在Linux/Unix系统中.每一个进程都有一个非负整型表示的唯一进程ID.尽管是唯一的.可是进程的ID能够重用.当一个进程终止后,其进程ID就能够再次使用了. 大多数Linux/Unix系统採用延迟 ...

  6. asp.net 使用JS获得串口数据

    使用JS获得串口数据 JavaScript语言通常是一种网页编程语言,比较适合前台的一些应用设计.对于本地设备和数据的操作有比较大的限制.由于项目的需要,我需要获得本机的串口数据并显示在web端.我们 ...

  7. 笔试题之ejb

    ejb部分 1.EJB是基于哪些技术实现的?并说出SessionBean和EntityBean的区别,StatefulBean和StatelessBean的区别. EJB包括Session Bean. ...

  8. Maven的settings.xml文件结构之mirrors

    Maven的远程库提供大量构件,供Maven项目直接下载使用.对于一个Maven项目,如果没有特别声明,默认使用Maven的central库,url如下: http://repo.maven.apac ...

  9. Best Time to Buy and Sell Stock I &amp;&amp; II &amp;&amp; III

    题目1:Best Time to Buy and Sell Stock Say you have an array for which the ith element is the price of ...

  10. 计算机顶级会议Rankings &amp;&amp; 英文投稿的一点经验

    英文投稿的一点经验[转载] From: http://chl033.woku.com/article/2893317.html 1. 首先一定要注意杂志的发表范围, 超出范围的千万别投,要不就是浪费时 ...