一、共享内存
1、进程的地址空间都是独立,受保护的。
2、A、B进程有一块逻辑地址空间共同映射到同一块物理内存上,作为共享内存。用内核对象来保存。
二、相关函数
1、shmget函数
得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符
(1)原型:
int shmget(key_t key, size_t size, int flg);
(2)key:共享内存标识
(3)size:共享内存大小
例如:申请128字节会映射到4k大小的物理内存,但是使用也只是使用128字节,其他的用不了,但内核分配是分配一页的大小。
- 大于0的整数:新建的共享内存大小,以字节为单位
- 0:只获取共享内存时指定为0
(3)flag - 0:取共享内存标识符,若不存在则函数会报错
- IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
- IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错
(4)返回值: - 成功:返回共享内存的标识符
- 出错:-1,错误原因存于error中
2、shmat函数
把共享内存区对象映射到调用进程的地址空间
(1)原型:
void *shmat(int shmid, const void *shmaddr, int shmflg)
(2)shmid:共享内存标识符
(3)shmaddr:指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
(4)shmflg:SHM_RDONLY:为只读模式,其他为读写模式
(5)返回值:返回一个虚拟地址,此虚拟地址就是共享内存的首地址。
- 成功:附加好的共享内存地址
- 出错:-1,错误原因存于error中
3、shmdt函数
断开共享内存连接
(1)原型:
int shmdt(const void *shmaddr)
(2)shmaddr:连接的共享内存的起始地址
4、shmctl函数
控制共享内存
(1)原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
(2) msqid:共享内存标识符
(3)cmd
- IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中
- IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内
- IPC_RMID:删除共享内存段, 并不会删除共享内存空间,但不能使用shmat方法与共享存储段建立映射关系。
(4)buf:共享内存管理结构体
三、实现共享内存
1、共享内存是最快的IPC
它是A、B进程指针指向共享内存,其实就是将地址映射到共享内存上,所以就可以通过指针访问共享内存的内容。不用进行拷贝。
2、共享内存也是需要进行同步控制的。
例如:A进程获取内存数据,B进程打印内存数据。
即B进程必须在A进程获取数据之后才能打印,A进程必须在B进程将上一次数据处理后才能再次获取。
因为两个进程是相互影响的,所以要用两个信号量来实现。
3、实现
共享内存必须用信号量去做支持做进程同步
所以要用到信号量的实现
//sem.h
#pragma once
#include <sys/sem.h>
union semval
{
int val;
};
int CreateSem(int key, int init_val[], int len);
void SemP(int semid, int index);
void SemV(int semid, int index);
void DeleteSem(int semid);
//sem.c
#include "./sem.h"
#include <stdio.h>
#include <malloc.h>
#include <assert.h>
/*获取信号量
如果内核中已经有了此key值对应的信号量,则直接返回
2、如果没有
2.1创建此信号量集
2.2所有的信号量根据init_val的值进行初始化
*/
int CreateSem(int key, int initval[], int len)
{
//获取
int semid = semget((key_t)key, 0, 0664);
if (semid != -1)
{
return semid;
}
//创建
semid = semget((key_t)key, len, IPC_CREAT | 0664);
if (-1 == semid)
{
perror("create sem fail ");
return -1;
}
//初始化
int i = 0;
for (; i < len; ++i)
{
union semval data;
data.val = initval[i];
if (-1 == semctl(semid, i, SETVAL, data))
{
perror("Init Sem Value Fail: ");
return -1;
}
}
return semid;
}
void SemP(int semid, int index)
{
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
if (-1 == semop(semid, &buf, 1))
{
perror("Sem P operation: ");
}
}
void SemV(int semid, int index)
{
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
if (-1 == semop(semid, &buf, 1))
{
perror("Sem P operation: ");
}
}
共享内存的实现
//shmA.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include <sys/shm.h>
#include "sem.h"
int main()
{
int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0664);//获取一个共享内存
assert(shmid != -1);
char *ptr = (char*)shmat(shmid, NULL, 0);
assert(ptr != (char*)-1);
//init_val[0]=0 : sem1 init_val[1]=1 : sem2
int init_val[2] = { 0, 1 };
int semid = CreateSem(1234, init_val, 2);
assert(semid != -1);
while(1)
{
//因为是A先执行,所以初始值为1
SemP(semid, 1);//B---->A ,B将数据处理完成后告诉A可以进行下一次操作了
printf("please input: ");
fgets(ptr, 127, stdin);
SemV(semid, 0);//A---->B ,当A获取到数据后,对信号量进行V操作,告诉B进程数据获取完毕可以进行处理了
if(strncmp(ptr, "end", 3) == 0)
{
break;
}
}
shmdt(ptr);
shmctl(shmid, IPC_RMID, NULL);
}
//shmB.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include <sys/shm.h>
#include "sem.h"
#include<time.h>
int main()
{
srand((unsigned int)(time(NULL)*time(NULL)));
int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0664);//获取一个共享内存
assert(shmid != -1);
char *ptr = (char*)shmat(shmid, NULL, 0);
assert(ptr != (char*)-1);
//init_val[0]=0 : sem1 init_val[1]=1 : sem2
int init_val[2] = { 0, 1 };
int semid = CreateSem(1234, init_val, 2);
assert(semid != -1);
while (1)
{
SemP(semid, 0);//A---->B
if (strncmp(ptr, "end", 3) == 0)
{
break;
}
printf("B process: %s",ptr);
int n = rand() % 3 + 1;
sleep(n);
printf("B Deal Over\n");
memset(ptr, 0, 128);
SemV(semid, 1);//B---->A
}
shmdt(ptr);
shmctl(shmid, IPC_RMID, NULL);
DeleteSem(semid);//最终结束的是B,当最后end后直接清除信号量
}
运行结果: