分享

进程通信之共享内存

 青松卓然 2012-08-07

进程通信之共享内存

分类: Unix高级编程 662人阅读 评论(0) 收藏 举报

1. 共享内存

1.1. 共享内存

共享内存是在两个正在运行的进程之间传递数据的一种非常有效的方式。共享内存允许两个不相关的进程访问同一个逻辑内存。由于它并没有提供同步机制,所以我们通常需要用其他的机制来同步访问共享的内存。

共享内存是由IPC为进程创建的一个特殊的地址范围,它将出现在该进程的地址空间中。其他进程可以将同一段共享内存连接到它们自己的地址空间中。所有进程都可以访问共享内存的数据,如果一个进程向共享内存写入了数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。

内核为每个共享内存设置了一个shmid_ds结构:

struct shmid_ds{

struct ipc_perm  shm_perm;    //see XSI IPC

size_t         shm_segsz;   // size of segment in bytes

pid_t         shm_lpid;    // pid of last shmop

pid_t         shm_cpid;    // pid of creator

shmatt_t       shm_nattch   //number of current attaches

time_t        shm_atime;    // last-attach time

time_t        shm_dtime;    // last-detach time

time_t        shm_ctime;    // last-change time.

};

 

为获得一个共享内存标识符,调用的第一个函数是shmget。

#include <sys/shm.h>

int shmget(key_t key, size_t size, int flag);

第一个参数key,与信号量一样,它有效地为共享内存命名,shmget函数返回一个共享内存的标识符,该标识符用于后续的共享内存函数。有一个特殊的键值IPC_PRIVATE(通常是0),作用是创建一个只属于创建进程的共享内存。

第二个参数size是该共享存储段的长度(单位:字节)。如果正在创建一个新段,则必须指定size;如果正在引用一个现有的段,则将size指定为0。当创建一个新段时,段内的内容初始化为0。

第三个参数flag包含九个比特权限标志,其作用类似于文件的访问权限。它们可以与值IPC_CREAT做按位或操作以创建一个新的共享内存段。即使设置了IPC_CREAT标志,给出的键是一个已经有信号量的键也不会产生错误。只有联合使用IPC_CREAT和IPC_EXCL来确保创建出的是一个新的唯一的共享内存段,如果该共享内存段存在,就返回一个错误(errno设置为EEXSIT)。

如果共享内存创建成功,shmget返回一个非负整数(共享内存标识符);如果失败,则返回-1。

 

函数shmctl对其共享内存执行多种操作。

#include <sys/shm.h>

int shmget(int shmid, int cmd, struct shmid_ds *buf);

第一个参数shmid是shmget返回的共享内存标识符。

第二个参数cmd是要执行的操作,有下列五种命令:

IPC_STAT 取此队列的shmid_ds结构,并将它存放着buf指向的结构中。

IPC_SET  按由buf指向结构中的值,设置与此队列相关结构中的下列四个字段:shm_perm.uid、shm_perm.gid和shm_perm.mode。此命令只能由下列两种进程执行:一种是其有效用户ID等于shm _perm.cuid或shm _perm.uid;另一种是具有超级用户特权的进程。

IPC_RMID 从系统中删除该共享内存段。因为每个共享内存段有一个连接计数(shmid_ds结构中的shm_nattch字段),所以除非使用该段的最后一个进程终止或与该段脱接,否则不会实际上删除该内存段。这种删除立即生效,所以不能再用shmat与该段连接。此命令只能由下列两种进程执行:一种是其有效用户ID等于shm _perm.cuid或shm _perm.uid;另一种是具有超级用户特权的进程。

SHM_LOCK 将共享内存段锁定在内存中。此命令只能由超级用户执行。

SHM_UNLOCK 解锁共享内存段。此命令只能由超级用户执行。

第三个参数buf是一个指针,它指向包含共享内存模式和访问权限的shmid_ds结构。

如果成功,shmctl返回0;如果失败,则返回-1。

 

一旦创建了一个共享内存段,进程就可调用shmat将其连接它的地址空间中。

#include <sys/shm.h>

int shmat(int shmid, const void *addr, int flag);

