进程间通信的本质是先让不同的进程看到同一份资源。
共享内存的原理
申请共享内存的原理:

释放共享内存的原理:
1.让进程地址空间与物理内存之间的映射去关联。
2.释放物理内存中的空间
上述的所有操作都是操作系统执行的,而不是进程。

操作系统也需要管理所有存在的共享内存——>先描述再组织。
编码实现
创建共享内存
shmget函数
【功能】用来创建共享内存
【原型】int shmget(key_t key, size_t size, int shmflg);
【参数】
- key:这个共享内存段的名字,具备唯一性
- size:共享内存大小
- shmflg:一般使用IPC_CREAT 和 IPC_CREAT | IPC_EXCEL这两种方式,并且需要添加权限
【返回值】成功返回一个非负整数,即该共享内存段的标识码;失败返回-1.


谈谈key
- key是一个数字,这个数字在内核中具有唯一性,能够让不同的进程进程唯一性标识,以此才能判断共享内存是否存在。
- 第一个进程可以通过key创建共享内存,第二个之后的进程只需要使用同一个key,就可以使用同一份共享内存。
- 已经创建好一个共享内存,想要使用共享内存的进程需要找到key,key在共享内存的描述对象中。
- 第一次创建的时候,必须有一个key值,这个key值可以自己设定,也可以通过一个函数ftok创建。
- key在一定意义类似于路径,具备唯一性。

ftok函数是一套算法函数,将对应的pathname和proj_id进行数值计算,得到唯一的数值key。pathname和proj_id由用户自己来决定。
【注意】key值是不需要由系统默认生成的,否则其他进程无法获取到该key值,所以key值是每一个进程都约定的一个值,即每一个进程都知道的值。
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include "log.hpp"
using namespace std;
const int size = 4096;
// 通过ftok函数获取key获得参数
const string pathname = "/home/dabai";
const int proj_id = 0x6666;
Log log;
key_t getKey()
{
key_t key = ftok(pathname.c_str(), proj_id);
if(key < 0)
{
log(Fatal, "ftok error: %s", strerror(errno));
exit(1);
}
log(Info, "ftok success, key is: %d", key);
return key;
}
int GetShareMem()
{
key_t k = getKey();
int shmid = shmget(k, size, IPC_CREAT | IPC_EXCL | 0666);
if(shmid < 0)
{
log(Fatal, "create share memory error: %s", strerror(errno));
exit(2);
}
log(Info, "create share memory success, shmid: %d", shmid);
return shmid;
}
第一次执行:

【注意】key是在操作系统内标定唯一性的,而shmid只在该进程内用来表示资源的唯一性的。
第二次执行:

指令:ipcs -m
【功能】查看共享内存

【注意】共享内存的生命周期是随内的,进程退出后,如果不主动关闭共享内存,系统并不会主动关闭共享内存,共享内存会一直存在。除非内核重启或者用户关闭才可以关闭共享内存。
指令:ipcrm -m [shmid]
【功能】通过shmid值关闭共享内存


获取共享内存
调整上面的代码,实现获取共享内存:
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include "log.hpp"
using namespace std;
// 共享内存的大小一般建议是4096的整数倍
// 假如是4097字节,实际上操作系统给用户提供的是4096*2字节
const int size = 4097;
// 通过ftok函数获取key获得参数
const string pathname = "/home/dabai";
const int proj_id = 0x6666;
Log log;
key_t getKey()
{
key_t key = ftok(pathname.c_str(), proj_id);
if(key < 0)
{
log(Fatal, "ftok error: %s", strerror(errno));
exit(1);
}
log(Info, "ftok success, key is: %d", key);
return key;
}
int GetShareMemHelper(int flag)
{
key_t k = getKey();
int shmid = shmget(k, size, flag);
if(shmid < 0)
{
log(Fatal, "create share memory error: %s", strerror(errno));
exit(2);
}
log(Info, "create share memory success, shmid: %d", shmid);
return shmid;
}
int CreatShm()
{
return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}
int GetShm()
{
return GetShareMemHelper(IPC_CREAT);
}

【注意】共享内存的大小一般建议是4096的整数倍,假如是4097字节,实际上操作系统给用户提供的是4096*2字节。虽然系统显示的是4097字节,但是实际上操作系统分配内存是按照4kb来分配的,只是显示4097字节,底层是4096*2字节。
连接共享内存
shmat函数
【功能】将共享内存段连接到进程地址空间
【原型】void *shmat(int shmid, const void *shmaddr, int shmflg);
【参数】
- shmid:共享标识
- shmaddr:指定连接的地址
- shmflg:可能取值是SHM_RND和SHM_RDONLY
【返回值】成功返回一个指针,执行共享内存第一个节;失败返回-1

