在 XSI IPC 中,有三種IPC:消息隊列、信號量 以及 共享內存,他們之間有許多相似之處。
每個進程各自有不同的用戶地址空間,任何一個進程的全局變量在另一個進程中都看不到,所以進程之間要交換數據必須通過內核,在內核中開闢一塊緩衝區,進程A把數據從用戶空間拷到內核緩衝區,進程B再從內核緩衝區把數據讀走,內核提供的這種機制稱爲進程間通信。
1、標識符 和 鍵
(1)key 鍵
每個內核中的IPC結構(消息隊列、信號量和共享內存)都用了一個非負數的標識符。例如,如果要向一個消息隊列發送消息或者從一個消息隊列取消息,只需要知道其隊列標識符。與文件描述符不同,IPC標識符不是最小整數,當一個IPC結構被創建,然後又被刪除,與這種結構相關的標識符連續加1,直至達到一整數的最大值,然後又迴轉到0。
標識符是IPC對象的內部名,爲使多個合作進程能夠在同一IPC對象上匯聚,需要提供一個外部命名方案,爲此,每個IPC對象都與一個key相關聯,這個key就是該對象的外部名。
對於key值,應用程序有如下三種選擇:
① 調用ftok,給它傳遞pathname和proj_id,操作系統根據兩者合成key值。
② 指定key爲IPC_PRIVATE,內核保證創建一個新的、唯一的IPC對象,IPC標識符與內存中的標識符不會衝突。IPC_PRIVATE爲宏定義,其值等於0。
③ 指定key爲大於0的常數,這需要用戶自行保證生成的IPC key值不與系統中存在的衝突,而前兩種是操作系統保證的。
ftok的典型實現是調用stat函數,然後組合以下三個值:
① path 所在的文件系統的信息(stat結構的st_dev成員)。
② 該文件在本文件系統內的索引節點號(stat結構的st_ino成員)。
③ id的低序8位(不能爲0)。
以下程序可以說明這一點:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char **argv)
{
struct stat stat1 ;
if ( argc != 2 )
{
printf("usage: ftok < pathname >\n" ) ;
exit(1) ;
}
stat( argv[1], &stat1 ) ;
printf("st_dev:%lx, st_ino:%lx, key:%x\n", \
(unsigned long)stat1.st_dev, (unsigned long)stat1.st_ino , ftok(argv[1],0x579 )) ;
printf("st_dev:%lx, st_ino:%lx, key:%x\n", \
(unsigned long)stat1.st_dev, (unsigned long)stat1.st_ino , ftok(argv[1],0x122 )) ;
printf("st_dev:%lx, st_ino:%lx, key:%x\n", \
(unsigned long)stat1.st_dev, (unsigned long)stat1.st_ino , ftok(argv[1],0x22 )) ;
exit(0) ;
}
運行結果:
st_dev:801, st_ino:a0001, key:79010001
st_dev:801, st_ino:a0001, key:22010001
st_dev:801, st_ino:a0001, key:22010001
根據上述結果可以看出,通過ftok返回的是根據文件(pathname)信息和計劃編號(proj_id)合成的IPC key鍵值,從而避免用戶使用key值的衝突。proj_id值的意義讓一個文件也能生成多個IPC key鍵值。ftok利用同一文件最多可得到IPC key鍵值0xff(即256)個,因爲ftok只取proj_id值二進制的後8位,即16進制的後兩位與文件信息合成IPC key鍵值。
需要注意的是:如果兩個路徑名引用的是兩個不同的文件,那麼ftok會返回不同的key;如果兩個路徑相同,試用同一個id值,那麼有可能會返回相同的key。
在 XSI IPC 三種get函數中(msgget、semget、shmget)都會有兩個相似的參數:key 和 flag
在創建新的IPC結構時(通常是由服務器創建),如果key是IPC_PRIVATE 或者 和當前某種類型的IPC結構無關,則flag需要用 IPC_CREAT 標誌位。
在引用現有的IPC結構時(通常是客戶端),key 必須等於 服務器創建的 key。 IPC_CREAT 不必可以聲明。注意:IPC_PRIVATE 不能作爲客戶端引用的key,IPC_PRIVATE只能創建,如果要引用 IPC_PRIVATE 需要用其相關的標識符(也就是前面講的內部名,下面詳細說明),然後利用msgsnd、msgrev等函數使用標識符。這樣繞過get。
flag的使用:
(2)標識符
給semget、msgget、shmget傳入key值,它們返回的都是相應的IPC對象標識符。注意IPC鍵值和IPC標識符是兩個概念,後者是建立在前者之上。下圖畫出了從IPC鍵值生成IPC標識符圖,其中key爲IPC鍵值,由ftok函數生成;ipc_id爲IPC標識符,由semget、msgget、shmget函數生成。ipc_id在信號量函數中稱爲semid,在消息隊列函數中稱爲msgid,在共享內存函數中稱爲shmid,它們表示的是各自IPC對象標識符。
2、創建 或 打開 IPC對象
semget、msgget、shmget函數的作用是創建一個新的IPC對象或者訪問一個已存在的IPC對象。其創建或訪問的規則如下:
① 指定key爲IPC_PRIVATE操作系統保證創建一個唯一的IPC對象。
② 設置flag參數的IPC_CREAT位但不設置它的IPC_EXCL位時,如果所指定key鍵的IPC對象不存在,那就是創建一個新的對象;否則返回該對象。
③ 同時設置flag的IPC_CREAT和IPC_EXCL位時,如果所指定key鍵的IPC對象不存在,那就創建一個新的對象;否則返回一個EEXIST錯誤,因爲該對象已存在。
綜上所述,flag創建模式標誌的作用如下表15-3所示。
表15-3 三種xxxget函數flag的創建模式標誌作用表
flag創建模式標誌 |
不存在 |
已存在 |
無特殊標誌 |
出錯,errno=ENOENT |
成功,引用已存在對象 |
IPC_CREAT |
成功,創建新對象 |
成功,引用已存在對象 |
IPC_CREAT|IPC_EXCL |
成功,創建新對象 |
出錯,errno=EEXIST |
下圖15-2畫出了semget、msgget、shmget創建或打開一個IPC對象的邏輯流程圖,它說明了內核創建和訪問IPC對象的流程。
圖15-2 semget、msgget、shmget創建或打開一個IPC對象的邏輯流程圖
使用semget、msgget、shmget創建一個IPC對象時,需要指定flag標誌,在key不等於IPC_PRIVATE情況下,flag標誌決定了創建方式和創建後IPC對象的存取權限。在key等於IPC_PRIVATE情況下,flag標誌決定了創建後IPC對象的存取權限。如果只是引用一個已經存在的IPC對象只需把flag標誌設爲0即可。
3、有關該函數的三個常見問題:
1.pathname是目錄還是文件的具體路徑,是否可以隨便設置
2.pathname指定的目錄或文件的權限是否有要求
3.proj_id是否可以隨便設定,有什麼限制條件
解答:
1、ftok根據路徑名,提取文件信息,再根據這些文件信息及project ID合成key,該路徑可以隨便設置。
2、該路徑是必須存在的,ftok只是根據文件inode在系統內的唯一性來取一個數值,和文件的權限無關。
3、proj_id是可以根據自己的約定,隨意設置。這個數字,有的稱之爲project ID; 在UNIX系統上,它的取值是1到255;
4、關於ftok()函數的一個陷阱
在使用ftok()函數時,裏面有兩個參數,即fname和id,fname爲指定的文件名,而id爲子序列號,這個函數的返回值就是key,它與指定的文件的索引節點號和子序列號id有關,這樣就會給我們一個誤解,即只要文件的路徑,名稱和子序列號不變,那麼得到的key值永遠就不會變。
事實上,這種認識是錯誤的,想想一下,假如存在這樣一種情況:在訪問同一共享內存的多個進程先後調用ftok()時間段中,如果fname指向的文件或者目錄被刪除而且又重新創建,那麼文件系統會賦予這個同名文件新的i節點信息,於是這些進程調用的ftok()都能正常返回,但鍵值key卻不一定相同了。由此可能造成的後果是,原本這些進程意圖訪問一個相同的共享內存對象,然而由於它們各自得到的鍵值不同,實際上進程指向的共享內存不再一致;如果這些共享內存都得到創建,則在整個應用運行的過程中表面上不會報出任何錯誤,然而通過一個共享內存對象進行數據傳輸的目 的將無法實現。
這是一個很重要的問題,希望能謹記!!!
所以要確保key值不變,要麼確保ftok()的文件不被刪除,要麼不用ftok(),指定一個固定的key值。