0. 引入
IPC(进程间通信):Internal Process Communication
实质:信息(数据)的交换
有人就说,这还不简单吗?
我在一个进程里面定义一个全局变量a,然后再给a赋值一些有含义的值,然后再让另一个进程去读取这个a的值,不就实现通信了吗?
这个想法,可以吗?
肯定是不可以的,因为进程的地址空间是独立的(虚拟地址),解决进程间通信的问题,就需要借助双方都可以访问的第三方
所以,如果两个进程要通信,必须把数据放到一个大家都可以访问(读,写)到的地方
文件? 一个文件可以被多个进程同时打开可以,文件在文件系统中,大家都可以访问,但是这种方式,有一个缺点:通信速度太慢了
================>
在操作系统内核中去开辟一段空间(某一种机制),进程去访问它
IPC方式:管道
pipe无名管道
fifo有名管道
信号
消息队列 System V消息队列 / POSIX消息队列
信号量 System V信号量 / POSIX信号量
共享内存 System V共享内存 / POSIX共享内存
网络 socket
很久很久以前,进程间通信的方式,都是通过文件!
这种方式,他有一个缺点:
它的效率太低了
但是这种方式,有一个非常大的好处:
简单,不需要提供额外的API函数接口(直接利用文件系统的API函数)
有人想,能不能改进一下?问题在哪里?文件内容在外设,文件系统上,造成访问效率低
能不能把文件的内容放到内核或者内存中去呢?
管道:管道文件 (内容放到内核或者内存中),又可以利用文件系统的API函数
1. PIPE 无名管道
它在文件系统中是没有名字(没有inode......)的,它的内容在内核中(而不是存在于硬盘),访问pipe的方式是通过文件系统的 API(read / write)
fd = open(文件名,)read(fd,) / write(fd, )
close()
它没有文件名,无法通过路径索引到相应的文件内容,不能使用open函数打开,但是read / write又需要一个文件描述符 !!!
所以在创建这个pipe的时候,就必须要返回文件描述符 !!!
pipe在创建的时候,在内核中开辟一块缓冲区,作为pipe文件的内容的存储空间,同时返回两个文件描述符(一个用来读,一个用来写)并且它还有如下特点:
(1) pipe有两端,一端是用来写,一端是用来读
(2) 内容读走了,就没有了(不存在于pipe文件中)
(3) 按顺序读,不支持lseek的
(4) pipe(无名管道)随内核的持续性
无名管道的创建:pipe
NAME pipe, pipe2 - create pipe SYNOPSIS #include <unistd.h> // pipe是用来创建一个无名管道的,pipefd用来保存创建好的无名管道的两个文件描述符 // pipe创建的管道,默认是"阻塞模式" int pipe(int pipefd[2]); pipefd:数组,指向一段可用的空间 pipefd[0]:保存读的文件描述符 pipefd[1]:保存写的文件描述符 返回值: 成功返回0 失败返回-1,同时errno被设置
无名管道练习:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> int main(int argc, char *argv[]) { int pipefd[2] = {0}; int r = pipe(pipefd); if (r == -1) { perror("create pipe failed"); return -1; } // printf("pipefd[0] = %d\n", pipefd[0]); // printf("pipefd[1] = %d\n", pipefd[1]); pid_t pid = fork(); if (pid < 0) { perror("fork error"); return -1; } else if (pid == 0) { // 子进程 char buf[32] = {"hello father"}; write(pipefd[1], buf, 32); exit(0); } else { // 父进程 wait(NULL); // 等待子进程结束 // 无名管道,随内核持续性 char buf[32] = {0}; read(pipefd[0], buf, sizeof(buf)); printf("message: %s\n", buf); } return 0; }
思考:
pipe(无名管道)是不是任意两个进程都可以使用pipe来通信呢?或者只能用于父子进程呢?
只要两个进程能够获取同一个pipe的文件描述符就可以使用pipe来通信
一般是用于有亲缘关系的进程
pipe本身是全双工的,但是两个进程使用一个管道去实现全双工的通信,就必须以某一种方式去同步,不然自己很容易读到自己写入的数据
通常在工程中,一般做成两个或者多个管道,一个用来读,一个用来写,人为的把pipe当成是半双工,同时收发容易造成自发自收
通信方式:全双工:通信双方可以同时进行数据接收和发送,比如:视频电话
半双工:通信双方,同一时刻,只能有一方进行发送,另一方接收,比如:对讲机
单工:通信双方中,一方固定为发送端,一方则固定为接收端,信息只能沿着一个方向 传输,比如广播
------------------------------------------------------------------------------------------------------------------------
pipe(无名管道)只能用于有亲缘关系的进程间通信原因就是因为pipe在文件系统中没有名字(inode),无法通过open去获取文件描述符
假如它在文件系统中有一个"名字",它是不是就可以用于任意两个进程间通信呢?可以,既然有名字,就可以通过open去获取文件描述符,而不是一定要通过"继承父进程"数据的方式 —— fifo:有名管道
2. FIFO 有名管道
fifo 是在 pipe 的基础上,给fifo在文件系统中创建一个 inode(它在文件系统中就会有一个文件名),但是文件的内容却在内核中 !!!
====================>
fifo的文件名是随文件系统的持续性(不删除就存在)
fifo的内容存在于内核,随内核的持续性
fifo 和 pipe 一样,但是 fifo 在文件系统中有一个文件名操作 fifo 就和操作普通文件类型一样:
open
read / write
close
fifo 文件是如何创建的呢?注意:不要在共享文件夹中创建管道文件,因为共享文件夹只是挂载在windows下,而windows不支持管道文件
有名管道的创建:mkfifo(1) 用户命令 mkfifo —— 查看:mkfifo --help
例如:mkfifo ~/SC/mkfifo.txt
(2) linux API 函数接口 mkfifo
NAME mkfifo, mkfifoat - make a FIFO special file (a named pipe) SYNOPSIS #include <sys/types.h> #include <sys/stat.h> // mkfifo用来在文件系统中创建一个 fifo(有名字的管道)的入口(inode) int mkfifo(const char *pathname, mode_t mode); pathname:路径名,要创建的有名管道,在文件系统中的名字(一般带路径) mode:权限,创建的有名管道的权限,有两种方式指定: a. S_IRUSR ... b. 0664 ... 返回值: 成功返回0 失败返回-1,同时errno被设置 errno == EEXIST 路径名已经存在
NAME fifo - first-in first-out special file, named pipe DESCRIPTION FIFO(有名管道)和 PIPE(无名管道)类似,只不过 FIFO 在文件系统中存在一个 inode,它可以被多个进程打开用来读和写,当进程用 FIFO 来交换数据的时候,内核根本 就没有把数据写入到文件系统中去,而是保存在内核的内部,所以 FIFO 在文件系统中没有 内容,仅仅作为文件系统的一个引用入口,提供一个文件名给其他的进程去 open 它 【在数据交换前,FIFO的两端(read / write)必须都被打开,通常情况下,你打开FIFO 的一端,会阻塞,直到另外一端也被打开】 一个进程也可以以"非阻塞"(O_NONBLOCK)方式去打开,这种情况下(以"非阻塞"方式 去打开),只读打开总会成功,即便写端没有被打开 只写打开总会失败,并且errno == EENXIO,除非读端已打开 阻塞模式: 阻塞的读或者写 读的时候,如果没有数据,则read会阻塞(等待),如果管道的写端被关闭, read会立即返回,返回值为0 读的时候,如果有数据,即使管道的写端被关闭,也能read到数据 写的时候,如果没有空间,则write会阻塞(等待) 非阻塞模式: 非阻塞的读或者写 读的时候,如果没有数据,则read会立即返回,返回值为0 写的时候,如果没有空间,则write会立即返回
有名管道练习:
(1)两个终端
// gcc read.c -o read // ./read /home/china/SC/fifo #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <errno.h> int main(int argc, char *argv[]) { if (argc != 2) { printf("input error\n"); return -1; } int r = mkfifo(argv[1], 0664); if (-1 == r) { if (errno != EEXIST) { perror("mkfifo error"); return -1; } } printf("mkfifo success\n"); printf("%s:%d\n", __FUNCTION__, __LINE__); // 操作管道 open read close int fifofd = open(argv[1], O_RDONLY); // 只读打开 if (-1 == fifofd) { perror("open fifo error"); return -1; } printf("%s:%d\n", __FUNCTION__, __LINE__); char buf[10] = {0}; r = read(fifofd, buf, 10); printf("r = %d\n",r); printf("buf = %s\n", buf); printf("%s:%d\n", __FUNCTION__, __LINE__); close(fifofd); return 0; } // mkfifo success // main:27 // main:36 // r = 6 // buf = hello // main:43
// gcc write.c -O write // ./write ~/SC/fifo #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <errno.h> int main(int argc, char *argv[]) { if (argc != 2) { printf("input error\n"); return -1; } int r = mkfifo(argv[1], 0664); if (r == -1) { if (errno != EEXIST) { perror("mkfifo error"); return -1; } } printf("mkfifo success\n"); printf("%s:%d\n", __FUNCTION__, __LINE__); int fifofd = open(argv[1], O_WRONLY); // 只写打开 if (fifofd == -1) { perror("open fifo error"); return -1; } printf("%s:%d\n", __FUNCTION__, __LINE__); char buf[10] = "hello"; r = write(fifofd, buf, strlen(buf) + 1); printf("r = %d\n", r); printf("%s:%d\n", __FUNCTION__, __LINE__); close(fifofd); return 0; } // mkfifo success // main:27 // main:35 // r = 6 // main:41
(2)一个终端
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> int main(int argc, char *argv[]) { pid_t pid = fork(); if (pid < 0) { perror("fork error"); return -1; } else if (pid == 0) { // 子进程 int fifofd = open(argv[1], O_WRONLY); // 只写打开 if (fifofd == -1) { perror("open fifo error"); return -1; } char buf[10] = "hello"; int w = write(fifofd, buf, strlen(buf) + 1); printf("w = %d\n", w); close(fifofd); } else { // 父进程 // 操作管道 open read close int fifofd = open(argv[1], O_RDONLY); // 只读打开 if (-1 == fifofd) { perror("open fifo error"); return -1; } wait(NULL); char buf[10] = {0}; int r = read(fifofd, buf, 10); printf("r = %d\n",r); printf("%s\n", buf); close(fifofd); } return 0; } // w = 6 // r = 6 // hello