一、管道
單向,一段輸入,另一端輸出,先進(jìn)先出FIFO。管道也是文件。管道大小4096字節(jié)。
特點(diǎn):管道滿時(shí),寫(xiě)阻塞;空時(shí),讀阻塞。
分類:普通管道(僅父子進(jìn)程間通信)位于內(nèi)存;命名管道位于文件系統(tǒng),沒(méi)有親緣關(guān)系管道只要知道管道名也可以通訊。
管道是由內(nèi)核管理的一個(gè)緩沖區(qū)(buffer),相當(dāng)于我們放入內(nèi)存中的一個(gè)紙條。管道的一端連接一個(gè)進(jìn)程的輸出。這個(gè)進(jìn)程會(huì)向管道中放入信息。管道的另一端連接一個(gè)進(jìn)程的輸入,這個(gè)進(jìn)程取出被放入管道的信息。一個(gè)緩沖區(qū)不需要很大,它被設(shè)計(jì)成為環(huán)形的數(shù)據(jù)結(jié)構(gòu),以便管道可以被循環(huán)利用。當(dāng)管道中沒(méi)有信息的話,從管道中讀取的進(jìn)程會(huì)等待,直到另一端的進(jìn)程放入信息。當(dāng)管道被放滿信息的時(shí)候,嘗試放入信息的進(jìn)程會(huì)等待,直到另一端的進(jìn)程取出信息。當(dāng)兩個(gè)進(jìn)程都終結(jié)的時(shí)候,管道也自動(dòng)消失。


圖7.1 中有兩個(gè) file 數(shù)據(jù)結(jié)構(gòu),但它們定義文件操作例程地址是不同的,其中一個(gè)是向管道中寫(xiě)入數(shù)據(jù)的例程地址,而另一個(gè)是從管道中讀出數(shù)據(jù)的例程地址。這樣,用戶程序的系統(tǒng)調(diào)用仍然是通常的文件操作,而內(nèi)核卻利用這種抽象機(jī)制實(shí)現(xiàn)了管道這一特殊操作。一個(gè)普通的管道僅可供具有共同祖先的兩個(gè)進(jìn)程之間共享,并且這個(gè)祖先必須已經(jīng)建立了供它們使用的管道。注意,在管道中的數(shù)據(jù)始終以和寫(xiě)數(shù)據(jù)相同的次序來(lái)進(jìn)行讀,這表示lseek()系統(tǒng)調(diào)用對(duì)管道不起作用。二、信號(hào)信號(hào)又稱軟中斷,通知程序發(fā)生異步事件,程序執(zhí)行中隨時(shí)被各種信號(hào)中斷,進(jìn)程可以忽略該信號(hào),也可以中斷當(dāng)前程序轉(zhuǎn)而去處理信號(hào),引起信號(hào)原因:
1).程序中執(zhí)行錯(cuò)誤碼;
2).其他進(jìn)程發(fā)送來(lái)的;
3).用戶通過(guò)控制終端發(fā)送來(lái);
4).子進(jìn)程結(jié)束時(shí)向父進(jìn)程發(fā)送SIGCLD;
5).定時(shí)器生產(chǎn)的SIGALRM;
(一)、信號(hào)在內(nèi)核中的表示
中斷的響應(yīng)和處理都發(fā)生在內(nèi)核空間,而信號(hào)的響應(yīng)發(fā)生在內(nèi)核空間,信號(hào)處理程序的執(zhí)行卻發(fā)生在用戶空間。那么,什么時(shí)候檢測(cè)和響應(yīng)信號(hào)呢?通常發(fā)生在以下兩種情況下:(1)當(dāng)前進(jìn)程由于系統(tǒng)調(diào)用、中斷或異常而進(jìn)入內(nèi)核空間以后,從內(nèi)核空間返回到用戶空間前夕;(2)當(dāng)前進(jìn)程在內(nèi)核中進(jìn)入睡眠以后剛被喚醒的時(shí)候,由于檢測(cè)到信號(hào)的存在而提前返回到用戶空間。當(dāng)有信號(hào)要響應(yīng)時(shí),處理器執(zhí)行路線的示意圖如圖33.2 所示。
.png)
當(dāng)用戶進(jìn)程通過(guò)系統(tǒng)調(diào)用剛進(jìn)入內(nèi)核的時(shí)候,CPU會(huì)自動(dòng)在該進(jìn)程的內(nèi)核棧上壓入下圖所示的內(nèi)容:

