Skip to content

LV020-SystemV共享内存

一、System V 共享内存

前边我们已经知道了System V IPC包含三种通信机制,分别是消息队列、共享内存和信号量,这一部分就刚好是学习共享内存的笔记,就一起写在这里吧。

1. 创建或者打开共享内存

1.1 shmget()

linux下可以使用man 2 shmget命令查看该函数的帮助手册。

c
/* 需包含的头文件 */
#include <sys/ipc.h>
#include <sys/shm.h>
/* 函数声明 */
int shmget(key_t key, size_t size, int shmflg);

函数说明】该函数得到一个共享内存标识符ID或创建一个共享内存对象。

函数参数

  • keykey_t类型,通过ftok创建的key(键)值或者是IPC_PRIVATE(这样的话只能用于具有血缘关系的进程通信)。
  • sizesize_t类型,指定共享内存的大小,以字节为单位。所有的内存分配操作都是以页为单位的。所以如果一段进程只申请一块只有一个字节的内存,内存也会分配整整一页(在i386机器中一页的缺省大小PACE_SIZE=4096字节)这样,新创建的共享内存的大小实际上是从size这个参数调整而来的页面大小。即如果size14096,则实际申请到的共享内存大小为4K(一页);40978192,则实际申请到的共享内存大小为8K(两页),依此类推。
  • shmflgint类型,共享内存标志位,使用时需要与IPC对象存取权限(如0666)进行|运算来确定读写权限。一般是需要创建共享内存时为IPC_CREAT|0666,若不需要创建,直接打开时,使用0666即可。常用可取的值及含义如下:
IPC_CREAT如果共享内存不存在,则创建一个共享内存,否则打开操作。
IPC_EXCL 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。
如果单独使用`IPC_CREAT`,`shmget()`函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符。如果将`IPC_CREAT`和`IPC_EXCL`标志一起使用,`shmget()`将返回一个新建的共享内存的标识符;如果该共享内存已存在,就会返回错误。

返回值int类型,成功返回共享内存的标识符,失败返回-1,并设置errno

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <sys/ipc.h>
#include <sys/shm.h>

/* 至少应该有的语句 */
int shmid;
key = ftok("./keytest.txt", 100);
shmid = shmget(key, 512, IPC_CREAT|0666);

注意事项】共享内存的大小是有限制的,可以使用以下命令查看:

shell
ipcs -l
# 或者
cat /proc/sys/kernel/shmmax

1.2 使用实例

暂无。

2. 映射共享内存

2.1 shmat()

linux下可以使用man 2 shmat命令查看该函数的帮助手册。

c
/* 需包含的头文件 */
#include <sys/types.h>
#include <sys/shm.h>
/* 函数声明 */
void *shmat(int shmid, const void *shmaddr, int shmflg);

函数说明】该函数将shmid标识的共享内存映射到当前进程的虚拟地址空间,随后可像本地空间一样访问。

函数参数

  • shmidint类型,共享内存IPC对象的ID
  • shmaddrvoid *类型,表示映射的方式,若为NULL,共享内存会被系统内核自动映射到一个合适的虚拟地址空间,建议使用NULL;若不为NULL,我自己没试过,可以参考函数帮助手册。
  • shmflgint类型,一些标志,一般是不指定的,写0就可以啦,表示可读可写。其他的可以查看帮助手册,若设置为SHM_RDONLY表示只读模式;不指定的话默认是读写权限;若设置为IPC_REMAP,表示替换位于shmaddr处的任意已有映射。

返回值void *类型,成功返回映射的共享内存段的地址,失败返回(void *) -1,并设置errno来指示错误的原因。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <sys/ipc.h>
#include <sys/shm.h>

/* 至少应该有的语句 */
char *buf;
buf = shmat(shmid, NULL, 0);

注意事项

(1)fork()后创建的子进程继承已连接的共享内存地址。

(2)exec执行时会执行其他进程,exec执行的进程将会与已连接的共享内存地址自动脱离(detach)。

(3)进程结束后,已连接的共享内存地址会自动脱离(detach).

2.2 使用实例

暂无。

3.撤销共享内存

3.1 shmdt()

linux下可以使用man 2 shmdt命令查看该函数的帮助手册。

