linuxc-共享内存
–详细概念见《unix环境高级编程》15.9小节
共享内存可以说是Linux 下最快速、最有效的进程间通信方式。两个不同进程A 、B 共享内存的意思是,同一块物理内存被映射到进程A 、B 各自的进程地址空间,进程A 可以即时看到进程B 对共享内存中数据的更新;反之,进程B 也可以即时看到进程A对共享内存中数据的更新。
这里简单说下映射的概念:
Linux系统会为每个进程分配 4GB 的虚拟地址空间,一定情况下,需要将虚拟内存转换成物理内存,这就需要内存映射。为什么我们需要使用虚拟地址呢?最主要的原因是不同PC的物理内存会不一样,如果直接使用物理地址,会造成程序的移植性很差,另外虚拟地址访问内存有以下优势:
1、程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
2、程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(通常大小为 4 KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。
3、不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。
进程可用的虚拟地址范围称为该进程的“虚拟地址空间”。每个用户模式进程都有其各自的专用虚拟地址空间。对于 32 位进程,虚拟地址空间通常为 4 GB,范围从 0x00000000 至 0xFFFFFFFF。
1. 共享内存的概念
共享内存从字面意义解释就是多个进程可以把一段内存映射到自己的进程空间,以此来实现数据的共享及传输,这也是所有进程间通信方式最快的一种,共享内存是存在于内核级别的一种资源。
在Shell 中可以使用ipcs 命令查看当前系统IPC中的状态,在文件系统中/proc目录下有对其描述的相应文件
root@jonathan-pc:~/test/shm# ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x01011d17 0 root 666 2176 1
0x02011d17 32769 root 666 2176 1
0xffffffff 65538 root 666 1024 0
ipcs -m ,其中 -m 是 memory 的意思 。
在系统内核为一个进程分配内存地址时,通过分页机制可以让一个进程的物理地址不连续,同时也可以让一段内存同时分配给不同的进程。共享内存机制就是通过该原理实现的,共享内存机制只是提供数据的传送,如何控制服务器端和客户端的读写操作互斥,这就需要一些其他的辅助工具,例如信号量。
采用共享内存通信的一个显而易见的好处就是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户控件进行四次数据的拷贝,而共享内存只拷贝两次数据:一次从输入文件到共享区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,知道通信完毕为止,这样,数据内同一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在接触映射时才写回文件的。因此,采用共享内存的通信方式效率是最高的。
共享内存最大不足之处在意,由于多个进程对同一块内存区域具有访问的权限,各个进程之间的同步问题显得尤为重要。必须控制同一时刻只有一个进程对共享内存区域写入数据,否则会造成数据的混乱。同步控制问题可以由信号量来解决;
2.相关函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
/* 创建或打开共享内存
Key:IPC_PRIVATE 或 ftok 的返回值
size:共享内存区大小
shmflag :同open函数的权限位,也可用8进制表示法
返回值:成功:共享内存段标识符 出错:-1
*/
int shmget(key_t, int size ,int shmflg );
/* 当一个共享内存创建或打开后,某个进程如果要使用该共享内存则必须将此内存区附加到它的地址空间
shmid :要映射的共享内存区标示符
shmaddr:将共享内存映射到指定地址(若为NULL,则表示由系统自动完成映射)
shmflg:默认0:共享内存只读
返回值:成功:映射后的地址 出错:-1
*/
void *shmat (int shmid, const void *shaddr, int shmflg);
/*当进程对共享内存段的操作完成后,应调用 shmdt 函数,作用是将指定的共享内存段从当前进程空间中脱离出去
shmaddr:共享内存映射后的地址
返回值:0 出错:-1
*/
int shmdt(const void *shmaddr);
/* 共享内存的控制
shmid:要操作的共享内存标示符
cmd: IPC_STAT (获取对象属性)
IPC_SET(设置对象属性)
IPC_RMID(删除对象)
buf:指定IPC_STAT/ IPC_SET 时用以保存/设置属性
返回值:0 出错:-1
*/
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
示例
无关进程之间使用共享内存
shm.h
、client.c
、server.c
//shm.h
#ifndef __SHM_H__
#define __SHM_H__
struct people {
char name[10];
int age;
};
union semun {
int val; // value for SETVAL
struct semid_ds *semid_dsbuf; // buffer for IPC_STAT, IPC_SET
unsigned short *array; // array foror GETALL, SETALL
struct seminfo *__buf; // buffer for IPC_INFO
};
#define SHM_KEY_FILE "./client.c"
#define SEM_KEY_FILE "./server.c"
#endif
//server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <errno.h>
#include <signal.h>
#include "shm.h"
int semid, shmid;
int p(int semid, int semnum)
{
struct sembuf sops = {semnum, -1, SEM_UNDO};
return semop(semid, &sops, 1);
}
int v(int semid, int semnum)
{
struct sembuf sops = {semnum, +1, SEM_UNDO};
return semop(semid, &sops, 1);
}
void * signal_set(int signo, void (*func)(int))
{
int ret;
struct sigaction sig;
struct sigaction osig;
sig.sa_handler = func;
sigemptyset(&sig.sa_mask);
sig.sa_flags = 0;
#ifdef SA_RESTART
sig.sa_flags |= SA_RESTART;
#endif
ret = sigaction(signo, &sig, &osig);
if (ret < 0)
return SIG_ERR;
else
return osig.sa_handler;
}
void sigint(int sig)
{
union semun arg;
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
fprintf(stderr, "shmctl delete error");
}
if (semctl(semid, 0, IPC_RMID, 0) == -1) {
fprintf(stderr, "semctl delete error");
}
exit(sig);
}
int main(int argc, const char *argv[])
{
key_t semkey;
key_t shmkey;
struct people *addr = NULL;
union semun arg;
signal_set(SIGINT, sigint);
signal_set(SIGTERM, sigint);
semkey = ftok(SEM_KEY_FILE, 0);
if (semkey < 0) {
fprintf(stderr, "ftok semkey error");
return -1;
}
shmkey = ftok(SHM_KEY_FILE, 0);
if (shmkey < 0) {
fprintf(stderr, "ftok shmkey error");
return -1;
}
semid = semget(semkey, 1, 0666 | IPC_CREAT);
if (semid < 0) {
fprintf(stderr, "semget error");
return -1;
}
shmid = shmget(shmkey, sizeof(struct people), 0666 | IPC_CREAT);
if (shmid < 0) {
fprintf(stderr, "shmget error");
goto err1;
}
arg.val = 0;
if (semctl(semid, 0, SETVAL, arg) < 0) {
fprintf(stderr, "ctl sem error");
goto err2;
}
addr = (struct people *)shmat(shmid, NULL, 0);
if (addr == (struct people *)-1) {
fprintf(stderr, "shmat error");
goto err2;
}
p(semid, 0);
strncpy(addr->name, "xiaoming", 10);
addr->age = 10;
v(semid, 0);
if (shmdt(addr) == -1) {
fprintf(stderr, "shmdt is error");
goto err2;
}
p(semid, 0);
err2:
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
fprintf(stderr, "shmctl delete error");
}
err1:
if (semctl(semid, 0, IPC_RMID, 0) == -1) {
fprintf(stderr, "semctl delete error");
}
return 0;
}
//client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <errno.h>
#include "shm.h"
int p(int semid, int semnum)
{
struct sembuf sops = {semnum, -1, SEM_UNDO};
return semop(semid, &sops, 1);
}
int v(int semid, int semnum)
{
struct sembuf sops = {semnum, +1, SEM_UNDO};
return semop(semid, &sops, 1);
}
int main(int argc, const char *argv[])
{
int semid, shmid;
key_t semkey;
key_t shmkey;
struct people *addr = NULL;
//获取ipc key
semkey = ftok(SEM_KEY_FILE, 0);
if (semkey < 0) {
fprintf(stderr, "ftok semkey error");
return -1;
}
shmkey = ftok(SHM_KEY_FILE, 0);
if (shmkey < 0) {
fprintf(stderr, "ftok shmkey error");
return -1;
}
//获取semid和shmid
semid = semget(semkey, 0, 0666);
if (semid < 0) {
fprintf(stderr, "semget error");
return -1;
}
shmid = shmget(shmkey, 0, 0666);
if (shmid < 0) {
fprintf(stderr, "shmget error");
return -1;
}
//将共享内存链接到进程内存
addr = (struct people*)shmat(shmid, 0, 0);
if (addr == (struct people *)-1) {
fprintf(stderr, "shmat error");
return -1;
}
v(semid, 0);//取消server的阻塞
sleep(1);
p(semid, 0);//获取共享内存内容
printf("name is : %s\n", addr->name);
printf("age is : %d\n", addr->age);
v(semid, 0);
if (shmdt(addr) == -1) {//断开共享内存
fprintf(stderr, "shmdt is error");
return -1;
}
return 0;
}
运行结果
先运行server,再运行client
root@jonathan-pc:~/test/shm# ls
client.c server.c shm.h
root@jonathan-pc:~/test/shm# gcc client.c -o c
root@jonathan-pc:~/test/shm# gcc server.c -o s
root@jonathan-pc:~/test/shm# ./s
root@jonathan-pc:~/test/shm# ./c
name is : xiaoming
age is : 10
相关进程的共享内存
TELL_WAIT
、WAIT_PARENT
、err_sys
等相关函数见《unix环境高级编程》的代码
/dev/zero的存储映射
相关联进程之间的共享内存,可以对/dev/zero
使用mmap,创建未命名的存储区
#include "apue.h"
#include <fcntl.h>
#include <sys/mman.h>
#define NLOOPS 1000
#define SIZE sizeof(long) /* size of shared memory area */
static int
update(long *ptr)
{
return((*ptr)++); /* return value before increment */
}
int
main(void)
{
int fd, i, counter;
pid_t pid;
void *area;
if ((fd = open("/dev/zero", O_RDWR)) < 0)
err_sys("open error");
if ((area = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, 0)) == MAP_FAILED)
err_sys("mmap error");
close(fd); /* can close /dev/zero now that it's mapped */
TELL_WAIT();
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid > 0) { /* parent */
for (i = 0; i < NLOOPS; i += 2) {
if ((counter = update((long *)area)) != i)
err_quit("parent: expected %d, got %d", i, counter);
TELL_CHILD(pid);
WAIT_CHILD();
}
} else { /* child */
for (i = 1; i < NLOOPS + 1; i += 2) {
WAIT_PARENT();
if ((counter = update((long *)area)) != i)
err_quit("child: expected %d, got %d", i, counter);
TELL_PARENT(getppid());
}
}
exit(0);
}
匿名存储映射
相关联进程之间的共享内存,可以对文件描述符-1使用mmap,创建未命名的存储区
#include "apue.h"
#include <fcntl.h>
#include <sys/mman.h>
#define NLOOPS 1000
#define SIZE sizeof(long) /* size of shared memory area */
static int
update(long *ptr)
{
return((*ptr)++); /* return value before increment */
}
int
main(void)
{
int i, counter;
pid_t pid;
void *area;
if ((area = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED,
-1, 0)) == MAP_FAILED)
err_sys("mmap error");
TELL_WAIT();
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid > 0) { /* parent */
for (i = 0; i < NLOOPS; i += 2) {
if ((counter = update((long *)area)) != i)
err_quit("parent: expected %d, got %d", i, counter);
TELL_CHILD(pid);
WAIT_CHILD();
}
} else { /* child */
for (i = 1; i < NLOOPS + 1; i += 2) {
WAIT_PARENT();
if ((counter = update((long *)area)) != i)
err_quit("child: expected %d, got %d", i, counter);
TELL_PARENT(getppid());
}
}
exit(0);
}