共享内存是高效的IPC方式。
一、创建共享内存:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
例如:
int shmid = shmget(key, size, 0660 | IPC_CREAT);
参数说明:
key: 若取值为IPC_PRIVATE,则创建的共享内存是进程私有的;
若取值不为IPC_PRIVATE,则分以下两种情况:
已经有共享内存关联到该key,则会返回该共享内存的标识符(shmid);
该key尚未被使用,则会创建新的共享内存段,且段的长度与参数size相关。
size: 作为共享内存段的大小。(man shmget说,size会被自动向上取整为PAGE_SIZE的整数倍;这点不敢苟同,因为实际通过ipcs -m查看共享内存段大小依然是size,并且看内核的newseg()函数也表明共享内存的大小是size。猜想可能是指实际占用的内存大小是PAGE_SIZE的整数倍,但共享内存在进程中的可用大小的的确确就是size。但是!!!用shmat将共享内存关联到进程地址空间时,返回的地址的确是PAGE_SIZE对齐的)。
flag: 0660是指共享内存段的权限;IPC_CREAT指创建新的共享内存段
更多信息见 man shmget
========
二、将共享内存附加到本进程的地址空间
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
例如:
void *attaddr = shmat(shmid, NULL, 0);
========
三、将共享内存从本进程地址空间中分离
shmdt:
将共享内存段从本进程的地址空间中分离。参数shmaddr是shmat的返回值。
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
例如:
void *attaddr = shmdt(shmaddr);
特别注意:该操作会使nattch递减。
=======
四、将共享内存置为待销毁状态
shmctl(shmid, IPC_RMID, NULL)
作用:将shmid标识的共享内存的状态置为dest.
dest的作用是,当共享内存段的nattch为0时,系统会销毁该共享内存。
dest还有一个作用是,被置为dest的共享内存段会释放它对key的占用,该共享内存段的key被置为0(但依旧保留对shmid的占用)。也就是说,dest状态的共享内存和它原来的key完全脱离了关系。如果之后再使用shmget和那个key,会创建一块新的共享内存,而找不到已dest的共享内存(即使dest共享内存的nattch不为0,尚未被系统真正销毁)。
=======
五、获取shmid标识的共享内存的状态信息。
struct shmid_ds ds = {0};
shmctl(shmid, IPC_STAT, &ds);
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
重点
1、执行shmget后在系统中用命令"ipcs -m"可以查看共享内存,但该段共享内存的 nattch 不会增加。只有在shmat后 nattch 才会递增。
2、shmid是共享内存在系统中的唯一标识符。不同进程对同一key多次执行shmget,只要第二次及以后没有创建新的共享内存(也就是说在前后两次shmget中间没有使用shmctl-shmid-IPC_RMID将共享内存置为dest状态),那么shmget返回的shmid就是相同的。
3、对同一key多次执行shmget-IPC_CREAT,第一次设置的size(rounded up to PAGE_SIZE)会在对齐PAGE_SIZE后成为共享内存段的实际大小;之后指定的size如果小于共享内存段的实际大小,那么shmget会正常返回相同的shmid,且共享内存的大小不变;若之后指定的size超过共享内存段的实际大小,shmget返回-1,errno报错"Invalid argument".
4、同一进程对同一shmid多次执行shmat,shmat返回的地址是不同的。即同一共享内存段可以被分配到一个进程的地址空间中的多个地址。 并且,这一操作还会导致 nattch 增加。
5、进程退出时,共享内存段的nattch会自动减少(进程对该段共享内存shmat了几次,nattch就会递增几次;进程退出时,nattch就会自动递减相应次数)。
6、dest状态的共享内存会在nattch为0时自动销毁。dest状态的共享内存不再关联创建它时使用的key。
7、非dest状态的共享内存即使nattch为0,也依然会被系统保留。
==============
下面用文字叙述一个例子,可以将上述重点串联起来。
步骤1:进程A使用shmget(key=0x11223344,size = 123, flag = 0660 | IPC_CREAT),在系统中创建了一块共享内存,shmget()返回的shmid为23456。
//执行后,shell输入"ipcs -m"命令可以看到,系统中新增加了一块共享内存
//key shmid owner perms bytes nattch status
//0x11223344 23456 user_12 660 123 0 空
步骤2:进程A使用shmat(shmid=23456),将共享内存关联到本进程的地址空间。shmat()返回的地址为 0x7f36f9df5000
//执行后,shell输入"ipcs -m"命令可以看到,nattch = 1,其他不变
//key shmid owner perms bytes nattch status
//0x11223344 23456 user_12 660 123 1 空
步骤3:进程B(或者依然是进程A),继续使用步骤1中的参数,只把size减小为100.
//执行后,shell输入"ipcs -m"命令可以看到,共享内存的size依然是123,其他属性也没有改变。
//key shmid owner perms bytes nattch status
//0x11223344 23456 user_12 660 123 1 空
步骤4:进程X (X可以是A,B,C…),继续使用步骤1中的参数,只是把size设置为300. 该步骤会执行失败,shmget()返回-1
//执行后,shell输入"ipcs -m"命令可以看到,没有发生任何变化。
//key shmid owner perms bytes nattch status
//0x11223344 23456 user_12 660 123 1 空
步骤5:进程A使用shmat(shmid=23456)将共享内存关联到本进程的地址空间。shmat()返回的地址为 0x7f36f9df4000(注意,在步骤2中返回的地址为0x7f36f9df5000,也就是说,虽然共享内存的size只有123byte,但在进程地址空间中占用的大小必须对齐到PAGE_SIZE的整数倍;同时也说明了一块共享内存可以在一个进程中多次关联,只是返回的关联地址不同)
//执行后,nattch加1,nattch=2
//key shmid owner perms bytes nattch status
//0x11223344 23456 user_12 660 123 2 空
步骤6:进程A 执行一次shmdt(0x7f36f9df4000)。
//执行后,nattch减1,nattch=1
//key shmid owner perms bytes nattch status
//0x11223344 23456 user_12 660 123 1 空
步骤7:进程A直接退出(注意,没有执行shmdt(0x7f36f9df5000))
//退出后,nattch直接变成了0。也就是说,进程退出时会自动递减共享内存的nattch。
//key shmid owner perms bytes nattch status
//0x11223344 23456 user_12 660 123 0 空
步骤8:进程B执行 shmat(shmid=23456)
//执行后,nattch加1,nattch=1
//key shmid owner perms bytes nattch status
//0x11223344 23456 user_12 660 123 1 空
然后执行shmctl(23456, IPC_RMID, NULL)
//执行后,key变为0x00000000,status变为dest
//key shmid owner perms bytes nattch status
//0x00000000 23456 user_12 660 123 1 dest
步骤9:进程B,继续使用步骤1中的参数,只是把size设置为300.
//执行后,shell输入"ipcs -m"命令可以看到。一块新的共享内存出现了。shmid=56789,size=300,nattch=0
//key shmid owner perms bytes nattch status
//0x00000000 23456 user_12 660 123 1 dest
//0x11223344 56789 user_12 660 300 0 空
步骤10:进程B直接退出(注意,进程B没有执行shmdt())
//B退出后,23456的nattch直接变成了0。然后由于23456共享内存已经处于dest状态,所以系统直接将它销毁了。
//执行后,shell输入"ipcs -m"命令可以看到,系统中已经没有shmid为23456的共享内存了。
//56789依然存在。也就是说,即使创建共享内存的进程已经终止运行,但只要该共享内存没有被置为dest,那么它就会继续存在于系统中,并且之后可以被其他进程获取和关联。
//key shmid owner perms bytes nattch status
//0x11223344 56789 user_12 660 300 0 空
最后附上代码。(代码只是简单示例,并不能完整体现上述特性)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define DO_IF(exp, how) \
if (exp) { \
how; \
}
#define TEST_SHM_KEY 0x11223344
int g_shmid = 0;
void print_shm_ds(int shmid)
{
struct shmid_ds ds = {0};
shmctl(shmid, IPC_STAT, &ds);
printf("\nds.shm_nattch\tds.shm_segsz\n%u\t%u\n", (unsigned) ds.shm_nattch, (unsigned) ds.shm_segsz);
}
void* get_shm(key_t key ,size_t size)
{
int shmid = 0;
void *attaddr = NULL;
shmid = shmget(key, size, 0666 | IPC_CREAT);
DO_IF(-1 == shmid, perror(0);return -1);
g_shmid = shmid;
attaddr = shmat(shmid, NULL, 0);
DO_IF(-1 == (int)attaddr, perror(0);return -1);
printf("shmid[%#x] attaddr[%p]", shmid, attaddr);
print_shm_ds(shmid);
return attaddr;
}
int detach_shm(void *addr)
{
return shmdt(addr);
}
int release_shm(int shmid)
{
PT("");
if (-1 == shmctl(shmid, IPC_RMID, NULL))
{
perror("shmctl return -1");
return -1;
}
return 0;
}
int main()
{
int shmid = 0;
void *a,*b,*c;
printf("private shm id %d\n", shmid = shmget(IPC_PRIVATE, 256, 0660 | IPC_CREAT));
shmat(shmid, NULL, 0);
printf("this is main\n");
a = get_shm(TEST_SHM_KEY, 228);
sleep(1);
b = get_shm(TEST_SHM_KEY, 5164);
sleep(1);
c = get_shm(TEST_SHM_KEY, 32);
sleep(1);
release_shm(g_shmid);
sleep(5);
return 0;
}