天天看點

程序間通信 —— IPC

程序間通信 —— IPC

目錄:

  • 1.管道
  • 2.消息隊列
  • 3.共享記憶體
  • 4.信号量

為什麼要有程序間通信?

每個程序各自有不同的使用者位址空間,任何一個程序的全局變量在另一個程序中都看不到,是以程序之間要交換資料必須通過核心,在核心中開辟一塊緩沖區,程序 1 把資料從使用者空間拷到核心緩沖區,程序 2 再從核心緩沖區把資料讀走,核心提供的這種機制稱為程序間通信。

程序間通信的目的:

  1. 資料傳遞:一個程序将資料傳送到另一個程序
  2. 資源共享:多個程序共享同樣的資源
  3. 事件通知:一個程序需要向另外一個程序發送消息,通知他們發生了某事件
1.管道

匿名管道 由pipe函數建立:

#include <unistd.h>
int pipe(int fileded[]);
           

調用 pipe 函數時在核心中開辟一塊緩沖區(稱為管道)用于通信,它有一個讀端一個寫端,然後通過 filedes 參數傳出給使用者程式兩個檔案描述符,filedes[0]指向管道的讀端,filedes[1]指向管道的寫端(很好記,就像 0 是标準輸入 1是标準輸出一樣)。

是以管道在使用者程式看起來就像一個打開的檔案,通過read(filedes[0]);或者 write(filedes[1]);向這個檔案讀寫資料其實是在讀寫核心緩沖區。

pipe 函數調用成功傳回 0,調用失敗傳回-1。

程式間通信 —— IPC

管道的最典型的用途就是為兩個不同的程序(一個是父程序,一個是子程序)提供程序間通信的手段。首先由一個程序建立一個管道後調用fork建立一個子程序。接着,父程序關閉這個管道的讀端,子程序關閉這個管道的寫端。這就在父子之間提供了一個單向的資料流。

程式間通信 —— IPC

例:建立管道,進行父子間通信

程式間通信 —— IPC

使用管道需要注意以下4種特殊情況:

1、如果所有指向管道寫端的檔案描述符都關閉了,而仍然有程序從管道的讀端讀資料,那麼管道中剩餘的資料都被取走後,再次read時會傳回0,就像讀到檔案末尾一樣。

2、如果有指向管道寫端的檔案描述符沒關閉,而持有管道寫端的程序也不往管道

中寫資料,這時有程序從管道中讀資料時,管道中的資料被讀取之後,就會再次read阻塞,直到管道中有資料可讀才讀取資料并傳回。

3、如果所有指向管道讀端的檔案描述符都關閉了,這時有程序向管道的寫端write,那麼該程序會受到SIGPIPE信号。通常會導緻程序異常終止。

4、如果有指向管道讀端的檔案描述符沒關閉,而持有管道讀端的程序也沒有從管道中讀資料,這時有程序向管道的寫端寫資料,那麼在管道被寫滿時會再次write阻塞,直到管道中有空位置了才寫入資料并傳回。

管道的限制:

  • 資料的流動方向是單向的
  • 隻能在有共同祖先的程序之間通信

也有一種特殊的管道稱為FIFO,也叫命名管道,它也能用于無關聯程序間的通信。

程式間通信 —— IPC

FIFO檔案在磁盤上沒有資料塊,僅用來辨別核心中的一條通道,各程序可以打開這個檔案進行read/write,實際上是在讀寫核心通道。

例子:從鍵盤讀取資料,寫入管道,讀取管道,寫到螢幕

程式間通信 —— IPC
程式間通信 —— IPC
程式間通信 —— IPC
程式間通信 —— IPC
2.消息隊列

消息隊列是兩個程序之間傳遞資料的一種簡單有效的方式。每個資料塊都有一個特定的類型,接收方可以根據類型來有選擇的接收資料。而不一定像管道和命名管道那樣必須以先進先出的方式接收資料。

建立消息隊列:msgget()

int  msgget(key_t key, int msgflg); 
參數: 
    key:某個消息隊列的名字
    msgflg:  / IPC_CREAT | 
傳回值:
    成功傳回該消息隊列的辨別碼;失敗傳回-
           
key_t ftok(const char* pathname, int proj id)
         id: 低位不能是
一般寫為:msgget(ftok(".", 'a'), IPC_CREAT|)自動生成key
           
程式間通信 —— IPC

往消息隊列裡發送資料:msgsnd()

struct msgbuf
{
      long channel; 消息類型(通道号),必須>=
      char mtext[];//寫上自己的消息内容
}

int msgsnd(int msqid, //msgget的傳回值
            const void *msgp, //要發送的消息在哪裡
            size_t msgsz, //消息的位元組數,不包括channel的大小
            int magflg);//IPC_NOWAIT表示隊列滿,不等待,傳回EAGAIN錯誤//0
傳回值:
    成功傳回,失敗傳回-
           

從消息隊列中取資料:msgrcv()

ssize_t  msgrcv(int msqid, //id
                void *msgp, //取出來的消息放在這裡
                size_t msgsz, //裝消息的地方的大小,不包括類型
                long msgtyp, //取哪個類型的消息
                int msgflg);//0

傳回值:
     成功傳回實際放到接收緩沖區裡去的字元個數,失敗傳回-
           

删除消息隊列:msgctl()

int magctl(int msqid,//由msgget函數傳回
           int cmd,//IPC_RMID(删除消息隊列)
           struct msqid_ds *buf); //0

