歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Unix知識 >> Unix基礎知識 >> UNIX環境高級編程:System V 共享內存區

UNIX環境高級編程:System V 共享內存區

日期:2017/3/3 15:19:48   编辑:Unix基礎知識

共享內存區域是被多個進程共享的一部分物理內存。如果多個進程都把該內存區域映射到自己的虛擬地址空間,則這些進程就都可以直接訪問該共享內存區域,從而可以通過該區域進行通信。共享內存是進程間共享數據的一種最快的方法,一個進程向共享內存區域寫入了數據,共享這個內存區域的所有進程就可以立刻看到其中的內容。這塊共享虛擬內存的頁面,出現在每一個共享該頁面的進程的頁表中。但是它不需要在所有進程的虛擬內存中都有相同的虛擬地址。

共享內存的實現,分為兩個步驟:

a. 創建共享內存,使用 shmget 函數。

b. 映射共享內存,將這段創建的共享內存映射到具體的進程空間去,使用 shmat 函數。

對於每個共享內存區,內核維護如下的信息結果,它定義在<sys/shm.h>頭文件中:

struct shmid_ds {  
    struct ipc_perm shm_perm;    /* Ownership and permissions */
    size_t          shm_segsz;   /* Size of segment (bytes) */
    time_t          shm_atime;   /* Last attach time */
    time_t          shm_dtime;   /* Last detach time */
    time_t          shm_ctime;   /* Last change time */
    pid_t           shm_cpid;    /* PID of creator */
    pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
    shmatt_t        shm_nattch;  /* current #attached */
    shmat_t         shm_cnattch; /* in-core #attached */
};

下面是ipc_per 結構,它含有本共享內存區的訪問權限。

struct ipc_perm {  
    key_t          __key;    /* Key supplied to shmget(2) */
    uid_t          uid;      /* Effective UID of owner */
    gid_t          gid;      /* Effective GID of owner */
    uid_t          cuid;     /* Effective UID of creator */
    gid_t          cgid;     /* Effective GID of creator */
    unsigned short mode;     /* Permissions + SHM_DEST and SHM_LOCKED flags */
    unsigned short __seq;    /* Sequence number */
};

1.shmget函數(創建共享內存)

shmget函數創建一個新的共享內存區,或者訪問一個已存在的共享內存區。

#include <sys/shm.h>  
int shmget(key_t key,size_t size,int oflag);

返回值是一個稱為共享內存區標識符的整數,其他三個shmXXX函數就用它來指代這個內存區。

key即可以是ftok的返回值,也可以是IPC_PRIVATE。

size以字節為單位指定內存區的大小。當時機操作為創建一個新的共享內存區時,必須指定一個不為0的size值。如果實際操作為訪問一個已存在的共享內存區,那麼size應為0.

oflag是讀寫權限的組合。它還可以與IPC_CREAT或IPC_CREAT | IPC_EXCL按位或。

當實際操作為創建一個新的共享內存區時,該內存區被初始化為size字節的0.

注意,shmget創建或打開一個共享內存區,但並沒有給調用進程提供訪問該內存區的手段。這是shmat函數的目的。

2.shmat函數(共享內存映射)

由shmget創建或打開一個共享內存區後,通過調用shmat把它附接到調用進程的地址空間。

#include <sys/shm.h>  
void* shmat(int shmid,const void *shmaddr,int flag);

其中shmid是由shmget返回的標識符。shmat的返回值是所指定的共享內存區在調用進程內的起始地址。確定這個地址的規則如下:

(1)如果shmaddr是一個空指針,那麼系統替調用者選擇地址。這是推薦的方法。

(2)如果shmaddr是一個非空指針,那麼返回地址取決於調用者是否給flag參數指定了SHM_RND值:

a.如果沒有指定SHM_RND,那麼相應的共享內存區附接到由shmaddr參數指定的地址;

b.如果指定了SHM_RND,那麼相應的共享內存附接到由shmaddr參數指定的地址向下捨入一個SHMLBA常值。LBA代表“低端邊界地址”。

默認情況下,只要調用進程具有某個共享內存區得讀寫權限,它附接該內存區後就能夠同時讀寫該內存區。flag參數中也可以指定SHM_RDONLY值,它限定只讀訪問。flag參數默認是0,表示共享內存可讀寫。

3.shmdt函數(共享內存解除映射)

當一個進程完成某個共享內存區的使用時,它可以調用shmdt斷接這個內存區。

#include <sys/shm.h>  
int shmdt(const void *shmaddr);

當一個進程終止時,它當前附接著的所有共享內存區都自動斷接掉。

注意本函數調用並不刪除所指定的共享內存區。這個刪除工作通過以IPC_RMID命令調用shmctl完成。

4.shmctl函數

shmctl提供了對一個共享內存區的多種操作。

#include <sys/shm.h>  
int shmctl(int shmid,int cmd,struct shmid_ds *buff);

該函數提供了三個命令:

IPC_RMID 從系統中刪除由shmid標識的共享內存區並拆除它。

IPC_SET 給所指定的共享內存區設置其shmid_ds結構的以下三個成員:shm_perm.uid,shm_perm.gid和shm_perm.mode,他們的值來自buff參數指向的結構中的相應成員。shm_ctime的值也用當前時間替換。

IPC_STAT (通過buff參數)向調用者返回所指定共享內存區當前的shmid_ds結構。

5.shmget程序(shmget.c)

下面是給出的shmget程序使用指定的路徑名和長度創建一個共享內存區。

