1.共享内存
1.1概念
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据.
1.2共享内存原理图
将物理内存映射到进程的虚拟地址空间,本质就是修改页表,在虚拟地址空间中开辟空间
1.3共享内存建立的过程
申请共享内存
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
申请共享内存我们需要使用shmget函数。第二个参数是我们申请空间的大小。一页的大小是4096bytes,在内存申请和释放的时候,操作系统是按页给我们提供空间,按页释放空间的。比如我们申请4096字节,系统会给我们提供1页,如果我们申请4097字节,系统会给我们提供两页,不过显示给我们的大小还是4097。第三个参数是权限标志,我们可以通过传“IPC_CREAT|IPC_EXCL”,创建出一块全新的共享内存,“IPC_CREAT”如果单独使用,如果共享内存不存在,则创建之,若存在,返回该共享内存,而“IPC_EXCL”单独使用无意义。第一个参数,是为了确保IPC资源(这里指共享内存的唯一性),是在操作系统层面的唯一标识。我们还需要借助ftok函数来生成key
#include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
我们使用ftok函数,传入路径名,在随便传一个proj_id,即可生成一个key值。这个函数就相当于使用这两个参数利用某种算法生成一个序号id。
而shmget函数的返回值,是shmid,就好比你在酒店开了一间房,你会得到一个房间号。这个shmid可以称为共享内存句柄,在用户层标识共享内存。
共享内存挂接到虚拟地址空间,建立映射关系
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);
挂接我们需要用到上面的这个函数,at是attach的缩写。参数中的shamaddr通常设置为NULL,由操作系统帮我们去设置。shmflg默认设置为0,就是读写。这个函数最重要的是理解它的返回值。
解释一下,这个返回值是对应共享内存映射到进程地址空间的虚拟地址的起始地址,之后我们使用的时候用这个地址访问共享内存。这个函数类似于我们c语言中的malloc函数。
开始通信
通信完成之后去关联共享内存,其实就是修改页表,取消映射关系
#include <sys/types.h> #include <sys/shm.h> int shmdt(const void *shmaddr);
将上面shmat的返回值穿进去即可。
释放共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
我们传入shmid,在cmd参数传入IPC_RMID,buf传NULL即可释放共享内存。
1.4共享内存实现进程间通信的测试代码
commo.h
1 #pragma once
2 #include <stdio.h>
3 #include <sys/types.h>
4 #include <unistd.h>
5 #include <sys/shm.h>
6 #include <sys/ipc.h>
7 #define PATHNAME "/home/lin/study_Linux_7_25/shm"
8 #define PROJ_ID 0x6666
9 #define SIZE 4096
server.c
#include "common.h"
int main()
{
key_t key = ftok(PATHNAME, PROJ_ID);
if(key < 0)
{
perror("ftok\n");
return 1;
}
printf("key: %x\n", key);
int shmid = shmget(key, SIZE, IPC_CREAT|IPC_EXCL|0644);//这里的0644是 设置权限,如果不设置会导致挂接失败
if(shmid < 0)
{
perror("shmget\n");
return 2;
}
char* mem = shmat(shmid, NULL, 0);//挂接
//开始通信
while(1)
{
printf("client msg# %s\n", mem);
sleep(1);
}
shmdt(mem);//去关联
shmctl(shmid, IPC_RMID, NULL);//共享内存的生命周期随内核,所以使用之后必须释放,否则曾经创建的共享内存一直存在,直到关机重启
return 0;
}
client.h
#include "common.h"
int main()
{
key_t key = ftok(PATHNAME, PROJ_ID);//为了使client与server获得同一个key
if(key < 0)
{
perror("ftok\n");
return 1;
}
int shmid = shmget(key, SIZE, IPC_CREAT);
if(shmid < 0)
{
perror("shmget\n");
return 2;
}
char* mem = shmat(shmid, NULL, 0);//挂接
//进行通信
int i = 0;
while(1)
{
mem[i] = 'A' + i;
sleep(1);
i++;
mem[i] = '\0';
}
shmdt(mem);//去关联
return 0;
}
1.5关于共享内存的一些细节
如上图,进程已经退出,在代码中如果没有释放共享内存,共享内存依然存在。共享内存的生命周期是随内核的,直到关机或重启才消失,此时如果我们想释放共享内存,可通过指令ipcrm -m shmid释放。
ipcs指令查看消息队列,共享内存和信号量
这个key和shmid的关系类似于fd和FILE*的关系
共享内存之所以速度最快,是因为它至少减少了两次拷贝。
共享内存有一个缺点是没有任何保护机制,(没有同步和互斥)。可能你还没写完我就去读了
2.浅谈消息队列
3.浅谈信号量
我们知道 ,进程间通信的本质就是让不同的进程看到同一份资源,在解决了通信的前提下,我们也引入了新的问题,就是“临界资源(多个进程共享的)”的问题。对于临界区,我们要加以保护,如AB两个进程,都访问同一块资源,但是总不能A在写入的时候B也写吧。就好比一个公共停车位,只有在没有车的时候你才能把你的车停进去。为了保护临界区,我们就用到了信号量,信号量的本质其实就是一个计数器,信号量分为二元信号量和多元信号量。以而元信号量为例,就是说有两个资源访问同一块资源。
整体来看,一个进程访问共享内存,要么没有访问,要么访问 完毕了这就是原子性。要么操作了,要么没操作,只有两态,没有中间过程。
去。为了保护临界区,我们就用到了信号量,信号量的本质其实就是一个计数器,信号量分为二元信号量和多元信号量。以而元信号量为例,就是说有两个资源访问同一块资源。
[外链图片转存中…(img-yL2YYnjm-1659359116195)]
整体来看,一个进程访问共享内存,要么没有访问,要么访问 完毕了这就是原子性。要么操作了,要么没操作,只有两态,没有中间过程。