💁♂️个人主页:进击的荆棘
👇作者其它专栏:
目录
1.进程间通信介绍
2.管道
3.匿名管道
4.命名管道
5.system V共享内存
6.system V消息队列
7.system V信息量
8.内核是如何组织管理IPC资源的
5.system V共享内存
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,即进程不再通过执行进入内核的系统调用来传递彼此的数据。
5.1共享内存示意图

5.2共享内存数据结构
cpp
truct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment(bytes) */
__kernel_time_t shm_atime; /* last attach time*/
__kernel_time_t shm_dtime; /* last detach time*/
__kernel_time_t shm_ctime; /* last change time*/
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of currentattaches */
unsigned short shm_unused; /* compatibility */
void shm_unused2; / ditto - used byDIPC */
void shm_unused3; / unused */
};
5.3共享内存函数
shmget函数
cpp
功能:⽤来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存⼤⼩
shmflg:由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。
取值为IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,
出错返回。
返回值:成功返回⼀个⾮负整数,即该共享内存段的标识码;失败返回-1
shmat函数
cpp
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回⼀个指针,指向共享内存第⼀个节;失败返回-1
●说明
shmaddr为NULL,核心自动选择⼀个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
shmdt函数
cpp
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数
cpp
功能:⽤于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向⼀个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

实例1-共享内存实现进程间通信
Makefile
cpp
.PHONY:all
all:server client
server:server.cc
g++ -o $@ $^
client:client.cc
g++ -o $@ $^
.PHONY:clean
clean:
rm -f server client
comm.h
cpp
#ifndef _COMM_H_
#define _COMM_H_
# include <stdio.h>
# include <sys/types.h>
# include <sys/ipc.h>
# include <sys/shm.h>
# define PATHNAME "."
# define PROJ_ID 0x6666
int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);
# endif
comm.c
cpp
#include "comm.h"
static int commShm(int size, int flags)
{
key_t key = ftok(PATHNAME, PROJ_ID);
if(key < 0){
perror("ftok");
return -1;
}
int shmid = 0;
if( (shmid = shmget(key, size, flags)) < 0){
perror("shmget");
return -2;
}
return shmid;
}
int destroyShm(int shmid)
{
if(shmctl(shmid, IPC_RMID, NULL) < 0){
perror("shmctl");
return -1;
}
return 0;
}
int createShm(int size)
{
return commShm(size, IPC_CREAT|IPC_EXCL|0666);
}
int getShm(int size)
{
return commShm(size, IPC_CREAT);
}
cpp
#include "comm.h"
int main()
{
int shmid = createShm(4096);
char *addr = shmat(shmid, NULL, 0);
sleep(2);
int i = 0;
while(i++<26){
printf("client# %s\n", addr);
sleep(1);
}
shmdt(addr);
sleep(2);
destroyShm(shmid);
return 0;
}
cpp
#include "comm.h"
int main()
{
int shmid = getShm(4096);
sleep(1);
char *addr = shmat(shmid, NULL, 0);
sleep(2);
int i = 0;
while(i<26){
addr[i] = 'A'+i;
i++;
addr[i] = 0;
sleep(1);
}
shmdt(addr);
sleep(2);
return 0;
}
运行结果

