共享内存的概念 共享内存是指多个进程可以把一段内存共同的内存映射到自己的进程空间中,从而实现数据的共享和传输,它是存在与内核级别的一种资源,是所有进程间通信中方式最快的一种。
在shell环境下可以使用ipcs
查看当前系统IPC中的状态,例如当前的电脑中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 $ ipcs ------ Message Queues -------- key msqid owner perms used-bytes messages ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00000000 2260992 deeplearni 600 524288 2 dest 0x00000000 2490369 deeplearni 600 67108864 2 dest 0x00000000 163842 root 777 7680 2 0x00000000 196611 root 777 8294400 2 0x00000000 229380 root 777 4096 2 0x00000000 262149 root 777 8192 2 0x00000000 294918 root 777 12288 2 ...省略 0x00000000 2064444 root 777 233472 2 0x00000000 2097213 root 777 237568 2 0x00000000 2129982 root 777 241664 2 0x00000000 2162751 root 777 245760 2 0x00000000 2654272 deeplearni 600 524288 2 dest 0x00000000 2687041 deeplearni 600 524288 2 dest 0x00000000 2719810 deeplearni 600 524288 2 dest 0x00000000 2752579 deeplearni 600 524288 2 dest 0x00000000 2981956 deeplearni 600 524288 2 dest 0x00000000 2949189 deeplearni 600 524288 2 dest 0x00000000 3014726 deeplearni 600 67108864 2 dest ------ Semaphore Arrays -------- key semid owner perms nsems
该命令使用附加参数可单独查看某一IPC:-m
(共享内存),-q
(消息队列),-s
(信号量)。
由于多个进程对同一块内存区域具有访问权限,各进程间的同步问题需要解决,可以配合信号量 进行控制。
对于每一个共享内存段,内核会为其维护一个shmid_ds
类型的结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 struct shmid_ds { struct ipc_perm shm_perm ; size_t shm_segsz; __time_t shm_atime; #ifndef __x86_64__ unsigned long int __glibc_reserved1; #endif __time_t shm_dtime; #ifndef __x86_64__ unsigned long int __glibc_reserved2; #endif __time_t shm_ctime; #ifndef __x86_64__ unsigned long int __glibc_reserved3; #endif __pid_t shm_cpid; __pid_t shm_lpid; shmatt_t shm_nattch; __syscall_ulong_t __glibc_reserved4; __syscall_ulong_t __glibc_reserved5; };
共享内存的相关操作 创建/打开共享内存 创建共享内存需要用到**shmget()**函数,原型如下:
1 2 3 4 #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
错误。
共享内存的附加(映射) 创建一个共享内存后,某个进程若想使用,需要将此内存区域附加(attach)到自己的进程空间(或称地址映射),需要用到shmat()函数 :
1 2 3 4 #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。
共享内存的分离 当进程使用完共享内存后,需要将共享内存从其进程空间中去除(detach),使用shmdt()函数 :
1 2 3 4 #include <sys/types,h> #include <sys/ipc.h> #include <sys/shm.h> int shmdt (void *addr) ;
运行成功返回0,出错返回-1。
参数addr 是调用shmat()
函数的返回值,即共享内存段的地址指针。shmdt()
函数执行成功后,shm_nattch
计数器值减1。
共享内存的控制 使用**shmctl()**可以对共享内存段进行多种控制操作,函数原型:
1 2 3 4 #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 配合使用:
cmd取值
含义
IPC_STAT
取shmid指向的共享内存的shmid_ds
结构,对参数buf指向的结构赋值
IPC_SET
使用buf指向的结构对sh_mid段的相关结构赋值
IPC_RMID
当shm_nattch
为0时,删除shmid所指向的共享内存段
IPC_LOCK
锁定共享内存段在内存(只能由超级用户执行)
IPC_UNLOCK
对共享内存段解锁(只能由超级用户执行)
编程示例 基本步骤:
生成key,ftok()
使用key创建/获得一个共享内存,shmget()
映射共享内存,得到虚拟地址,shmat()
使用共享内存,通过地址指针
移除映射,shmdt()
销毁共享内存,shmctl()
示例1 进程1创建共享内存并写入数据,shm1.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int main () { key_t key = ftok("./" , 200 ); printf ("key=%#x\n" , key); int shmid = shmget(key, 8 , IPC_CREAT|0666 |IPC_EXCL); if (shmid == -1 ) { perror("shmget failed\n" ); exit (1 ); } printf ("shmid=%#x\n" , shmid); void *p = shmat(shmid, 0 , 0 ); if ((void *)-1 == p) { perror("shmat failed" ); exit (2 ); } int *pi = p; *pi = 0xaaaaaaaa ; *(pi+1 ) = 0x55555555 ; if (shmdt(p) == -1 ) { perror("shmdt failed" ); exit (3 ); } 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;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int main () { key_t key = ftok("./" , 200 ); printf ("key=%#x\n" , key); int shmid = shmget(key, 0 , 0 ); if (shmid == -1 ) { perror("shmget failed\n" ); exit (1 ); } printf ("shmid=%#x\n" , shmid); void *p = shmat(shmid, 0 , 0 ); if ((void *)-1 == p) { perror("shmat failed" ); exit (2 ); } int x = *((int *)p); int y = *((int *)p+1 ); printf ("x=%#x y=%#x\n" , x, y); if (shmdt(p) == -1 ) { perror("shmdt failed" ); exit (3 ); } return 0 ; }
编译运行测试,在一个shell中先运行shm1程序:
1 2 3 4 5 6 $ ./shm1 key=0xc81102ed shmid=0x2e8047 use Enter to destroy the share memory
在另一个shell中先运行shm2程序:
1 2 3 4 5 $ ./shm2 key=0xc81102ed shmid=0x2e8047 x=0xaaaaaaaa y=0x55555555
可以看到通信成功,在第一个shell中按下“Enter”可以销毁共享内存。
示例2 示例1使用ftok()
函数生成的key创建共享内存,本示例使用IPC_PRIVATE
参数创建共享内存。
创建一个共享内存,并输出其ID号,create_shm.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #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 ); } shm_id = atoi(argv[1 ]); p_map = (people *)shmat(shm_id, NULL , 0 ); temp = 'a' ; for (i=0 ; i<10 ; i++) { temp+=1 ; memcpy ((*(p_map+i)).name, &temp, 1 ); (*(p_map+i)).age=20 +i; } if (shmdt(p_map)==-1 ) perror("detach error!\n" ); return 0 ; }
打开指定ID的共享内存,读取内容,read_shm.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #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 ); } shm_id = atoi(argv[1 ]); p_map = (people*)shmat(shm_id, NULL , 0 ); for (i=0 ; i<10 ; i++) { printf ("name:%s " , (*(p_map+i)).name); printf ("age %d\n" , (*(p_map+i)).age); } if (shmdt(p_map)==-1 ) perror("detach error!\n" ); return 0 ; }
编译上述3个程序,首先运行create_shm程序创建一个共享内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $ ./create_shm create a shared memory segment successfully: 3080264 ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00000000 2260992 deeplearni 600 524288 2 dest 0x00000000 2490369 deeplearni 600 67108864 2 dest 0x00000000 163842 root 777 7680 2 0x00000000 196611 root 777 8294400 2 0x00000000 229380 root 777 4096 2 ...省略 0x00000000 2031675 root 777 229376 2 0x00000000 2064444 root 777 233472 2 0x00000000 2097213 root 777 237568 2 0x00000000 2129982 root 777 241664 2 0x00000000 2162751 root 777 245760 2 0x00000000 2654272 deeplearni 600 524288 2 dest 0x00000000 2687041 deeplearni 600 524288 2 dest 0x00000000 2719810 deeplearni 600 524288 2 dest 0x00000000 2752579 deeplearni 600 524288 2 dest 0x00000000 2981956 deeplearni 600 524288 2 dest 0x00000000 2949189 deeplearni 600 524288 2 dest 0x00000000 3014726 deeplearni 600 67108864 2 dest 0xc81102ed 3047495 deeplearni 666 8 0 0x00000000 3080264 deeplearni 666 4096 0
可以看到程序创建了一个shmid为3080264 的共享内存,然后可以运行write_shm程序,需要将ID号作为参数:
虽然没有输出,但程序已将内容写入共享内存,并且共享内存没有被删除,可以运行read_shm程序读取共享内存中的内容:
1 2 3 4 5 6 7 8 9 10 11 12 $ ./read_shm 3080264 name:b age 20 name:c age 21 name:d age 22 name:e age 23 name:f age 24 name:g age 25 name:h age 26 name:i age 27 name:j age 28 name:k age 29
可以看到,程序读取到了共享内存中存储的10个字符。
另外,无用的共享内存也可以通过命令的方式收到删除,使用ipcrm -m 共享内存id
的形式,如:
此时再用ipcs -m
命令查看,已经没有shmid为3080264的共享内存。
参考:
《精通Linux C编程》- 程国钢
《Linux C编程完全解密》- 闫敬 吴淑坤