写点什么

深入了解 Linux 共享内存及函数详解 (含编程示例)

用户头像
ShenDu_Linux
关注
发布于: 2020 年 12 月 05 日
深入了解Linux共享内存及函数详解(含编程示例)

一、共享内存的概念


共享内存是指多个进程可以把一段内存共同的内存映射到自己的进程空间中,从而实现数据的共享和传输,它是存在与内核级别的一种资源,是所有进程间通信中方式最快的一种。


在 shell 环境下可以使用 ipcs 查看当前系统 IPC 中的状态,例如当前的电脑中:


$ ipcs
------ Message Queues --------key msqid owner perms used-bytes messages
------ Shared Memory Segments --------key shmid owner perms bytes nattch status0x00000000 2260992 deeplearni 600 524288 2 dest0x00000000 2490369 deeplearni 600 67108864 2 dest0x00000000 163842 root 777 7680 20x00000000 196611 root 777 8294400 20x00000000 229380 root 777 4096 20x00000000 262149 root 777 8192 20x00000000 294918 root 777 12288 2...省略0x00000000 2064444 root 777 233472 20x00000000 2097213 root 777 237568 20x00000000 2129982 root 777 241664 20x00000000 2162751 root 777 245760 20x00000000 2654272 deeplearni 600 524288 2 dest0x00000000 2687041 deeplearni 600 524288 2 dest0x00000000 2719810 deeplearni 600 524288 2 dest0x00000000 2752579 deeplearni 600 524288 2 dest0x00000000 2981956 deeplearni 600 524288 2 dest0x00000000 2949189 deeplearni 600 524288 2 dest0x00000000 3014726 deeplearni 600 67108864 2 dest
------ Semaphore Arrays --------key semid owner perms nsems
复制代码


该命令使用附加参数可单独查看某一 IPC:-m(共享内存),-q(消息队列),-s(信号量)。


由于多个进程对同一块内存区域具有访问权限,各进程间的同步问题需要解决,可以配合信号量进行控制。


【文章福利】小编推荐自己的 Linux、C/C++技术交流群:【1106675687】整理了一些个人觉得比较好的学习书籍、视频资料共享在里面,有需要的可以自行添加哦!~



对于每一个共享内存段,内核会为其维护一个 shmid_ds 类型的结构体:


// 摘自所用ubuntu18.04电脑中的/usr/include/i386-linux-gnu/bits/shm.hstruct shmid_ds  {    struct ipc_perm shm_perm;           /* operation permission struct */    size_t shm_segsz;                   /* size of segment in bytes */    __time_t shm_atime;                 /* time of last shmat() */#ifndef __x86_64__    unsigned long int __glibc_reserved1;#endif    __time_t shm_dtime;                 /* time of last shmdt() */#ifndef __x86_64__    unsigned long int __glibc_reserved2;#endif    __time_t shm_ctime;                 /* time of last change by shmctl() */#ifndef __x86_64__    unsigned long int __glibc_reserved3;#endif    __pid_t shm_cpid;                   /* pid of creator */    __pid_t shm_lpid;                   /* pid of last shmop */    shmatt_t shm_nattch;                /* number of current attaches */    __syscall_ulong_t __glibc_reserved4;    __syscall_ulong_t __glibc_reserved5;  };
复制代码


二、共享内存的相关操作


(1)创建/打开共享内存:创建共享内存需要用到 shmget()函数,原型如下:


#include <sys/types,h>#include <sys/ipc.h>#include <sys/shm.h>int shmget(key_t key, int size, int flag);
复制代码


创建成功返回共享内存的 ID,出错返回-1。


参数 key 为共享内存的键值,参数 size 为创建共享内存的大小,参数 flag 为调用函数的操作类型。参数 key 和参数 flag 共同决定的 shmget()的作用:


  • key 为 IPC_PRIVATE 时,创建一个新的共享内存,flag 取值无效。

  • key 不为 IPC_PRIVATE,且 flag 设置了 IPC_CREAT 位,而没有设置 IPC_EXCL 位时,如果 key 为内核中的已存在的共享内存键值,则打开,否则创建一个新的共享内存。

  • key 不为 IPC_PRIVATE,且 flag 设置了 IPC_CREAT 和 IPC_EXCL 位时,则只执行创建共享内存操作。如果 key 为内核中的已存在的共享内存键值,返回 EEXIST 错误。


(2)共享内存的附加(映射)