第一个参数shmid是shmget返回的共享内存标识符。

第二个参数addr指定的是共享内存连接到当前进程中的地址位置。它通常是一个空指针,表示让系统来选择共享内存出现的地址。

1). 如果addr为0,则此段连接到由内核选择的第一个可用的地址上。(推荐)

2). 如果addr非0,并且没有指定SHM_RND,则此段连接到addr指定的地址上。

3). 如果addr非0,并且指定SHM_RND,则此段连接到(addr - (addr mod ulus SHMLBA ))指定的地址上。SHM_RND命令的意思是"取整"。SHMLBA的意思是"低边界地址倍数",它总是2的乘方。该算式是将地址向下取最近的1个SHMLBA的倍数。

第四个参数flag是一组标志。它可能有两个取值是SHM_RND(这个与shm_addr联合使用,用来控制共享内连接的地址)和SHM_RDONLY(它使得连接的内存为只读)

如果shmat调用成功,返回的是该段所连接的实际地址,如果出错,返回-1。如果执行成功,则内核使该共享内存段shmid_ds结构中的shm_nattch计数器加一。

共享内存的读写权限由它的属主(共享内存的创建者)、它的访问权限和当前进程的属主决定。有个规则例外:当flag&SHM_RDONLY为真时,此时即使该共享内存的访问权限允许写操作,它都不能被写入。 

当对共享内存段的操作已经结束时,则调用shmdt脱接该段。

#include <sys/shm.h>

int shmdt(void *addr);

第一个参数是以前调用shmat时的返回值。

如果成功,shmdt将使相关shmid_ds结构的shm_nattch计数器值减一。

注意:这并不是从系统中删除其标识符及其数据结构。该标识符仍然存在,直到某个进程调用shmctl(带命令IPC_RMID)特定的删除它。 

1.2. 实例

我们希望熟悉这些函数后,能够联系它们。下面将是两个程序:一个是消费者,将创建一个共享内存段,然后吧写到里面的数据都显示出来;另一个是生产者,将连接一个已经存在的共享内存段,并允许先其中输入数据。

第一个程序,在共享内存的开始处使用了一个结构SHARE_DATA_SEG_S。该结构中有一个标志writtenFlag,当共享内存有数据写入时,就设置该标志。这个标志被设置时,程序就从共享内存中读取文本,将打印出来,然后清除这个标志已经读完数据。收到字符串"end"退出循环。最后,程序分离共享内存段并删除它。

第二个程序,获得共享内存标识符,链接到同一个共享内存段。然后用户输入字符串。如果标志writtenFlag被设置,就知道第一个程序没有读完上一次数据,因此就继续等待。当其它进程清除了这个标志后,就写入新数据并设置该标志。写入字符串"end"终止并分离共享内存段。

