15.9 system V 共享内存
(1)概述
1.共享内存允许两个或多个进程共享一个给定的存储区。
2.共享内存是最快的一种IPC方式。
1.因为数据不需要再客户进程和服务器进程之间复制
2.一旦共享内存映射到进程的地址空间,进程之间的数据操作就不涉及内核了。
3.信号量通常用于同步多个进程对共享内存的访问。
因为服务进程在将数据完全写到共享内存之前,客户进程不应该去取这些数据。
4.XSI 共享内存是内存的匿名端。
5.XSI 共享内存和内存映射的区别是:前者没有相关文件,后者有相关文件。
注意:
3.多个进程共存时,如果某个进程有多个线程,那么多个线程和其他进程一样可以执行semop操作。
因为内核将线程当做进程一样执行调度。
(2)共享内存结构字段
struct shmid_ds {
struct ipc_perm shm_perm; /* see 15.6.2*/ 包含SHM_DEST和SHM_LOCKED等字段
size_t shm_segsz; // 共享内存的字节数
pid_t shm_lpid; // 操作该共享内存的最后一个进程
pid_t shm_cpid; // 最后一个修改该共享内存的进程
shmatt_t shm_nattch; // attach该共享内存到地址空间的进程个数
time_t shm_atime; // attach共享内存的时间
time_t shm_dtime; // 分离共享内存的时间
time_t shm_ctime; // 调用IPC_SET的时间。
}
(2)创建新的/引用已经存在的共享内存
int shmget(key_t key, size_t size, int flag)
key: XSI IPC键
size: 共享内存的长度,以字节为单位。系统会取为页长的整数倍
1.如果shmget函数引用一段共享内存,则size参数被忽略。
2.创建一个新段时,共享内存区域被初始化为0;
flag:
----------------------------------------------------------------
oflag参数 key不存在(ENOENT) key已经存在
无特殊标志 出错,返回-1 成功,获取到已有标识符
IPC_CREATE 成功 成功,获取到已有标识符
IPC_CREATE|IPCEXCL 成功 出错,返回-1(EEXIST)
----------------------------------------------------------------
(3)控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
shmid: 共享内存标识符
IPC_STAT 取shmid_ds字段, 存储到buf中
IPC_SET 按buf的结构设置该共享内存shmid_ds中的某些字段
IPC_RMID 从系统中删除该共享内存
SHM_LOCK 在内存中对共享内存段加锁,此命令只能由超级用户执行。
SHM_UNLOCK 解锁共享内存段,此命令只能由超级用户执行。
注意:
1.内核为system V共享内存的attach进程维护了引用计数,
2.当使用IPC_RMID时,只有attach引用共享内存的进程数为1时,系统才会真正的删除共享内存。
==》如果进程不使用共享内存,要及时调用shmdt函数对共享内存进行分离。
(4)进程使用共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg)
shmid: 共享内存标识符
返回值: 成功返回指向共享内存存储段的指针,出错返回-1。
shmaddr:共享内存存放到虚拟地址空间的什么位置。
1.shmaddr不为NULL
1.shmflg指定了SHM_RND标志
1)如果shmaddr是页地址的整数倍
返回shmaddr
2)如果shmaddr不是页地址的整数倍
系统会找一个附近相应的页地址返回
2.shmflg没有指定SHM_RND标志
1)如果shmaddr是页地址的整数倍
返回shmaddr
2)如果shmaddr不是页地址的整数倍
返回错误
2.shmaddr为NULL (UNNIX 推荐)
共享内存要attach到本进程的地址由内核决定,地址通过返回值返回。
flag:
SHM_RND:
attach到该进程的地址进行页地址取整
SHM_RDONLY:
1.如果指定了SHM_RDONLY标记,进程仅仅是读取共享内存段的内容。
2.未指定SHM_RDONLY时,进程默认对共享内存拥有读写权限。
(5)进程分离共享内存
1.当进程对共享内存操作结束时,则调用shmdt与共享内存段分离。
注意:1.注意这不是从系统中删除共享内存标识符以及相应的数据结构
2.只有调用shmctl函数并传入IPC_RMID时,才会删除共享内存。
2.shmdt(const void *shmaddr);
1.该函数仅仅是使进程和共享内存脱离关系,并未删除共享内存
2.shmdt的作用是将共享内存的引用计数shm_nattch减一
(6)测试
1.有两个进程使用共享内存,进程二中有多个线程都执行PV操作(这是没问题的)线程与进程的调度方式一样
2.每个进程读取共享内存中的数据后加1
3.共享内存数据访问的保护,使用初值为1的信号量的PV操作进行。
// 测试代码见博客
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/wait.h>
#include <sys/acct.h>
#include <errno.h>
#include <sys/times.h>
#include <pthread.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
/*
测试目的:
1.有两个进程使用共享内存
2.每个进程读取共享内存中的数据后加1
3.共享内存数据访问的保护,使用初值为1的信号量的PV操作进行。
执行结果:
child:0
parent:1
work:2
child:3
parent:4
work:5
child:6
parent:7
work:8
*/
#define SHMKEY_PATH "/data1/zhouhouping/nfs/testCode/chapter15/shm/systemV_shm/shmkey"
#define SEMKEY_PATH "/data1/zhouhouping/nfs/testCode/chapter15/shm/systemV_shm/semkey"
#define PRJ_NUM 45258793
#define PAGESIZE sysconf(_SC_PAGESIZE)
//注意: union semun联合体已经被内核注释掉了,需要自己定义一遍
union semun{
int val;
struct senid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
// 每个进程都有自己独特的副本,但是值肯定是相同的。这样写的目的是为了测试代码简洁。
static int semid;
static int shmid;
static key_t semkey;
static key_t shmkey;
static int *shmp; // 共享内存的前4字节
static void *shmaddr;
void print_semop_errno()
{
printf("E2BIG=%d\n", E2BIG);
printf("EACCES=%d\n", EACCES);
printf("EFAULT=%d\n", EFAULT);
printf("EAGAIN=%d\n", EAGAIN);
printf("EIDRM=%d\n", EIDRM);
printf("EINTR=%d\n", EINTR);
printf("EINVAL=%d\n", EINVAL);
printf("ENOMEM=%d\n", ENOMEM);
printf("ERANGE=%d\n", ERANGE);
}
void proc_init()
{
// 生成键
assert((key_t)-1 != (semkey = ftok(SEMKEY_PATH, PRJ_NUM)));
assert((key_t)-1 != (shmkey = ftok(SHMKEY_PATH, PRJ_NUM)));
// 创建IPC标识符,获取信号量和共享内存
assert(-1 != (semid = semget(semkey, 1, IPC_CREAT | S_IRUSR| S_IWUSR))); // 没有就创建, 有就直接引用。
assert(-1 != (shmid = shmget(shmkey, PAGESIZE, IPC_CREAT | S_IRUSR| S_IWUSR)));
// attach shm
assert((void *)-1 != (shmaddr = shmat(shmid, NULL, SHM_RND))); // shmat
shmp = (int *)shmaddr;
*shmp = 0;
}
void semp()
{
struct sembuf semoparry[1];
semoparry[0].sem_num = 0;
semoparry[0].sem_op = -1;
semoparry[0].sem_flg = 0;
semoparry[0].sem_flg |= SEM_UNDO;
if(0 != semop(semid, semoparry, 1)){
printf("ERROR: semp, errno=%d\n",errno);
print_semop_errno();
exit(0);
}
}
void semv()
{
int ret;
struct sembuf semoparry[1];
semoparry[0].sem_num = 0;
semoparry[0].sem_op = +1;
semoparry[0].sem_flg = 0;
semoparry[0].sem_flg |= SEM_UNDO;
if(0 != (ret == semop(semid, semoparry, 1))){
printf("ERROR: semp, errno=%d\n",errno);
exit(0);
}
}
int get_semval()
{
union semun getsemun;
return semctl(semid, 0, GETVAL, getsemun);
}
void delete_sem()
{
union semun ignsemun;
semctl(semid, 0, IPC_RMID, ignsemun);
}
void delete_shm()
{
struct shmid_ds ign;
shmctl(shmid, IPC_RMID, &ign);
}
void *work(void *arg)
{
while(1)
{
// P操作,读取数据+1,V操作
semp();
printf("work:%d\n", *shmp);
sleep(1);
*shmp += 1;
semv();
}
return NULL;
}
int main(int argc, char **argv)
{
pid_t pid;
pid =fork();
union semun setsemun;
pthread_t tid;
assert(pid >= 0);
if (pid == 0){ // child
proc_init();
// 初始化为0
setsemun.val = 0;
assert(0 == semctl(semid, 0, SETVAL, setsemun));
semv(); // 设置初值为1
while(1)
{
// P操作,读取数据+1,V操作
semp();
printf("child:%d\n", *shmp);
sleep(1);
*shmp += 1;
semv();
}
shmdt(shmaddr);
exit(0);
}
// parent
else if ((pid > 0)){
proc_init();
sleep(1);
pthread_create(&tid, NULL, work, NULL);
while(1)
{
// P操作,读取数据+1,V操作
semp();
printf("parent:%d\n", *shmp);
sleep(1);
*shmp += 1;
semv();
}
}
pause();
delete_sem();
delete_shm();
return 0;
}