创建一个共享内存后,某个进程若想使用,需要将此内存区域附加(attach)到自己的进程空间(或称地址映射),需要用到 shmat()函数:


#include <sys/types,h>#include <sys/ipc.h>#include <sys/shm.h>int *shmat(int shmid, const void *addr, int flag);
复制代码


运行成功返回指向共享内存段的地址指针,出错返回-1。


参数 shmid 为共享内存的 ID,参数 addr 和参数 flag 共同说明要引入的地址值,通常只有 2 种用法:


  • addr 为 0,表明让内核来决定第 1 个可引用的位置

  • addr 非 0,且 flag 中指定 SHM_RND,则此段引入到 addr 所指向的位置。


shmat()函数执行成功后,会将 shmid 的共享内存段的 shmid_ds 结构的 shm_nattch 计数器值加 1。


(3)共享内存的分离

当进程使用完共享内存后,需要将共享内存从其进程空间中去除(detach),使用 shmdt()函数:


#include <sys/types,h>#include <sys/ipc.h>#include <sys/shm.h>int shmdt(void *addr);
复制代码


运行成功返回 0,出错返回-1。


参数 addr 是调用 shmat()函数的返回值,即共享内存段的地址指针。shmdt()函数执行成功后,shm_nattch 计数器值减 1。

(4)共享内存的控制

使用 shmctl()可以对共享内存段进行多种控制操作,函数原型:


#include <sys/types,h>#include <sys/ipc.h>#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_s *buf);
复制代码


运行成功返回 0,出错返回-1。


参数 shmid 为共享内存的 ID,参数 cmd 指明了所要进行的操作,与参数*buf 配合使用:


取 shmid 指向的共享内存的 shmid_ds 结构,对参数 buf 指向的结构赋值。


三、编程示例


基本步骤:


  • 生成 key,ftok()

  • 使用 key 创建/获得一个共享内存,shmget()

  • 映射共享内存,得到虚拟地址,shmat()

  • 使用共享内存,通过地址指针

  • 移除映射,shmdt()

  • 销毁共享内存,shmctl()


示例 1


进程 1 创建共享内存并写入数据,shm1.c:


#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>
int main(){ // generate key key_t key = ftok("./", 200); printf("key=%#x\n", key);
// create a share memory int shmid = shmget(key, 8, IPC_CREAT|0666|IPC_EXCL); if(shmid == -1) { perror("shmget failed\n"); exit(1); } printf("shmid=%#x\n", shmid);
// map share memory to get the virtual address void *p = shmat(shmid, 0, 0); if((void *)-1 == p) { perror("shmat failed"); exit(2); }
// write data to share memory int *pi = p; *pi = 0xaaaaaaaa; *(pi+1) = 0x55555555;
// remove the map if(shmdt(p) == -1) { perror("shmdt failed"); exit(3); }
// delete the share memory printf("use Enter to destroy the share memory\n"); getchar(); if(shmctl(shmid, IPC_RMID, NULL) == -1) { perror("shmctl"); exit(4); }
return 0;}
复制代码


进程 2 读取共享内存中的内容,shm2.c;


#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>
int main(){ // generate key key_t key = ftok("./", 200); printf("key=%#x\n", key);
// get the share memory int shmid = shmget(key, 0, 0); if(shmid == -1) { perror("shmget failed\n"); exit(1); } printf("shmid=%#x\n", shmid);
// map share memory to get the virtual address void *p = shmat(shmid, 0, 0); if((void *)-1 == p) { perror("shmat failed"); exit(2); }
// read: get data from the share memory int x = *((int *)p); int y = *((int *)p+1); printf("x=%#x y=%#x\n", x, y);
// remove the map if(shmdt(p) == -1) { perror("shmdt failed"); exit(3); }
return 0;}
复制代码


编译运行测试,在一个 shell 中先运行 shm1 程序:


$ ./shm1key=0xc81102edshmid=0x2e8047use Enter to destroy the share memory
复制代码


在另一个 shell 中先运行 shm2 程序:


$ ./shm2key=0xc81102edshmid=0x2e8047x=0xaaaaaaaa y=0x55555555
复制代码


可以看到通信成功,在第一个 shell 中按下“Enter”可以销毁共享内存。

示例 2

示例 1 使用 ftok()函数生成的 key 创建共享内存,本示例使用 IPC_PRIVATE 参数创建共享内存。


创建一个共享内存,并输出其 ID 号,create_shm.c:


