Unix环境高级编程(十六)进程间通信
进程间通信(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函数。优点:可靠、流是受控的,面向记录、可以用非先进先出方式处理。
Queue):消息队列是消息的链接表,包括Posix消息队列SystemV消息队列。它克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新消息;对消息队列有读权限的进程则可以从消息队列中读取消息。
Unix环境高级编程(十六)进程间通信的更多相关文章
- (十二) 一起学 Unix 环境高级编程 (APUE) 之 进程间通信(IPC)
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- Unix环境高级编程(十八)高级进程间通信
本章主要介绍了基于STREAM的管道和UNIX域套接字,这些IPC可以在进程间传送打开文件描述符.服务进程可以使用它们的打开文件描述符与指定的名字相关联,客户进程可以使用这些名字与服务器进程通信. 1 ...
- Unix环境高级编程(十五)高级I/O
1.非阻塞I/O 对低速设备的I/O操作可能会使进程永久阻塞,这类系统调用主要有如下情况:(1)如果数据并不存在,则读文件可能会使调用者永远阻塞(例如读管道.终端设备和网络设备).(2)如果数据不能立 ...
- Unix环境高级编程(十二)线程控制
本章介绍了一个进程中多个线程之间如何保持数据的似有性及进程的系统调用如何与线程进行交互. 1.线程限制: Single Unix定义了一线线程操作的限制,和其他的限制一样,可以通过sysconf来查询 ...
- Unix环境高级编程(十)信号续
1.signal函数 Unix系统的信号机制最简单的接口是signal函数,函数原型如下: #include <signal.h> typedef void (*sighandler_t) ...
- unix环境高级编程第六章笔记
口令文件 阴影口令 组文件 附属组ID 登录账户记录 系统标识 口令文件<\h2> /etc/passwd文件是UNIX安全的关键文件之一.该文件用于用户登录时校验用户的口令,文件中每行的 ...
- Unix环境高级编程(十九)终端I/O
终端I/O应用很广泛,用于终端.计算机之间的直接连线.调制解调器以及打印机等等.终端I/O有两种不同的工作模式: (1)规范模式输入处理:终端输入以行为单位进行处理,对于每个读要求,终端驱动程序最多返 ...
- Unix环境高级编程(十四)守护进程实现时间服务器
守护进程是在后台运行不受终端控制的进程(如输入.输出等),一般的网络服务都是以守护进程的方式运行.守护进程脱离终端的主要原因有两点:(1)用来启动守护进程的终端在启动守护进程之后,需要执行其他任务.( ...
- (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
随机推荐
- CentOS 7 开放防火墙端口命令
CentOS 7 开放防火墙端口 命令 最近公司新的server要求用CentOS7, 发现以前CentOS 6 系列中的 iptables 相关命令不能用了,查了下,发现Centos 7使用fire ...
- ADB用法
作为android开发人员,adb是常用的工具之一.具体怎么使用了. 1. 安装完ADB后(ADB的安装请参考<Android开发平台搭建及配置.doc>),用电脑USB连接机器,然后使用 ...
- Android基础新手教程——1.6 .9(九妹)图片怎么玩
Android基础新手教程--1.6 .9(九妹)图片怎么玩 标签(空格分隔): Android基础新手教程 1.本节引言: 可能有的一些疑问: 1.什么是.9图片? 答:图片后缀名前有.9的图片,如 ...
- 菜鸟运维笔记:小记编译安装Nginx所遇到的坑
转载请注明出处:http://blog.csdn.net/guodongxiaren/article/details/40950249 谢谢合作 前言 无论是CentOS,或是Debian/Ubunt ...
- [Node.js]27. Level 5: URL Building & Doing the Request
Let's create a page which calls the twitter search API and displays the last few results for Code Sc ...
- 剑指offer面试题12-打印1到最大的n位数
题目: 输入一个数字n,按顺序打印出从1最大的n位十进制数.比方输入3,则打印出1.2.3最大的三位数即999 这道题的主要陷阱就在大数的处理,仅仅要将这个考虑进去,用字符串来表示.就好说了. 那差点 ...
- C#.NET常见问题(FAQ)-interface接口如何理解
个人把interface理解为一种比较特殊的判断技巧,不是常规的变量类型比如判断字符串,判断数组,而是判断类的实例是否拥有某些属性或者方法(比如有十个女的穿一样的衣服,头上盖住,让新郎去猜哪一个是他的 ...
- STL - 容器 - Set
Set根据特定排序准则,自动将元素排序. Set不允许元素重复. 一些常规操作: SetTest.cpp #include <iostream> #include <set> ...
- Java从零开始学十八(抽象类和接口)
一.什么是抽象类和接口 抽象类.接口与类是一个层次的概念,是java中极其重要的概念. 抽象类是从多个类中抽象出来的公共模板,提供子类均具有的功能. 接口是从多个类中抽象出来的规范,体现的是规范和实现 ...
- eclipse Java compiler level does not match the version of the installed Java project facet.
eclipse Java compiler level does not match the version of the installed Java project facet. Create ...