进程之间的通信(共享内存 + 其他IPC原理)
进程之间的通信(共享内存)
- 1 共享内存
- 1.1 共享内存原理
- 1.2 预备知识
- 3 (无保护)共享内存的代码示例
- 4 共享内存的特点
- 5 加保护的共享内存(管道保护)
- 6 其他IPC
- 6.1 system V消息队列(后序补充)
- 6.2 system V信号量
- 7 system v是如何实现IPC的?和滚到为什么不同?
- 7.1 应用角度,IPC属性
- 7.2 内核角度,看IPC结构
1 共享内存
1.1 共享内存原理
1、共享内存本质上就是物理内存上的一块区域,我们通过将其与页表建立链接,并映射到进程的共享区,这样我们的进程就能够看到这份资源,如果我们将另一个进程也与其建立链接,那么物理内存上的这块区域就叫做共享区。
2、共享内存本质上也是让不同的进程,看到同一份资源
3、当我们不需要使用共享区时,我们需要将物理内存区域与共享区去关联
4、共享内存可以有多个,其需要被OS管理(先描述,在组织)
5、共享内存 == 共享内存的数据结构 + 内存块
1.2 预备知识
1、共享内存也是由一个进程创建,另一个进程使用
2、man shmget(获取共享内存)
第一个参数:共享内存有唯一的标识符(key)
(1)必须用户输入
(2)怎么保证shm唯一性?怎么保证不同进程看到的是同一个共享内存?
---- 我们自己输入一个唯一的key,
第三个参数一般只使用两个(IPC_CREAT | IPC_EXCL)
(1)单独使用IPC_CREAT,如果shm不存在则创建,存在则获取它,并返回 ---- 保证能拿到共享内存
(2)单独使用IPC_EXCL,单独使用无意义
(3)两者共同使用,如果shm不存在,则创建,如果存在,出错返回 ---- 只要成功,一定是新的共享内存(不拿老的共享内存)
3、为什么key不由OS创建?而是用户传入?
如果key由OS创建了,另一个进程怎么拿到key?用户定义key才能使两个进程互相通信
4、我们设置key会不会发生冲突,发生冲突怎么办?
会发生冲突,发生时需要由用户手动更改,OS封装ftok(man ftok),由OS的算法形成唯一值
5、linux是通过路径+项目ID确定不同进程看到同一个资源
6、共享内存的生命周期随内核(必须手动释放 / OS重启),malloc出来的空间随进程,进程结束自动释放
共享内存的管理指令
(1)ipcs -m: 系统层面查看共享内存
(2)ipcrm -m + shmid:删除共享内存
(3)shmid:只给用户的一个标识shm的标识符
key:只作为内核中区分shm唯一性的标识符,不作为管理shm的id值
7、代码释放shm (man shmctl)
8、将共享内存挂接到自己的地址空间中
(1)shmaddr:由用户决定挂接到什么虚拟空间,在这里我们不需要关注,所以设置为nullptr即可
(2)shmflg:共享内存用来干什么,这里我们也不会用到,设置为0即可
(3)返回值void*:成功挂载时,返回虚拟地址的起始地址,失败则返回-1
9、去共享内存和进程地址空间的关联(shmdt)
参数就是挂接共享内存的返回值
3 (无保护)共享内存的代码示例
1、makefile
2、server.cc
3、client.cc
4、ShareMemory.hpp
#pragma once#include <iostream>
#include <string>
#include <cstdio>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>const std::string path = "/home/tsy/lesson";
int projId = 0x6666;
int gshmsize = 4096; // OS按照1k 4k 4m申请空间
mode_t gmode = 0600;std::string ToHex(key_t k)
{char buff[64];snprintf(buff, sizeof(buff), "0x%x", k);return buff;
}class ShareMemory
{void CreateShmHeaper(int shmflg){// 1、创建key_key = ::ftok(path.c_str(), projId);if (_key < 0){std::cerr << "ftok error!" << std::endl;return;}// 2、创建共享内存 && 获取// 注意:共享内存也有权限!shmflg_shmid = ::shmget(_key, gshmsize, shmflg);if (_shmid < 0){std::cerr << "shmget error!" << std::endl;return;}return;}public:ShareMemory() : _shmid(-1), _key(0), _addr(nullptr){}~ShareMemory() {}void CreateShm(){if (_shmid == -1)CreateShmHeaper(IPC_CREAT | IPC_EXCL | gmode); //|mode 给定共享内存权限}void GetShm(){CreateShmHeaper(IPC_CREAT);}void AttachShm(){// 3、共享内存挂接到自己的地址空间_addr = shmat(_shmid, nullptr, 0);if ((long long)_addr == -1){std::cout << "attach error" << std::endl;}}void DetachShm(){// 5、去关联地址空间if (_addr != nullptr)::shmdt(_addr);}void DeleteShm(){// 6、删除共享内存shmctl(_shmid, IPC_RMID, nullptr);}void *GetAddr(){return _addr;}void ShmMeta(){}private:int _shmid;key_t _key;void *_addr;
};ShareMemory shm;
共享内存不光可以传递字符串信息,图片、文件等结构化信息都是可以的
4 共享内存的特点
1、通信速度最快
2、让两个进程在各自的用户空间共享内存块,但是没有加任何保护机制
3、共享内存的保护机制需要用户完成保护(我们使用信号量 / 命名管道,锁我们没学),被保护的共享资源就是临界资源,访问公共资源的代码叫做临界区
5 加保护的共享内存(管道保护)
Fifo.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>const std::string gpipeFile = "./fifo";
const mode_t gfifomode = 0600; // 设置权限
const int gdefaultfd = -1;
const int gsize = 1024;
const int gForRead = O_RDONLY;
const int gForWrite = O_WRONLY;class Fifo
{void OpenFifo(int flag){_fd = open(gpipeFile.c_str(), flag);if (_fd < 0){std::cerr << "open error" << std::endl;}}public:Fifo() : _fd(-1){umask(0);int n = ::mkfifo(gpipeFile.c_str(), gfifomode); // 创建管道文件if (n < 0){std::cerr << "mkfifo error" << std::endl;}std::cout << "mkfifo success" << std::endl;}bool OpenPipeForRead(){OpenFifo(gForRead);if (_fd < 0)return false;return true;}bool OpenPipeForWrite(){OpenFifo(gForWrite);if (_fd < 0)return false;return true;}int Wait(){int code = 0;ssize_t n = ::read(_fd, &code, sizeof(code)); // 可以读1024个字节,但是我们这里以字符形式读取,最后一个得补\0if (n == sizeof(code))return 0;else if (n == 0)return 1;elsereturn 2;}void Signal(){int code = 1;::write(_fd, &code, sizeof(code));}~Fifo(){if (_fd >= 0)::close(_fd);int n = unlink(gpipeFile.c_str()); // 删除管道文件if (n < 0){std::cout << "unlink error" << std::endl;return;}std::cout << "unlink success" << std::endl;}private:int _fd;
};Fifo gpipe;
Client.cc
server.cc
6 其他IPC
6.1 system V消息队列(后序补充)
6.2 system V信号量
nsems:信号量个数
1、临界区、临界代码
2、信号和信号量没有任何关系,不要混淆,信号量生命周期随内核
3、信号量本质是一个计算器
4、资源整体使用(互斥访问,临界区串行执行),将资源分开使用(不同进程的进程可以访问共享资源,具有一定并发性),为了防止过量进程进入临界资源,我们设置一个计数器cnt(cnt代表剩余资源的数目)
5、当cnt最大值为1时,就是将资源整体使用,所谓的cnt不光起到计算器的作用,更起到了资源预定的作用
6、信号量:对资源进行预定的计数器,资源整体使用的信号量就是二元信号量(锁)
计算器cnt是int型,能在多个进程之间被看到?被修改?
---- 不能,所以使用信号量这个计数器必须解决这个问题,假如多个进程之间能看到同一个计数器,那这个计数器本身就变为了公共资源,计数器怎么保证自己的安全?信号量的操作本质上只有两种(cnt–,cnt++),只要我们将这两种类型的操作打个包,包含cnt–的操作被称为P操作,包含cnt++的操作叫做V操作,只要保证这两种操作运行时不能被别人打断(原子性)
7、信号量可以为多个,system V申请信号量,是通过信号量集的方式申请的
8、信号量的操作(p / v)
7 system v是如何实现IPC的?和滚到为什么不同?
7.1 应用角度,IPC属性
在OS层面,IPC是同类
共享内存获取属性
代码获取共享内存的属性
7.2 内核角度,看IPC结构
1、IPC资源一定是全局资源,能被所有进程看到
2、