Chinaunix首页 | 论坛 | 博客
  • 博客访问: 41616
  • 博文数量: 14
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 165
  • 用 户 组: 普通用户
  • 注册时间: 2022-11-22 23:41
个人简介

将分享技术博文作为一种快乐,提升自己帮助他人

文章分类

全部博文(14)

文章存档

2023年(9)

2022年(5)

我的朋友

分类: LINUX

2023-04-26 22:51:40

一、共享内存的含义

共享内存支持不同进程访问同一个逻辑内存,是不同进程间共享传递数据的一种有效方式。不同进程共享的内存一般具有相同的物理内存。进程可将这段共享的物理内存连接到自己的虚拟地址空间,通过操作虚拟地址从而操作这段共享物理内存。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

注意:共享内存只是共享内存,并未提供同步机制。在{BANNED}中国第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。因此,实际应用中通常需要配合同步机制对共享内存进行访问。

二、共享内存的操作函数

Linux提供了一组函数接口用于操作共享内存,他们声明在sys/shm.h中。
1、shmget()函数
该函数用来创建和获取共享内存,原型定义如下:

点击(此处)折叠或打开

  1. #include <sys/ipc.h>
  2. #include <sys/shm.h>

  3. int shmget(key_t key, size_t size, int shmflg);
{BANNED}中国第一个参数key,是一个非零正数,一般采取ftok()函数生成。
第二个参数size,以字节为单位指定了需要共享的内存大小。
第三个参数shmflg,指定了调用者操作共享内存的权限。权限描述方式与open函数的mode参数一样。若需要在共享内存不存在时,仍然能创建共享内存标识符,则需对shmflg或上IPC_CREAT标识,此时如果共享内存标识符已存在,则直接引用该共享内存标识符对象。若或上IPC_EXCL,标识符对象已存在时则返回失败。
返回值:成功返回一个有效的共享内存标识符,失败返回-1,errno会被设置成对应的错误。

与共享内存相关的数据结构如下:

点击(此处)折叠或打开

  1. struct shmid_ds {
  2.     struct ipc_perm shm_perm; /* Ownership and permissions */
  3.     size_t shm_segsz; /* Size of segment (bytes) */
  4.     time_t shm_atime; /* Last attach time */
  5.     time_t shm_dtime; /* Last detach time */
  6.     time_t shm_ctime; /* Last change time */
  7.     pid_t shm_cpid; /* PID of creator */
  8.     pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
  9.     shmatt_t shm_nattch; /* No. of current attaches */
  10.     ...
  11. };
其中ipc_perm的定义如下:

点击(此处)折叠或打开

  1. struct ipc_perm {
  2.    key_t __key; /* Key supplied to shmget(2) */
  3.    uid_t uid; /* Effective UID of owner */
  4.    gid_t gid; /* Effective GID of owner */
  5.    uid_t cuid; /* Effective UID of creator */
  6.    gid_t cgid; /* Effective GID of creator */
  7.    unsigned short mode; /* Permissions + SHM_DEST and
  8.                              SHM_LOCKED flags */
  9.    unsigned short __seq; /* Sequence number */
  10. };
当一个共享内存段被创建时,它的内存会被初始化为0。以上共享内存相关的数据结构中的域段会按照如下进行初始化:
1)设置shm_perm.cuid和shm_perm.uid为调用进程的有效user ID;
2)设置shm_perm.cgid和shm_perm.gid为调用进程的有效group ID;
3)设置shm_perm.mode为shmflg的地9bit的值,若共享内存段已存在,则权限会被修改。
4)设置shm_segsz为size值;
5)设置shm_lpid、shm_nattch、shm_atime和shm_dtime为0;
6)设置shm_ctime为当前的时间。
2、shmat()函数

创建完共享内存后得到的共享内存标识符,还不能被任何进程访问。使用shmat()函数,则可将共享内存attach到调用进程的进程地址空间。原型如下:

点击(此处)折叠或打开

  1. #include <sys/types.h>
  2. #include <sys/shm.h>

  3. void *shmat(int shmid, const void *shmaddr, int shmflg);

{BANNED}中国第一个参数shm_id,是由shmget()函数返回的共享内存标识符。
第二个参数shm_addr,指定共享内存连接到当前进程中的地址,通常为空,表示让系统来选择共享内存地址。shm_addr不为空且shmflg指定了SHM_RND,这个attach发生在shmaddr的向下舍入SHMLBA的{BANNED}最佳近倍数的位置。若只是shmaddr不为空,则shmaddr必须时一个页对齐的地址。
第三个参数shmflag,表示一组flag,除了上述的SHM_RND外,还可以为以下三个值:
SHM_EXEC:共享内存段的内存允许被执行,调用者必须对共享内存段有执行权限。
SHM_RDONLY:attach的内存段是只读的,进程对共享内存段必须有可读权限。若该标识为指定,则调用进程对共享内存段必须有读写权限。

SHM_REMAP:此标志指定段的映射应该替换从shmadr开始并继续到段大小范围内的任何现有映射。
返回值:成功返回指向共享内存的{BANNED}中国第一个字节地址,失败返回-1。

2、shmdt()函数

