Unix 環境中 System V
進程間通信(IPC)包括3種機制:消息隊列、信號量、共享內存。消息隊列和信號量均是內核空間的系統對象,經由它們的數據需要在內核和用戶空間進行額外的數據拷貝;而共享內存和訪問它的所有應用程序均同處於用戶空間,應用進程可以通過地址映射的方式直接讀寫內存,從而獲得非常高的通信效率。
1.1、創建共享內存
核心函數 shmget 函數用於創建(或者獲取)一個由key鍵值指定的共享內存對象,返回該對象的系統標識符:shmid;
共享內存對象系統標識 shmid = shmget(key, size, 0666|IPC_CREAT|IPC_EXCL)
shmget() -- 建立共享內存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
key_t key
-----------------------------------------------
key標識共享內存的鍵值: 0/IPC_PRIVATE。 當key的取值爲IPC_PRIVATE,則函數shmget()將創建一塊新的共享內存;如果key的取值爲0,而參數shmflg中設置了IPC_PRIVATE這個標誌,則同樣將創建一塊新的共享內存。
在IPC的通信模式下,不管是使用消息隊列還是共享內存,甚至是信號量,每個IPC的對象(object)都有唯一的名字,稱爲“鍵”(key)。通過“鍵”,進程能夠識別所用的對象。“鍵”與IPC對象的關係就如同文件名稱之於文件,通過文件名,進程能夠讀寫文件內的數據,甚至多個進程能夠共用一個文件。而在IPC的通訊模式下,通過“鍵”的使用也使得一個IPC對象能爲多個進程所共用。
Linux系統中的所有表示System V中IPC對象的數據結構都包括一個ipc_perm結構,其中包含有IPC對象的鍵值,該鍵用於查找System V中IPC對象的引用標識符。如果不使用“鍵”,進程將無法存取IPC對象,因爲IPC對象並不存在於進程本身使用的內存中。
通常,都希望自己的程序能和其他的程序預先約定一個唯一的鍵值,但實際上並不是總可能的成行的,因爲自己的程序無法爲一塊共享內存選擇一個鍵值。因此,在此把key設爲IPC_PRIVATE,這樣,操作系統將忽略鍵,建立一個新的共享內存,指定一個鍵值,然後返回這塊共享內存IPC標識符ID。而將這個新的共享內存的標識符ID告訴其他進程可以在建立共享內存後通過派生子進程,或寫入文件或管道來實現。
int size(單位字節Byte)
-----------------------------------------------
size是要建立共享內存的長度。所有的內存分配操作都是以頁爲單位的。所以如果一段進程只申請一塊只有一個字節的內存,內存也會分配整整一頁(在i386機器中一頁的缺省大小PACE_SIZE=4096字節)這樣,新創建的共享內存的大小實際上是從size這個參數調整而來的頁面大小。即如果size爲1至4096,則實際申請到的共享內存大小爲4K(一頁);4097到8192,則實際申請到的共享內存大小爲8K(兩頁),依此類推。
int shmflg
-----------------------------------------------
shmflg主要和一些標誌有關。其中有效的包括IPC_CREAT和IPC_EXCL,它們的功能與open()的O_CREAT和O_EXCL相當。
IPC_CREAT 如果共享內存不存在,則創建一個共享內存,否則打開操作。
IPC_EXCL 只有在共享內存不存在的時候,新的共享內存才建立,否則就產生錯誤。
如果單獨使用IPC_CREAT,shmget()函數要麼返回一個已經存在的共享內存的操作符,要麼返回一個新建的共享內存的標識符。如果將IPC_CREAT和IPC_EXCL標誌一起使用,shmget()將返回一個新建的共享內存的標識符;如果該共享內存已存在,或者返回-1。IPC_EXEL標誌本身並沒有太大的意義,但是和IPC_CREAT標誌一起使用可以用來保證所得的對象是新建的,而不是打開已有的對象。對於用戶的讀取和寫入許可指定SHM_R和SHM_W,(SHM_R>3)和(SHM_W>3)是一組讀取和寫入許可,而(SHM_R>6)和(SHM_W>6)是全局讀取和寫入許可。
返回值
-----------------------------------------------
成功返回共享內存的標識符;不成功返回-1,errno儲存錯誤原因。
EINVAL 參數size小於SHMMIN或大於SHMMAX。
EEXIST 預建立key所致的共享內存,但已經存在。
EIDRM 參數key所致的共享內存已經刪除。
ENOSPC 超過了系統允許建立的共享內存的最大值(SHMALL )。
ENOENT 參數key所指的共享內存不存在,參數shmflg也未設IPC_CREAT位。
EACCES 沒有權限。
ENOMEM 核心內存不足。
struct shmid_ds
-----------------------------------------------
shmid_ds數據結構表示每個新建的共享內存。當shmget()創建了一塊新的共享內存後,返回一個可以用於引用該共享內存的shmid_ds數據結構的標識符。
include/linux/shm.h
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
struct ipc_perm
-----------------------------------------------
對於每個IPC對象,系統共用一個struct ipc_perm的數據結構來存放權限信息,以確定一個ipc操作是否可以訪問該IPC對象。
struct ipc_perm {
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode;
unsigned short seq;
};
1.2、獲取共享內存的指針
核心處理函數: shmat(shmid, NULL, 0);
shmat()是用來允許本進程訪問一塊共享內存的函數。
int shmid是那塊共享內存的ID。
char *shmaddr是共享內存的起始地址
int shmflag是本進程對該內存的操作模式。如果是SHM_RDONLY的話,就是隻讀模式。其它的是讀寫模式
成功時,這個函數返回共享內存的起始地址。失敗時返回-1
1.3、其他的共享內存操作函數簡要說明
# include <sys/types.h>
# include <sys/ipc.h>
# include <sys/shm.h>
key_t ftok(const char *pathname, int proj_id);
int shmget(key_t key, int size, int shmflg);
void* shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
ftok |
函 數用於生成一個鍵值:key_t key,該鍵值將作爲共享內存對象的唯一性標識符,並提供給爲shmget函數作爲其輸入參數;ftok 函數的輸入參數包括一個文件(或目錄)路徑名:pathname,以及一個額外的數字:proj_id,其中pathname所指定的文件(或目錄)要求 必須已經存在,且proj_id不可爲0; |
shmget |
函數用於創建(或者獲取)一個由key鍵值指定的共享內存對象,返回該對象的系統標識符:shmid; |
shmat |
函數用於建立調用進程與由標識符shmid指定的共享內存對象之間的連接; |
shmdt |
函數用於斷開調用進程與共享內存對象之間的連接; |
shmctl |
函數用於對已創建的共享內存對象進行查詢、設值、刪除等操作; |
1.4、/*創建信號量*/
其中的核心函數:
if((semid = semget(key, semnum, 0666|IPC_CREAT|IPC_EXCL)) < 0)
信號量函數 semget() semop() semctl()
semget()
可以使用系統調用semget()創建一個新的信號量集,或者存取一個已經存在的信號量集:
系統調用:semget();
原型:intsemget(key_t key,int nsems,int semflg);
返回值:如果成功,則返回信號量集的IPC標識符。如果失敗,則返回-1:errno=EACCESS(沒有權限)
EEXIST(信號量集已經存在,無法創建)
EIDRM(信號量集已經刪除)
ENOENT(信號量集不存在,同時沒有使用IPC_CREAT)
ENOMEM(沒有足夠的內存創建新的信號量集)
ENOSPC(超出限制)
系統調用semget()的第一個參數是關鍵字值(一般是由系統調用ftok()返回的)。系統內核將此值和系統中存在的其他的信號量集的關鍵字值進行比較。打開和存取操作與參數semflg中的內容相關。IPC_CREAT如果信號量集在系統內核中不存在,則創建信號量集。IPC_EXCL當和 IPC_CREAT一同使用時,如果信號量集已經存在,則調用失敗。如果單獨使用IPC_CREAT,則semget()要麼返回新創建的信號量集的標識符,要麼返回系統中已經存在的同樣的關鍵字值的信號量的標識符。如果IPC_EXCL和IPC_CREAT一同使用,則要麼返回新創建的信號量集的標識符,要麼返回-1。IPC_EXCL單獨使用沒有意義。參數nsems指出了一個新的信號量集中應該創建的信號量的個數。信號量集中最多的信號量的個數是在linux/sem.h中定義的:
#defineSEMMSL32/*<=512maxnumofsemaphoresperid*/
下面是一個打開和創建信號量集的程序:
intopen_semaphore_set(key_t keyval,int numsems)
{
intsid;
if(!numsems)
return(-1);
if((sid=semget(mykey,numsems,IPC_CREAT|0660))==-1)
{
return(-1);
}
return(sid);
}
};
==============================================================
semop()
系統調用:semop();
調用原型:int semop(int semid,struct sembuf*sops,unsign ednsops);
返回值:0,如果成功。-1,如果失敗:errno=E2BIG(nsops大於最大的ops數目)
EACCESS(權限不夠)
EAGAIN(使用了IPC_NOWAIT,但操作不能繼續進行)
EFAULT(sops指向的地址無效)
EIDRM(信號量集已經刪除)
EINTR(當睡眠時接收到其他信號)
EINVAL(信號量集不存在,或者semid無效)
ENOMEM(使用了SEM_UNDO,但無足夠的內存創建所需的數據結構)
ERANGE(信號量值超出範圍)
第一個參數是關鍵字值。第二個參數是指向將要操作的數組的指針。第三個參數是數組中的操作的個數。參數sops指向由sembuf組成的數組。此數組是在linux/sem.h中定義的:
/*semop systemcall takes an array of these*/
structsembuf{
ushortsem_num;/*semaphore index in array*/
shortsem_op;/*semaphore operation*/
shortsem_flg;/*operation flags*/
sem_num將要處理的信號量的個數。
sem_op要執行的操作。
sem_flg操作標誌。
如果sem_op是負數,那麼信號量將減去它的值。這和信號量控制的資源有關。如果沒有使用IPC_NOWAIT,那麼調用進程將進入睡眠狀態,直到信號量控制的資源可以使用爲止。如果sem_op是正數,則信號量加上它的值。這也就是進程釋放信號量控制的資源。最後,如果sem_op是0,那麼調用進程將調用sleep(),直到信號量的值爲0。這在一個進程等待完全空閒的資源時使用。
===============================================================
semctl()
系統調用:semctl();
原型:int semctl(int semid,int semnum,int cmd,union semunarg);
返回值:如果成功,則爲一個正數。
如果失敗,則爲-1:errno=EACCESS(權限不夠)
EFAULT(arg指向的地址無效)
EIDRM(信號量集已經刪除)
EINVAL(信號量集不存在,或者semid無效)
EPERM(EUID沒有cmd的權利)
ERANGE(信號量值超出範圍)
系統調用semctl用來執行在信號量集上的控制操作。這和在消息隊列中的系統調用msgctl是十分相似的。但這兩個系統調用的參數略有不同。因爲信號量一般是作爲一個信號量集使用的,而不是一個單獨的信號量。所以在信號量集的操作中,不但要知道IPC關鍵字值,也要知道信號量集中的具體的信號量。這兩個系統調用都使用了參數cmd,它用來指出要操作的具體命令。兩個系統調用中的最後一個參數也不一樣。在系統調用msgctl中,最後一個參數是指向內核中使用的數據結構的指針。我們使用此數據結構來取得有關消息隊列的一些信息,以及設置或者改變隊列的存取權限和使用者。但在信號量中支持額外的可選的命令,這樣就要求有一個更爲複雜的數據結構。
系統調用semctl()的第一個參數是關鍵字值。第二個參數是信號量數目。
參數cmd中可以使用的命令如下:
·IPC_STAT讀取一個信號量集的數據結構semid_ds,並將其存儲在semun中的buf參數中。
·IPC_SET設置信號量集的數據結構semid_ds中的元素ipc_perm,其值取自semun中的buf參數。
·IPC_RMID將信號量集從內存中刪除。
·GETALL用於讀取信號量集中的所有信號量的值。
·GETNCNT返回正在等待資源的進程數目。
·GETPID返回最後一個執行semop操作的進程的PID。
·GETVAL返回信號量集中的一個單個的信號量的值。
·GETZCNT返回這在等待完全空閒的資源的進程數目。
·SETALL設置信號量集中的所有的信號量的值。
·SETVAL設置信號量集中的一個單獨的信號量的值。
參數arg代表一個semun的實例。semun是在linux/sem.h中定義的:
/*arg for semctl systemcalls.*/
unionsemun{
intval;/*value for SETVAL*/
structsemid_ds*buf;/*buffer for IPC_STAT&IPC_SET*/
ushort*array;/*array for GETALL&SETALL*/
structseminfo*__buf;/*buffer for IPC_INFO*/
void*__pad;
val當執行SETVAL命令時使用。buf在IPC_STAT/IPC_SET命令中使用。代表了內核中使用的信號量的數據結構。array在使用GETALL/SETALL命令時使用的指針。
下面的程序返回信號量的值。當使用GETVAL命令時,調用中的最後一個參數被忽略:
intget_sem_val(intsid,intsemnum)
{
return(semctl(sid,semnum,GETVAL,0));
}
下面是一個實際應用的例子:
#defineMAX_PRINTERS5
printer_usage()
{
int x;
for(x=0;x<MAX_PRINTERS;x++)
printf("Printer%d:%d\n\r",x,get_sem_val(sid,x));
}
下面的程序可以用來初始化一個新的信號量值:
void init_semaphore(int sid,int semnum,int initval)
{
union semunsemopts;
semopts.val=initval;
semctl(sid,semnum,SETVAL,semopts);
}
注意系統調用semctl中的最後一個參數是一個聯合類型的副本,而不是一個指向聯合類型的指針。