轉載:UNIX系統共享內存的應用編程技術

UNIX系統共享內存的應用編程技術

中國工商銀行惠安縣支行(362100) 莊文祥

 

共享內存(Shared Memory,下簡稱SHM)是指由一個進程創建並可與其他進程共享的內存
,UNIX系統中利用SHM可以實現進程間通信(IPC)。爲了對系統共享資源(包括SHM)進行
訪問的互斥控制,就要用到信號量(Semaphore)機制。系統要求進程在存取共享內存之前應
該先獲得相應的信號量的控制。在編程中,共享內存常常要與信號量產生相應的關係。
,共享內存的創建與控制
UNIX
系統開發軟件包提供了一系列的函數來實現共享內存的創建與控制。比如調用sh
mget
函數用於創建共享內存;調用shmctl函數用於控制共享內存。例程如下
:
#include
#include
#include
#include
#include
#include
#define SHMKEY Ox1688
#define NODENUM 20
int SHM_id;
struct shmid_ds shm_f;
if((SHM_id=shmget((ket_t)SHMKEY,NODENUM*sizeof(struct pidtos),IPC_CREAT
IPC
_EXCL
0660))<0)
prinf("Create Shared Memory Fail!");
shm_f.shm_perm.uid=220;/*
有效用戶主標識
*/
shm_f.shm_perm.gid=110;/*
有效用戶組標識
*/
shm_f.shm_perm.mode=0660;/*
操作許可
*/
if(shmctl(SHM_id,IPC_SET,&shm_f)<0)
printf("Set Shard Memory Error");
函數shmget的調用格式如下
:
int shmget(key,size,shmflg)
key_t key;/*SHM
關鍵值
*/
unsigned int size;/*SHM
長度
*/
int shmflg;/*
標誌
*/
作用是創建共享內存或獲取已創建的共享內存的標識符。實參shmflg的值必須是一個

整數,且規定下列內容:(1)訪問權限,(2)執行方式,(3)控制字段。在創建SHM,該標誌可設
IPC_CREAT(創建,值是01000)!IPC_EXCL(限制唯一創建,值是02000)!0660(訪問權限)。成
功時返回創建的共享內存標識符,失敗時返回-1。實參size的值應大於SHMMIN且小於SHMMA
X,
否則該系統調用失敗。當shmflg=0,用於獲取已存在的SHM的標識符。

函數shmctl的調用格式如下:
int shmctl(shmid,cmd,sbuf)
int shmid;/*
shmget獲取的SHM的標識符
*/
int cmd;/*
將對SHM進行的控制命令
*/
struct shmid_ds*sbuf;/*
存放操作數
*/
作用是對共享內存進行由cmd指定的控制操作。cmd的值比較有用的是下面兩個
:
IPC_SET
對於規定的shmid,設置有效用戶和組標識及操作權限。

IPC_RMID
連同其相關的SHM段數據結構一起,刪除規定的shmid。執行IPC_SETIPC_R
MID
的進程必須具有Owner/Creator或超級用戶的有效用戶標識。

系統創建的SHM僅僅是內存中一塊連續的區域,本身並沒有結構。用戶進程對共享內存
不能直接進行存取,需要將共享內存附接在進程的數據段上,進程才能對其進行存取,實現方
法是:用戶進程可以根據需要自行定義一個數據結構(pidtos),然後將共享內存的地址用
函數shmat賦值給指向結構pidtos的指針buf,相當於是給指針變量分配內存,buf指向共享
內存的起始處。然後就可用數組的方法,按數據結構的長度等分共享內存。這個過程可稱之
爲共享內存的"結構化"。例程如下:
struct pidtos{
char rhostname[10];
long pidsc;
}*buf;
int
if((buf=(struct pidtos*)shmat(SHM_idm,(char*)0,0))<0)
printf("Access SHM Error!");
for(i=0;i<NODENUM;I++)(
Strcpy(buf[i].rhostname,"");
buf[i].pidsc=0;
) /*
如果有必要,就初始化
SHM*/
shmdt((char*)buf);/*
拆接SHM,釋放指針
buf*/
函數shmatshmdt的調用格式
:
char*shmat(shmid,shmaddr,shmflg)
int shmid;/*SHM
標識符
*/
char*shmaddr;/*
相當於偏移量
*/
int shmflg;/*
標誌
*/
作用是將共享內存附接到進程的數據段上。實際上是將共享內存在主存中的地址
+shm
addr
後賦值給進程中的某一指針。shmaddr相當於偏移量,相對於共享內存在主存中的起始

地址。調用失敗時返回(char*)-1.shmflg可取值爲0,或者是SHM_RNDSHM_EDONLY中的一個
或兩者的或。
int shmdt(shmaddr)
char *shmaddr;/*
採用shmat函數的返回值*/
作用是拆接共享內存段,成功時返回0,失敗時返回-1

