國嵌視訊學習第四天-程序通訊
目的
為什麼程序間需要通訊?
1. 資料傳輸
一個程序需要將它的資料傳送給另一個程序
2. 資源共享
多個程序之間共享同樣的資源
3. 通知事件
一個程序需要向另一個或一組程序傳送訊息,通知它們發生了某種事件
4. 程序控制
有些程序希望完全控制另一個程序的執行(如Debug程序),此時控制程序希望能夠攔截另一個程序的所有操作,並能夠及時知道它的狀態改變
發展
Linux程序間通訊(IPC)由以下幾部分發展而來:
1. Unix程序間通訊
2. 基於System V程序間通訊
3. POSIX程序間通訊
分類
現在Linux使用的程序間通訊方式包括:
1. 管道(pipe)和有名管道(FIFO)
2. 訊號(signal)(事件通知)
3. 訊息佇列
4. 共享記憶體
5. 訊號量
6. 套接字(socket)
管道通訊
管道是單向的、先進先出的,它把一個程序的輸出和另一個程序的輸入連線在一起。一個程序(寫程序)在管道的尾部寫入資料,另一個程序(讀程序)從管道的頭部讀出資料。
管道建立
管道包括無名管道和有名管道兩種,前者用於父程序和子程序間的通訊,後者可用於運行於同一系統中的任意兩個程序間的通訊。
無名管道有pipe()函式建立:
int pipe(int filedis[2]);
當一個管道建立時,它會建立兩個檔案描述符:filedis[0]用於讀管道,filedis[1]用於寫管道(將管道的頭部和尾部分別看做兩個檔案,這兩個檔案就應該對應於兩個檔案描述符)
管道關閉
關閉管道只需將這兩個檔案描述符關閉即可,可以使用普通的close函式逐個關閉
管道讀寫
管道用於不同程序間通訊。通常先建立一個管道,再通過fork函式建立一個子程序,該子程序會繼承父程序所建立的管道(父程序和子程序通過該無名管道進行通訊,父程序向該管道寫資料,子程序向管道讀資料)
注意事項
必須在系統呼叫fork()前呼叫pipe(),否則子程序將不會繼承檔案描述符(如果順序相反,則會發現會建立兩個管道
例pipe_rw.c
命名管道(FIFO)
命名管道和無名管道基本相同,但也有不同點:無名管道只能由父子程序使用;但是通過命名管道,不相關的程序也能交換資料
從實質上來講命名管道就是一個檔案
建立
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char* pathname, mode_t mode)
--pathname:FIFO檔名(命名管道的名字)
--mode:屬性(見檔案操作章節)
一旦建立了一個FIFO,就可用open開啟它,一般的檔案訪問函式(close、read、write等)都可用於FIFO
操作
當開啟FIFO時,非阻塞標誌(O_NONBLOCK)將對以後的讀寫產生如下影響:
1.沒有使用O_NONBLOCK:訪問要求無法滿足時程序將阻塞。如試圖讀取空的FIFO,將導致程序阻塞
2.使用O_NONBLOCK:訪問要求無法滿足時不阻塞,立刻出錯返回,errno是ENXIO
例fifo_write.c、fifo_read.c
訊號通訊
訊號(signal)機制是Unix系統中最為古老的程序間通訊機制,很多條件可以產生一個訊號:
1.當用戶按某些按鍵時,產生訊號
2.硬體異常產生訊號:除數為0、無效的儲存訪問等等。這些情況通常由硬體檢測到,將其通知核心,然後核心產生適當的訊號通知程序,例如,核心對正訪問一個無效儲存區的程序產生一個SIGSEGV訊號
3.程序用kill函式將訊號傳送給另一個程序
4.使用者可用kill命令將訊號傳送給其他程序
訊號型別
下面是幾種常見的訊號:
SIGHUP:從終端發出的結束訊號
SIGINT:來自鍵盤的中斷訊號(Ctrl-C)
SIGKILL:該訊號結束接受訊號的程序
SIGTERM:kill命令發出的訊號
SIGCHLD:標示子程序停止或結束的訊號
SIGSTOP:來自鍵盤(Ctrl-Z)或除錯程式的停止執行訊號
其他的訊號型別baidu.com
訊號處理
當某訊號出現時,將按照下列三種方式中的一種進行處理:
1.忽略此訊號
大多數訊號都按照這種方式進行處理,但有兩種訊號決不能被忽略。他們是:SIGKILL和SIGSTOP。這兩種訊號不能被忽略的原因是:它們向超級使用者提供了一種終止或停止程序的方法
2.執行使用者希望的動作
通知核心在某種訊號發生時,呼叫一個使用者函式。在使用者函式中,執行使用者希望的處理
3.執行系統預設動作
對大多數訊號的系統預設動作時終止該程序。
訊號傳送
傳送訊號的主要函式有kill和raise
區別:
kill既可以向程序本身傳送訊號,也可以向其他程序傳送訊號。Raise函式是向程序自身傳送訊號
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int signo)
---pid是要進行處理的程序ID
---sigo是要發出的訊號型別
int raise(int signo)
kill的pid引數有四種不同的情況:
1.pid>0將訊號傳送給程序ID為pid的程序
2.Pid==0將訊號傳送給同組的程序
3.Pid<0將訊號傳送給其程序組ID等於pid絕對值的程序
4.Pid==-1將訊號傳送給所有程序。
Alarm
使用alarm函式可以設定一個時間值(鬧鐘時間),當所設定的時間到了時,產生SIGALRM訊號(傳送給自身)。如果不捕捉此訊號,則預設動作是終止該程序。
#include <unistd.h>
unsigned int alarm(unsigned int seconds)
---seconds:經過了指定的seconds秒後會產生訊號SIGALRM
每個程序只能有一個鬧鐘時間。如果在呼叫alarm時,以前已為該程序設定過鬧鐘時間,而且它還沒有超時,以前登記的鬧鐘時間則被新值代換。
如果有以前登記的尚未超過的鬧鐘時間,而這次seconds值是0,則表示取消以前的鬧鐘
Pause
Pause函式使呼叫過程掛起直至捕捉到一個訊號。
#include <unistd.h>
int pause(void)
只有執行了一個訊號處理函式後,掛起才結束。
訊號的處理
當系統捕捉到某個訊號時,可以忽略該訊號或是使用指定的處理函式來處理該訊號,或者使用系統預設的方式
訊號處理的主要方法有兩種,一種是使用簡單的signal函式,另一種是使用訊號集函式組
Signal原型
#include <signal.h>
void (*signal (int signo, void(*func)(int)))(int)
如何理解?
typedef void (*sighandler_t)(int)
sighandler_t signal(int signum, sighandler_t handler)
第一個引數signum指明瞭所要處理的訊號型別,它可以取除了SIGKILL和SIGSTOP外的任何一種訊號。handler引數是一個指標,指向某個處理該訊號的函式。這個處理訊號函式帶有一個int型引數,並應返回void。
func(handler)可能的值是:
1.SIG_IGN:忽略此訊號
2.SIG_DFL:按系統預設方式處理
3.訊號處理函式名:使用該函式處理.(所有的訊號處理函式都只有一個int型別的引數)
例mysignal.c
共享記憶體
共享記憶體是被多個程序共享的一部分實體記憶體。共享記憶體是程序間共享資料的一種最快的方法,一個程序向共享記憶體區域寫入了資料,共享這個記憶體區域的所有程序就可以立刻看到其中的內容。
共享記憶體實現分為兩個步驟:
一、建立共享記憶體,使用shmget函式
二、對映共享記憶體,將這段建立的共享記憶體對映到具體的程序空間去,使用shmat函式
建立
int shmget(key_t key, int size, int shmflg)
key標示共享記憶體的鍵值:0/IPC_PRIVATE。當key的取值為IPC_PRIVATE,則函式shmget()將建立一塊新的共享記憶體;如果key的取值為0,而引數shmflg中又設定IPC_PRIVATE這個標誌,則同樣會建立一塊新的共享記憶體
返回值:如果成功,返回共享記憶體識別符號;如果失敗,返回-1.
對映
void* shmat(int shmid, char* shmaddr, int flag)
引數:
---shmid:shmget函式返回的共享儲存識別符號
---flag:決定以什麼方式來確定對映的地址(通常為0)
---shmaddr是指該共享記憶體在各程序中的地址值,如果是0則表示系統自動為其找一個地址址(同一塊共享記憶體在各程序中是不一樣的)
返回值:如果成功,則返回共享記憶體對映到程序中的地址;如果失敗,則返回-1.
當一個程序不再需要共享記憶體時,需要把它從程序地址空間中脫離
int shmdt(char* shmaddr),其中shmaddr是從shmat()函式中的shmaddr中來
例shmem.c