【Linux】程序間通訊
1.程序間通訊的目的
- 資料傳輸,一個程序需要將它的資料傳送給另一個程序
- 資源共享:多個程序之間共享同樣的資源
- 通知事件:一個程序需要向另一個或一組程序傳送資訊,通知發生了某種事件(如程序終止時要通知父程序)
- 程序控制:有些程序希望完全控制另一個程序的執行(如Dubug程序),此時控制程序希望能夠攔截另一個程序的所有陷入與異常,並能夠及時知道它的狀態改變。
2.程序間通訊的本質
不同的程序共同管理一份共同的資源(檔案)
3.程序通訊的不同方式
-
管道
-
System V IPC a.訊息佇列 b.共享記憶體 c.訊號量
-
POSIX IPC a.訊息佇列 b.共享記憶體 c.訊號量 d.互斥量 e.條件變數 f.讀寫鎖
4.管道
1)管道基本理解
我們把**從一個程序連線到另一個程序的一個數據流稱為一個“管道”,它是一塊檔案或共享區,一個往裡面寫資料,一個拿資料,是UNIX中最古老的程序間通訊的形式。**其實通俗來說,就像是現實中的水管道,一方將水接入管道,一方從管道接出使用水,所以不難理解,管道是單向的,先進先出的,它將一個程式的輸入與另一個程式的輸出連線起來,資料被一個程序讀取後,該資料便被刪除。
2)管道的分類
- 匿名管道: 只能完成具有血緣關係間的程序(或者具有公共祖先的程序)。
- 命名管道:允許同一個程序中任意兩個程序的通訊。
3)匿名管道(PIPE)
- 建立匿名管道函式pipe
#include <unistd.h> 功能:建立一無名管道 原型:`int pipe(int fd[2])` 引數: fd:檔案描述符陣列,其中fd[0]表示讀端,fd[1]表示寫端。結合Linux一切皆檔案可以理解為管道是一個檔案,使用者在這個檔案讀寫資料(讀寫核心緩衝區)。 返回值:成功返回0,失敗返回錯誤程式碼。
我們建立一個管道,從鍵盤讀取資料,寫入管道,讀取管道,寫到螢幕。
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<string.h> #define NUM 128 int main() { int fds[2];//檔案描述符陣列,fd[0]表示讀端,fd[1]表示寫端。 int p = pipe(fds); //建立管道 if( p < 0 ){ printf("建立管道失敗\n"); exit(1); } //從鍵盤輸入讀取資料 while(1){ char buf[NUM] = {0}; ssize_t s1 = read(0,buf,sizeof(buf)-1); if(s1<0){ perror("read"); exit(1); } buf[s1] = 0; //寫進管道 write(fds[1],buf,strlen(buf)); //從管道中讀取 ssize_t s2 =read(fds[0],buf,sizeof(buf)-1); buf[s2] = 0; //輸出到螢幕上 write(1,buf,strlen(buf)); } return 0; }
結果演示:
-
站在檔案描述符號-深度理解管道
-
父程序呼叫pipe()開闢一個管道,使用open得到兩個檔案描述符號,一個指向管道的讀端,一個指向管道的寫端。
-
父程序呼叫fork()建立子程序,得到的子程序也有兩個檔案描述符指向這個管道的兩端。
-
父程序關閉寫端,子程序關閉讀端,子程序往管道中寫入資料,父程序從中讀出,這樣就實現了程序間通訊。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fds[2];
int p = pipe(fds);
if(p < 0)
{
perror("pipe");
return 1;
}
pid_t id =fork();
if(id == 0)//子程序寫
{
close(fds[0]);
write(fds[1],"haha",4);
close(fds[1]);
exit(EXIT_SUCCESS);
}
else if (id > 0){
//父程序
close(fds[1]);
char buf[12]={0};
read(fds[0],buf,12);
printf("buf=%s\n",buf);
}
return 0;
}
-
匿名管道的四種情況 a.當管道的寫端關閉後,這時讀取輸的的程序把管道中的資料讀完後,read就返回0,相當於讀到檔案末尾。 b.寫端沒有關閉當讀取資料程序的速度大於寫資料程序的速度時,在管道內的資料被讀取完後,read會阻塞,直到管道中有新的資料。 c.當管道的讀端被關閉後,若還寫資料,這時程序會受到SIGPIPE訊號,導致程序異常終止。 d.當管道讀端沒有關閉,寫的速度大於讀的速度,在管道寫滿後,write被阻塞直到管道有空位置。
-
特點 管道提供流式服務(面向位元組流),半雙工,核心對管道操作進行同步與互斥。
4)命名管道(FIFO)
- 概念:命名管道不同於匿名管道之處在於命名管道是一個裝置檔案,它真真正正的存在於硬體之上,存在於檔案系統中。而匿名管道存在於記憶體或者核心中,它對檔案系統不可見,也因為這樣,命名管道可以完成任意兩個程序間的通訊。命名管道的建立有一個路徑名path與之關聯,以命名管道檔案的形式儲存在檔案系統中,所以只要訪問該路徑,就能夠通過命名管道互相通訊。FIFO也是先進先出的規則。
- 建立管道:
a.在shell命令上可以通過
mkfifo 檔名
建立命名管道檔案 b.在程式內通過mkfifo函式建立命名管道檔案
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *filename,mode_t mode)
- 匿名管道與命名管道的區別 a.匿名管道由pipe函式建立並開啟 b.命名管道由mkfifo函式建立,開啟open c.FIFO(命名管道)與pipe(匿名管道)之間唯一的區別在它們創建於開啟的方式不同,一旦這些工作完成,它們具有相同的語義 d. 對於命名管道檔案系統中的路徑名是全域性的,各個程序都可以訪問,因此可以用檔案系統的路徑名來標識一個IPC通訊。 e.命名管道是真實存在於硬碟上的,匿名管道是存在於記憶體中的特殊檔案。
- 使用命名管道完成server&client通訊 a.makefile檔案
.PHONY:all
all:client server
client:client.c
gcc -o [email protected] $^
server:server.c
gcc -o [email protected] $^
.PHONY:clean
clean:
rm -rf server client
b.server.c檔案
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
if(mkfifo("mypipe",0644)<0){
perror("mkfifo");
exit(EXIT_FAILURE);
}
int fd = open("mypipe",O_RDONLY);
if(fd <0 ){
perror("open");
}
char buf[1024];
while(1){
buf[0] = 0;
printf("Please wait......\n");
ssize_t s =read(fd,buf,sizeof(buf)-1);
if(s > 0){
buf[s-1] = 0;
printf("client say# %s\n",buf);
}else if(s == 0){
printf("client quit\n");
exit(EXIT_SUCCESS);
}
}
close(fd);
return 0;
}
c.client.c檔案
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
int fd = open("mypipe",O_WRONLY);
char buf[1024];
while(1){
buf[0]=0;
printf("Please Enter# ");
fflush(stdout);
ssize_t s =read(0,buf,sizeof(buf)-1);
if(s >0 ){
buf[s] = 0;
write(fd,buf,strlen(buf));
}else if(s <= 0)
{
perror("read");
exit(1);
}
}
close(fd);
return 0;
}
程式碼執行結果:(先開啟server埠,再開啟一個client埠)
5.訊息佇列
6.共享記憶體
1)基本概念
共享記憶體是最快的IPC形式,一旦這樣的記憶體對映到共享它的程序的地址空間,這些程序間資料傳遞不再涉及到核心,也就是說程序不再通過執行核心的系統呼叫來傳遞彼此的資料,所以共享記憶體沒有互斥與同步機制(使用者自主完成)。生命週期隨核心。
2)共享記憶體示意圖
3)共享記憶體資料結構
4)共享記憶體函式
a.shmget函式
功能:用來建立共享記憶體
原型:int shmget(key_t key,size_t size,int shmflg);
引數:
key:這個共享記憶體段名字
size:共享記憶體大小 (OS將記憶體分為頁size是頁的整數倍)
shmflg:由9個許可權標誌志構成,它們的⽤法和建立⽂件時使⽤的mode模式標誌是⼀樣的
返回值:成功返回⼀個⾮負整數,即該共享記憶體段的標識碼;失敗返回-1
b.shmat函式
功能:將共享記憶體段連線到程序地址空間
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
引數
shmid: 共享記憶體標識
shmaddr:指定連線的地址
shmflg:它的兩個可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回⼀個指標,指向共享記憶體第⼀個節;失敗返回-1
說明:
shmaddr為NULL,核⼼⾃動選擇⼀個地址
shmaddr不為NULL且shmflg⽆SHM_RND標記,則以shmaddr為連線地址。
shmaddr不為NULL且shmflg設定了SHM_RND標記,則連線的地址會⾃動向下調整為SHMLBA的整數倍。公式:s
hmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表⽰連線操作⽤來只讀共享記憶體
c.shmdt函式
功能:將共享記憶體段與當前程序脫離
原型
int shmdt(const void *shmaddr);
引數
shmaddr: 由shmat所返回的指標
返回值:成功返回0;失敗返回-1
注意:將共享記憶體段與當前程序脫離不等於刪除共享記憶體段
d.shmctl函式
功能:⽤於控制共享記憶體
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
引數
shmid:由shmget返回的共享記憶體標識碼
cmd:將要採取的動作(有三個可取值)
buf:指向⼀個儲存著共享記憶體的模式狀態和訪問許可權的資料結構
返回值:成功返回0;失敗返回-1
5)例項程式碼
makefile檔案
.PHONY:all
all:server client
client:client.c comm.c
gcc -o [email protected] $^
server:server.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -rf client server
comm.h檔案
#ifndef __COMM_H__
#define __COMM_H__
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
int creatShm(int size);
int destroyShm(int shmid);
int getShm(int size);
#endif
comm.c檔案
#include"comm.h"
static int commShm(int size,int flags)
{
key_t _key = ftok(PATHNAME,PROJ_ID);
if(_key < 0){
perror("ftok");
return -1;
}
int shmid = 0;
if((shmid = shmget(_key,size,flags)) < 0){
perror("shmget");
return -3;
}
return shmid;
}
int destroyShm(int shmid)
{
if(shmctl(shmid,IPC_RMID,NULL)< 0){
perror("shmctl");
return -1212;
}
return 0;
}
int creatShm(int size)
{
return commShm(size,IPC_CREAT|IPC_EXCL|0666);
}
int getShm(int size)
{
return commShm(size,IPC_CREAT);
}
server.c檔案
#include"comm.h"
int main()
{
int shmid = creatShm(4096);
char *addr =shmat(shmid,NULL,0);
sleep(2);
int i =0;
while(i++<26){
printf("client# %s\n",addr);
sleep(1);
}
shmdt(addr);
sleep(2);
destroyShm(shmid);
return 0;
}
client.c檔案
#include"comm.h"
int main()
{
int shmid =getShm(4096);
sleep(1);
char *addr =shmat(shmid,NULL,0);
sleep(2);
int i = 0 ;
while(i<26)
{
addr[i] = 'A' +i;
i++;
addr[i] = 0;
sleep(1);
}
shmdt(addr);
sleep(2);
return 0;
}