,信號量的創建與控制
爲了支持併發進程對共享內存的互斥訪問,保證共享內存中數據的一致性以及對共享內
存操作的完整性(原子性)。一個進程在SHM操作之前先要將SHM加鎖,在操作完成後再將其解
,爲此引入了信號量機制。信號量可用來控制一個共享資源,當然也包括共享內存。由於
許多應用需要使用一個以上的信號量,因此UNIX系統中具備有創建信號量組的能力。信號量
組可以含有一個或多個信號量。比如我們可以用一個信號量組中編號爲0的信號量來控制一
個資源,再用編號爲1的信號量來控制另一資源。當我們在編制程序時應該知道它們之間的
對應關係。信號量的創建與控制和共享內存的創建與控制很相似。如下面的例程:
#define SEMKEY 0x1680
int SEM_id;
struct semid_ds sem_f;
union semun{
int val;
struct semid_ds *buf;
ushort array[1];/*[]
中的值應根據信號量數目具體定義
*/
}arg;
if((SEM_id=semget((key_t)SEMKEY,1,IPC_CREAT
IPC_EXCL;0660)<0)
printf("Creat Semaphore Fail!");
arg.val=0;
if(semct(SEM_id,0,SETVAL,arg.val)<0)
printf("Access Semaphore Error!");
/*
信號量的值必須初始化。將編號爲0的信號量的值初始化爲
0*/
arg.buf=&sem_f;
sem_f.sem_perm.uid=220;/*
有效用戶主標識
*/
shm_f.shm_perm.gid=110;/*
有效用戶組標識
*/
shm_f.shm_perm.mode=0660;/*
操作許可
*/
if(semctl(SEM_id,IPC_SET,arg)<0)
printf("Set Semaphore Error!");
上述例程首先設置一個信號量組的關鍵值(SEMKEY),然後調用semgetr利用該關鍵值創

建只有一個信號量的信號量組。其中第三個參數的含義與函數shmget第三個參數的含義一
樣。
函數semget的調用格式:
int semget(key,nsems,semflg)
key_t key;/*
信號量組的關鍵值
*/
int nsems;/*
信號量個數
*/
int semflg;/*
信號量組的創建標誌
*/
用來創建一個信號量組,其中包含nsems個信號量,編號從0nsems-1;創建方式及訪問

權限由semflg指定。成功時初始化相應的控制塊信息,並返回創建的信號量組的標識符,
錯時返回-1。當semflg=0時用於獲取已存在的信號量的標識符。
函數semctl的調用格式:
int semctl(semid,semnum,cmd,arg)
int semid;/*
信號量組的標識符
*/
int semnum;/*
信號量的編號
*/
int cmd;/*
控制命令
*/
union semun{
int val;
struct semid_ds *buf;
ushort array[nsems];/*nsems
具體根據信號量的數目定義
*/
}arg;/*
操作數
*/
作用是對指定的信號量組或組中編號爲semnum的信號量進行由cmd指定的控制操作。比

較有用的cmd命令如下:
cmd
作用

SETVAL
將信號量(semid,semnm)的當前值置爲arg.val的值,常用於初始化某個信號量
IPC_SET
將信號量組的狀態信息設置成arg.buf中的內容
IPC_RMID
刪除信號量組標識符semid
對信號量的操作由semop函數來實現。當一個進程存取某一共享內存時,先查看相應信

號量的值,若爲0,則表示SHM目前空閒,未被其他進程佔用,那麼就將信號量置1,表示SHM已被
佔用,然後進程就可存取SHM;若信號量不爲0,表明此時SHM已被其他進程佔用了,於是這個進
程開始睡眠,等待其他進程釋放SHM,即信號量爲0,才被系統喚醒。如下面的例程:
static struct sembuf sem_lock[2]=(0,0,0,0,1,0);
if(semop(SEM_id,&sem_lock[0],2)<0)
printf("Access Semaphore Error!");/*
加鎖*/其中結構sembuf由系統定義
:
struct sembuf{
int sem_num;/*
信號量編號
*/
int sem_op;/*
信號量操作數
*/
int sem_flg;}/*
操作標誌
*/
數組sem_lock[2]可以看作是sem_lock[0]=(0,0,0)sem_lock[1]=(0,1,0)的組合。

上面的例程實際上是兩步合爲一步來做,是對同一個信號量(編號爲0)做兩次不同的操作。
經過這一步後,實際上可以看作是已經將SHM加鎖,進程接下來就可以創建或存取SHM了。如
果其他進程此時想佔用SHM,就必須等待。當操作完成後,爲了其他進程可以存取SHM,就要釋
放資源,將信號量的值重新清零,即一個解鎖的過程。
static struct sembuf sem_unlock[1]=(0,-1,IPC_NOWAIT);
if(semop(SEM_id,&sem_unlock[0],1)<0)
printf("Access Semaphore Error!");/*
解鎖*/
在此之前,信號量的值爲1,小於等於-1的絕對值,於是就將信號量減1,重置爲0,表示資

源已經釋放。如果信號量的當前值爲0,小於-1的絕對值,因爲標誌設爲IPC_NOWAIT,就會立
即返回,此時因爲信號量的值爲0,表示資源空閒,也就無所謂解鎖了。
顯然對於共享內存的互斥控制採用的是VP算法。該算法還有另一種實現方式:用信號量
1表示資源空閒,信號量爲0表示資源佔用,正好與上面的做法相反。在這種情況下,應將信
號量的值初始化爲1。例程如下:
static struct sembuf sem_lock[1]=(0,-1,0),
sem_unlock[1]=(0,1,0);
semop(SEM_id,&sem_lock[0],1);/*
解鎖
*/
加鎖時,如果信號量當前值爲1(資源空閒),那麼就減1,這時信號量的值變爲0,表示資源

已經被佔用。如果信號量當前值爲0(資源佔用),因爲0小於-1的絕對值,於是進程開始睡眠
,
直到該信號量變爲1,才被系統喚醒。解鎖時,如果信號量當前值爲0,那麼就加一,這時信
號量的值爲1,表示資源空閒。這種方式更加簡單一些。
函數semop的調用格式:
int semop(semid,sops,nsops)
int semid;/*
信號量組標識符
*/
struct sembuf**sops;/*
指向信號操作量數緩衝區
*/
unsigned nsops;/*
操作的信號量信數
*/
功能是完成對標識符爲semid的信號量組中若干信號量的操作,根據sops[i]sem_flg

操作數sops[i].sem_op,對編號爲sops[i]·sem_num的信號量進行操作。一共要做nsops次這
樣的操作,這個過程可稱之爲信號量的"塊操作",類似批處理方法。
利用上面的三個函數,可以實現對共享資源的互斥訪問,也可設計出具有複雜同步操作
要求的併發程序。不同進程對SHM的互斥訪問可用圖1表示:
@@46P16100.GIF;
1@@
,共享內存的實際應用

我們假設一個初始化程序(程序init)已經完成了SHM及信號量組的創建過程。這時不同
的進程可用相同的關鍵值(SHMKEY)去存取共享內存,互斥訪問也用相同的信號量去控制,
鍵值是SEMKEY。我們假設進程(程序progl)是一個與遠程主機連接的通信進程,其所要連接
的遠程主機名由命令行參數argv[1]指定。進程將遠程主機名和本身的進程號在SHM中登記
或更新。如:
SEM_id=semget((key_t)SEMKEY,1,0);
semop(SEM_id,&sem_lock[0],2);/*
加鎖
*/
SHM_id=shmget((key_t)SHMKEY,NODENUM*sizeof(struct pidtos),0);
buf=(struct pidtos*)shmat(SHM_id,(char*)0,0);
for(i=0;i
if(srcmp(buf[i].rhostname,argv[1]==0) break;
if(buf[i].pidsc==0) break;
if(i==NODENUM) return(-1);
strcpy(buf[i].rhostname,argv[1]);
buf[i].pidsc=getpid();/*
操作部分結束
*/
shmdt((char *)buf);/*
拆接
SHM*/
semop(SEM_id,&sem_unlock[0],1);/*
解鎖
*/
這樣相同的一批進程帶不同的命令行參數運行後,就在SHM中登記了一批遠程主機名和

與之相連的通信進程的進程號。下面我們就可讓進程(程序prog2)依據遠程主機名(Remote
Host)
SHM中查出與該主機相連的通信進程的進程號,以實現對這些通信進程的管理。只要

將上面例程中的操作部分換成下面的程序段即可。
for(i=0;i<NODENUM;I++)
if(strcmp(buf[i].rhostname,RemoteHost)==0)
break;
if(i==NODENUM) pid=-1;else
pid=buf[i].pidsc;
總之,不同進程通過相同的關鍵值SHMKEYSEMKEY,來獲取共享內存及信號量的標識符
,
然後使用標識符分別對它們進行操作。相同的關鍵值是實現不同進程共享資源的基礎與前
提。
,有關安全性的問題
所謂安全性問題指的是在一個進程將共享內存加鎖以後,由於某種原因該進程停止了運
,沒能執行解鎖操作,從而使SHM一直處於被鎖狀態,致使其他進程無法使用SHM。爲了儘量
避免出現這種情況,我們可以將一些系統信號忽略掉。具體做法是先定義一些函數指針,
原來系統對這些信號的處理功能暫放函數指針中,然後設置對這些信號的處理方式爲SIG_I
GN(
忽略)。如
:
int (*f1)();
int (*f2)();
int (*f3)();
int (*f4)();
f1=signal(SIGINT,SIG_IGN);
f2=signal(SIGTERM,SIG_IGN);
f3=signal(SIGQUIT,SIG_IGN);
f4=signal(SIGHUP,SIG_IGN);
在對SHM操作後,無論成功與否都要將對這些系統信號原來的處理功能恢復過來。如
:
signal(SIGINT,f1);
signal(SIGTERM,f2);
signal(SIGQUIT,f3);
signal(SIGHUP,f4);

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章