在處理完系統(tǒng)調(diào)用以后,就要調(diào)用do_signal()函數(shù)進(jìn)行設(shè)置frame等工作。這時(shí)內(nèi)核堆棧的狀態(tài)應(yīng)該跟下圖左半部分類似(系統(tǒng)調(diào)用將一些信息壓入棧了):
在找到了信號(hào)處理函數(shù)之后,do_signal() 函數(shù)首先把內(nèi)核堆棧中存放返回執(zhí)行點(diǎn)的eip保存為old_eip,然后將eip替換為信號(hào)處理函數(shù)的地址,然后將內(nèi)核中保存的“原ESP”(即用戶態(tài)棧地址)減去一定的值,目的是擴(kuò)大用戶態(tài)的棧,然后將內(nèi)核棧上的內(nèi)容保存到用戶棧上,這個(gè)過(guò)程就是設(shè)置frame.值得注意的是下面兩點(diǎn):1、之所以把EIP的值設(shè)置成信號(hào)處理函數(shù)的地址,是因?yàn)橐坏┻M(jìn)程返回用戶態(tài),就要去執(zhí)行信號(hào)處理程序,所以EIP要指向信號(hào)處理程序而不是原來(lái)應(yīng)該執(zhí)行的地址。2、之所以要把frame從內(nèi)核棧拷貝到用戶棧,是因?yàn)檫M(jìn)程從內(nèi)核態(tài)返回用戶態(tài)會(huì)清理這次調(diào)用所用到的內(nèi)核棧(類似函數(shù)調(diào)用),內(nèi)核棧又太小,不能單純的在棧上保存另一個(gè)frame(想象一下嵌套信號(hào)處理),而我們需要EAX(系統(tǒng)調(diào)用返回值)、EIP這些信息以便執(zhí)行完信號(hào)處理函數(shù)后能繼續(xù)執(zhí)行程序,所以把它們拷貝到用戶態(tài)棧以保存起來(lái)。這時(shí)進(jìn)程返回用戶空間,就會(huì)根據(jù)內(nèi)核棧中的EIP值執(zhí)行信號(hào)處理函數(shù)。那么,信號(hào)處理程序執(zhí)行完后,怎么返回程序繼續(xù)執(zhí)行呢?信號(hào)處理程序執(zhí)行完畢之后,進(jìn)程會(huì)主動(dòng)調(diào)用sigreturn()系統(tǒng)調(diào)用再次回到內(nèi)核,查看有沒(méi)有其他信號(hào)需要處理,如果沒(méi)有,這時(shí)內(nèi)核就會(huì)做一些善后工作,將之前保存的frame恢復(fù)到內(nèi)核棧,恢復(fù)eip的值為old_eip,然后返回用戶空間,程序就能夠繼續(xù)執(zhí)行。至此,內(nèi)核遍完成了一次(或幾次)信號(hào)處理工作。 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 機(jī)制為了提供與其他系統(tǒng)的兼容性,Linux 也支持3 種system Ⅴ的進(jìn)程間通信機(jī)制:消息、信號(hào)量(semaphores)和共享內(nèi)存,Linux 對(duì)這些機(jī)制的實(shí)施大同小異。我們把信號(hào)量、消息和共享內(nèi)存統(tǒng)稱System V IPC 的對(duì)象,每一個(gè)對(duì)象都具有同樣類型的接口,即系統(tǒng)調(diào)用。就像每個(gè)文件都有一個(gè)打開(kāi)文件號(hào)一樣,每個(gè)對(duì)象也都有唯一的識(shí)別號(hào),進(jìn)程可以通過(guò)系統(tǒng)調(diào)用傳遞的識(shí)別號(hào)來(lái)存取這些對(duì)象,與文件的存取一樣,對(duì)這些對(duì)象的存取也要驗(yàn)證存取權(quán)限,System V IPC 可以通過(guò)系統(tǒng)調(diào)用對(duì)對(duì)象的創(chuàng)建者設(shè)置這些對(duì)象的存取權(quán)限。在Linux 內(nèi)核中,System V IPC 的所有對(duì)象有一個(gè)公共的數(shù)據(jù)結(jié)構(gòu)pc_perm 結(jié)構(gòu),它是IPC 對(duì)象的權(quán)限描述,在linux/ipc.h 中定義如下: C++ Code
在這個(gè)結(jié)構(gòu)中,要進(jìn)一步說(shuō)明的是鍵(key)。鍵和識(shí)別號(hào)指的是不同的東西。系統(tǒng)支持兩種鍵:公有和私有。如果鍵是公有的,則系統(tǒng)中所有的進(jìn)程通過(guò)權(quán)限檢查后,均可以找到System V IPC 對(duì)象的識(shí)別號(hào)。如果鍵是私有的,則鍵值為0,說(shuō)明每個(gè)進(jìn)程都可以用鍵值0 建立一個(gè)專供其私用的對(duì)象。注意,對(duì)System V IPC 對(duì)象的引用是通過(guò)識(shí)別號(hào)而不是通過(guò)鍵。(一)、信號(hào)量Linux 中信號(hào)量是通過(guò)內(nèi)核提供的一系列數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的,這些數(shù)據(jù)結(jié)構(gòu)存在于內(nèi)核空間,對(duì)它們的分析是充分理解信號(hào)量及利用信號(hào)量實(shí)現(xiàn)進(jìn)程間通信的基礎(chǔ),下面先給出信號(hào)量的數(shù)據(jù)結(jié)構(gòu)(存在于include/linux/sem.h 中) C++ Code
12345678910 struct ipc_perm{ key_t key; /* 鍵 */ ushort uid; /* 對(duì)象擁有者對(duì)應(yīng)進(jìn)程的有效用戶識(shí)別號(hào)和有效組識(shí)別號(hào) */ ushort gid; ushort cuid; /* 對(duì)象創(chuàng)建者對(duì)應(yīng)進(jìn)程的有效用戶識(shí)別號(hào)和有效組識(shí)別號(hào) */ ushort cgid; ushort mode; /* 存取模式 */ ushort seq; /* 序列號(hào) */}; C++ Code
1234567891011121314151617181920212223242526272829303132333435 (1)系統(tǒng)中每個(gè)信號(hào)量的數(shù)據(jù)結(jié)構(gòu)(sem)struct sem{ int semval; /* 信號(hào)量的當(dāng)前值 */ unsigned short semzcnt; /* # waiting for zero */ unsigned short semncnt; /* # waiting for increase */ int sempid; /*在信號(hào)量上最后一次操作的進(jìn)程識(shí)別號(hào)*/};(2)系統(tǒng)中表示信號(hào)量集合(set)的數(shù)據(jù)結(jié)構(gòu)(semid_ds)struct semid_ds{ struct ipc_perm sem_perm; /* IPC 權(quán)限 */ long sem_otime; /* 最后一次對(duì)信號(hào)量操作(semop)的時(shí)間 */ long sem_ctime; /* 對(duì)這個(gè)結(jié)構(gòu)最后一次修改的時(shí)間 */ struct sem *sem_base; /* 在信號(hào)量數(shù)組中指向第一個(gè)信號(hào)量的指針 */ struct sem_queue *sem_pending; /* 待處理的掛起操作*/ struct sem_queue **sem_pending_last; /* 最后一個(gè)掛起操作 */ struct sem_undo *undo; /* 在這個(gè)數(shù)組上的undo 請(qǐng)求 */ ushort sem_nsems; /* 在信號(hào)量數(shù)組上的信號(hào)量號(hào) */};(3)系統(tǒng)中每一信號(hào)量集合的隊(duì)列結(jié)構(gòu)(sem_queue)struct sem_queue{ struct sem_queue *next; /* 隊(duì)列中下一個(gè)節(jié)點(diǎn) */ struct sem_queue **prev; /* 隊(duì)列中前一個(gè)節(jié)點(diǎn), *(q->prev) == q */ struct wait_queue *sleeper; /* 正在睡眠的進(jìn)程 */ struct sem_undo *undo; /* undo 結(jié)構(gòu)*/ int pid; /* 請(qǐng)求進(jìn)程的進(jìn)程識(shí)別號(hào) */ int status; /* 操作的完成狀態(tài) */ struct semid_ds *sma; /*有操作的信號(hào)量集合數(shù)組 */ struct sembuf *sops; /* 掛起操作的數(shù)組 */ int nsops; /* 操作的個(gè)數(shù) */};
123456 struct sembuf{ ushort sem_num; /* 在數(shù)組中信號(hào)量的索引值 */ short sem_op; /* 信號(hào)量操作值(正數(shù)、負(fù)數(shù)或0) */ short sem_flg; /* 操作標(biāo)志,為IPC_NOWAIT 或SEM_UNDO*/}; 如果進(jìn)程被掛起,Linux 必須保存信號(hào)量的操作狀態(tài)并將當(dāng)前進(jìn)程放入等待隊(duì)列。為此,Linux 內(nèi)核在堆棧中建立一個(gè) sem_queue 結(jié)構(gòu)并填充該結(jié)構(gòu)。新的 sem_queue 結(jié)構(gòu)添加到集合的等待隊(duì)列中(利用 sem_pending 和 sem_pending_last 指針)。當(dāng)前進(jìn)程放入sem_queue 結(jié)構(gòu)的等待隊(duì)列中(sleeper)后調(diào)用調(diào)度程序選擇其他的進(jìn)程運(yùn)行。當(dāng)某個(gè)進(jìn)程修改了信號(hào)量而進(jìn)入臨界區(qū)之后,卻因?yàn)楸罎⒒虮弧皻⑺溃╧ill)”而沒(méi)有退出臨界區(qū),這時(shí),其他被掛起在信號(hào)量上的進(jìn)程永遠(yuǎn)得不到運(yùn)行機(jī)會(huì),這就是所謂的死鎖。Linux 通過(guò)維護(hù)一個(gè)信號(hào)量數(shù)組的調(diào)整列表(semadj)來(lái)避免這一問(wèn)題。其基本思想是,當(dāng)應(yīng)用這些“調(diào)整”時(shí),讓信號(hào)量的狀態(tài)退回到操作實(shí)施前的狀態(tài)。(二)、消息隊(duì)列
消息隊(duì)列是先進(jìn)先出FIFO原則
消息結(jié)構(gòu)模板
strut msgbuf{long int mtype;//消息類型char mtext[1];//消息內(nèi)容}Linux 中的消息可以被描述成在內(nèi)核地址空間的一個(gè)內(nèi)部鏈表,每一個(gè)消息隊(duì)列由一個(gè)IPC 的標(biāo)識(shí)號(hào)唯一地標(biāo)識(shí)。Linux 為系統(tǒng)中所有的消息隊(duì)列維護(hù)一個(gè) msgque 鏈表,該鏈表中的每個(gè)指針指向一個(gè) msgid_ds 結(jié)構(gòu),該結(jié)構(gòu)完整描述一個(gè)消息隊(duì)列。C++ Code
(三)、共享內(nèi)存
123456789101112131415161718192021222324252627282930313233 (1)消息緩沖區(qū)(msgbuf)/* msgsnd 和msgrcv 系統(tǒng)調(diào)用使用的消息緩沖區(qū)*/struct msgbuf{ long mtype; /* 消息的類型,必須為正數(shù) */ char mtext[1]; /* 消息正文 */};(2)消息結(jié)構(gòu)(msg)struct msg{ struct msg *msg_next; /* 隊(duì)列上的下一條消息 */ long msg_type; /*消息類型*/ char *msg_spot; /* 消息正文的地址 */ short msg_ts; /* 消息正文的大小 */};(3)消息隊(duì)列結(jié)構(gòu)(msgid_ds)/* 在系統(tǒng)中的每一個(gè)消息隊(duì)列對(duì)應(yīng)一個(gè)msgid_ds 結(jié)構(gòu) */struct msgid_ds{ struct ipc_perm msg_perm; struct msg *msg_first; /* 隊(duì)列上第一條消息,即鏈表頭*/ struct msg *msg_last; /* 隊(duì)列中的最后一條消息,即鏈表尾 */ time_t msg_stime; /* 發(fā)送給隊(duì)列的最后一條消息的時(shí)間 */ time_t msg_rtime; /* 從消息隊(duì)列接收到的最后一條消息的時(shí)間 */ time_t msg_ctime; /* 最后修改隊(duì)列的時(shí)間*/ ushort msg_cbytes; /*隊(duì)列上所有消息總的字節(jié)數(shù) */ ushort msg_qnum; /*在當(dāng)前隊(duì)列上消息的個(gè)數(shù) */ ushort msg_qbytes; /* 隊(duì)列最大的字節(jié)數(shù) */ ushort msg_lspid; /* 發(fā)送最后一條消息的進(jìn)程的pid */ ushort msg_lrpid; /* 接收最后一條消息的進(jìn)程的pid */}; 共享內(nèi)存是分配一塊能被其他進(jìn)程訪問(wèn)的內(nèi)存,實(shí)現(xiàn)是通過(guò)將內(nèi)存去映射到共享它的進(jìn)程的地址空間,使這些進(jìn)程間的數(shù)據(jù)傳送不再涉及內(nèi)核,即,進(jìn)程間通信不需要通過(guò)進(jìn)入內(nèi)核的系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn);共享內(nèi)存與其他的進(jìn)程間通信最大的優(yōu)點(diǎn)是:數(shù)據(jù)的復(fù)制只有兩次,一次是從輸入文件到共享內(nèi)存區(qū),一次從共享內(nèi)存區(qū)到輸出文件而其他的則是需要復(fù)制4次:服務(wù)器將輸入文件讀入自己的進(jìn)程空間,再?gòu)淖约旱倪M(jìn)程空間寫(xiě)入管道/消息隊(duì)列等;客戶進(jìn)程從管道/消息隊(duì)列中讀出數(shù)據(jù)到自己的進(jìn)程空間,最后輸出到客戶指定的文件中;
要使用共享內(nèi)存,應(yīng)該有如下步驟:1.開(kāi)辟一塊共享內(nèi)存 shmget()2.允許本進(jìn)程使用共某塊共享內(nèi)存 shmat()3.寫(xiě)入/讀出4.禁止本進(jìn)程使用這塊共享內(nèi)存 shmdt()5.刪除這塊共享內(nèi)存 shmctl()或者命令行下ipcrm與消息隊(duì)列和信號(hào)量集合類似,內(nèi)核為每一個(gè)共享內(nèi)存段(存在于它的地址空間)維護(hù)著一個(gè)特殊的數(shù)據(jù)結(jié)構(gòu)shmid_ds,這個(gè)結(jié)構(gòu)在include/linux/shm.h 中定義如下: C++ Code我們用圖 7.4 來(lái)表示共享內(nèi)存的數(shù)據(jù)結(jié)構(gòu)shmid_ds 與其他相關(guān)數(shù)據(jù)結(jié)構(gòu)的關(guān)系。
12345678910111213141516 /* 在系統(tǒng)中 每一個(gè)共享內(nèi)存段都有一個(gè)shmid_ds 數(shù)據(jù)結(jié)構(gòu). */struct shmid_ds{ struct ipc_perm shm_perm; /* 操作權(quán)限 */ int shm_segsz; /* 段的大小(以字節(jié)為單位) */ time_t shm_atime; /* 最后一個(gè)進(jìn)程附加到該段的時(shí)間 */ time_t shm_dtime; /* 最后一個(gè)進(jìn)程離開(kāi)該段的時(shí)間 */ time_t shm_ctime; /* 最后一次修改這個(gè)結(jié)構(gòu)的時(shí)間 */ unsigned short shm_cpid; /*創(chuàng)建該段進(jìn)程的 pid */ unsigned short shm_lpid; /* 在該段上操作的最后一個(gè)進(jìn)程的pid */ short shm_nattch; /*當(dāng)前附加到該段的進(jìn)程的個(gè)數(shù) */ /* 下面是私有的 */ unsigned short shm_npages; /*段的大小(以頁(yè)為單位) */ unsigned long *shm_pages; /* 指向frames -> SHMMAX 的指針數(shù)組 */ struct vm_area_struct *attaches; /* 對(duì)共享段的描述 */}; 某個(gè)進(jìn)程第1 次訪問(wèn)共享虛擬內(nèi)存時(shí)將產(chǎn)生缺頁(yè)異常。這時(shí),Linux 找出描述該內(nèi)存的vm_area_struct 結(jié)構(gòu),該結(jié)構(gòu)中包含用來(lái)處理這種共享虛擬內(nèi)存段的處理函數(shù)地址。共享內(nèi)存缺頁(yè)異常處理代碼對(duì)shmid_ds 的頁(yè)表項(xiàng)表進(jìn)行搜索,以便查看是否存在該共享虛擬內(nèi)存的頁(yè)表項(xiàng)。如果沒(méi)有,系統(tǒng)將分配一個(gè)物理頁(yè)并建立頁(yè)表項(xiàng),該頁(yè)表項(xiàng)加入 shmid_ds 結(jié)構(gòu)的同時(shí)也添加到進(jìn)程的頁(yè)表中。這就意味著當(dāng)下一個(gè)進(jìn)程試圖訪問(wèn)這頁(yè)內(nèi)存時(shí)出現(xiàn)缺頁(yè)異常,共享內(nèi)存的缺頁(yè)異常處理代碼則把新創(chuàng)建的物理頁(yè)給這個(gè)進(jìn)程。因此說(shuō),第1 個(gè)進(jìn)程對(duì)共享內(nèi)存的存取引起創(chuàng)建新的物理頁(yè)面,而其他進(jìn)程對(duì)共享內(nèi)存的存取引起把那個(gè)頁(yè)添加到它們的地址空間。當(dāng)某個(gè)進(jìn)程不再共享其虛擬內(nèi)存時(shí),利用系統(tǒng)調(diào)用將共享段從自己的虛擬地址區(qū)域中移去,并更新進(jìn)程頁(yè)表。當(dāng)最后一個(gè)進(jìn)程釋放了共享段之后,系統(tǒng)將釋放給共享段所分配的物理頁(yè)。當(dāng)共享的虛擬內(nèi)存沒(méi)有被鎖定到物理內(nèi)存時(shí),共享內(nèi)存也可能會(huì)被交換到交換區(qū)中。四、Posix 的IPC 機(jī)制信號(hào)量:分為命名和匿名信號(hào)量。命名信號(hào)量通常用于不共享內(nèi)存的進(jìn)程之間(內(nèi)核實(shí)現(xiàn));匿名信號(hào)量可以用于線程通信(存放于線程共享的內(nèi)存,如全局變量),或者用于進(jìn)程間通信(存放于進(jìn)程共享的內(nèi)存,如System V/ Posix 共享內(nèi)存)。消息隊(duì)列、共享內(nèi)存:與System V 類似。互斥鎖mutex + 匿名信號(hào)量:線程通信互斥鎖mutex + 條件變量condition :線程通信轉(zhuǎn):http://blog.csdn.net/jnu_simba/article/details/11746217http://blog.csdn.net/yangcs2009/article/details/39697303
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注