同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。所以必须要某种同步机制来保证该共享内存中资源的准确性。
共享内存相关函数
shmget()创建共享内存并返回标识符
头文件 #include<sys/shm.h>
int shmget(key_t key, size_t size, int shmflag);
第一个参数key,与信号量一样,程序需要提供一个参数key,有效地为共享内存段命名。有一个特殊的键值:IPC_PRIVATE,它用于创建一个只属于创建进程的共享内存,通常你不会用到这个值。
第二个参数size,以字节为单位指定需要共享的内存容量。
第三个参数shmflag,与创建文件时使用的mode、标志一样,由IPC_CREAT定义的一个特殊比特必须和权限标志按位或才能创建一个新的共享内存段。如果无需用到IPC_CREAT标志,该标志就会被悄悄忽略。
如果共享内存创建成功,shmget返回一个共享内存标识符(非负整数),该标识符将用于后续的共享内存函数。如果失败了就返回-1.
shmat()函数将共享内存链接到当前进程
void* shmat(int shm_id, const void* shm_addr, int shmflg);
第一个参数shm_id是由shmget函数返回的共享内存标识符
第二个参数shm_addr指定的是共享内存链接到当前进程中的地址位置。它通常是一个空指针,表示让系统来选择共享内存出现的地址
第三个参数shmflg十一组位标志。它的两个可能取值是SHM_RND和SHM_RDONLY。其中SHM_RND与shm_addr联合使用,用来控制共享内存的链接地址。SHM_RDONLY使得链接的内存只读。
我们很少需要控制共享内存链接的地址,通常都是让系统来选择一个地址,否则就会使应用程序对硬件的依赖过高。
如果shmat调用成功,它返回一个指向共享内存第一个字节的指针:如果失败,它就返回-1。
共享内存的访问权限类似于文件的访问权限,即共享内存的读写权限由它的属主(共享内存的创建者)、它的访问权限和当前进程的属主决定。这个规则的一个例外是:当shmflg&SHM_RDONLY为true的时,即使该共享内存的访问权限允许写操作,它都不能被写入。
shmdt()函数将共享内存从当前进程中分离。
它的参数是shmat返回的地址指针。
成功时返回0,失败时返回-1
注意:将共享内存分离并未删除它,只是使得该共享内存对当前进程不再可用。
shmctl()共享内存控制函数
int shmctl(int shm_id, int command, struct shmid_ds *buf);
第一个参数shm_id是shmget返回的共享内存标识符。
第二个参数command是要采取的动作,它可以取三个值:
IPC_STAT 把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET 如果进程有足够的权限,就把共享内存当前的关联值设置为shmid_ds结构中给出的值
IPC_RMID 删除共享内存段
第三个参数buf是一个指针,它指向包含共享内存模式和访问权限的结构。
成功时返回0,失败时返回-1
shmid_ds 结构至少包含以下成员:
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
}
具体应用举例
我们首先申请一块256字节的共享内存,让进程一往其中写数据,进程B从中读数据。并且在进程一写数据时,进程二不能读取这块共享内存的数据,而进程二读取的时候,进程一不能向其中写数据。
这里我们就又用到了之前讲过的信号量,再通过以下代码回顾一下:
/*
sem.c
*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/sem.h>
#include"sem.h"
static int semid = -1;
void sem_init()
{
semid = semget((key_t)1234, 1,IPC_CREAT | IPC_EXCL | 0600);
if(semid == -1)
{
semid = semget((key_t)1234, 1, 0600);
if(semid == -1)
{
perror("semget error");
}
}
else
{
union semun a;
a.val = 0;
//注意:这里的初始值和之前的不同
if(semctl(semid, 0, SETVAL, a) == -1)
{
perror("semctl error");
}
}
}
void sem_p()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
if(semop(semid, &buf, 1) == -1)
{
perror("semop p error");
}
}
void sem_v()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
if(semop(semid, &buf, 1) == -1)
{
perror("semop v error");
}
}
void sem_destroy()
{
if(semctl(semid, 0, IPC_RMID) == -1)
{
perror("del error");
}
}
然后我们把pv操作的头文件也写好:
/*
sem.h
*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/sem.h>
union semun
{
int val;
};
void sem_init();
void sem_p();
void sem_v();
void sem_destroy();
接下来就是两个进程的代码了,我们把shma.c作为写入的程序,shmb.c作为读取的程序,则:
/*
shma.c 往共享内存中写入数据
*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/sem.h>
#include<sys/shm.h>
#include"sem.h"
int main()
{
int shmid = shmget((key_t)2345, 256, IPC_CREAT | 0600); //创建共享内存
assert(shmid != -1);
char*s = (char*)shmat(shmid, NULL, 0); //把共享内存连接到该进程空间
sem_init(); //信号量初始化
while(1)
{
char buff[128] = {0};
printf("input:\n");
fgets(buff, 128, stdin);
strcpy(s,buff);
sem_v(); //进行v操作,把信号量的值改为1,通知shmb可以访问共享内存了
if(strncmp(buff, "end", 3) == 0) //如果写入的是end,就结束该进程
{
break;
}
}
shmdt(s);
exit(0);
}
/*
shmb.c 从共享内存中读取数据
*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/sem.h>
#include<sys/shm.h>
#include"sem.h"
int main()
{
int shmid = shmget((key_t)2345, 256, IPC_CREAT | 0600);
assert(shmid != -1);
char* p = (char*)shmat(shmid, NULL, 0);
sem_init();
while(1)
{
sem_p(); //从共享内存读取数据时,不允许其他人访问
if(strncmp(p, "end", 3) == 0)
{
break;
}
printf("%s\n", p);
}
shmdt(p); //将共享内存与进程空间的链接断开
sem_destroy();
shmctl(shmid, IPC_RMID, NULL); //删除该块共享内存
exit(0);
}
注意编译时要带上sem.c,如:gcc -o shma shma.c sem.c
并且我们可以使用 ipcs -m 命令查看申请好的共享内存。
这时,我们再用ipcs -m 命令查看时,之前申请的共享内存已经没有了。