linux共享内存的使用方法

共享内存是高效的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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值