源程序如下:

  1. #include <stdlib.h>  
  2. #include <stdio.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5. #include <errno.h>  
  6. #include <sys/types.h>  
  7. #include <sys/ipc.h>  
  8. #include <sys/shm.h>  
  9.   
  10. #define MY_SEM_PROC_ID   4  
  11. #define MY_PROC_PATH       "/user/local/wzhwho"  
  12. #define BUF_SIZE         2048  
  13.   
  14. typedef struct tagShareDataSeg  
  15. {  
  16.     int writtenFlag;  
  17.     char text[BUF_SIZE];  
  18.   
  19. } SHARE_DATA_SEG_S;  
  20.       
  21. /******************************************************************************* 
  22. * 
  23. * 共享内存实例(消费者程序) 
  24. * 
  25. *******************************************************************************/  
  26. int main(void)  
  27. {  
  28.     int running = 1;  
  29.     void *share_mem_addr =(void *)0;  
  30.     SHARE_DATA_SEG_S *share_data;  
  31.     key_t my_sem_kt;  
  32.     int shmid;  
  33.       
  34.     srand((unsigned int)getpid());  
  35.     /* 获取共享内存的ID */  
  36.     my_sem_kt = ftok(MY_PROC_PATH, MY_PROC_RCV_ID);  
  37.     shmid = shmget(my_sem_kt, sizeof(SHARE_DATA_SEG_S), 0666|IPC_CREAT);  
  38.     if(shmid == -1)  
  39.     {  
  40.          printf("Failed to shmget share memory/n");  
  41.          exit(EXIT_FAILURE);  
  42.     }  
  43.     share_mem_addr = shmat(shmid, (void *)0, 0);  
  44.     if(share_mem_addr = (void *) -1)  
  45.     {  
  46.         printf("Failed to shmget share memory/n");  
  47.         exit(EXIT_FAILURE);  
  48.     }  
  49.     printf("Memory attach at 0x%X/n", (int)share_mem_addr);  
  50.     share_data = (SHARE_DATA_SEG_S *)share_mem_addr;  
  51.     share_data->writtenFlag = 0;  
  52.     while(running)  
  53.     {  
  54.         if(share_data->writtenFlag)  
  55.         {  
  56.             printf("You wrote: %s", share_data->text);  
  57.             sleep(rand()% 4);  
  58.             share_data->writtenFlag = 0;  
  59.             if(strncmp(share_data->text, "end", 3) == 0)  
  60.             {  
  61.                 running = 0;  
  62.             }  
  63.         }  
  64.     }  
  65.     if(shmdt(share_mem_addr) == -1)  
  66.     {  
  67.         printf("Failed to shmdt share memory/n");  
  68.         exit(EXIT_FAILURE);  
  69.     }  
  70.     if(shmctl(shmid, IPC_RMID, 0) == -1)  
  71.     {  
  72.         printf("Failed to shmctl share memory with IPC_RMID/n");  
  73.         exit(EXIT_FAILURE);  
  74.     }  
  75.     exit(EXIT_SUCCESS);  
  76. }  
  77.   
  78. /******************************************************************************* 
  79. * 
  80. * 共享内存实例(生产者程序) 
  81. * 
  82. *******************************************************************************/  
  83. int main(void)  
  84. {  
  85.     int running = 1;  
  86.     void *share_mem_addr =(void *)0;  
  87.     SHARE_DATA_SEG_S *share_data;  
  88.     key_t my_sem_kt;  
  89.     int shmid;  
  90.     char buf_tmp[BUF_SIZE];  
  91.       
  92.     srand((unsigned int)getpid());  
  93.     /* 获取共享内存的ID */  
  94.     my_sem_kt = ftok(MY_PROC_PATH, MY_PROC_RCV_ID);  
  95.     shmid = shmget(my_sem_kt, sizeof(SHARE_DATA_SEG_S), 0666|IPC_CREAT);  
  96.     if(shmid == -1)  
  97.     {  
  98.          printf("Failed to shmget share memory/n");  
  99.          exit(EXIT_FAILURE);  
  100.     }  
  101.     share_mem_addr = shmat(shmid, (void *)0, 0);  
  102.     if(share_mem_addr = (void *) -1)  
  103.     {  
  104.         printf("Failed to shmget share memory/n");  
  105.         exit(EXIT_FAILURE);  
  106.     }  
  107.     printf("Memory attach at 0x%X/n", (int)share_mem_addr);  
  108.     share_data = (SHARE_DATA_SEG_S *)share_mem_addr;  
  109.     while(running)  
  110.     {  
  111.         while(share_data->writtenFlag == 1)  
  112.         {  
  113.             sleep(1);  
  114.             printf("waiting for you.../n");  
  115.         }  
  116.         printf("Please enter some context:");  
  117.         fgets(buf_tmp, BUF_SIZE, stdin);  
  118.         strncpy(share_data->text, buf_tmp, sizeof(share_data->text));  
  119.         share_data->writtenFlag = 1;          
  120.         if(strncmp(share_data->text, "end", 3) == 0)  
  121.         {  
  122.             running = 0;  
  123.         }  
  124.           
  125.     }  
  126.     if(shmdt(share_mem_addr) == -1)  
  127.     {  
  128.         printf("Failed to shmdt share memory/n");  
  129.         exit(EXIT_FAILURE);  
  130.     }  
  131.     exit(EXIT_SUCCESS);  
  132. }  

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多