c
/* 需包含的头文件 */
#include <sys/types.h>
#include <sys/shm.h>
/* 函数声明 */
int shmdt(const void *shmaddr);

函数说明】该函数是用来撤销共享内存映射,将会禁止本进程访问此片共享内存。

函数参数

  • shmaddrvoid *类型,表示要撤销的共享内存的首地址。

返回值int类型,成功0,失败返回-1,并设置errno来指示错误的原因。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <sys/ipc.h>
#include <sys/shm.h>

/* 至少应该有的语句 */
char *buf;
buf = shmat(shmid, NULL, 0);

shmdt(buf);

注意事项】撤销后,内存地址不可再访问,但是共享内存依然存在,共享内存占用的内存空间并未被释放。

3.2 使用实例

暂无。

4. 共享内存控制

4.1 shmctl()

linux下可以使用man 2 shmctl命令查看该函数的帮助手册。

c
/* 需包含的头文件 */
#include <sys/types.h>
#include <sys/shm.h>
/* 函数声明 */
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

函数说明】该函数是用来控制创建的共享内存,一般是用来删除创建的共享内存。

函数参数

  • shmidint类型,表示共享内存IPC对象的ID,由shmget函数生成,不同的key值对应不同的ID值。
  • cmdint类型,表示要执行的操作。常见 cmd 取值如下:
IPC_RMID 删除共享内存(释放内存空间)。公共的IPC选项(ipc.h中)
IPC_SET 设置ipc_perm参数,改变共享内存状态。公共的IPC选项(ipc.h中)
IPC_STAT 获取ipc_perm参数,获取共享内存状态。>公共的IPC选项(ipc.h中)
SHM_LOCK 锁定共享内存段,需要root权限。共享内存自己的选项(shm.h中)
SHM_UNLOCK解锁共享内存段,需要root权限。共享内存自己的选项(shm.h中)
- `buf`:`struct shmid_ds`类型,保存或设置共享内存属性的地址。

返回值int类型,成功0,失败返回-1,并设置errno来指示错误的原因。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <sys/ipc.h>
#include <sys/shm.h>

/* 至少应该有的语句 */
shmctl(shmid, IPC_RMID, NULL);/* 释放共享内存空间 */

注意事项none

4.2 使用实例

暂无。

二、使用实例

1. 使用实例1

这个例子是使用ftok()函数创建key,以实现不同进程的通信。

1.1 shm_read.c

c
/* 头文件 */
#include <stdio.h>   /* perror */
#include <sys/ipc.h> /* ftok shmget */
#include <sys/shm.h> /* shmget shmat */
#include <string.h>  /* strcpy */
#include <unistd.h>  /* sleep */
/* 主函数 */
int main(int argc, char *argv[])
{
	key_t key;
	int shmid;
	char *buf;
	/* 1. 生成key */
	key = ftok("./keytest.txt", 100);
	if(key < 0)
	{
		perror("ftok");
		return -1;
	}
	printf("key=%x\n", key);
	/* 2. 打开共享内存(之前已创建) */
	shmid = shmget(key, 512, 0666);
	if(shmid < 0)
	{
		perror("shmget");
		return -1;
	}
	printf("shmid=%d\n", shmid);
	/* 3. 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问*/
	buf = shmat(shmid, NULL, 0);
	if(buf < 0)
	{
		perror("shmat");
		return -1;
	}
	printf("buf=%p\n", buf);
	/* 4. 读取数据 */
	printf("share memory=%s\n", buf);

	/* 5. 撤销共享内存(但并未释放内存空间) */
	shmdt(buf);
	/* 6. 释放共享内存空间 */
    shmctl(shmid, IPC_RMID, NULL);
}

1.2 shm_write.c

c
/* 头文件 */
#include <stdio.h>   /* perror */
#include <sys/ipc.h> /* ftok shmget */
#include <sys/shm.h> /* shmget shmat */
#include <string.h>  /* strcpy */

