一、管道
單向,一段輸入,另一端輸出,先進先出FIFO。管道也是文件。管道大小4096字節。
特點:管道滿時,寫阻塞;空時,讀阻塞。
分類:普通管道(僅父子進程間通信)位于內存;命名管道位于文件系統,沒有親緣關系管道只要知道管道名也可以通訊。
管道是由內核管理的一個緩沖區(buffer),相當于我們放入內存中的一個紙條。管道的一端連接一個進程的輸出。這個進程會向管道中放入信息。管道的另一端連接一個進程的輸入,這個進程取出被放入管道的信息。一個緩沖區不需要很大,它被設計成為環形的數據結構,以便管道可以被循環利用。當管道中沒有信息的話,從管道中讀取的進程會等待,直到另一端的進程放入信息。當管道被放滿信息的時候,嘗試放入信息的進程會等待,直到另一端的進程取出信息。當兩個進程都終結的時候,管道也自動消失。
信號又稱軟中斷,通知程序發生異步事件,程序執行中隨時被各種信號中斷,進程可以忽略該信號,也可以中斷當前程序轉而去處理信號,引起信號原因:
1).程序中執行錯誤碼;
2).其他進程發送來的;
3).用戶通過控制終端發送來;
4).子進程結束時向父進程發送SIGCLD;
5).定時器生產的SIGALRM;
(一)、信號在內核中的表示當用戶進程通過系統調用剛進入內核的時候,CPU會自動在該進程的內核棧上壓入下圖所示的內容:
1、之所以把EIP的值設置成信號處理函數的地址,是因為一旦進程返回用戶態,就要去執行信號處理程序,所以EIP要指向信號處理程序而不是原來應該執行的地址。2、之所以要把frame從內核??截惖接脩魲?,是因為進程從內核態返回用戶態會清理這次調用所用到的內核棧(類似函數調用),內核棧又太小,不能單純的在棧上保存另一個frame(想象一下嵌套信號處理),而我們需要EAX(系統調用返回值)、EIP這些信息以便執行完信號處理函數后能繼續執行程序,所以把它們拷貝到用戶態棧以保存起來。這時進程返回用戶空間,就會根據內核棧中的EIP值執行信號處理函數。那么,信號處理程序執行完后,怎么返回程序繼續執行呢?信號處理程序執行完畢之后,進程會主動調用sigreturn()系統調用再次回到內核,查看有沒有其他信號需要處理,如果沒有,這時內核就會做一些善后工作,將之前保存的frame恢復到內核棧,恢復eip的值為old_eip,然后返回用戶空間,程序就能夠繼續執行。至此,內核遍完成了一次(或幾次)信號處理工作。 C++ Code
12 (By default, the signal handler is invoked on the normal PRocess stack. It is possible to arrange that the signal handler uses an alternate stack; see sigaltstack(2) for a discussion of how to do this and when it might be useful.) 三、System V 的IPC 機制為了提供與其他系統的兼容性,Linux 也支持3 種system Ⅴ的進程間通信機制:消息、信號量(semaphores)和共享內存,Linux 對這些機制的實施大同小異。我們把信號量、消息和共享內存統稱System V IPC 的對象,每一個對象都具有同樣類型的接口,即系統調用。就像每個文件都有一個打開文件號一樣,每個對象也都有唯一的識別號,進程可以通過系統調用傳遞的識別號來存取這些對象,與文件的存取一樣,對這些對象的存取也要驗證存取權限,System V IPC 可以通過系統調用對對象的創建者設置這些對象的存取權限。在Linux 內核中,System V IPC 的所有對象有一個公共的數據結構pc_perm 結構,它是IPC 對象的權限描述,在linux/ipc.h 中定義如下: C++ Code
在這個結構中,要進一步說明的是鍵(key)。鍵和識別號指的是不同的東西。系統支持兩種鍵:公有和私有。如果鍵是公有的,則系統中所有的進程通過權限檢查后,均可以找到System V IPC 對象的識別號。如果鍵是私有的,則鍵值為0,說明每個進程都可以用鍵值0 建立一個專供其私用的對象。注意,對System V IPC 對象的引用是通過識別號而不是通過鍵。(一)、信號量Linux 中信號量是通過內核提供的一系列數據結構實現的,這些數據結構存在于內核空間,對它們的分析是充分理解信號量及利用信號量實現進程間通信的基礎,下面先給出信號量的數據結構(存在于include/linux/sem.h 中) C++ Code
12345678910 struct ipc_perm{ key_t key; /* 鍵 */ ushort uid; /* 對象擁有者對應進程的有效用戶識別號和有效組識別號 */ ushort gid; ushort cuid; /* 對象創建者對應進程的有效用戶識別號和有效組識別號 */ ushort cgid; ushort mode; /* 存取模式 */ ushort seq; /* 序列號 */}; C++ Code
1234567891011121314151617181920212223242526272829303132333435 (1)系統中每個信號量的數據結構(sem)struct sem{ int semval; /* 信號量的當前值 */ unsigned short semzcnt; /* # waiting for zero */ unsigned short semncnt; /* # waiting for increase */ int sempid; /*在信號量上最后一次操作的進程識別號*/};(2)系統中表示信號量集合(set)的數據結構(semid_ds)struct semid_ds{ struct ipc_perm sem_perm; /* IPC 權限 */ long sem_otime; /* 最后一次對信號量操作(semop)的時間 */ long sem_ctime; /* 對這個結構最后一次修改的時間 */ struct sem *sem_base; /* 在信號量數組中指向第一個信號量的指針 */ struct sem_queue *sem_pending; /* 待處理的掛起操作*/ struct sem_queue **sem_pending_last; /* 最后一個掛起操作 */ struct sem_undo *undo; /* 在這個數組上的undo 請求 */ ushort sem_nsems; /* 在信號量數組上的信號量號 */};(3)系統中每一信號量集合的隊列結構(sem_queue)struct sem_queue{ struct sem_queue *next; /* 隊列中下一個節點 */ struct sem_queue **prev; /* 隊列中前一個節點, *(q->prev) == q */ struct wait_queue *sleeper; /* 正在睡眠的進程 */ struct sem_undo *undo; /* undo 結構*/ int pid; /* 請求進程的進程識別號 */ int status; /* 操作的完成狀態 */ struct semid_ds *sma; /*有操作的信號量集合數組 */ struct sembuf *sops; /* 掛起操作的數組 */ int nsops; /* 操作的個數 */};
123456 struct sembuf{ ushort sem_num; /* 在數組中信號量的索引值 */ short sem_op; /* 信號量操作值(正數、負數或0) */ short sem_flg; /* 操作標志,為IPC_NOWAIT 或SEM_UNDO*/}; 如果進程被掛起,Linux 必須保存信號量的操作狀態并將當前進程放入等待隊列。為此,Linux 內核在堆棧中建立一個 sem_queue 結構并填充該結構。新的 sem_queue 結構添加到集合的等待隊列中(利用 sem_pending 和 sem_pending_last 指針)。當前進程放入sem_queue 結構的等待隊列中(sleeper)后調用調度程序選擇其他的進程運行。當某個進程修改了信號量而進入臨界區之后,卻因為崩潰或被“殺死(kill)”而沒有退出臨界區,這時,其他被掛起在信號量上的進程永遠得不到運行機會,這就是所謂的死鎖。Linux 通過維護一個信號量數組的調整列表(semadj)來避免這一問題。其基本思想是,當應用這些“調整”時,讓信號量的狀態退回到操作實施前的狀態。(二)、消息隊列
消息隊列是先進先出FIFO原則
消息結構模板
strut msgbuf{long int mtype;//消息類型char mtext[1];//消息內容}Linux 中的消息可以被描述成在內核地址空間的一個內部鏈表,每一個消息隊列由一個IPC 的標識號唯一地標識。Linux 為系統中所有的消息隊列維護一個 msgque 鏈表,該鏈表中的每個指針指向一個 msgid_ds 結構,該結構完整描述一個消息隊列。C++ Code
(三)、共享內存
123456789101112131415161718192021222324252627282930313233 (1)消息緩沖區(msgbuf)/* msgsnd 和msgrcv 系統調用使用的消息緩沖區*/struct msgbuf{ long mtype; /* 消息的類型,必須為正數 */ char mtext[1]; /* 消息正文 */};(2)消息結構(msg)struct msg{ struct msg *msg_next; /* 隊列上的下一條消息 */ long msg_type; /*消息類型*/ char *msg_spot; /* 消息正文的地址 */ short msg_ts; /* 消息正文的大小 */};(3)消息隊列結構(msgid_ds)/* 在系統中的每一個消息隊列對應一個msgid_ds 結構 */struct msgid_ds{ struct ipc_perm msg_perm; struct msg *msg_first; /* 隊列上第一條消息,即鏈表頭*/ struct msg *msg_last; /* 隊列中的最后一條消息,即鏈表尾 */ time_t msg_stime; /* 發送給隊列的最后一條消息的時間 */ time_t msg_rtime; /* 從消息隊列接收到的最后一條消息的時間 */ time_t msg_ctime; /* 最后修改隊列的時間*/ ushort msg_cbytes; /*隊列上所有消息總的字節數 */ ushort msg_qnum; /*在當前隊列上消息的個數 */ ushort msg_qbytes; /* 隊列最大的字節數 */ ushort msg_lspid; /* 發送最后一條消息的進程的pid */ ushort msg_lrpid; /* 接收最后一條消息的進程的pid */}; 共享內存是分配一塊能被其他進程訪問的內存,實現是通過將內存去映射到共享它的進程的地址空間,使這些進程間的數據傳送不再涉及內核,即,進程間通信不需要通過進入內核的系統調用來實現;共享內存與其他的進程間通信最大的優點是:數據的復制只有兩次,一次是從輸入文件到共享內存區,一次從共享內存區到輸出文件而其他的則是需要復制4次:服務器將輸入文件讀入自己的進程空間,再從自己的進程空間寫入管道/消息隊列等;客戶進程從管道/消息隊列中讀出數據到自己的進程空間,最后輸出到客戶指定的文件中;
要使用共享內存,應該有如下步驟:1.開辟一塊共享內存 shmget()2.允許本進程使用共某塊共享內存 shmat()3.寫入/讀出4.禁止本進程使用這塊共享內存 shmdt()5.刪除這塊共享內存 shmctl()或者命令行下ipcrm與消息隊列和信號量集合類似,內核為每一個共享內存段(存在于它的地址空間)維護著一個特殊的數據結構shmid_ds,這個結構在include/linux/shm.h 中定義如下: C++ Code我們用圖 7.4 來表示共享內存的數據結構shmid_ds 與其他相關數據結構的關系。
12345678910111213141516 /* 在系統中 每一個共享內存段都有一個shmid_ds 數據結構. */struct shmid_ds{ struct ipc_perm shm_perm; /* 操作權限 */ int shm_segsz; /* 段的大?。ㄒ宰止潪閱挝唬?nbsp;*/ time_t shm_atime; /* 最后一個進程附加到該段的時間 */ time_t shm_dtime; /* 最后一個進程離開該段的時間 */ time_t shm_ctime; /* 最后一次修改這個結構的時間 */ unsigned short shm_cpid; /*創建該段進程的 pid */ unsigned short shm_lpid; /* 在該段上操作的最后一個進程的pid */ short shm_nattch; /*當前附加到該段的進程的個數 */ /* 下面是私有的 */ unsigned short shm_npages; /*段的大小(以頁為單位) */ unsigned long *shm_pages; /* 指向frames -> SHMMAX 的指針數組 */ struct vm_area_struct *attaches; /* 對共享段的描述 */}; 某個進程第1 次訪問共享虛擬內存時將產生缺頁異常。這時,Linux 找出描述該內存的vm_area_struct 結構,該結構中包含用來處理這種共享虛擬內存段的處理函數地址。共享內存缺頁異常處理代碼對shmid_ds 的頁表項表進行搜索,以便查看是否存在該共享虛擬內存的頁表項。如果沒有,系統將分配一個物理頁并建立頁表項,該頁表項加入 shmid_ds 結構的同時也添加到進程的頁表中。這就意味著當下一個進程試圖訪問這頁內存時出現缺頁異常,共享內存的缺頁異常處理代碼則把新創建的物理頁給這個進程。因此說,第1 個進程對共享內存的存取引起創建新的物理頁面,而其他進程對共享內存的存取引起把那個頁添加到它們的地址空間。當某個進程不再共享其虛擬內存時,利用系統調用將共享段從自己的虛擬地址區域中移去,并更新進程頁表。當最后一個進程釋放了共享段之后,系統將釋放給共享段所分配的物理頁。當共享的虛擬內存沒有被鎖定到物理內存時,共享內存也可能會被交換到交換區中。四、Posix 的IPC 機制信號量:分為命名和匿名信號量。命名信號量通常用于不共享內存的進程之間(內核實現);匿名信號量可以用于線程通信(存放于線程共享的內存,如全局變量),或者用于進程間通信(存放于進程共享的內存,如System V/ Posix 共享內存)。消息隊列、共享內存:與System V 類似?;コ怄imutex + 匿名信號量:線程通信互斥鎖mutex + 條件變量condition :線程通信轉:http://blog.csdn.net/jnu_simba/article/details/11746217http://blog.csdn.net/yangcs2009/article/details/39697303
|
新聞熱點
疑難解答