说明:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
QQ 群 号:513683159 【相互学习】
内容来源:
《Linux系统编程》、《Linux网络编程》、《Unix环境高级编程》
目录:
共享内存
共享内存是在多个进程之间共享内存区域的进程间的通信方式,它是在多个进程之间对内存段进行映射的方式实现内存共享的。这是IPC最快捷的方式,因为共享内存方式的通信没有中间过程,而管道、消息队列等方式则是需要将数据通过中间机制进行转换;与此相反,共享内存方式直接将某段内存段进行映射,多个进程间的共享内存是同一块的物理空间,仅仅是地址不同而已,因此不需要进行复制,可以直接使用此段空间。
一、函数简介
(1)shmget()——共享内存创建函数
1.函数功能:创建一个新的共享内存段 或 访问一个现有的共享内存段(获取共享内存段)
项目 | 说明 |
---|---|
函数原型 | int shmget(key_t key, size_t size, int shmflg); |
头文件 | sys/ipc.h、sys/shm.h |
参数说明 | key:键值(可用flok函数生成) 拿来与内核其他消息队列关键字相比较,打开或访问操作依赖于shmflg参数内容 |
size: | |
shmflg:标记变量 (参数说明如下) | |
返回值 | 若成功则返回 若失败则返回-1 |
注意 |
2.shmflg
参数:
IPC_CREAT
:如果在内核中不存在该内存段,则创建它。
IPC EXCL
:当与IPC _CREAT
一起使用时,如果该内存段早已存在,则此次调用将失败。
如果只使用了IPC_CREAT
,shmget()
函数或者返回新创建的内存段的段标识符,或者会返回现有的具有同一个关键字值的内存段的标识符。
如果同时使用了IPC_EXCL
和IPC_CREAT
,那么将可能会有两个结果:
或者创建一个新的内存段;
或者如果该内存段存在,则调用将出错,并返回-1
。
IPC_EXCL
本身是没有什么用处的,但在与IPC_CREAT
组合使用时,它可用于防止一个现有的内存段为了访问而打开着。一旦进程获得了给定内存段的合法IPC标识符,它的下一步操作就是连接该内存段,或把该内存段映射到自己的寻址空间中。
(2)shmat()——获得共享内存地址函数
1.函数功能:获取共享内存的地址,成功获取后,可像使用通用内存一样对其进行读写操作(附加共享内存段)
项目 | 说明 |
---|---|
函数原型 | void *shmat(int shmid, const void *shmaddr, int shmflg); |
头文件 | sys/ipc.h、sys/shm.h |
参数说明 | shmid:共享内存的句柄 (shmget()获取的返回值) |
shmaddr:指向某地址 等于0,内核将试着查找一个未映射的区域 指定一个地址,该地址只用于访问所拥有的硬件,或解决与其他应用程序的冲突 | |
shmflg:标记变量 (参数说明如下) | |
返回值 | 若成功则返回 若失败则返回-1 |
注意 |
SHM_RND
标志可以与标志参数进行OR操作,结果再置为标志参数,这样可以让传送的地址页对齐(舍入到最相近的页面大小)。
此外,如果把 SHM_RDONLY
标志与标志参数进行OR操作,结果再置为标志参数,这样映射的共享内存段只能标记为只读方式。
当申请成功时,对内存的操作与一般内存一样,可以直接进行写入和读出,以及偏移的操作。
(3)shmdt()——删除共享内存函数
1.函数功能:删除一段共享内存(分离共享内存段)
当某进程不再需要一个共享内存段时,它必须调用这个函数来断开与该内存段的连接
这与从内核删除内存段是两回事!在成功完成了断开连接操作以后,相关的shmid_ds
结构的shm_nattch
成员的值将减去1。如果这个值减到0,则内核将真正删除该内存段。
项目 | 说明 |
---|---|
函数原型 | int shmdt(const void *shmaddr); |
头文件 | sys/ipc.h、sys/shm.h |
参数说明 | shmaddr:指向某地址 |
返回值 | 若成功则返回 若失败则返回-1 |
注意 |
(4)shmctl()——共享内存控制函数
1.函数功能:向共享内存的句柄发送命令,来完成某种功能。(共享内存控制操作)
类似ioctl()
的方式对共享内存进行操作。
项目 | 说明 |
---|---|
函数原型 | int shmctl(int shmid, int cmd, struct shmid_ds *buf); |
头文件 | sys/ipc.h、sys/shm.h |
参数说明 | shmid:共享内存的句柄 (shmget()获取的返回值) |
cmd:共享内存发送的命令 | |
buf:向共享内存发送命令的参数 | |
返回值 | 若成功则返回 若失败则返回-1 |
注意 |
2.shmid_ds结构:
struct shmid_ds
{
struct ipc_perm shm_perm; /* 所有者和权限 */
size_t shm_segsz; /* 段大小,以字节为单位 */
time_t shm_atime; /* 最后挂接时间 */
time_t shm_dtime; /* 最后取出时间 */
time_t e_t shm_ctime; /* 最后修改时间 */
pid_t shm_cpid; /* 建立者PID */
pid_t shm_lpid; /* 最后调用shmat()/shmdt()的PID */
shmatt_t shm_nattch; /* 现在挂接的数量 */
__syscall_ulong_t __glibc_reserved4;
__syscall_ulong_t __glibc_reserved5;
};
struct ipc_perm
{
key_t __key; /* Key提供给semget(2) */
uid_t uid; /* owner的有效UID */
gid_t gid; /* owner的有效GID */
uid_t cuid; /* 创建器的有效UID */
gid_t cgid; /* E创建者的有效GID */
unsigned short mode; /* 权限 */
unsigned short __seq; /* 序列号 */
};
3.合法命令值:
1️⃣IPC_STAT
:获取内存段的 shmid_ds
结构,并把它存储在 buf参数所指足的地址中。
2️⃣IPC_SET
设置内存段shmid_ds
结构的ipc_perm
成员的值,此命令是从 buf
参数中获得该值的。
3️⃣IPC_RMID
:标记某内存段,以备删除。该命令并不真正地把内存段从内存中删除。相反,它只是标记上该内存段,以备将来删除。只有当前连接到该内存段的最后一个进程正确地断开了与它的连接,实际的删除操作才会发生。当然,如果当前没有进程与该内存段相连接,则删除将立刻发生。为了正确地断开与其共享内存段的连接,进程需要调用shmdt()
函数。
二、示例实践:
1.示例介绍:
父进程和子进程之间利用共享内存进行通信,父进程向共享内存中写入数据,子进程读出数据。
两个进程之间的控制采用了信号量的方法,父进程写入数据成功后,信号量加1,子进程在访问信号量之前先等待信号。
2.源程序 :shm.c
#include <stdio.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>
#include <unistd.h>
static char msg[]="你好,共享内存\n";
typedef int sem_t;
union semun{
int val; //整型变量
struct semid_ds *buf; //semid_ds结构指针
unsigned short *array; //数组类型
}arg;
/**
* @function:建立信号量
*
* @param key: 魔数
* @param value: 信号量的初始值
*
* @desciption:
* 按用户键值生成一个信号量,
* 信号量的初始值设为用户输入的value
*/
sem_t CreateSem(key_t key, int value)
{
union semun sem; //信号量结构变量
sem_t semid; //信号量ID
sem.val = value; //设置初始值
semid = semget(key,1,IPC_CREAT|0666); //获得信号量的ID
if(semid == -1) //获得信号量ID失败
{
printf ("create semaphore error\n");
return -1; //返回错误
}
semctl(semid,0,SETVAL,sem); //发送命令,建立value个初始值的信号
return semid; //返回建立的信号量
}
/*增加信号量*/
int Sem_P(sem_t semid)
{
struct sembuf sops={0,+1,IPC_NOWAIT}; //建立信号量结构值(对信号量0,进行+1的操作,)
return (semop(semid,&sops,1)); //发送命令
}
/*减小信号量值*/
int Sem_V(sem_t semid)
{
struct sembuf sops={0,-1,IPC_NOWAIT}; //建立信号量结构值(对信号量0,进行-1的操作,)
return (semop(semid, &sops,1)); //发送信号量操作方法
}
/* 设置信号量的值 */
void SetvalueSem(sem_t semid, int value)
{
union semun sem; //信号量操作的结构
sem.val = value; //值初始化
semctl(semid,0,SETVAL,sem); //设置信号量的值
}
/* 获得信号量的值 */
int GetvalueSem(sem_t semid)
{
union semun sem; //信号量操作的结构
return semctl(semid,0,GETVAL,sem); //获得信号量的值
}
/* 销毁信号量 */
void DestroySem(sem_t semid)
{
union semun sem; //信号量操作的结构
sem.val = 0; //信号量值的初始化
semctl(semid,0,IPC_RMID,sem); //设置信号量
}
int main (int agrc,char *argv[])
{
/* Step 1 初始化 */
key_t key; //键值
int semid; //信号量ID
int shmid; //共享内存ID
char i;
char *shms = NULL;
char *shmc = NULL;
int value = 0;
char buffer[80];
pid_t p; //进程
struct semid_ds buf;
/*
Step 2 生成键值
将路径名和项目的标识符转变为系统V的IPC键值
*/
key = ftok("/ipc/sem",'a');
/*
Step 3 创建共享内存
大小为1024个字节
*/
shmid = shmget(key,1024, IPC_CREAT|0604);
/*
Step 4 建立信号量
*/
semid = CreateSem(key, 0);
/*
Step 5 父子进程通讯
*/
p = fork(); //分叉程序
if(p > 0) /*父进程*/
{
shms = (char *)shmat(shmid,0,0); //挂接共享内存
memcpy (shms,msg,strlen(msg)+1); //复制内容
sleep(10); //等待10s,另一个进程将数据读出
Sem_P(semid); //获得共享内存的信号量
shmdt(shms); //摘除共享内存
DestroySem(semid); //销毁信号量
}
else if(p == 0) /*子进程*/
{
shmc = (char*)shmat(shmid, 0,0); //挂接共享内存
Sem_V(semid); //减小信号量
printf("共享内存的值为:%s\n",shmc);
shmdt(shmc); //摘除共享内存
}
return 0;
}
3.执行步骤 :
①编译:gcc shm.c -o shm
②运行:./shm