1.程间通信目的
:
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
2.匿名管道
1.原理
上图表示进程打开了一个文件,每一个进程都有一个文件描述符表struct files_struct,
根据文件的路径在磁盘中找到文件的属性和内容,建立文件内核缓冲区 和 文件描述符struct file 里面有指向inode(文件属性)和文件内核缓冲区的指针。然后把文件描述符指针按由小到大的顺序填入文件描述符表的fd_arry[]数组中,最后返回下标。
那如果以该进程为父进程创建子进程会是什么样的?
fork()创建一个子进程,肯定会把task_struct struct files_struct这些进程部分复制下来,
struct file不属于进程的一部分,但父子进程需要独立读写文件,也就意味着struct file里面的pos不同(
pos
通常指的是文件的当前偏移量,即下一个读写操作将从文件的哪个位置开始。),需要子进程复制一份。inode对文件的管理一份就够了,文件内核缓冲区是系统提供的,系统服务每个进程。
那父子进程如何完成通信呢?
父子进程在同一个文件的文件内核缓冲区进行操作,那文件内核缓冲区的内容要不要刷新到磁盘呢?这个文件的作用就是为了让进程间相互通信,没必要保存到磁盘。
有没有一种特殊的文件只用于进程间的通信呢?
管道
父进程读写打开管道,fork()创建子进程。父进程关闭读端,子进程关闭写端。父进程向管道写入数据,子进程可以随时从管道读取数据,而不会干扰彼此的读写操作。形成单向通信.
可以不关读端吗?
建议关,因为管道的设计就是为了单向通信,父进程没必要去进行读操作,不关浪费数组资源,文件描述符泄漏。还有可能会进行误操作。
补充:关于重定向 >
./cout.c > test 表示把cout.c文件输出的内容重定向到test文件中。但比不上所有的打印信息都会重定向到test文件中,错误流的信息并不会重定向到test,而是打印到显示器上。
我们知道文件描述符 0输入流 1输出流 2错误流 ,
./cout.c > test 完整的表示是 ./cout.c 1>test
如果想把错误流也重定向到test文件中
./cout.c &> test 这里的&表示将标准错误与标准输出一起处理。
或者
./cout.c 1>test 2>&1
>&表示将文件描述符的目标重定向。
2>&1 的意思是将标准错误重定向到标准输出所指向的位置,也就是test文件
2.pipe()系统调用
pipe()系统调用创建一个管道。
#include <unistd.h>int pipe(int fd[2]);
参数:int fd[2]是一个输出行参数,返回文件描述符,fd[0]以读方式打开管道(读端)
fd[1]以写方式打开管道(写端)
返回值:成功返回0 失败返回-1 error
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>int main()
{// 1.创建管道int fd[2] = {0};int n = pipe(fd);if (n == -1){std::cerr << "pipe error" << std::endl;return -1;}// 2.创建子进程pid_t id = fork();if (id < 0){std::cerr << "fork error" << std::endl;return -2;}if (id == 0){int count = 0;// 子进程 关闭读端close(fd[0]);while (1){close(fd[0]);std::string message = "hello ";message += std::to_string(getpid());message += ",";message += std::to_string(count++);// 写入管道int n = write(fd[1], message.c_str(), message.size());sleep(2);}exit(0);}else{char buff[1024];// 父进程 关闭写端close(fd[1]);// 从管道中读while (1){int n = read(fd[0], buff, 1024);if (n > 0){// 读到数据buff[n] = '\0'; // 系统字符串没有规定以0结尾std::cout << "子进程->父进程 message:" << buff << std::endl;}else if (n == 0){// 写端关闭 读到结尾break;}}int status;pid_t rid = waitpid(id, &status, 0);if (rid == -1){std::cerr << "waitpid error" << std::endl;return -3;}//int 16字节 前7位退出信号 后8位退出码std::cout<<"子进程pid"<<rid<<"退出码"<<((status<<8)&0xFF)<<"退出信号"<<(status&0x7F);}return 0;
}
上面是子进程写入,父进程读入并输出。
root@hcss-ecs-178e:~/dir1# ./pipe 子进程->父进程 message:hello 8128,0 子进程->父进程 message:hello 8128,1 子进程->父进程 message:hello 8128,2 子进程->父进程 message:hello 8128,3 子进程->父进程 message:hello 8128,4 子进程->父进程 message:hello 8128,5 子进程->父进程 message:hello 8128,6 root@hcss-ecs-178e:~# ps axj|head -1;ps axj|grep pipePPID PID PGID SID TTY TPGID STAT UID TIME COMMAND7039 8127 8127 7039 pts/2 8127 S+ 0 0:00 ./pipe8127 8128 8127 7039 pts/2 8127 S+ 0 0:00 ./pipe7918 8132 8131 7918 pts/3 8131 S+ 0 0:00 grep --color=auto pipe
子进程不断向管道内写入,父进程读入并输出。
3.匿名管道的四种情况
1.管道为空&&管道正常 read会阻塞
子进程写入的速度小于父进程读入的速度,read会阻塞。
2.管道为满&&管道正常 wirte会阻塞
父进程读入速度小于子进程写入的速度,wirte会阻塞。
子进程每次写入一个字符,re表示写入多少字符。
让父进程读入一次就停止,子进程不停写 直到写满管道 wirte阻塞。
65536/1024=64KB 可见管道一次性最大存入64KB数据,父进程读入,子进程才能进行写入。也就意味着父进程读过后的内容在管道内不会保存。
3.管道写端关闭,读端继续。读端读到0,表示读到文件结尾。
子进程只写入一次
父进程读到0,buff为空,意味着管道为空,读到结尾。退出返回退出码 退出信号
4.写端正常,读端关闭。OS会直接杀死进程
子进程写入管道就是为了让父进程读,如果读端关闭,意味着管道就没有存在的意义,会被系统杀死。
父进程读一次就关闭读端
可以看到退出信号为13,它表示一个进程试图向一个已关闭的管道或套接字写入数据。这种情况下,操作系统会终止该进程,通常会导致进程接收到该信号。
管道的特性
单向通信:管道通常是单向的,数据从一个进程流向另一个进程。可以使用两个管道实现双向通信。
缓冲区:管道有一个内置的缓冲区,可以暂时存储数据。写入操作不会立即导致读取操作,因此可以在某些情况下实现异步通信。同步互斥
匿名性:在 Unix 和类 Unix 系统中,匿名管道不具名,不需要在文件系统中创建实体。它们只存在于相关联的进程之间。
阻塞行为:默认情况下,管道的读写操作是阻塞的。写入端在缓冲区满时会阻塞,读取端在管道为空时会阻塞。这可以帮助协调两个进程的执行。
进程关系:通常,管道用于父进程与子进程之间的通信。子进程可以继承父进程创建的管道描述符。
资源释放:当管道的最后一个读取端被关闭时,所有写入该管道的进程都会收到
SIGPIPE
信号,这可能导致它们终止。生命周期随进程大小限制:管道的缓冲区大小通常受到操作系统的限制,不同系统的大小可能不同。