int shmid = CreatShm();
log(Debug, "create shm done...");
sleep(5);
char* shmaddr = (char*)shmat(shmid, NULL, 0);
log(Debug, "attach shm done...");
sleep(5);
脱离连接共享内存
shmdt函数
【功能】将共享内存段与当前进程脱离
【原型】int shmdt(const void *shmaddr);
【参数】
- shmaddr:由shmat所返回的指针
【返回值】成功返回0;失败返回-1


释放共享内存
shmctl函数
【功能】用于控制共享内存
【原型】int shmctl(int shmid, int cmd, struct shmid_ds *buf);
【参数】
- shmid:由shmget返回的共享内存标识符
- cmd:将要采取的动作(有三种可取值)
- buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
【返回值】成功返回0;失败返回-1.


使用共享内存进行通信
- 对于接收方,一旦有数据写入到共享内存中,立马就可以看到。不要经过系统调用直接就可以看到数据。
- 对于发送方,一旦存在共享内存,挂接到自己的地址空间中,就可以直接当作内存空间使用即可,不需要调用系统调用。
接收方代码简单示例:
int shmid = CreatShm();
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
// ipc code
while(true)
{
cout << "client say: " << shmaddr << endl;
sleep(1);
}
shmdt(shmaddr);
shmctl(shmid, IPC_RMID, nullptr);
发送方代码简单示例:
int shmid = GetShm();
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
// ipc code
while(true)
{
cout << "Please Enter@: ";
fgets(shmaddr, sizeof(shmaddr), stdin);
}
shmdt(shmaddr);
共享内存的特性
- 共享内存没有同步互斥之类的保护机制
- 共享内存是所有的进程间通信间,速度最快的——拷贝少
- 共享内存内部的数据,由用户自己维护。
共享内存的属性


struct shmid_ds shmds;
shmctl(shmid, IPC_STAT, &shmds);
cout << "shm size: " << shmds.shm_segsz << endl;
cout << "shm nattch: " << shmds.shm_nattch << endl;
cout << "shm key: " << shmds.shm_perm.__key << endl;
cout << "shm mode: " << shmds.shm_perm.mode << endl;
结合通道的共享内存代码实现
processa.cc文件
#include "comm.hpp"
extern Log log;
int main()
{
Init init;
int shmid = CreatShm();
char *shmaddr = (char *)shmat(shmid, nullptr, 0);
int fd = open(FIFO_FILE, O_RDONLY);
if (fd < 0)
exit(FIFO_OPEN_ERR);
struct shmid_ds shmds;
// ipc code
while (true)
{
char c;
ssize_t s = read(fd, &c, 1);
if(s == 0) break;
else if(s < 0) break;
cout << "client say: " << shmaddr << endl;
sleep(1);
// shmctl(shmid, IPC_STAT, &shmds);
// cout << "shm size: " << shmds.shm_segsz << endl;
// cout << "shm nattch: " << shmds.shm_nattch << endl;
// cout << "shm key: " << shmds.shm_perm.__key << endl;
// cout << "shm mode: " << shmds.shm_perm.mode << endl;
}
shmdt(shmaddr);
shmctl(shmid, IPC_RMID, nullptr);
close(fd);
return 0;
}
processb.cc文件
#include "comm.hpp"
int main()
{
int shmid = GetShm();
char *shmaddr = (char *)shmat(shmid, nullptr, 0);
int fd = open(FIFO_FILE, O_WRONLY);
if (fd < 0)
exit(FIFO_OPEN_ERR);
// ipc code
while (true)
{
cout << "Please Enter@: ";
fgets(shmaddr, sizeof(shmaddr), stdin);
write(fd, "c", 1); //通知对方
}
shmdt(shmaddr);
close(fd);
return 0;
}
makefile文件
.PHONY:all
all:processa processb
processa:processa.cc
g++ -o $@ $^ -std=c++11
processb:processb.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f processa processb
comm.hpp文件
#ifndef _COMM_HPP_
#define _COMM_HPP_
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "log.hpp"
using namespace std;
// 共享内存的大小一般建议是4096的整数倍
// 假如是4097字节,实际上操作系统给用户提供的是4096*2字节
const int size = 4097;
// 通过ftok函数获取key获得参数
const string pathname = "/home/dabai";
const int proj_id = 0x6666;
Log log;
key_t getKey()
{
key_t key = ftok(pathname.c_str(), proj_id);
if(key < 0)
{
log(Fatal, "ftok error: %s", strerror(errno));
exit(1);
}
log(Info, "ftok success, key is: %d", key);
return key;
}
int GetShareMemHelper(int flag)
{
key_t k = getKey();
int shmid = shmget(k, size, flag);
if(shmid < 0)
{
log(Fatal, "create share memory error: %s", strerror(errno));
exit(2);
}
log(Info, "create share memory success, shmid: %d", shmid);
return shmid;
}
int CreatShm()
{
return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}
int GetShm()
{
return GetShareMemHelper(IPC_CREAT);
}
#define FIFO_FILE "./myfifo"
#define MODE 0664
enum
{
FIFO_CREATE_ERR = 1,
FIFO_DELETE_ERR,
FIFO_OPEN_ERR
};
class Init
{
public:
Init()
{
// Create a channel
int n = mkfifo(FIFO_FILE, MODE);
if (n == -1)
{
perror("mkfifo");
exit(FIFO_CREATE_ERR);
}
}
~Init()
{
int m = unlink(FIFO_FILE);
if (m == -1)
{
perror("unlink");
exit(FIFO_DELETE_ERR);
}
}
};
#endif
消息队列

