目录
一 共享内存的作用
共享内存主要是用来实现进程间通讯的一种机制,只不过,需要进程间互斥的访问这段内存,因此需要使用信号量等锁机制来对共享内存进行访问。
二 共享内存的实现
共享内存的实现主要由两种方式:一种是shm系统调用;一种是mmap实现共享(因为mmap也可以实现私有映射)的页面映射。
2.1 shm系统调用
shm系统调用主要是在sys/shm.h头文件中。
2.1.1 共享内存的创建
int shmget(key_t key, size_t size, int shmflg);
函数功能:用于创建或获取用键值为key的一段大小为size的共享内存。
函数参数:
1.key:IPC内核资源(信号量,消息队列,共享内存)的键值,用于在全局唯一的表示该资源。
2.size:申请的共享内存大小。
3.shmflg:主要讲解几个参数:
a.IPC_CREAT和IPC_EXCL与信号量的含义相同;
b.SHM_HUGETLB:用于申请大页面作为共享内存。
c.SHM_NORESERVE:不为该共享内存创建swap交换分区。会导致如果物理内存不够用了,要对该共享内存读写的话会出错并且返回SIGSGV信号量。
创建成功的话,会在内核创建一个shmid_ds类型的结构体变量与该共享内存关联:
struct shmid_ds{
IPC_PERM shm_perm;//用于表示该共享内存的键值和权限
size_t shm_segsz;//用于表示该共享内存的大小
_time_t shm_atime;//用于表示最后一次调用shmat()函数的时间
_time_t shm_dtime;//用于表示最后一次调用shmdt()函数的时间
_time_t shm_ctime;//用于表示最后以此调用shmclt()函数的时间
_pid_t shm_cpid;//用于表示创建共享内存的进程PID
_pid_t shm_lpid;//用于表示最后以此调用shmat()函数和shmdt()函数的进程PID
shmatt_t shm_nattach;//用于表示关联到该共享内存的进程个数
};
2.1.2 共享内存的关联与分离
void* shmat(int shm_id, const void* shm_addr, int shmflg);//将共享内存shm_id绑定到进程地址shm_addr
int shmdt(const void* shm_addr);//将进程地址shm_addr与共享内存分离
函数功能:
1.shmat():主要是将调用进程的内存地址shm_addr与shm_id标识的共享内存关联:
2.shmdt():主要是讲调用进程的内存地址shm_addr与共享内存分离。
函数参数:
1.shm_id:是shmget()函数返回的用于标识该共享内存的id号;
2.shm_addr:是进程的指针,主要是用于关联共享内存的起始地址:当不设置该参数时,系统自动分配地址(推荐选择);当设置该参数时,会讲共享内存关联到该内存地址。
3.shmflg:主要参数有如下几个:
a.SHM_RND:当不设置设置该参数时,直接将共享内存关联到shm_addr标识的内存地址(前提是设置了要映射的内存地址shm_addr);当设置该参数时,会将内存地址向下圆整到SHMLBA的整数倍地址处(SHMLBA是内存页面大小的整数倍);
b.SHM_RDONLY:该参数主要是设置该进程对共享内存的访问权限为只读;
c.SHM_REMAP:如果该内存地址shm_addr映射了其他共享内存,重新映射该共享内存。
2.1.3 共享内存的设置
int shmctl(int shm_id, int command, shmid_ds* buf);
函数功能:主要是给该共享内存进行相关设置。
具体使用(常用功能):
1.IPC_STAT:主要是将shm_id标识的共享内存的内核shmid_ds结构体存入函数的参数buf所指向的shmid_ds结构体中;
2.IPC_SET:主要是将buf所指向的shmid_ds结构体赋值给shm_id所标识的共享内存的内核shmid_ds结构体中;
3.IPC_RMID:给该共享内存打上删除的标记,当最后一个与该共享内存关联的进程调用shmdt()函数时,自动删除该共享内存以及与之关联的shmid_ds结构体变量。
2.1.4 测试案例
测试代码:
#include<sys/sem.h>
#include<sys/shm.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
union semun{
int val;
struct semid_ds* buf;
unsigned short int* array;
struct seminfo* _buf;
};
void pv(int sem_id, int sem_op)
{
struct sembuf sem_ops;
sem_ops.sem_num = 0;
sem_ops.sem_op = sem_op;
sem_ops.sem_flg = SEM_UNDO;
semop(sem_id, &sem_ops, 1);
}
int main()
{
char* message1 = "a";
char* message2 = "b";
printf("开始:\n");
//创建信号量
int sem_id = semget(IPC_PRIVATE, 1, 0666);//创建信号量集(其中只有一个信号量)
union semun sem_un;
sem_un.val = 1;//信号量初始值设为1,这里只测试二元信号量
semctl(sem_id, 0, SETVAL, sem_un);
//创建共享内存
int shm_id = shmget(IPC_PRIVATE, 10, 0666);
pid_t pid = fork();
if(pid < 0)
return -1;
else if(pid == 0)
{
char* shmaddr = shmat(shm_id, NULL, 0);
pv(sem_id, -1);//进入临界区
for(int i = 0; i < 5; i++)
{
strcat(shmaddr, message1);
sleep(1);
printf("%s\n", shmaddr);
}
pv(sem_id, 1);//退出临界区
shmdt(shmaddr);
exit(0);//终止子进程
}
else
{
char* shmaddr = shmat(shm_id, NULL, 0);
pv(sem_id, -1);//进入临界区
for(int i = 0; i < 5; i++)
{
strcat(shmaddr, message2);
sleep(1);
printf("%s\n", shmaddr);
}
pv(sem_id, 1);//退出临界区
shmdt(shmaddr);
}
waitpid(pid, NULL, 0);//回收子进程
semctl(sem_id, 0, IPC_RMID, sem_un);//删除信号量
struct shmid_ds *buf;
shmctl(shm_id, IPC_RMID, buf);//最后一个进程分离,删除共享内存及其相应的shmid_ds变量
return 0;
}
加锁对共享内存进行读写:
不加锁对共享内存进行读写:
2.2 mmap内存映射
mmap函数主要是用于申请一段内存空间:1.作为共享内存;2.作为私有内存;3.文件映射;4.匿名映射。(1,2和3,4排列组合可以达到不同的效果)。
2.2.1 函数的使用
void* mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void*start,size_t length);
函数功能:
1.mmap():内存映射。
2.munmap():解除起始地址为start,长度为length的内存映射。
函数参数:
1.start:主要是映射在内存的起始位置,如果为空则系统自动分配(推荐设置方法);
2.length:映射的长度(文件长度与内存长度对应);
3.prot:用来设置该段内存的访问权限:
a.PROT_READ:可读;
b.PROT_WRITE:可写;
c.PROT_EXEC:可执行;
d.PROT_NONE:不能被访问。
4.flag:常用参数:
a.MAP_SHARED:作为共享内存进行映射,并且对于该内存的修改会回写到对应的文件中(如果时文件映射的话);
b.MAP_PRIVATE:作为私有内存进行映射,被调用进程所私有,并且不会回写到对应的文件中;
c.MAP_ANONYMOUS:作为匿名内存进行映射,该段内存不是来自于文件的,所以mmap()函数的后两个参数可以忽略;
d.MAP_HUGETLB:按照大内存页面进行分配。
2.2.2 测试案例
这里我采用的是匿名映射的方式,为了展示的方便,不去映射具体的文件,具体代码如下:
#include<sys/sem.h>
#include<sys/mman.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
union semun{
int val;
struct semid_ds* buf;
unsigned short int* array;
struct seminfo* _buf;
};
void pv(int sem_id, int sem_op)
{
struct sembuf sem_ops;
sem_ops.sem_num = 0;
sem_ops.sem_op = sem_op;
sem_ops.sem_flg = SEM_UNDO;
semop(sem_id, &sem_ops, 1);
}
int main()
{
char* message1 = "a";
char* message2 = "b";
printf("开始:\n");
//创建信号量
int sem_id = semget(IPC_PRIVATE, 1, 0666);//创建信号量集(其中只有一个信号量)
union semun sem_un;
sem_un.val = 1;//信号量初始值设为1,这里只测试二元信号量
semctl(sem_id, 0, SETVAL, sem_un);
//创建共享内存
char* mmap_addr = mmap(NULL, 10, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_ANONYMOUS, -1, 0);//MAP_ANONYMOUS:匿名映射
pid_t pid = fork();
if(pid < 0)
return -1;
else if(pid == 0)
{
pv(sem_id, -1);//进入临界区
for(int i = 0; i < 5; i++)
{
strcat(mmap_addr, message1);
sleep(1);
printf("%s\n", mmap_addr);
}
pv(sem_id, 1);//退出临界区
exit(0);//终止子进程
}
else
{
pv(sem_id, -1);//进入临界区
for(int i = 0; i < 5; i++)
{
strcat(mmap_addr, message2);
sleep(1);
printf("%s\n", mmap_addr);
}
pv(sem_id, 1);//退出临界区
}
waitpid(pid, NULL, 0);//回收子进程
semctl(sem_id, 0, IPC_RMID, sem_un);//删除信号量
munmap(mmap_addr, 10);
return 0;
}
加锁对共享内存进行读写:
不加锁对共享内存进行读写: