1. 程式人生 > >第一部分 簡介(第一章 簡介 + 第二章 Posix IPC + 第三章 System V IPC)

第一部分 簡介(第一章 簡介 + 第二章 Posix IPC + 第三章 System V IPC)

前言
應用程式三種構建方法:函式間通過引數返回值來交換資訊的龐大程式;多個程式或程序間通過某種形式的IPC進行通訊;程式包含多個執行緒,執行緒間使用某種IPC。後兩種方法可減少指定任務時間。
4種不同的IPC:訊息傳遞(管道,FIFO,訊息佇列);同步(互斥量,條件變數,訊號量,檔案和記錄鎖,讀寫鎖);共享記憶體(匿名和具名);遠端過程呼叫(門和RPC)。其中共享記憶體和同步只能用於單主機。

第一部分 簡介
1.1 概述
訊息傳遞發展階段:管道-->System V訊息佇列-->Posix訊息佇列-->遠端過程呼叫(客戶主機呼叫伺服器主機的某個函式)
同步發展階段:記錄上鎖-->System V訊號量-->Posix訊號量-->互斥鎖和條件變數-->讀寫鎖

親緣關係開始於一個登陸shell(會話)以及由該shell派生的所有程序,意味著具有共同的祖先。

1.2 程序,執行緒與資訊共享
3種程序間資訊共享訪問方式:穿越核心共享檔案系統某檔案資訊;共享駐留核心的某些資訊;不涉及核心,訪問共享記憶體區。
執行緒:一個程序下的所有執行緒共享程序的全域性變數,但需要考慮同步訪問。若某一執行緒阻塞在空管道的read呼叫上,而其他執行緒也可繼續執行。

1.3 IPC物件的持續性(P5 圖1-3:各種型別IPC物件的持續性)
隨程序:由最後一個程序關閉。如管道(無名字)和FIFO(路徑名,在檔案系統中有名字),他們的資料分別在記憶體和檔案系統中;
隨核心:核心重新自舉或顯式刪除。如訊息佇列,訊號量和共享記憶體區。

隨檔案系統:顯式刪除。如使用對映檔案實現的Posix訊息佇列,訊號量和共享記憶體區。

1.4 名字空間
針對無親緣關係的IPC物件進行命名或給予識別符號,它是某種給定IPC型別中可能的名字的集合。即某種IPC物件名字的集合。
IPC由Posix.1和Unix 98標準化,套接字API在Posix.1g工作組標準化。對各種IPC特性都有說明,每種特性都有強制,未定義和可選三種選擇。

1.5 fork,exec和exit對IPC物件的影響
如System V IPC的三種形式沒有開啟或關閉的說法,知道IPC識別符號的任何程序都能訪問它們。我之前看過一遍,以下的兩個表格會在以後的閱讀中經常用到。
表一注意記住IPC型別對應的IPC開啟後的標識;表二注意記住fork後的作用,exec也較多涉及,之後的_exit遇到相應程式時再回頭查閱即可。

表1:


表2:


1.6 出錯處理:包裹函式
包裹函式執行實際的函式呼叫,測試其返回值,並在碰到錯誤時終止程序,此外,在包裹函式中,執行緒函數出錯時可以分配一個變數來儲存函式返回值,賦值給errno,之後輸出相應的出錯訊息。
void Pthread_mutex_lock(pthread_mutex_t *mptr){ //包裹函式可以縮短篇幅
int n;
if((n = pthread_mutex_lock(mptr)) == 0) //最底層函式
return;
errno = n;
err_sys("pthread_mutex_lock error");
}
全域性變數errno被設定成一個指示錯誤型別的正數,定義在標頭檔案<sys/errno.h>中,err_sys函式檢查errno的值並輸出相應的出錯訊息。errno的值只在某個函式發生錯誤時設定,如果該函式不返回錯誤,errno的值就無定義。

1.7 Unix標準
可移植作業系統介面Posix標準:Posix.1定義了訪問Unix的基本C介面,Posix定義了標準命令,商業標準也在迅速的吸納並擴充套件Posix標準,如Open Group的Unix98標準。

1.8 書中IPC例子索引表

三種互動模式:檔案伺服器(客戶傳送路徑名,伺服器返回檔案內容);生產者-消費者(共享緩衝區中資料的存放與取出);序列號持續增1(多執行緒將序列號持續加1,該序列號有時在共享檔案中,有時在共享記憶體區中)。
/***P12 相應例子在文中的頁碼,最好實現一下***/

1.9 小結
a,4個主要領域:訊息傳遞(管道,FIFO,訊息佇列);同步(互斥鎖,條件變數,讀寫鎖,訊號量);共享記憶體區(匿名共享記憶體區,有名共享記憶體區);過程呼叫(Solaris門,Sun RPC)。
b,有些型別的IPC沒有名字(管道,互斥鎖,條件變數,讀寫鎖),有些具有在檔案系統中的名字(FIFO)。
c,posix和system v有什麼區別:System VC IPC存在時間比較老,許多系統都支援但是介面複雜,可能各平臺上實現略有區別;POSIX是新標準,現在多數UNIX也已實現,語法簡單,並且各平臺上實現都一樣。他們都是在Unix下應用程式共同遵循的一種規範。

第二章 Posix IPC
2.1 概述
3種Posix IPC的共同屬性:用於標識IPC的路徑名,開啟或建立時需要指定的標誌以及訪問許可權,即pathname,oflag,mode。
下表彙總了所有Posix IPC函式,最好記住這些介面函式,記憶時主要看_前面的字首:
圖1:


說說unlink與close的區別:每一個檔案,都可以通過一個stat結構體來獲得檔案資訊,其中一個成員st_nlink代表檔案的連結數。當通過touch或指定O_CREAT建立檔案時,會新增一目錄項且檔案的連結數為1。當檔案存在時,程序開啟該檔案並不會影響該檔案的連結數,只是使呼叫程序與檔案之間建立一種訪問關係,即引用數增1,open之後返回fd,此後呼叫程序可以通過fd來read 、write 、 ftruncate等等一系列對檔案的操作,close()的作用是消除這種呼叫程序與檔案之間的訪問關係,使檔案引用數減1,自然,不會影響檔案的連結數。在呼叫close時,核心會檢查開啟該檔案的程序數,如果此引用數為0,進一步檢查檔案的連結數,如果這個數也為0,那麼就刪除檔案(在記憶體中的)內容。
unlink函式刪除目錄項,並且減少一個連結數,如果連結數達到0並且沒有任何程序開啟該檔案,該檔案內容才被真正刪除。如果在unlilnk之前沒有close,即還有程序引用,那麼依舊可以訪問檔案內容,當然檔名不存在了,fd還是可以訪問。
link函式建立一個新目錄項,並且增加一個連結數。綜上所訴,真正影響連結數的操作是link、unlink以及open的建立。 刪除檔案內容的真正含義是檔案的連結數為0,而這個操作的本質完成者是unlink。

2.2 IPC名字
IPC名字是指mq_open,sem_open和shm_open的第一個引數,如/tmp/queue.1234。名字以單獨的斜槓符打頭,假如在/tmp目錄下用mq_open建立訊息佇列,且引數為/queue.1234,在平臺Solaris2.6上的/tmp下建立
/tmp/.MQDqueue.1234,/tmp/.MQLqueue.1234,/tmp/.MQPqueue.1234,而平臺Digital Unix4.0僅建立/tmp/.queue.1234。
考慮到移植性:1,應該把Posix IPC名字的#define行放在一個頭檔案中,移植到另一平臺上,只需修改該命令列即可;2此外也可以使用px_ipc_name函式解決移植性。
例子:

1,i = snprintf(a, 9, "%012d", 12345); printf("i = %lu, a = %s\n", i, a);// 輸出:i = 12, a = 00000001;函式功能是將可變個引數(...)按照format格式化成字串,返回值是欲寫入的字串長度。

2,char *getenv(char *envvar); //getenv()用來取得引數envvar環境變數的內容,找不到符合的環境變數名稱則返回NULL。

3,char *pc_ipc_name(const char *name); //為IPC名字新增上正確的字首目錄,並返回完整的IPC名字,如呼叫pc_ipc_name("test1")

{pc_ipc_name函式具體實現中包含snprintf(dst, PATH_MAX, "%S%S%S", dir, "/", name); 其中當dir = getenv("PC_IPC_NAME")返回NULL後,dir = POSIX_IPC_PREFIX或"/tmp",即直接獲取IPC名字或獲取系統環境變數設定的目錄作為字首}

此外,Posix.1定義了三個巨集:S_TYPEISMQ(buf),S_TYPEISSEM(buf),S_TYPEISSHM(buf),buf指向stat結構,用來判別是否是佇列,訊號量或共享記憶體的IPC物件。


2.3 建立與開啟IPC物件
mq_open,sem_open,shm_open函式的三個引數:name + oflag(如共享儲存區不以O_WRONLY模式開啟) + mode(umask函式指定許可權位S_IRUSR,S_IRGRP,S_IWOTH等)
如下所示,其中共享儲存區不以O_WRONLY模式開啟,訊號量操作都需要讀寫訪問權:

圖2:


oflag:表示怎樣開啟IPC物件,O_RDONLY(只讀),O_WRONLY(只寫),O_RDWR(讀寫),O_CREAT(新建),O_EXCL(排他,檔案不能在已存在時建立),O_NOBLOCK(不阻塞),O_TRUNC(截斷)
O_CREATE:開啟IPC物件時,其使用者ID設定為當前程序有效使用者ID,組ID設定為當前程序有效組ID。
Posix建立與開啟一個IPC物件的邏輯:程序open某一IPC物件,若不存在,檢查是否設定O_CREAT,未設定時,出錯返回ENOENT,否則檢查裝置表格,無空間返回ENOSPC;
若物件存在,檢查是否同時設定了O_CREAT和O_EXCL,是返回EEXIST,否則檢查訪問許可權,出錯返回EACCES。
說說在Unix程序中使用者(組)ID和有效使用者(組)ID的區別:
1、實際使用者ID和實際使用者組ID:標識我是誰。也就是登入使用者的uid和gid,比如我的Linux以simon登入,在Linux執行的所有的命令的實際使用者ID都是simon的uid,實際使用者組ID都是simon的gid(可以用id命令檢視)。
2、有效使用者ID和有效使用者組ID:程序用來決定我們對資源的訪問許可權。一般情況下,有效使用者ID等於實際使用者ID,有效使用者組ID等於實際使用者組ID。當設定-使用者-ID(SUID)位設定,則有效使用者ID等於檔案的所有者的uid,而不是實際使用者ID;同樣,如果設定了設定-使用者組-ID(SGID)位,則有效使用者組ID等於檔案所有者的gid,而不是實際使用者組ID。

2.4 IPC許可權
程序的相關許可權資訊:建立IPC物件時的許可權位+訪問型別(O_RDONLY..);呼叫程序的有效使用者ID,有效組ID和輔助組ID。
IPC物件的許可權測試步驟:
1,程序身份為root直接允許訪問IPC物件;2,程序有效使用者ID等於IPC物件屬主ID,且設定使用者許可權位(讀位或寫位)則可訪問(讀或寫IPC物件);
3,程序有效組ID和輔助組ID等於IPC物件的組ID且設定組訪問許可權(讀位或寫位)則可訪問(讀或寫IPC物件);
4,程序的其他使用者訪問許可權位已設定(讀位或寫位)則可訪問(讀或寫IPC物件)。

2.5 小結
三種類型的Posix IPC都是用路徑名標識的,以pc_ipc_name獲得實際路徑名。
建立或開啟IPC物件時,指定了一組oflag標記。




第三章 System V IPC
3.1 概述
本章描述System V IPC三種方式的共同屬性。
下圖是System V IPC函式彙總,注意兩種標準的函式命名區別:msgget,msgctl,msgsnd,msgrcv VS mq_open,mq_close,mq_unlink,mq_getattr,mq_setattr,mq_send,mq_receive,mq_notify
圖1:


3.2 key_t鍵和ftok函式
key_t ftok(const char *pathname, int id); //把一個路徑名和一個整數識別符號的低序8位轉換成至少32位的整數型別key_t值,即返回IPC鍵或-1
注意檢視第一章的表1,System V的三種IPC是以IPC識別符號或key_t值標識。
由pathname查詢所在檔案的資訊存到stat結構中,id的低序8位(標識一個通道,從1開始編號) + stat結構的st_dev的低12位(檔案裝置編號/檔案系統資訊) + st_ino的低12位(檔案i-node索引結點號) = 組合成IPC鍵
檔案每次建立時由系統賦予的索引節點號很可能不一樣,對於下一個呼叫者來說,由ftok返回的鍵也可能不同,因此路徑名產生鍵的檔案不能是伺服器存活期間反覆建立並刪除的檔案。