/* 主函数 */
int main(int argc, char *argv[])
{
	key_t key;
	int shmid;
	char *buf;
	/* 1. 生成key */
	key = ftok("./keytest.txt", 100);
	if(key < 0)
	{
		perror("ftok");
		return -1;
	}
	printf("key=%x\n", key);
	/* 2. 创建或者打开共享内存 */
	shmid = shmget(key, 512, IPC_CREAT|0666);
	if(shmid < 0)
	{
		perror("shmget");
		return -1;
	}
	printf("shmid=%d\n", shmid);
	/* 3. 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问*/
	buf = shmat(shmid, NULL, 0);
	if(buf < 0)
	{
		perror("shmat");
		return -1;
	}
	printf("buf=%p\n", buf);
	/* 4. 写入数据 */
	strcpy(buf, "Fanhua write!");
}

1.3 Makefile

makefile
CC = gcc

DEBUG = -g -O2 -Wall
CFLAGS += $(DEBUG)

# 所有.c文件去掉后缀
TARGET_LIST = ${patsubst %.c, %, ${wildcard *.c}} 

all : $(TARGET_LIST)

%.o : %.c
	$(CC) $(CFLAGS) -c $< -o $@

.PHONY: all clean clean_o
clean : clean_o
	@rm -vf $(TARGET_LIST) 
	
clean_o :
	@rm -vf *.o

1.4 测试效果

在终端执行以下命令编译程序:

shell
make # 生成可执行文件
./shm_wirte    # 在一个终端执行共享内存写入程序
./shm_read     # 在另一个终端执行读取共享内存的程序

然后,终端会有以下信息显示:

shell
# 执行 ./shm_write 的终端
hk@vm:~/2Sharedfiles/5temp$ ./shm_write
key=643d0b42
shmid=10
buf=0x7fb80b514000

# 执行 ./shm_read 的终端
hk@vm:~/2Sharedfiles/5temp$ ./shm_read
key=643d0b42
shmid=10
buf=0x7f24393f6000
share memory=Fanhua write!

2. 使用实例2

这个例子是,使用IPC_PRIVATE创建IPC对象的key,这样的话只能是具有血缘关系的进程之间通信。

c
#include <stdio.h>   /* perror */
#include <sys/ipc.h> /* shmget */
#include <unistd.h>  /* fork */
#include <sys/shm.h> /* shmget shmat */
#include <string.h>  /* strcpy */
#include <stdlib.h>  /* exit */

int main()
{
	int shmid;
	char *buf = NULL;
	pid_t pid;
	/* 1.创建共享内存 */
	if((shmid = shmget(IPC_PRIVATE, 512, 0666)) == -1)
	{
		perror("shmget");
		exit(-1);
	}
	/* 2.创建父子进程 */
	pid = fork();
	if (pid < 0)/* 创建失败 */
	{
		perror("fork");
		exit(-1);
	}
	else if (pid == 0)/* 子进程 */
	{
		printf("This is child process!\n");
		/* 映射共享内存 */
		if ((buf = shmat(shmid, NULL, 0)) == (void *)-1)
		{
			perror("shmat");
			exit(-1);
		}
		/* 向共享内存写数据 */
		strcpy(buf, "This is child process!\n");
		system("ipcs -m");/* 显示IPC信息 */
		/* 撤销共享内存 */
		if (shmdt(buf) == -1)
		{
			perror("shmdt");
			exit(-1);
		}
		system("ipcs -m");/* 显示IPC信息 */
		printf("child process end!\n");
	}
	else /* 父进程 */
	{
		sleep(1);
		printf("This is father process, please enter a char:");
		getchar();
		if ((buf = shmat(shmid, NULL, 0)) == (void *)-1)
		{
			perror("shmat");
			exit(-1);
		}
		printf("share mem=%s\n", (char *)buf);
		if (shmctl(shmid, IPC_RMID, NULL) == -1)
		{
			perror("RM");
			exit(-1);
		}
	}
	return 0;
}

在终端执行以下命令编译程序:

shell
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

shell
This is child process!

------------ 共享内存段 --------------
        shmid      拥有者  权限     字节     连接数  状态      
0x27f87e6c 9          hk         644        10         0                       
0x00000000 15         hk         666        512        1                       


------------ 共享内存段 --------------
        shmid      拥有者  权限     字节     连接数  状态      
0x27f87e6c 9          hk         644        10         0                       
0x00000000 15         hk         666        512        0                       

child process end!
This is father process, please enter a char: 
share mem=This is child process!

子进程先运行,父进程1s后运行,这时候子进程已经运行完毕,当我们输入任意字符,按下enter按键后,父进程读取共享内存数据。