消息队列会让两个进程以数据块的形式发送数据。
- 必须让不同进程看到同一个队列
- 允许不同的进程,向内核中发送带类型的数据库,放在消息队列中

创建消息队列

释放消息队列

发送消息与接收消息

消息队列的属性


Linux中消息队列相关指令
指令:ipcs -q
【功能】查看消息队列‘

指令:ipcrm -q [msqid]
【功能】删除一个消息队列
信号量
当一个进程正在写入的时候(写入一部分),另外一个进程已经获取到这部分内容,导致进程双方的数据不完整,这种情况称为数据不一致问题。
数据不一致问题在管道中不存在,是因为管道中存在原子性和同步互斥的。
- 1进程和2进程看到同一份资源,共享资源,这份共享资源如果不加保护会出现数据不一致问题。
- 可以通过加锁方式来进行互斥访问,也就是说任何时刻只允许一个执行流访问共享资源,这种概念是互斥
- 共享的,且任何时刻只允许一个执行流访问的资源,称为临界资源。这个临界资源是操作系统维护的一块内存空间
- 执行流访问资源,也就是执行访问代码。访问临界资源的代码,被称为临界区。
【现象】多进程多线程在并发打印的时候,显示器上的消息可能会出现错乱、混乱、且与命令行混在一起的情况。
【解释】显示器也是共享资源,如果没有添加互斥保护机制的话,就会出现这种情况。。
信号量的原理
信号量/信号灯的本质是一把计数器。类似于int cnt = n。用来描述临界资源中资源数量的多少。当存在一个执行流的时候,cnt--,申请资源,当cnt<=0的时候,资源就被申请完,执行流不能再继续申请资源了。
例如:看电影买票的本质是对资源的预定机制,存在卖电影票的计数器,每卖一张票,计数器就会-1,相当于放映厅的资源-1。当票数减少到0时,资源已经被申请完毕。
- 申请计数器成功,就表示具有访问资源的权限。
- 申请计数器资源,但并不是当前已经访问了对应的资源。申请资源的本质是对资源的预定机制。
- 计数器可以有效保证进入共享资源的执行流的数量。
- 每一个执行流想访问共享资源中的一部分内容时,不是直接访问,而是先申请计数器资源。
也就是说将计数器的概念称为信号量。
例如:当只有一个人能够进入电影院看电影,也就是说只有一个执行流能够访问临界资源,这种方式称为互斥。
我们将只能为1,0两态的计数器叫做二元信号量——本质就是一个锁。资源为1的本质就是将临界资源不要分成多块,而是当作一个整体,来进行整体申请,整体释放。
申请信号量的本质是对计数器进行--操作——即P操作,释放资源或者释放信号量的本质是对计数器进行++操作——即V操作。申请和释放(PV操作)可以说是原子的,即要么不做,要么就做完,不会存在“正在做”的概念。
创建信号量

删除释放信号量

信号量操作

信号量的属性


为什么信号量是进程间通信的一种呢?
- 通信不仅仅是通信数据,互相协同也是通信的一种。
- 要协同,本质也是通信,信号量首先要被所有的通信进程看到。
进程间通信的数据结构设计
SystemV中的属性都是被精心设计过的,都有着类似的结构体。
在操作系统中,所有的IPC资源都是整合进操作系统的IPC模块中的。



不管是共享内存、消息队列还是信号量都有ipc_perm结构体,这些结构体都保存在一个结构体数组中,每一个结构体中的id值,例如shmid都是这个数组中的下标。



1153

被折叠的 条评论
为什么被折叠?



