Linux共享内存及函数
在shell环境下可以使用ipcs
查看当前系统IPC中的状态,例如当前的电脑中:
$ 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
类型的结构体:
// 摘自所用ubuntu18.04电脑中的/usr/include/i386-linux-gnu/bits/shm.h
struct 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;
};
2 共享内存的相关操作
2.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.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。
2.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。
2.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配合使用:
cmd取值 | 含义 |
---|---|
IPC_STAT | 取shmid指向的共享内存的shmid_ds 结构,对参数buf指向的结构赋值 |
IPC_SET | 使用buf指向的结构对sh_mid段的相关结构赋值 |
IPC_RMID | 当shm_nattch 为0时,删除shmid所指向的共享内存段 |
IPC_LOCK | 锁定共享内存段在内存(只能由超级用户执行) |
IPC_UNLOCK | 对共享内存段解锁(只能由超级用户执行) |
3 编程示例
基本步骤:
-
生成key,ftok()
-
使用key创建/获得一个共享内存,shmget()
-
映射共享内存,得到虚拟地址,shmat()
-
使用共享内存,通过地址指针
-
移除映射,shmdt()
-
销毁共享内存,shmctl()
3.1 示例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程序:
$ ./shm1
key=0xc81102ed
shmid=0x2e8047
use Enter to destroy the share memory
在另一个shell中先运行shm2程序:
$ ./shm2
key=0xc81102ed
shmid=0x2e8047
x=0xaaaaaaaa y=0x55555555
可以看到通信成功,在第一个shell中按下“Enter”可以销毁共享内存。
3.2 示例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_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号作为参数:
$ ./write_shm 3080264
虽然没有输出,但程序已将内容写入共享内存,并且共享内存没有被删除,可以运行read_shm程序读取共享内存中的内容:
$ ./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
的形式,如:
$ ipcrm -m 3080264
此时再用ipcs -m
命令查看,已经没有shmid为3080264的共享内存。
- 点赞
- 收藏
- 关注作者
评论(0)