共用記憶體(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_SET或IPC_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 Strcpy(buf[i].rhostname,""); buf[i].pidsc=0; ) /*如果有必要,就初始化SHM*/ shmdt((char*)buf);/*拆接SHM,釋放指標buf*/ 函數shmat及shmdt的調用格式: char*shmat(shmid,shmaddr,shmflg) int shmid;/*SHM標識符*/ char*shmaddr;/*相當於位移量*/ int shmflg;/*標誌*/ 作用是將共用記憶體附接到進程的資料區段上。實際上是將共用記憶體在主存中的地址+shm addr後賦值給進程中的某一指標。shmaddr相當於位移量,相對於共用記憶體在主存中的起始 地址。調用失敗時返回(char*)-1.shmflg可取值為0,或者是SHM_RND和SHM_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個訊號量,編號從0至nsems-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 if(strcmp(buf[i].rhostname,RemoteHost)==0) break; if(i==NODENUM) pid=-1;else pid=buf[i].pidsc; 總之,不同進程通過相同的關索引值SHMKEY及SEMKEY,來擷取共用記憶體及訊號量的標識符 ,然後使用標識符分別對它們進行操作。相同的關索引值是實現不同進程共用資源的基礎與前 提。 四,有關安全性的問題 所謂安全性問題指的是在一個進程將共用記憶體加鎖以後,由於某種原因該進程停止了運 行,沒能執行解鎖操作,從而使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);
|