1.什么是進程通信?
答:兩個或多個進程實現數據層面的交互;但是因為進程的獨立性,導致進程通信的成本較高;
2.為什么要通信?
答:多進程之間由協同的需求,所以通信;以下是通信要實現的目的:
2.1.基本數據:
2.2.發送命令:一個進程控制另一個進程
2.3.多進程之間實現協同
2.4.通知
2.6.通信是有成本的:因為是要打破進程的獨立性
3.怎么通信?
3.1進程間通信的本質:必須讓不同的進程看到同一份“資源”
3.2“資源”——特定形式的內存空間;
3.3這個資源誰提供?答:一般是操作系統提供;
3.4為什么不是我們兩個進程中的一個提供呢?答:進程具有獨立性;假設是由一個進程提供,這個資源應該屬于誰?他不是共享資源,還是進程獨有,因為破環了進程的獨立性;OS是第三方空間,誰用誰申請
3.5進程訪問這個空間,進行通信,本質就是訪問操作系統;進程代表的就是用戶;“資源”從創建、使用、釋放——必須調用系統接口;
3.6操作系統提供資源,保障了進程通信,而且底層設計、接口設計、都要由操作系統獨立設計;一般操作系統,會有一個獨立的通信模塊——隸屬于文件系統——IPC通信模塊(進程間通信的意思)
3.7進程間通信是由標準的;兩套標準——system V? 和 posix
3.8基于文件級別的通信方式——管道(此時的文件,只是代稱,我可以往內存中寫啊)
思想是正確的,但是效率并不高;管道是Linux(unix)最古老的進程通信方式;
3.9管道原理(自我理解):基礎IO部分,緩沖區里的數據要寫入磁盤文件,現在不寫入磁盤,而是直接與子進程交互,把數據給子進程;并且通過引用計數原理(智能指針)控制著文件的打開和關閉,進行讀寫;(只能單向通信所以命名為管道)
3.10強烈建議如果單向通信,一定要關閉不必要的文件描述符,防止寫錯;
3.11 如果我要進行雙向通信呢——答:建兩個管道;
3.12 以上講的是父子進程,如果沒有任何關系,可以用以上講的原理進行通信嗎?答:不能,做不到;
3.13兄弟之間也可以用管道,進行通信;爺爺和孫子也可以進行通信——使用管道通信,必須得是具有血緣關系;常用于父子之間
3.14 以上所說的文件,是在緩沖區中進行的,并不是在真實的文件中交互的,所以這個管道叫做匿名管道!
4.接口
4.1open、close是磁盤級的接口,不能用他,而是專用的接口pipe(系統接口)
int pipe(int pipefd[2]);//pipdfd[2] 輸出型參數
//pipefd[0] 只讀下標
//pipefd[1] 只寫下標
4.2代碼實現管道
// 實現系統間通信
#include <iostream>
#include <unistd.h>
#include <string>
#include <cstdlib> //stdlib.h C++風格的頭文件寫法
#include <sys/types.h>
#include <sys/wait.h>using namespace std;#defind N 2
#define NUM 1024
void Writer(int wfd)
{string s = "hello,i am child!";pid_t self = getpid();int number = 0;char buffer[NUM];while (true){//構建字符串buffer[0] = 0; // 字符串清空;只是為了提醒閱讀代碼的人,我把這個數組當作字符串了snprintf(buffer, sizeof(buffer), "%s-%d-%d", s.c_str(), self, number);//cout<<buffer<<endl; //發送給父進程write(wfd,buffer,strlen(buffer));sleep(1);}
}
void Reader(int rfd)
{char buffer[NUM];while(true){buffer[0]=0;ssize_t n=read(rfd,buffer,sizeof(buffer));if(n>0){buffer[n]=0; //0=='\0'cout<<"father get a message["<<getpid()<<"]#"<<buffer<<endl;}}
}
int main()
{int pipefd[N] = {0};int n = pipe(pipefd); // 0下標是讀;1下標是寫if (n < 0){perror("pipe");return 1;}// cout<<"pipefd[0]:"<<pipefd[0]<<",pipefd[1]:"<<pipefd[1]<<endl;// 子進程寫入,父進程讀pid_t id = fork();if (id < 0)return 2;if (id == 0){// 子進程close(pipefd[0]);Writer(pipefd[1]);close(pipefd[1]);exit(0);}close(pipefd[1]);Reader(pipefd[1]);pid_t rid = waitpid(id, nullptr, 0);if (rid < 0)return 3;close(pipefd[0]);return 0;
}
4.3根據以上代碼可以得到管道的本質:將上層緩沖區的數據拷貝到系統緩沖區,再將數據拷貝給上層緩沖區;
4.4進程間通信的本質:需要讓不同的進程,看到同一份資源——但是是多執行流共享的,難免會出現訪問沖突的問題;
5.管道的特征:五點
5.1具有血緣關系的進程進行進程間通信;
5.2管道只能單向通信;
5.3父子進程是會進程協同的,同步與互斥的——保護管道文件的數據安全;
5.4管道是面向字節流的;
5.5管道是基于文件的,文件的生命周期是跟隨進程的(父子進程退出,管道會被操作系統回收)
6.管道的4種情況:
6.0管道是有大小的,Linux版本2.6.11之前是4KB,之后是64KB;(為了保障傳輸數據的整體性,有序性,定義了一個pipe_buf 大小是4KB,當寫入系統緩沖區的數據小于4KB時,必須要等子進程傳輸完數據,父進程才能讀——這個工作就是讀取的原子性問題)
6.1讀寫端正常,管道如果為空,讀端就要阻塞;
6.2讀寫端正常,管道如果被寫滿,寫端就要阻塞
6.3讀端正常,寫端關閉,讀端就會讀到0,表明讀到了文件(pipe)結尾,不會被阻塞;一直在讀;
6.4讀端關閉,寫端正常,操作系統就要殺掉這個正在寫入的進程;如何干掉?答:操作系統通過信號(是幾號信號? 答:13號)干掉(操作系統是不會做低效,浪費等類似的工作的,如果做了,那就是操作系統的bug)
ulimit -a //查看系統對一些非常重要的資源的限制
7.管道的應用場景
7.1這個管道和我們以前學的知識,哪些是有關系的?
Linux shell 指令 :cat test.txt | head -10 |tail -5? 這個語句的實現,就是shell命令+進程替換+管道實現的;
7.2使用管道實現一個簡易版本的進程池;(請看下一節)