傳回值: 成功傳回,失敗傳回-
           

1、系統中最多能建立多少個消息隊列?

cat /proc/sys/kernel/msgmni

2、一條消息最多能夠裝多少位元組?

cat /proc/sys/kernel/msgmax

/3、一個消息隊列中所有的消息的總位元組數是多少?

cat /proc/sys/kernel/msgmnb

ipcs :檢視消息隊列、共享記憶體、信号量全部的

檢視目前已經存在的消息隊列:pcs -q

删除消息隊列:ipcrm -q + key

消息隊列的缺點:

  1. 系統能建立的隊列個數有限
  2. 每個消息能裝的内容有限
  3. 一個消息隊列中所有消息的總位元組數也有限
  4. 每一次通路的時候需要調用系統的函數,意味着要從使用者空間切換到核心空間,發送完畢後,再從核心空間切換到使用者空間,開銷比較大。

例子:用消息隊列實作用戶端和伺服器之間的消息發送

每一個程序的程序id是唯一的,收消息時從id那收。最終将消息顯示到用戶端的顯示器上

伺服器:收1号通道的資料

發送到pid通道号

用戶端:往1号通道發(帶上自己的程序id)

程式間通信 —— IPC
3.共享記憶體—— 最快的程序間通信方式

将其映射到自己的虛拟位址空間,程序間的資料傳遞不用從使用者空間到核心。

System V 共享記憶體方式

//建立或打開共享記憶體

int shmget(key_t  key, //共享記憶體的名字
           size_t  size,//要建立的共享記憶體段的大小,向上對齊到記憶體頁的大小
           int shmflg) // 打開:0   建立:IPC_CREAT|0644
           

//将共享記憶體挂載到自己的位址空間

void* shmat(int shmid, //shmget得到的id
            const void* shmaddr, //NULL(讓作業系統自己選擇)想讓作業系統挂載到這個位址空間
            int shmflg); //0
傳回值:實際挂載到的虛拟位址的起始位置
           

//解除安裝掉共享記憶體,但不删除共享記憶體段

int shmdt(const  void* shmaddr);
           

//删除共享記憶體段

int shmctl(int shmid,  
           int cmd, //IPC_RMID 删除共享記憶體
           struct shmid_ds* buf); // NULL/0
           

檢視共享記憶體:ipcs -m

删除共享記憶體 : ipcm -m + key

例:将id和name映射到自己的虛拟位址空間,然後讀出來。

程式間通信 —— IPC
程式間通信 —— IPC
程式間通信 —— IPC
程式間通信 —— IPC

共享記憶體為程序間通信提供了一種效率很高的手段,但是這種手段并不提供程序間同步和互斥的功能,是以,共享記憶體作為廣義的程序間通信手段還必須有其它機制來配合。是以我們一般将信号量和共享記憶體來搭配使用。

4.信号量—— 解決同步互斥問題

同步:多個程序為了完成一件事情需要互相協同工作。

互斥:多個程序通路共享資源,共享資源具有排它性,一次僅允許一個人通路,由于各個程序都要使用共享資源。而且這個資源必須同時隻能有一個程序使用。

信号量的使用

互斥通路:臨界區的互斥通路控制

條件同步:線程間的事件等待

用信号量實作臨界區的互斥通路:

每類資源設定一個信号量,其初值為1

必須成對使用P()操作和V()操作

  • P()操作保證互斥通路臨界資源
  • V()操作在使用後釋放臨界資源
  • PV操作不能次序錯誤。重複或遺漏

用信号量實作條件同步:

條件同步設定一個信号量,其初值為0

信号量值的含義

struct semphore{
     int value;
     struct PCB* queue;//等待隊列,有哪些程序在等待該資源
}
s.value >  表示資源的個數
s.value =  表示沒有資源可以使用,也沒有程序等待該資源
s.value <  |s.value|個程序在等待該資源
           

P、V原語

//P原語                                                 //V原語
P(s){                                                   V(s){
    s.value--;                                            s.value++;
    if(s.value < ){                                      if(s.value <= ){
       将該程序置為等待狀态                                     喚醒等待隊列的程序
       加入等待隊列queue                                       将程序的狀态改為就緒态,放到就緒隊列
    }                                                       }
}                                                       }
           

//建立或打開信号量

int semget(key_t key,
           int nsems,  //在信号量集中信号量的個數
           int semflg); //打開是0  建立是 IPC_CREAT|0644
           

//删除

int semctl(int semid, //segmet的傳回值
           int semnum, //
           int cmd, //IPC_RMID
           ...);
           

//設定信号量初值

union semun{
    int val;
};

int semctl(int semid,   //semget的傳回值
           int semnum, // 對信号量集的第幾個信号量操作
           int cmd,  //SETVAL
           su); //設定信号量初值
           

//檢視信号量的值

int semctl(int semid,  //semget的傳回值
           int semnum, // 對信号量集的第幾個信号量操作
           int cmd,  //GETVAL
           ); 
傳回值:目前信号量的值
           

//PV操作

struct sembuf{
    unsigned short sem_num;//信号量集中的第幾個信号量,下标
    short  sem_op;  //P -1  ,  V +1
    short  sem_flg  //0
};

int semop(int semid,
          struct sembuf* buf,
          unsigned nsops);
           

檢視信号量:ipcs -s

删除信号量:ipcrm -s + key

例子:哲學家進餐

程式間通信 —— IPC
程式間通信 —— IPC

繼續閱讀