#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#include <stdlib.h>#include <stdio.h>#define BUFSZ 4096
int main(void){ int shm_id;
shm_id = shmget(IPC_PRIVATE, BUFSZ, 0666); if(shm_id < 0) { printf("shmget failed!\n"); exit(1); } printf("create a shared memory segment successfully: %d\n", shm_id);
system("ipcs -m");
exit(0);}
复制代码


打开指定 ID 的共享内存,写入内容,write_shm.c:


#include <sys/ipc.h>#include <sys/shm.h>#include <sys/types.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
typedef struct{ char name[4]; int age;}people;
int main(int argc, char **argv){ int shm_id, i; char temp; people *p_map;
if(argc != 2) { printf("USAGE:atshm <identifier>\n"); exit(1); }
// get share memory ID from input shm_id = atoi(argv[1]);// str to int
// map the share memory to get the virtul address p_map = (people *)shmat(shm_id, NULL, 0);
// write temp = 'a'; for(i=0; i<10; i++) { temp+=1; memcpy((*(p_map+i)).name, &temp, 1); (*(p_map+i)).age=20+i; }
// remove map if(shmdt(p_map)==-1) perror("detach error!\n");
return 0;}
复制代码


打开指定 ID 的共享内存,读取内容,read_shm.c:


#include <sys/ipc.h>#include <sys/shm.h>#include <sys/types.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>
typedef struct{ char name[4]; int age;}people;
int main(int argc, char** argv){ int shm_id, i; people *p_map;
if(argc != 2) { printf("USAGC: atshm <identifier>\n"); exit(1); }
// get the share memory ID from input shm_id = atoi(argv[1]);// str to int
// map the share memory to get the virtual address p_map = (people*)shmat(shm_id, NULL, 0);
// read data for(i=0; i<10; i++) { printf("name:%s ", (*(p_map+i)).name); printf("age %d\n", (*(p_map+i)).age); }
// remove the map if(shmdt(p_map)==-1) perror("detach error!\n");
return 0;}
复制代码


编译上述 3 个程序,首先运行 create_shm 程序创建一个共享内存:


$ ./create_shmcreate a shared memory segment successfully: 3080264
------ Shared Memory Segments --------key shmid owner perms bytes nattch status0x00000000 2260992 deeplearni 600 524288 2 dest0x00000000 2490369 deeplearni 600 67108864 2 dest0x00000000 163842 root 777 7680 20x00000000 196611 root 777 8294400 20x00000000 229380 root 777 4096 2...省略0x00000000 2031675 root 777 229376 20x00000000 2064444 root 777 233472 20x00000000 2097213 root 777 237568 20x00000000 2129982 root 777 241664 20x00000000 2162751 root 777 245760 20x00000000 2654272 deeplearni 600 524288 2 dest0x00000000 2687041 deeplearni 600 524288 2 dest0x00000000 2719810 deeplearni 600 524288 2 dest0x00000000 2752579 deeplearni 600 524288 2 dest0x00000000 2981956 deeplearni 600 524288 2 dest0x00000000 2949189 deeplearni 600 524288 2 dest0x00000000 3014726 deeplearni 600 67108864 2 dest0xc81102ed 3047495 deeplearni 666 8 00x00000000 3080264 deeplearni 666 4096 0
复制代码


可以看到程序创建了一个 shmid 为 3080264 的共享内存,然后可以运行 write_shm 程序,需要将 ID 号作为参数:


$ ./write_shm 3080264
复制代码


虽然没有输出,但程序已将内容写入共享内存,并且共享内存没有被删除,可以运行 read_shm 程序读取共享内存中的内容:


$ ./read_shm 3080264name:b   age 20name:c   age 21name:d   age 22name:e   age 23name:f   age 24name:g   age 25name:h   age 26name:i   age 27name:j   age 28name:k   age 29
复制代码


可以看到,程序读取到了共享内存中存储的 10 个字符。


另外,无用的共享内存也可以通过命令的方式收到删除,使用 ipcrm -m 共享内存 id 的形式,如:


$ ipcrm -m 3080264
复制代码


此时再用 ipcs -m 命令查看,已经没有 shmid 为 3080264 的共享内存。


发布于: 2020 年 12 月 05 日阅读数: 37
用户头像

ShenDu_Linux

关注

还未添加个人签名 2020.11.26 加入

还未添加个人简介

评论 (1 条评论)

发布
用户头像
666
2020 年 12 月 05 日 15:23
回复
没有更多了
深入了解Linux共享内存及函数详解(含编程示例)