進程間通信
- 由於進程之間在執行時,是完全獨立執行的,所有,進程間如果需要進行通信,就需要一塊共享的區域,我們可以通過創建一個多個進程共享的區域,將所需要共享的數據放在這個共享區當中,當進程間需要通信的時候,寫數據的進程將數據寫入該區域,需要讀數據的進程將數據從該區域中讀取出來。
##進程間通信的分類- 管道
- System V進程間通信
- POSIX IPC
##管道- 管道是進程間通信的一種手段,通過在內核中創建一個共享區域,來實現進程間的通信,注意,所在區域是 內核,內核,內核
###管道應該如何理解呢 ?- 管道在邏輯結構上就是一根管道,你可以理解爲水管道,這根管道有兩端,一個是讀端,一個是寫端
- 管道的設計是半雙工的,也就是,一個進程在對管道進行寫操作的時候,其他進程只能讀取,這個寫的進程佔據了這個管道的寫端,除非這個進程關閉這個管道的寫端,否則其他進程不能對這個管道進行寫入操作,讀端同理,也是隻能有一個進程進行讀取,直到這個進程關閉讀端。那麼爲什麼不能多個進程同時對這個管道進行讀取或者寫入呢? 因爲如果多個進程對管道進行寫入的話,那管道里存儲的數據順序就會亂,因爲操作系統在進行進程調度的時候,每一個進程都可能會被隨時切走和切入,這樣的話,如果多個進程同時擁有同一個管道的讀端或寫端,就會出現讀寫混亂.大概可以理解爲,一根管道同時只能被兩個進程所使用,如果有多個進程同時使用一根管道的話,就會出現讀寫混亂的情況。
- 在使用管道的時候,如果進程只進行寫操作,那就關閉讀操作。反之亦然。
- 在linux中,一切皆文件,既然如此,那管道是就是文件,那進程在打開文件的時候,就會在PCB中的file_struct記錄文件的文件描述符,後面對管道的一切操作都是對文件描述符的操作。(如果你不是很懂文件描述符是什麼東西,我還有一個博客是介紹文件描述符的,可以去看看)
如何開闢一個匿名管道以及使用這個匿名管道
- 在linux中,系統已經提供了管道的系統調用 ,下面的這個是匿名管道,在這個後面還有命名管道
- 匿名管道只能進行有親緣關係的進程進行通信,常用於父子進程間的通信
- 命名管道可以進行任意進程間的通信
#include<unistd.h>
int pipe(fd[2]);//匿名管道
- 這裏面的參數 fd[0] 是管道的讀端 fd[1]是管道的寫端。我們等會對管道進行操作的時候,就是通過這兩個文件描述符進行操作的。返回值如果是0,就表示管道申請成功。如果失敗,返回失敗碼
- 在下面的例子中,我用兩個進程的方式,進行了一個進程間管道通信的演示,父進程讀取,子進程寫數據,最後,父進程將數據打印在屏幕上
- 這個代碼是匿名管道的創建方法,匿名管道只能進行有親緣關係的進程進行通信,常用於父子進程
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<error.h>
int main()
{
if(pipe(fd) != 0)//創建管道
{perror("pipe create");}
int id = fork();//創建進程
if(id > 0)//父進程
{
char buf[100];
close(fd[1]);
sleep(5);
ssize_t s = read(fd[0],buf,sizeof(buf));//從讀端讀取數據
if(s < 0)
{perror("read");}
buf[s] = 0;//將讀到的最後一個字符的後一個字符設置爲 \0,方便打印
printf("%s\n",buf);
}
else if( id == 0 )//子進程
{
int i = 0;
const char *msg = "hello world\n";
close(fd[0]);
while(1)
{
write(fd[1],msg,strlen(msg));//向寫端寫數據
sleep(1);
i++;
if(i > 5)
{break;}
}
}
else//創建進程失敗
{perror("fork create");}
return 0;
}
- 以上便是匿名管道的創建和使用了
如何開闢一個命名管道,並使用這個命名管道進行進程間的通信
- 命名管道相比於匿名管道,更像一個文件,命名管道通過在目錄下創建一個管道文件,通過這個管道文件進行通信,而不是匿名管道那樣,在內核中進行管道的創建。
相關函數
int fifo(const char *filename,mode_t mode);
- 如果使用命名管道,需要注意的是mode的值,權限mode決定了這個管道可以進行寫和讀。
- 爲了驗證管道可以進行不同非親緣進程間的通信,我們可以採用兩個程序,一個負責向管道里寫,一個負責讀,然後打印出來,這樣的程序,來驗證進程間的通信。
- 命名管道在打卡的時候,進程必須知道,要打開的管道文件的路徑,然後才能打開這個管道文件
#include<stdio.h> //這個進程負責讀取管道的數據
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#define FIFO "./myfifo"
int main()
{
umask(0);
if(mkfifo(FIFO,0644) < 0)
{
perror("mkfifo");
return 1;
}
char buf[1024];
int fd = open(FIFO,O_RDONLY);
if(fd > 0)
{
while(1)
{
ssize_t s = read(fd, buf, sizeof(buf) - 1);
if(s > 0)
{
buf[s]= 0;
printf("client->server : %s",buf);
}else{
printf("read error\n");
exit(1);
}
}
}
else{
perror("open file");
return 1;
}
close(fd);
return 0;
}
#include<stdio.h>//這個進程負責往管道里面寫數據
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#define FIFO "./myfifo"
int main()
{
char buf[1024];
int fd = open(FIFO,O_WRONLY);
if(fd < 0)
{
perror("open file");
}
while(1)
{
printf("Please Enter# ");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf) - 1);
if(s > 0)
{
buf[s] = 0;
write(fd,buf,s);
}
else{
break;
}
}
close(fd);
return 0;
}
管道的四種情況
- 讀端不讀,但是寫端已經把緩衝區寫滿了
這種情況的話,由於寫端已經把緩衝區寫滿了,但是讀端不讀,管道就會阻塞,寫端不能進行寫入,直到讀端開始將數據讀走,才能繼續進行寫入
- 寫端不寫,但是讀端已經把緩衝區讀完了
如果讀端已經將緩衝區讀完了,緩衝區裏已經沒有數據了,讀端就會阻塞,直到寫端往緩衝區裏寫數據,讀端才能繼續讀
- 讀端不讀,同時關閉了讀端
讀端如果不讀數據,同時關閉了讀端的話,就說明,寫端寫入的數據不會被讀端讀走,那麼寫端所寫的數據就是沒有意義的,所以,操作系統對此的處理就是,結束寫進程
- 寫端不寫,同時關閉了寫端
寫端不寫數據,但是關閉了寫端,由於已經沒有人會給寫端裏寫數據了,讀端在讀完緩衝區裏的所有數據後,讀進程就會被操作系統殺掉
共享內存
- 共享內存就和他的名字一樣,是一個被多個進程所共享的一個內存區域,共享內存所在的區域是用戶態,不需要像管道一樣,如果需要數據的訪問,就必須進行用戶態和內核態的切換,提高了效率。
共享內存存在的時間,是不受進程結束限制的,即使一塊共享內存沒有一個進程掛接,但是依舊是存在於內存中的數據區的,所以,在使用完共享內存之後,一定要記得釋放共享內存。
- 共享內存在使用的時候,需要進程顯示的掛接共享內存,才能進行共享內存的使用,當共享內存的掛接數不爲0時,是不能被釋放的。一塊共享內存可以被多個進程所掛接,一個進程也可以掛接多個共享內存
- 共享內存在開闢的時候,需要一個唯一的共享內存標識,記住,這個共享內存標識,是多少不重要,重要的是,必須是唯一的,要產生這個唯一的共享內存標識, 就需要調用一個共享內存標識生成方法 ---- ftok ,通過這個方法,可以產生一個這樣的唯一的標識,然後用這個標識,去向操作系統申請共享內存
共享內存接口展示
int shmget(key_t key,size_t size,int shmflg);//申請共享內存
//key :共享內存段的名字,必須是唯一的
//size:你所要開闢的共享內存的大小
//shmflg :權限,具體是啥,可以查看man手冊
void *shmat(int shmid, const *shmaddr, int shmflg);//掛接共享內存
//shmid :在進行shmget的時候,返回的值就是id
//shmaddr:連接指定的地址,想讓核心自動選擇的話,設置爲NULL
//shmflg: 也是權限 兩個值 SHM_RND和SHM_RDONLY
int shmdt(const void *shmaddr);//刪除掛接共享內存
//shmaddr:由shmat所返回的指針
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//刪除共享內存
//shmid:有shmget所獲得的標識
//cmd:要採取的動作 三個 有:IPC_STAT IPC_SET IPC_RMID,最後一個是刪除共享內存段
//指向一個保存着共享內存模式狀態和訪問權限的數據結構,一般是NULL
- 爲了實現兩個不相干的進程間進行通信,我和前面一樣,通過創建兩個進程,一個負責向共享內存裏寫數據,一個負責從共享內存裏讀數據,通過這種形式,實現共享內存的進行間通信
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATH_NAME "/tmp"
#define PROJ_ID 0x6666
int main()
{
int ret;
key_t key = ftok(PATH_NAME,PROJ_ID);//ftok函數通過路徑名加一個proj_id來生成一個唯一的共享內存標識符 key
if(key < 0)
{
perror("ftok");
ret = 1;
return ret;
}
int shm_id = shmget(key,4096,IPC_CREAT);//創建一個共享內存,如果這個共享內存已經存在了,就返回這個共享內存,如果不存在,就返回錯誤
if(shm_id == -1)
{
perror("shmget");
ret = 2;
return ret;
}
void *str = shmat(shm_id,NULL,0);//掛接共享內存
if((signed long long )str < 0)
{
perror("shmat");
ret = 3;
return ret;
}
int i;
for(i = 0;i < 26;i++)
{
((char *)str)[i] = 'A' + i;//向這個共享內存裏寫數據
sleep(1);
}
return 0;
}
#include<unistd.h>
#include<string.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATH_NAME "/tmp"
#define PROJ_ID 0x6666
int main()
{
int ret;
key_t key = ftok(PATH_NAME,PROJ_ID);
if(key < 0)
{
perror("ftok");
ret = 1;
return ret;
}
int shm_id = shmget(key,4096,IPC_CREAT|IPC_EXCL);//創建共享內存,如果這個共享內存不存在就創建它,然後返回
if(shm_id == -1)
{
perror("shmget");
ret = 2;
return ret;
}
void *str = shmat(shm_id,NULL,0);//掛接共享內存
if((signed long long )str < 0)
{
perror("shmat");
ret = 3;
return ret;
}
int i = 0;
sleep(3);
for(i = 0;i < 26; i++)
{
printf("cilent -> server:%s\n",(char *)str);//輸出這個共享內存裏的數據
sleep(1);
}
shmdt(str);
shmctl(shm_id,IPC_RMID,NULL);
}
以上就是進程間通信的一部分東西了,包括匿名管道,命名管道,共享內存