3.3 ipc_perm結構
核心為每一個IPC物件維護一個資訊結構:
struct ipc_perm { uid; gid; cuid; cgid; mode; seq; key;}; //IPC物件資訊結構。實際使用者ID,實際使用者組ID,屬主使用者ID,屬主使用者組ID,讀寫許可權;槽位使用序列號;鍵值

3.4 建立與開啟IPC通道
int msgget(key_t key, int msgflag); //用於建立一個新的(IPC_PRIVATE)或開啟一個已經存在的(由pathname和id生成的key_t值)訊息佇列。呼叫成功返回佇列識別符號或者返回-1。
msgget(), semget(), shmget(),如果key為IPC_PRIVATE則能保證建立一個唯一的IPC物件。 由key + oflag來判斷邏輯

圖2:


System V建立與開啟一個IPC物件的邏輯:程序首先判斷鍵值是否為IPC_PRIVATE,若是,則檢測系統表格是否還有空間,若還有空間,則此時oflag標誌為O_CREATE或O_CREATE|O_EXCL時都會呼叫成功;

若系統表格已滿,則返回ENOSPC。若鍵值不為IPC_PRIVATE,且鍵值不存在,若指定了O_CREAT,也會呼叫成功,未指定O_CREAT則會返回ENOENT;若此時鍵值存在,則看O_CREAT和O_EXCL是否都被設定,
是的話返回EEXIST,否則檢視訪問許可權是否允許,最後成功呼叫或返回EACCES。

3.5 IPC許可權
System V的6個許可權位:MSG_R MSG_W MSG_R>>3 MSG_W>>3 MSG_R>>6 MSG_W>>6。(訊號量SEM_R...;共享記憶體區SHM_R...)
呼叫getXXX函式時,oflag引數中的某些位初始化ipc_perm結構中的mode成員。建立者ID(cuid)和屬主ID(uid)設定初始時都可設定為呼叫程序的有效使用者ID和有效組ID。其中建立者(組)ID(ipc_perm結構中的cuid成員)一直不變,因為對IPC物件而言,建立者只有一個,但是可呼叫相應IPC機制的ctlXXX函式或者超級使用者使用chown命令來修改屬主ID(ipc_perm結構的uid成員),即改變IPC物件的屬主。
每當有一個程序試圖使用msgsnd函式往某個訊息佇列放置一個訊息時,就按照以下順序測試許可權:
超級使用者直接賦予訪問權-->當前程序的有效使用者ID等於IPC物件的uid或cuid值,而且相應的訪問位在該IPC物件的mode成員中是開啟的,則賦予訪問權-->當前程序的有效使用者組ID等於IPC物件的gid或cgid值,而且相應的訪問位在該IPC物件的mode成員中是開啟的,則賦予訪問權-->其他使用者訪問位在該IPC物件的mode成員中必須是開啟的才能賦予訪問權。

3.6 識別符號重用
int msgctl(int msgqid, int cmd, struct msqid_ds *buf);//對msqid標識的訊息佇列執行cmd操作列。成功返回0否則返回-1.佇列的msqid_ds結構來描述隊列當前的狀態。

cmd:

IPC_STAT讀取訊息佇列的資料結構msqid_ds並存儲在buf中;IPC_SET由buf來設定訊息佇列的msqid_ds中的ipc_perm元素的值;  IPC_RMID從系統核心中移走訊息佇列。
每刪除一個IPC物件時,核心就遞增相應的槽位號seq,如果一個程序最多有約50個描述符,則遞增50。它實則是核心為系統中每個潛在的IPC物件維護的計數器。
每次重用一個IPC表項時,把返回給呼叫程序的識別符號值增加一個IPC表項數。
槽位號seq存在的原因:1,把IPC識別符號的可能範圍擴大到包含所有整數;2,避免短時間內重用System V IPC識別符號。

3.7 ipcs和ipcrm程式
輸出System V IPC物件特性的各種資訊;刪除一個System V訊息佇列,訊號量集或共享記憶體區。

3.8 核心限制
例如訊息佇列的最大數目,每個訊號量集的最大訊號量等等。
/sbin/sysconfig -q ipc //查詢System V IPC的當前各種輸出限制值,如max-max,msg-mnb等
也可在/etc/system檔案中加入一些語句進行修改,如:set msgsys:msginfo_msgseg = value

3.9 小結
msgget,semget,shmget函式的用途和引數使用;ipc_perm結構各成員表示的資訊,如槽號seq;屬主uid及許可權等;核心限制。