共享內存(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<NODENUM;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<NODENUM;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);
|