第八章 进程间通信 [notice pipe at RIL.pdf] [popen] [mkfifo]
前言:
管道只能用于父子进程或兄弟进程。pipe创建管道,fork创建子进程,完全继承管道,可以理解为对同一个通道拥有读写权(见图8.4),父子进程分别关闭其中一个不同的权限,形成父读子写或父写子读的一个通道。
popen
mkfifo 用于命名管道,但权限问题目前还没有搞清楚,难道管道必须开放读写权限。任何进程都可以访问。
========================================================================
8.1 Linux下进程间通信概述
Linux下的进程间通信基本上是从UNIX平台继承下来的,而AT&T和BSD对UNIX都做出了重大贡献,但侧重点不同。
1.AT&T对UNIX早期的进程间通信手段进行了系统的改进和扩充,形成了"system V IPC",其通信进程主要局限在单个计算机内。
2.BSD跳过了单机局限,形成了套接口(socket)的进程间通信机制。
(1+2)Linux则把两者的优势都继承了下来。
1.UNIX进程间通信(IPC)方式包括管道、FIFO、信号。
2.System V 进程间通信(IPC)包括System V 消息队列、System V 信号灯、System V 共享内存区。
3.Posix 进程间通信(IPC)包括Posix消息队列、Posix信号灯、Posix共享内存区。
现在Linux中使用较多的进程间通信方式主要有以下几种:
(1)管道及命名管道
(2)信号:信号是在软件层次上对中断机制的一种模拟。
(3)消息队列:
(4)共享内存:可以说这是最有用的进程间通信方式。这种通信方式需要依靠同步机制,如互斥锁和信号量等。
(5)信号量:主要作为进程间以及同一进程不同线程之间的同步手段。<--第10章中单独介绍
(6)套接字:这是一种更为一般的进程间通信机制,它可用于不同机器之间的进程间通信
管道可用于具有亲缘关系进程间的通信
命名管道,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
8.2 管道通信
8.2.1 管道概述
它是把一个程序的输出直接连接到另一个程序的输入。ps -ef | grep ntp
*它只能用于父子进程或者兄弟进程。
*它是一个半双工的通信模式,具有固定的读端和写端
*管理也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但它不是普通文件,并不属于其他任何文件系统,并且只存在内存中。
8.2.2 管道的创建与关闭
1.管道创建与关闭说明
管理是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fds[0]和fds[1],其中fds[0]固定用于读管道,而fds[1]固定用于写管道。
就构成了一个半双工的通道。
管道关闭时,只需要将这两个文件描述符关闭即可,可使用普通的close函数关闭各个文件描述符
2.管道创建函数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<unistd.h> #include<errno.h> #include<stdio.h> #include<stdlib.h> int main(){ int pipe_fd[2]; if(pipe(pipe_fd)<0){ printf("pipe create errorn"); return -1; } else printf("pipe create successn"); close(pipe_fd[0]); close(pipe_fd[1]); }
8.2.3管道读写
1.管道读写说明
用pipe函数创建的管道两端处于一个进程中,由于管道主要用于在不同进程间通信的,因此这在实际应用中没有太大意义。
实际上,通常先创建一个管理,再通过fork()函数创建一子进程,该子进程会继承父进程所创建的管道(fork是完全复制,且有两个返回值,分别是父子进程)
父进程fd[0]读管道,fd[1]写管道
子进程fd[0]读管道
,fd[1]写管道
如果关闭父进程fd[1]和子进程fd[0],就在父子进程间建立一条“子进程写入父进程读”的通道
如果关闭父进程fd[0]和子进程fd[1],就在父子进程间建立一条“父进程写,子进程读”的通道
![](//images0.cnblogs.com/blog/49826/201301/16164057-0de9cad166a54e5498bc6e9fe3a97525.png)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<unistd.h> #include<sys/types.h> #include<errno.h> #include<stdio.h> #include<stdlib.h> int main(){ int pipe_fd[2]; pid_t pid; char buf_r[100]; char* p_wbuf; int r_num; memset(buf_r, 0, sizeof(buf_r)); if(pipe(pipe_fd)<0){ printf("pipe create error\n"); return -1; } printf("1 getpid() = %d\n", getpid()); printf("1 getppid() = %d\n", getppid()); if((pid = fork()) == 0){ // call fork will return twice printf("\n"); close(pipe_fd[1]);//run at child printf("2 getpid() = %d\n", getpid()); printf("2 getppid() = %d\n", getppid()); sleep(2); if((r_num=read(pipe_fd[0], buf_r, 100))>0){ printf("%d numbers read from the pipe is %s\n", r_num, buf_r); } close(pipe_fd[0]); exit(0); } else if(pid > 0){ close(pipe_fd[0]);//run at parent printf("3 getpid() = %d\n", getpid()); printf("3 getppid() = %d\n", getppid()); if(write(pipe_fd[1], "Hello", 5)!=-1) printf("parent write1 success!\n"); if(write(pipe_fd[1], " Pipe", 5)!=-1) printf("parent write2 success!\n"); close(pipe_fd[1]); sleep(3); waitpid(pid,NULL, 0); exit(0); } }
waitpid,即等到子进程pid退出后才退出。
3.管道读写注意点
只有在管道的读端存在时向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号。(通常是Broken pipe错误,以后遇到Broken pipe时,可以知道,管道读端已经不存在了)。
向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞。
父子进程在运行时,它们的先后次序并不能保证,因此,在这里为了保证父进程已经关闭了读描述符,可在子进程中调用sleep函数。
8.2.4 标准流管道
1.标准流管道函数说明
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<fcntl.h> #define BUFSIZE 1000 int main(){ FILE *fp; char *cmd = "ps -ef"; char buf[BUFSIZE]; if((fp = popen(cmd,"r")) == NULL) perror("popen"); while((fgets(buf, BUFSIZE, fp)) != NULL) printf("%s", buf); pclose(fp); exit(0); }
8.2.5 FIFO
1.有名管道说明
有名管道可以使互不相关的两个进程实现彼此通信
该管道可以通过路径名来指出,并且在文件系统中是可见的。
在建立了管道之后,两个进程就可以把它当作普通文件一样进程读写操作,使用非常方便。
不过值得注意的是,FIFO是严格遵循先进先出规则的,对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾,它们不支持如lseek()等文件定位操作。
有名管道的创建可以使用函数mkfifo(),该函数类似文件中的open()操作,可以指定管道的路径和打开的模式。
[用户还可以在命令行使用"mknod 管道名 p"来创建有名管道]
在创建管道成功之后,就可以使用open read write这些函数了。
对于为读而打开的管道可在open中设置O_RDONLY,对为写而打开的管道可在open中设置O_WRONLY
在这里与普通文件不同的是阻塞问题。在管道中读写中却有阻塞的可能,这里的非阻塞标志可以在open函数中设定为O_NONBLOCK
对于读进程
• 若该管道是阻塞打开,且当前 FIFO 内没有数据,则对读进程而言将一直阻塞直到有数据写入。
• 若该管道是非阻塞打开,则不论 FIFO 内是否有数据,读进程都会立即执行读操作。
对于写进程
• 若该管道是阻塞打开,则写进程而言将一直阻塞直到有读进程读出数据。
• 若该管道是非阻塞打开,则当前 FIFO 内没有读操作,写进程都会立即执行读操作。
2. mkfifo函数格式
3.使用实例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<sys/types.h> #include<sys/stat.h> #include<errno.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<limits.h> #define MYFIFO "/tmp/myfifo" #define MAX_BUFFER_SIZE PIPE_BUF int main() { char buff[MAX_BUFFER_SIZE]; int fd; int nread; //判断管道是否存在,如果不存在则创建 if(access(MYFIFO, F_OK) == -1) { if((mkfifo(MYFIFO, 0666) < 0) && (errno != EEXIST)) { printf("cannot creat fifo file!\n"); exit(1); } } fd = open(MYFIFO, O_RDONLY);//打开管道,只读阻塞方式 if(fd == -1) { printf("open fifo file error!\n"); exit(1); } while(1) { memset(buff, 0, sizeof(buff)); if((nread = read(fd, buff, MAX_BUFFER_SIZE)) > 0)//读管道 { printf("read '%s' from FIFO\n", buff); } } close(fd);//关闭 exit(0); }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<sys/types.h> #include<sys/stat.h> #include<errno.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<limits.h> #define MYFIFO "/tmp/myfifo" #define MAX_BUFFER_SIZE PIPE_BUF int main(int argc, char* argv[]) { char buff[MAX_BUFFER_SIZE]; int fd; int nwrite; if(argc <= 1) { printf("usage: ./write string!\n"); exit(1); } sscanf(argv[1], "%s", buff); fd = open(MYFIFO, O_WRONLY);//打开管道,写阻塞方式 if(fd == -1) { printf("open fifo file error!\n"); exit(1); } if((nwrite = write(fd, buff, MAX_BUFFER_SIZE)) > 0)//写管道 { printf("write '%s' to FIFO!\n ", buff); } close(fd);//关闭 exit(0); }
8.3 信号通信
8.3.1信号概述
信号是UINX中所使用的进程通信的一种最古老的方法,它是在软件层次上对中断机制的一种模拟,是一种异步通信方式。
信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
它可以在任何时候发给某一进程,而无需知道该进程的状态。