1. 程式人生 > >國嵌視訊學習第四天-程序通訊

國嵌視訊學習第四天-程序通訊

目的

為什麼程序間需要通訊?

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開啟它,一般的檔案訪問函式(closereadwrite)都可用於FIFO

操作

當開啟FIFO時,非阻塞標誌(O_NONBLOCK)將對以後的讀寫產生如下影響:
1.沒有使用O_NONBLOCK:訪問要求無法滿足時程序將阻塞。如試圖讀取空的FIFO,將導致程序阻塞

2.使用O_NONBLOCK:訪問要求無法滿足時不阻塞,立刻出錯返回,errnoENXIO

fifo_write.cfifo_read.c

訊號通訊

訊號(signal)機制是Unix系統中最為古老的程序間通訊機制,很多條件可以產生一個訊號:

1.當用戶按某些按鍵時,產生訊號

2.硬體異常產生訊號:除數為0、無效的儲存訪問等等。這些情況通常由硬體檢測到,將其通知核心,然後核心產生適當的訊號通知程序,例如,核心對正訪問一個無效儲存區的程序產生一個SIGSEGV訊號

3.程序用kill函式將訊號傳送給另一個程序

4.使用者可用kill命令將訊號傳送給其他程序

訊號型別

下面是幾種常見的訊號:

SIGHUP:從終端發出的結束訊號

SIGINT:來自鍵盤的中斷訊號(Ctrl-C

SIGKILL:該訊號結束接受訊號的程序

SIGTERMkill命令發出的訊號

SIGCHLD:標示子程序停止或結束的訊號

SIGSTOP:來自鍵盤(Ctrl-Z)或除錯程式的停止執行訊號

其他的訊號型別baidu.com

訊號處理

當某訊號出現時,將按照下列三種方式中的一種進行處理:

1.忽略此訊號

大多數訊號都按照這種方式進行處理,但有兩種訊號決不能被忽略。他們是:SIGKILLSIGSTOP。這兩種訊號不能被忽略的原因是:它們向超級使用者提供了一種終止或停止程序的方法

2.執行使用者希望的動作

通知核心在某種訊號發生時,呼叫一個使用者函式。在使用者函式中,執行使用者希望的處理

3.執行系統預設動作

對大多數訊號的系統預設動作時終止該程序。

訊號傳送

傳送訊號的主要函式有killraise

區別:

kill既可以向程序本身傳送訊號,也可以向其他程序傳送訊號。Raise函式是向程序自身傳送訊號

#include <sys/types.h>

#include <signal.h>

int kill(pid_t pid, int signo)

---pid是要進行處理的程序ID

---sigo是要發出的訊號型別

int raise(int signo)

killpid引數有四種不同的情況:

1.pid>0將訊號傳送給程序IDpid的程序

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指明瞭所要處理的訊號型別,它可以取除了SIGKILLSIGSTOP外的任何一種訊號。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)

引數:

---shmidshmget函式返回的共享儲存識別符號

---flag:決定以什麼方式來確定對映的地址(通常為0

---shmaddr是指該共享記憶體在各程序中的地址值,如果是0則表示系統自動為其找一個地址址(同一塊共享記憶體在各程序中是不一樣的)

返回值:如果成功,則返回共享記憶體對映到程序中的地址;如果失敗,則返回-1.

當一個程序不再需要共享記憶體時,需要把它從程序地址空間中脫離

int shmdt(char* shmaddr),其中shmaddr是從shmat()函式中的shmaddr中來

shmem.c