#include "unpipc.h"  
int main(int argc,char ** argv)  
{  
  int c,id,oflag;  
  char *ptr;  
  size_t length;  
  oflag = SVSHM_MODE | IPC_CREAT;  
  while( ( c= getopt(argc,argv,"e") ) != -1 )  
   {  
     switch(c){  
     case 'e':  
         oflag |= IPC_EXCL;  
         break;  
      }  
    }  
   if(optind != argc -2)  
     err_quit("usage: shmget [-e] <pathname> <length>");  
      
   length = atoi(argv[optind + 1]);  
   id = shmget(ftok(argv[optind],0),length,oflag);  
   ptr = shmat(id,NULL,0);  
   exit(0);  
}

20 shmget創建由用戶指定其名字和大小的共享內存區。作為命令行參數傳遞進來的路徑名由ftok映射成一個system V IPC鍵。如果指定了- e選項,那麼一旦該內存已存在就會出錯。如果我們知道該內存區已存在,那麼在命令行上的長度參數必須指定為0.

21 shmat把該內存區附接到當前進程的地址空間。本程序然後終止,不過既然system V共享內存區至少具有隨內核的持續性,那麼這不會刪除該共享內存區。

6.shmrnid程序(shmrmid.c)

如下給出的只是以一個IPC_RMID命令調用shmctl,以便從系統中刪除一個共享內存區。

#include "unpipc.h"  
int main(int argc,char ** argv)  
{  
  int id;  
  if(argc != 2)  
   err_quit("usage: shmrmid<pathname>");  
      
  id = shmget(ftok(argv[1],0),0,SVSHM_MODE);  
  shmctl(id,IPC_RMID,NULL);  
  exit(0);  
}

7.shmwrite程序(shmwrite.c)

下面給出了shmwrite.c程序,它往一個共享內存區中寫入一個模式:0.1.2........254.255.0.1等等。

#include "unpipc.h"  
int main(int argc,char **argv)  
{  
  int i,id;  
  struct shmid_ds buff;  
  unsigned char *ptr;  
      
  if(argc != 2)  
    err_quit("usage: shmwrite <pathname>");  
      
  id = shmget(ftok(argv[1],0),0,SVSHM_MODE);  
  ptr = shmat(id,NULL,0);  
  shmctl(id,IPC_STAT,&buff);  
      
  /*set: ptr[0] = 0,ptr[1] = 1,etc.*/
  for(i = 0;i < buff.shm_segsz;i++)  
      *ptr++ = i%256;  
  exit(0);  
}

11-13 使用shmget打開所指定的共享內存區後由shmat把它附接到當前進程的地址空間。其大小通過以一個IPC_STAT命令調用shmctl取得。

16-17 往該共享內存區中寫入給定的模式。

8.shmread程序(shmread.c)

下面給出的代碼驗證shmwrite寫入的模式。

#include "unpipc.h"  
int main(int argc,char** argv)  
{  
  int i,id;  
  struct shmid_ds buff;  
  unsigned char c,*ptr;  
      
  if(argc != 2)  
    err_quit("usage: shmread <pathname>");  
      
  id = shmget(ftok(argv[1],0),0,SVSHM_MODE);  
  ptr = shmat(id,NULL,0);  
  shmctl(id,IPC_STAT,&buff);  
      
  /*check that ptr[0] = 0,ptr[1] = 1,etc.*/
  for(i = 0;i < buff.shm_segsz;i++)  
    if((c = *ptr++) != (i%256))  
       err_ret("ptr[%d] = %d",i,c);  
  exit(0);  
}

查看本欄目更多精彩內容:http://www.bianceng.cn/OS/unix/

11-13 打開並附接所指定的共享內存區。其大小通過以一個IPC_STAT命令調用shmctl獲取。

16-18 驗證由shmwrite寫入的模式。

示例代碼:

#include <stdlib.h>    
#include <stdio.h>    
#include <string.h>    
#include <errno.h>    
#include <unistd.h>    
#include <sys/stat.h>    
#include <sys/types.h>    
#include <sys/ipc.h>    
#include <sys/shm.h>    
         
#define PERM S_IRUSR | S_IWUSR    
       
int main(int argc, char **argv)    
{    
         int shmid;    
         char *p_addr, *c_addr;    
         
         if(argc != 2){    
                 fprintf(stderr,"Usage: %s\n", argv[0]);    
                 exit(0);    
         }    
         
         //創建1k的共享內存,可讀可寫    
         if((shmid = shmget(IPC_PRIVATE, 1024, PERM)) == -1){    
                 fprintf(stderr, "Create share memory error: %s\n",strerror(errno));    
         }    
         
         //創建子進程    
         if(fork()){ //父進程,寫操作    
                 p_addr = shmat(shmid, NULL , 0); //映射到父進程中    
                                              //地址為NULL,說明讓系統自動指定地址。    
                 memset(p_addr, '\', 1024);    
                 strncpy(p_addr, argv[1], 1024);  //拷貝命令行輸入字符到共享內存    
                 wait(NULL);  
                 shmctl(shmid,IPC_RMID,NULL);  
                 exit(0);    
         } else{    
                 sleep(1);  //子進程,讀操作    
                 c_addr = shmat(shmid, 0 ,0); //共享內存映射到子進程    
                 printf("Client get %s\n", c_addr);                    
                 exit(0);    
         }    
}

運行結果:

huangcheng@ubuntu:~$ ./a.out huangcheng  
Client get huangcheng
Copyright © Linux教程網 All Rights Reserved