爲什麼進程間要通信
- 數據傳輸 進程需要把數據傳遞給其他進程
- 資源共享 多個進程之間有時需要共享一份資源
- 通知事件 比如子進程要把自己的退出信息交給父進程
- 進程控制 比如Debug進程需要控制待調試進程的所有陷入和異常 ,
並且知道他現在的狀態。
因爲每個進程本來是擁有自己的虛擬地址空間 用頁表映射到每個進程自己的物理內存上的 每個進程自己視爲獨佔系統資源 各個進程的地址空間 爲了安全期間並不關聯 相對封閉 所以需要一些機制來保證進程間能夠通信
學習進程間通信必須瞭解臨界資源的概念
1. 臨界資源:把多個進程(執行流)能夠看到訪問的共同資源叫臨界資源,管道是一種臨界資源。
2 . 臨界區:把訪問臨界資源的代碼叫臨界區。
進程間通訊的本質是二進程共享資源(不同進程看到公共資源),一進程以讀方式一進程以寫方式打開同一份文件
linux 下一切接文件,管道也是一種特殊的文件。
3. 互斥:在任意一個時刻只能有一個“人”訪問臨界資源,採用原子性原則訪問。
4. 原子性操作:狹義的原子性操作指該操作的彙編代碼只有一句(該操作不可分),廣義的原子操作指一個操作正在執行 時決不會被切出。原子操作要麼做了,要麼沒做,不存在正在做。
System V 的進程間通信機制有三種
1. 消息隊列
2. 信號量
3. 共享內存
這四個機制的共同點是 讓兩個或幾個要通信的進程 能夠按照一定的規則訪問同一內核內存空間 該資源也叫臨界資源。 臨界資源架起了兩個進程之間的橋樑 。
1. 管道
管道是最古老的unix進程間通信方式 大概的思想是 讓兩個進程打開並訪問到同一個文件 看到同一份內存 一個進程往文件裏面寫數據 另一個進程從文件裏面讀數據
這個文件於是就想鏈接兩個進程的管道一樣。
1.1匿名管道
匿名管道的創建
#include <unistd.h>
int pipe(int pipefd[2]);
int pipe2(int pipefd[2], int flags);
系統調用pipe 的參數是一個文件描述符數組 有兩個數組元素
f[0] 表示文件的讀端 f[1]表示文件的寫端 返回值 成功返回0 失敗返回 -1 並置errno 錯誤代碼。
flag 表示 讀寫方式
pipe()系統調用默認讀寫方式阻塞
具體怎麼實現通信呢?
這張圖做了說明 父進程調用pipe() 創建了一個匿名管道(在內核空間)
並且默認打開這個管道文件的讀端寫端 f[0] f[1]文件描述符
之後 fork() 創建子進程 子進程繼承了父進程的文件描述符表 故也默認打開f[0] f[1] 兩個文件描述符
之後父進程關閉讀端文件描述符 子進程關閉寫端文件描述符 父進程往文件裏寫 子進程從文件裏讀 。 這樣父子進程實現了通信
這是對於想要父寫 子讀的 也可以子寫父讀 父關f[1] 子關f[0]
代碼實現 :
#include<stdio.h>
#include<sys/types.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
if(-1 == ret){
printf(" Create pipe error!, error code id %d",ret);
}
pid_t pid = fork();
if(pid < 0){
perror("fork");
}else if(0 == pid ){
//Child
close(_pipe[0]);
char* masg = NULL;
int i = 0;
while( i < 10){
masg = "I am Child!";
write(_pipe[1],masg,strlen(masg) + 1);
sleep(1);
}
}
else if( 0 < pid){
//Father
close(_pipe[1]);
char masg_buf[20];
int i = 0;
while( i < 10){
memset(masg_buf, '\0',sizeof(masg_buf));
read(_pipe[0],masg_buf,sizeof(masg_buf));
printf("Child said :%s\n",masg_buf);
+`````
i;
}
}
return 0;
}
從內核角度看進程間通信
兩個進程的進程控制塊 task_struct——-> file_struct———–>file 結構體——–>dentry 結構體——–> inode結構體
同時指向同一個inode結構體 一個file結構體的f_op ——>file_operators 爲讀 一個爲寫
這一段看不懂請看:http://blog.csdn.net/x__016meliorem/article/details/78825904
管道的讀寫規則:
和 pipe2() 函數的flag設置有關
沒有數據可讀時:
1. O_NONBLOCK diabale read()調用阻塞 進程暫停執行 直到有數據寫進去才繼續開始讀
2. O_NONBLOCK enable read調用返回-1, errno值設置爲 EAGAIN
當管道被寫滿的時候:
1. O_NONBLOCK disanble write()調用阻塞 進程暫停執行 直到有數據被讀走纔開始繼續寫。
2. O_NONBLOCK enable write()調用返回-1, errno至設置爲 EAGAIN。
如果所有管道的對應寫端文件描述符被關閉 ,read()返回0。
如果所有管道的對應讀端文件描述符被關閉 , write()操作會產生信號SIGPIPE 該信號可能導致write()進程退出。(因爲沒有人讀 寫是沒有意義的)
當寫入數據量不大的 PIPE_BUF時 linux保證寫入的原子操作。
當寫入數據量很大的 PIPE_BUF時 linux保不證寫入的原子操作。
管道的特點:
1. 生命週期隨進程 當進程退出時 管道的內存空間被釋放 。
2. 只用於有親源關係的進程之間通信 , 因爲有相同親源的進程從共同祖先那裏繼承了一份文件描述符表 表中有同一管道的讀寫 端文件描述符
這樣才能讓不同進程看到同一管道
3. 半雙工 數據只朝一個方向流動 要實現雙向通信 就必須建立兩個管道
4 管道通信是面向字節流的 一次寫的數據不一定要一次都出來 ,一次讀的數據不一定要一次寫進去。
5. 內核對管道的操作提供同步與互斥保證
什麼是同步與互斥
同步:多人多進程要完成最終目標就一定要按照一定次序協同完成 , 否則無法完成或效率底下。
互斥:任意時刻只能有一個“人” 訪問臨界資源 採用原子操作訪問。
管道的同步與互斥是指:
同步:寫滿了就不再寫 讀完了就不再讀
互斥:不可以兩個進程同時讀 或者寫管道
命名管道fifo
如果想在兩個並不相關的進程之間實現通信 就得使用FIFO命名管道
FIFO的意思是先進先出 mkfifo創建的文件 內核提供機制讓 先寫入的數據被先讀到。
命名管道是一種特殊類型的文件
創建命名管道的函數
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
該函數的第一個參數是 要創建的命名管道文件的路徑名 第二個參數mode是擁有者,所屬組 , 和其他三種“人” 的權限。
命名管道的實質是創建一個文件, 兩個進程以open方式打開這個文件 一個寫一個讀
命名管道與之前的匿名管道的區別
只在於 創建方式不同 可以在非親元關係的進程
根本都是內核提供一塊內存讓 兩個進程通過文件描述符 讀寫公共資源
在內核角度都是讓兩個進程的file結構體指向同一個inode節點
命名管道實現 client —–server 通信
client.c
#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/types.h>
int main()
{
int ret = mkfifo("./myfifo", 0666);
if(ret < 0){
perror("mkfifo");
return 1;
}
int fd = open("./myfifo", O_RDONLY);
if(fd < 0){
perror("open");
return 2;
}
char buf[1024] = {0};
while(1){
ssize_t read_size = read(fd, buf, sizeof(buf) - 1);
if(read_size > 0){
buf[read_size - 1] = 0;
printf("client say : %s\n", buf);
}
else if(read_size == 0){
printf("client quit!, quit now\n");
return 4;
}else if(read_size < 0){
perror("read");
return 3;
}
}
close(fd);
return 0;
}
server.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
int ret = mkfifo("./myfifo", 0666);
if(ret == 0){
perror("mkfifo");
return 1;
}
int fd = open("./myfifo", O_WRONLY);
if(fd < 0){
perror("open");
return 2;
}
while(1){
char buf[1024] = {0};
printf("plase enter!:");
fflush(stdout);
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if(read_size > 0){
buf[read_size] = 0;
write(fd, buf, strlen(buf));
}
if(read_size <= 0){
perror("read");
return 3;
}
}
close(fd);
return 0;
}