ctrl+c终止进程,再次启动
bash
# ./server
shmget: File exists
# ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x66026a25 688145 root 666 4096 0
# ipcrm -m 688145 #删除shm ipc资源,注意,不是必须通过⼿动来删除,这⾥只为演⽰相关指
令,删除IPC资源是进程该做的事情
注:共享内存没有进行同步与互斥。共享内存缺乏访问控制,会带来并发问题
实例2-借助管道实现访问控制版的进程通信
comm.hpp
cpp
#pragma once
#include <cstdio>
#include <cstdlib>
#define ERR_EXIT(n) \
do{ \
perror(n); \
exit(EXIT_FAILURE); \
}while(0) \
fifo.hpp
cpp
#pragma once
#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string>
#include "Comm.hpp"
#define PATH "."
#define FIFONAME "fifo"
class FileFifo{
public:
FileFifo(const std::string &path,const std::string &name)
:_path(path),_name(name)
{
_pathname=_path+"/"+_name;
umask(0);
//创建管道文件
int n=mkfifo(_pathname.c_str(),0666);
if(n<0){
std::cerr<<"mkfifo error"<<std::endl;
ERR_EXIT("mkfifo");
}
}
~FileFifo(){
//删除管道文件
int n=unlink(_pathname.c_str());
if(n==0){
std::cout<<"unlink success"<<std::endl;
}
else{
std::cerr<<"remove fifo failed"<<std::endl;
ERR_EXIT("unlink");
}
}
private:
std::string _path;
std::string _name;
std::string _pathname;
};
class FifoOper{
public:
FifoOper(const std::string &path,const std::string &name)
:_path(path),_name(name),_fd(-1)
{
_pathname=_path+"/"+_name;
}
void OpenForRead(){
//打开文件
//当write没有执行open时,read会在open内部进行阻塞
//直到有人把管道文件打开了,open才会返回
_fd=open(_pathname.c_str(),O_RDONLY);
if(_fd<0){
std::cerr<<"open mkfifo failed"<<std::endl;
ERR_EXIT("open");
}
std::cout<<"open fifo success"<<std::endl;
}
void OpenForWrite(){
//打开文件
_fd=open(_pathname.c_str(),O_WRONLY);
if(_fd<0){
std::cerr<<"open mkfifo failed"<<std::endl;
ERR_EXIT("open");
}
std::cout<<"open fifo success"<<std::endl;
}
bool Wait(){
//读取文件内容
char c;
int number=read(_fd,&c,1);
if(number>0){
printf("醒来:%d\n",number);
return true;
}
return false;
}
void WakeUp(){
//向文件写入内容
char c='c';
int n=write(_fd,&c,1);
printf("尝试唤醒:%d\n",n);
}
void Close(){
if(_fd>0)
close(_fd);
}
~FifoOper(){}
private:
std::string _path;
std::string _name;
std::string _pathname;
int _fd;
};
shm.hpp
cpp
#pragma once
#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include "Comm.hpp"
const int gdefult = -1;
const int gsize = 4096;
const std::string pathname = ".";
const int projid = 0x66;
const int gmode = 0666;
#define CREATER "creater"
#define USER "user"
class Shm
{
private:
// 不同的用户需要不同的权限,比如CREATER,我希望它有创建shm和读的权限,但不希望它有写的权限
// 所以,让一些方法私有,禁止用户直接访问
void CreateHelper(int flg)
{
printf("key is 0x%x\n", _key);
// 需要给读写执行权限,否则,创建好共享内存后,可能无法读写等
_shmid = shmget(_key, _size, flg);
if (_shmid < 0)
{
ERR_EXIT("shmget");
}
printf("shemget success,id:%d\n", _shmid);
}
void Creat()
{
CreateHelper(IPC_CREAT | IPC_EXCL | gmode);
}
void Get()
{
CreateHelper(IPC_CREAT);
}
void Attch()
{
_start_mm = shmat(_shmid, nullptr, 0);
if ((long long)_start_mm < 0)
{
ERR_EXIT("shmat");
}
std::cout<<"attach success"<<std::endl;
}
void Detach()
{
int n = shmdt(_start_mm);
if (n < 0)
{
ERR_EXIT("shmdt");
}
std::cout << "detach success" << std::endl;
}
void Destory()
{
Detach();
if (_usertype == CREATER)
{
int n = shmctl(_shmid, IPC_RMID, nullptr);
if (n < 0)
{
ERR_EXIT("shmctl");
}
std::cout << "delete shm:"<<_shmid<<" successful" << std::endl;
}
}
public:
Shm(const std::string& pathname,int projid,const std::string& usertype)
: _size(gsize), _shmid(gdefult), _start_mm(nullptr), _usertype(usertype)
{
_key = ftok(pathname.c_str(), projid);
if (_key < 0)
{
ERR_EXIT("ftok");
}
if (_usertype == CREATER)
{
Creat();
}
else if (_usertype == USER)
{
Get();
}
else
{
}
Attch();
}
void *VirtualAddr()
{
printf("virtual addr:%p\n",_start_mm);
return _start_mm;
}
//输出共享内存的各种属性
void Attr()
{
struct shmid_ds ds;
int n=shmctl(_shmid,IPC_STAT,&ds);
printf("shm_segsz:%ld\n",ds.shm_segsz);
printf("key:0x%x\n",ds.shm_perm.__key);
}
int Size()
{
return _size;
}
~Shm()
{
Destory();
}
private:
int _size;
key_t _key;
int _shmid;
void *_start_mm;
std::string _usertype;
};
cpp
#include "fifo.hpp"
#include "shm.hpp"
int main(){
Shm sh(pathname,projid,CREATER);
sh.Attr();
//因为虚拟内存没有保护机制,它写的数据会直接被读出
//所以我们使用管道来保护它的数据完整性
FileFifo fifo(PATH,FIFONAME);
FifoOper readerfile(PATH,FIFONAME);
readerfile.OpenForRead();
//读写共享内存,不用使用系统调用,使用它就像使用我们从堆栈上申请的一样
char *mem=(char*)sh.VirtualAddr();
while(true){
if(readerfile.Wait()){
printf("%s\n",mem);
}
else break;
}
readerfile.Close();
return 0;
}
cpp
#include "fifo.hpp"
#include "shm.hpp"
int main(){
FifoOper writerfile(PATH,FIFONAME);
writerfile.OpenForWrite();
Shm sh(pathname,projid,USER);
char * mem=(char*)sh.VirtualAddr();
int index=0;
for(char c='A';c<='Z';c++){
sleep(1);
mem[index]=c;
mem[index+1]=c;
sleep(1);
mem[index+2]=0;
index+=2;
writerfile.WakeUp();
}
writerfile.Close();
return 0;
}
6.system V消息队列
●消息队列提供了一个从一个进程向另一个进程发送一块数据的方法
●每个数据块都被认为有一个类型,接收者进程接收的数据块可以有不同的类型值
●特性
▢IPC资源必须删除,否则不会自动清除,除非重启(因为system V IPC资源的生命周期随内核
7.system V信号量
信号量主要用于同步和互斥
7.1并发编程-前导
●多个执行流(进程),能看到的同一份公共资源:共享资源
●被保护起来的共享资源叫做临界资源
●保护的方式常见:互斥和同步
●任何时刻,只允许一个执行流访问资源,叫做互斥
●多个执行流,访问临界资源的时候,具有一定的顺序性,就叫做同步
●系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源
●在进程中涉及到互斥资源的程序段叫临界区。我们写的代码=访问临界区资源的代码(临界区)+不访问临界区资源的代码(非临界区)
●所谓对共享资源进行保护,本质是对访问共享资源的代码进行保护(约束)

7.2信号量
特性
●IPC资源必须删除,否则不会自动清除,除非重启,因为system V IPC资源的生命周期随内核
理解
●信号量是一个计数器
作用
●保护临界区
本质
●信号量本质是对资源的预定机制
操作
●申请资源,计数器--,P操作
●释放资源,计数器++,V操作

8.内核是如何组织管理IPC资源的

●参考Linux2.6.18源码



