LV020-SystemV共享内存
一、System V 共享内存
前边我们已经知道了System V IPC包含三种通信机制,分别是消息队列、共享内存和信号量,这一部分就刚好是学习共享内存的笔记,就一起写在这里吧。
1. 创建或者打开共享内存
1.1 shmget()
在linux下可以使用man 2 shmget命令查看该函数的帮助手册。
/* 需包含的头文件 */
#include <sys/ipc.h>
#include <sys/shm.h>
/* 函数声明 */
int shmget(key_t key, size_t size, int shmflg);【函数说明】该函数得到一个共享内存标识符ID或创建一个共享内存对象。
【函数参数】
key:key_t类型,通过ftok创建的key(键)值或者是IPC_PRIVATE(这样的话只能用于具有血缘关系的进程通信)。size:size_t类型,指定共享内存的大小,以字节为单位。所有的内存分配操作都是以页为单位的。所以如果一段进程只申请一块只有一个字节的内存,内存也会分配整整一页(在i386机器中一页的缺省大小PACE_SIZE=4096字节)这样,新创建的共享内存的大小实际上是从size这个参数调整而来的页面大小。即如果size为1至4096,则实际申请到的共享内存大小为4K(一页);4097到8192,则实际申请到的共享内存大小为8K(两页),依此类推。shmflg:int类型,共享内存标志位,使用时需要与IPC对象存取权限(如0666)进行|运算来确定读写权限。一般是需要创建共享内存时为IPC_CREAT|0666,若不需要创建,直接打开时,使用0666即可。常用可取的值及含义如下:
| IPC_CREAT | 如果共享内存不存在,则创建一个共享内存,否则打开操作。 |
| IPC_EXCL | 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。 |
【返回值】int类型,成功返回共享内存的标识符,失败返回-1,并设置errno。
【使用格式】一般情况下基本使用格式如下:
/* 需要包含的头文件 */
#include <sys/ipc.h>
#include <sys/shm.h>
/* 至少应该有的语句 */
int shmid;
key = ftok("./keytest.txt", 100);
shmid = shmget(key, 512, IPC_CREAT|0666);【注意事项】共享内存的大小是有限制的,可以使用以下命令查看:
ipcs -l
# 或者
cat /proc/sys/kernel/shmmax1.2 使用实例
暂无。
2. 映射共享内存
2.1 shmat()
在linux下可以使用man 2 shmat命令查看该函数的帮助手册。
/* 需包含的头文件 */
#include <sys/types.h>
#include <sys/shm.h>
/* 函数声明 */
void *shmat(int shmid, const void *shmaddr, int shmflg);【函数说明】该函数将shmid标识的共享内存映射到当前进程的虚拟地址空间,随后可像本地空间一样访问。
【函数参数】
shmid:int类型,共享内存IPC对象的ID。shmaddr:void *类型,表示映射的方式,若为NULL,共享内存会被系统内核自动映射到一个合适的虚拟地址空间,建议使用NULL;若不为NULL,我自己没试过,可以参考函数帮助手册。shmflg:int类型,一些标志,一般是不指定的,写0就可以啦,表示可读可写。其他的可以查看帮助手册,若设置为SHM_RDONLY表示只读模式;不指定的话默认是读写权限;若设置为IPC_REMAP,表示替换位于shmaddr处的任意已有映射。
【返回值】void *类型,成功返回映射的共享内存段的地址,失败返回(void *) -1,并设置errno来指示错误的原因。
【使用格式】一般情况下基本使用格式如下:
/* 需要包含的头文件 */
#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命令查看该函数的帮助手册。
/* 需包含的头文件 */
#include <sys/types.h>
#include <sys/shm.h>
/* 函数声明 */
int shmdt(const void *shmaddr);【函数说明】该函数是用来撤销共享内存映射,将会禁止本进程访问此片共享内存。
【函数参数】
shmaddr:void *类型,表示要撤销的共享内存的首地址。
【返回值】int类型,成功0,失败返回-1,并设置errno来指示错误的原因。
【使用格式】一般情况下基本使用格式如下:
/* 需要包含的头文件 */
#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命令查看该函数的帮助手册。
/* 需包含的头文件 */
#include <sys/types.h>
#include <sys/shm.h>
/* 函数声明 */
int shmctl(int shmid, int cmd, struct shmid_ds *buf);【函数说明】该函数是用来控制创建的共享内存,一般是用来删除创建的共享内存。
【函数参数】
shmid:int类型,表示共享内存IPC对象的ID,由shmget函数生成,不同的key值对应不同的ID值。cmd:int类型,表示要执行的操作。常见 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中) |
【返回值】int类型,成功0,失败返回-1,并设置errno来指示错误的原因。
【使用格式】一般情况下基本使用格式如下:
/* 需要包含的头文件 */
#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
/* 头文件 */
#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
/* 头文件 */
#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
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 *.o1.4 测试效果
在终端执行以下命令编译程序:
make # 生成可执行文件
./shm_wirte # 在一个终端执行共享内存写入程序
./shm_read # 在另一个终端执行读取共享内存的程序然后,终端会有以下信息显示:
# 执行 ./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,这样的话只能是具有血缘关系的进程之间通信。
#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;
}在终端执行以下命令编译程序:
gcc test.c -Wall # 生成可执行文件 a.out
./a.out # 执行可执行程序然后,终端会有以下信息显示:
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按键后,父进程读取共享内存数据。