该函数用于将共享内存从当前进程中分离。该函数不会删除共享内存段,而只是使得当前进程不可在访问该共享内存。原型如下:

点击(此处)折叠或打开
  1. int shmdt(const void *shmaddr);

参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1.


3、shmctl()函数

该函数与信号量的semctl()函数作用类似,用来控制共享内存,原型定义如下:

点击(此处)折叠或打开


  1. #include <sys/ipc.h>
  2. #include <sys/shm.h>

  3. int shmctl(int shmid, int cmd, struct shmid_ds *buf);

{BANNED}中国第一个参数shm_id,是shmget()函数返回的共享内存标识符;
第二个参数cmd,是表示待作用的操作,可取以下三个值:

  • IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
  • IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
  • IPC_RMID:删除共享内存段
第三个参数shmid_ds结构的buf指针,它执行共享内相关的数据结构。

三、共享内存实操

两个进程的共享内存结构如下:

点击(此处)折叠或打开

  1. #define BUF_SIZE 256
  2. typedef struct {
  3.     int has_data;
  4.     char text[BUF_SIZE];
  5. } share_data;
其中的has_data标记是否有数据,1表示有数据,0表示数据被读取。
写数据的进程代码shm_write.c如下:

点击(此处)折叠或打开

  1. #include <sys/ipc.h>
  2. #include <sys/shm.h>
  3. #include <string.h>
  4. #include <stdio.h>
  5. #include <unistd.h>

  6. #define BUF_SIZE 256
  7. typedef struct {
  8.     int has_data;
  9.     char text[BUF_SIZE];
  10. } share_data;

  11. int main(int agrc, char **agrv)
  12. {
  13.     char buf[BUF_SIZE];
  14.     
  15.     key_t key = ftok("/dev/shm/myshm2", 0);
  16.     int shm_id = shmget(key, sizeof(share_data), IPC_CREAT | 0666);
  17.     share_data *shm = (share_data *)shmat(shm_id, NULL, 0);
  18.     if (shm == (void *)-1) {
  19.         fprintf(stderr, "shmat failed\n");
  20.         return -1;
  21.     }

  22.     printf("shm_addr=0x%p\n", shm);
  23.     while (1) {
  24.         while (shm->has_data == 1) {
  25.             sleep(1);
  26.             printf("wait for reading text...\n");
  27.         }

  28.         printf("write data to share memory:");
  29.         fgets(buf, BUF_SIZE, stdin);
  30.         strcpy(shm->text, buf);
  31.         shm->has_data = 1;
  32.         if (strncmp(shm->text, "end", 3) == 0)
  33.             break;
  34.     }

  35.     int rc = shmdt(shm);
  36.     if (rc == -1) {
  37.         fprintf(stderr, "shmdt failed\n");
  38.         return -1;
  39.     }

  40.     return 0;
  41. }
读取共享数据的进程代码shm_read.c如下:

点击(此处)折叠或打开

  1. #include <sys/ipc.h>
  2. #include <sys/shm.h>
  3. #include <string.h>
  4. #include <stdio.h>
  5. #include <unistd.h>

  6. #define BUF_SIZE 256
  7. typedef struct {
  8.     int has_data;
  9.     char text[BUF_SIZE];
  10. } share_data;

  11. int main(int agrc, char **agrv)
  12. {
  13.     char buf[BUF_SIZE];
  14.     
  15.     key_t key = ftok("/dev/shm/myshm2", 0);
  16.     int shm_id = shmget(key, sizeof(share_data), IPC_CREAT | 0666);
  17.     share_data *shm = (share_data *)shmat(shm_id, NULL, 0);
  18.     if (shm == (void *)-1) {
  19.         fprintf(stderr, "shmat failed\n");
  20.         return -1;
  21.     }

  22.     printf("shm_addr=0x%p\n", shm);
  23.     while (1) {
  24.         // 有数据待读取
  25.         if (shm->has_data == 1) {
  26.             printf("Received: %s", shm->text);
  27.             sleep(1);

  28.             // 读取完数据,请标记has_data
  29.             shm->has_data = 0;

  30.             // 输入了end,退出循环
  31.             if (strncmp(shm->text, "end", 3) == 0) {
  32.                 break;
  33.             }
  34.         } else {
  35.             sleep(1);
  36.         }
  37.     }

  38.     int rc = shmdt(shm);
  39.     if (rc == -1) {
  40.         fprintf(stderr, "shmdt failed\n");
  41.         return -1;
  42.     }

  43.     // 删除共享内存
  44.     if (shmctl(shm_id, IPC_RMID, 0) == -1)
  45.     {
  46.         fprintf(stderr, "shmctl(IPC_RMID) failed\n");
  47.         return -1;
  48.     }

  49.     return 0;
  50. }
编译两个进程,如下:
gcc shm_read.c -o sr
gcc shm_write.c -o sw

分别运行写共享内存进程和读共享内存进程。写进程显示等待输入数据,数据还未被读取时,则等待读取完毕;读共享内存数据的进程,感知到有数据可读时,则获取数据内容,并清空数据标记,无数据可读时则一直等待数据写入。
测试结果如下:


注意:在实际应用时,has_data的修改必须保证原子性且需加上barrier操作确保读的数据的准确性。


阅读(1950) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~