进程间通信
目录
一、进程间通信理解
1.1.进程间通信的概念
进程间通信(Inter Process Communication):多个进程间进行数据交换和通信的技术
进程间通信的本质:让不同的进程先看到同一份资源(内存,不能由进程申请而是由OS提供)
开发者会设计一套统一的通信接口
1.2.进程间通信的目的
多个进程之间需要数据传输
多个进程之间需要共享资源
多个进程之间需要通知事件
多个进程之间需要进程控制
1.3.进程间通信的方式
- 基于文件的管道通信
- System V进程间通信
- POSIX进程间通信
二、管道
基于已有的技术通过文件直接进行通信
将文件缓冲区的数据刷新给另一个进程
管道:从一个进程连接到另一个进程的一个数据流(Unix早期进程间通信的形式)


三、匿名管道
3.1.匿名管道的概念
匿名管道:内存级文件,不需要文件路径,也没有文件名,常用作父子通信
子进程通过继承父进程的文件描述符表,来确认打开的是同一个管道文件
3.2.pipe函数
功能:创建匿名管道
int pipe(int fd[2]);
参数:
fd:⽂件描述符数组
fd[0]:表示读端
fd[1]:表示写端
返回值:成功返回0,失败返回错误代码

3.3.匿名管道的理解


3.4.实例代码
从键盘输入数据,写入管道,读取管道,输出到屏幕
实验代码
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <string.h>
//子进程写
void ChildWrite(int wfd)
{
char buffer[1024];
int cnt = 0;
while(true)
{
//将字符串内容写入数组
snprintf(buffer,sizeof(buffer),"I am child,pid: %d,cnt: %d",getpid(),cnt++);
//向文件写入时不用写\0
write(wfd,buffer,strlen(buffer));
sleep(1);
}
}
//父进程读
void FatherRead(int rfd)
{
char buffer[1024];
while(true)
{
buffer[0] = 0;
//将文件内容读取到数组
ssize_t n = read(rfd,buffer,sizeof(buffer) - 1);
if(n > 0)
{
//读取文件内容时要加\0
buffer[n] = 0;
std::cout << "child say: " << buffer << std::endl;
}
}
}
int main()
{
//1. 创建管道
int fds[2] = {0};//fds[0]:读 fds[1]:写
int n = pipe(fds);
if(n < 0)
{
std::cerr << "pipe error" << std::endl;
return 1;
}
std::cout << "fds[0]: " << fds[0] << std::endl;
std::cout << "fds[1]: " << fds[1] << std::endl;
//2. 创建子进程
pid_t id = fork();
if(id == 0)
{
//关闭子进程读
close(fds[0]);
//子进程写入
ChildWrite(fds[1]);
//关闭子进程写
close(fds[1]);
exit(0);
}
//3. 形成通信管道(父进程读,子进程写)
//关闭父进程写
close(fds[1]);
//父进程读取
FatherRead(fds[0]);
//回收子进程
waitpid(id,nullptr,0);
//关闭父进程读
close(fds[0]);
return 0;
}
实验现象


3.5.匿名管道的特性
特性一:只能是具有血缘关系的进程间通信
特性二:管道文件进行IO操作具有同步机制
特性三:管道是面向字节流的,怎么读与怎么写没有必然关系
- 子进程写的快,父进程读的慢:

- 子进程写的慢,父进程读的快:

特性四:管道是单向通信的,子进程写数据,父进程读数据,属于半双工
- 半双工:任何一个时刻,一方发送,一方接收
- 全双工:任何一个时刻,双方可以同时收发
特性五:(管道)文件的生命周期由进程决定
3.6.四种通信情况
- 写慢读快
读端进程阻塞等待写端进程写入
- 写快读慢
写满后,写端进程阻塞等待读端
- 写关读开
read的返回值0表示文件结束

- 写开读关
写端写入无意义,浪费内存空间,OS发送异常信号杀掉写端进程

实验代码
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <string.h>
//子进程写
void ChildWrite(int wfd)
{
char buffer[1024];
int cnt = 0;
while(true)
{
//将字符串内容写入数组
snprintf(buffer,sizeof(buffer),"I am child,pid: %d,cnt: %d",getpid(),cnt++);
//向文件写入时不用写\0
write(wfd,buffer,strlen(buffer));
printf("child: %d\n",cnt);
sleep(2);
}
}
//父进程读
void FatherRead(int rfd)
{
char buffer[1024];
while(true)
{
buffer[0] = 0;
ssize_t n = read(rfd,buffer,sizeof(buffer) - 1);
if(n > 0)
{
//读取文件内容时要加\0
buffer[n] = 0;
std::cout << "child say: " << buffer << std::endl;
}
else if(n == 0)
{
std::cout << "n : " << n << std::endl;
std::cout << "child 退出 father也退出" << std::endl;
break;
}
else
{
break;
}
break;
}
}
int main()
{
//1. 创建管道
int fds[2] = {0};//fds[0]:读 fds[1]:写
int n = pipe(fds);
if(n < 0)
{
std::cerr << "pipe error" << std::endl;
return 1;
}
std::cout << "fds[0]: " << fds[0] << std::endl;
std::cout << "fds[1]: " << fds[1] << std::endl;
//2. 创建子进程
pid_t id = fork();
if(id == 0)
{
//关闭子进程读
close(fds[0]);
//子进程写入
ChildWrite(fds[1]);
//关闭子进程写
close(fds[1]);
exit(0);
}
//3. 形成通信管道(父进程读,子进程写)
//关闭父进程写
close(fds[1]);
//父进程读取
FatherRead(fds[0]);
//关闭父进程读
close(fds[0]);
sleep(5);
//回收子进程
int status = 0;
int ret = waitpid(id,&status,0);
if(ret > 0)
{
printf("exit code : %d,exit signal : %d\n",(status >> 8) & 0xff,status & 0x7f);
sleep(5);
}
return 0;
}
实验现象


四、进程池
4.1.管道的容量
64KB(65536 / 1024)


4.2.管道写入的原子性
单次写入数据长度≤PIPE_BUF(4KB),OS保证原子写入
单次写入数据长度>PIPE_BUF(4KB),为非原子写入
原子写入:多进程同时向同一个管道写数据时,单次写入的数据不会出现穿插和拆分
示例:进程A写"aaa",进程B写“bbb”,读端只会读到“aaabbb”或"bbbaaa",不会"aababba"

4.3.基于管道的进程池
进程池:父进程通过管道暂停和唤醒子进程,发送任务码让子进程执行相应任务

- ProcessPool.hpp
//.hpp文件:头文件与源文件没有分离
//.cxx文件:与.cc和.cpp一样,都是C++源文件的后缀
//法一:#pragma once
//法二:条件编译
#ifndef __PROCESS_POOL_HPP__
#define __PROCESS_POOL_HPP__
//头文件
#include <iostream>
#include <vector>
#include <unistd.h>
#include <cstdlib>
#include <sys/wait.h>
#include "Task.hpp"
//管道类(先描述)
class Channel
{
public:
//构造
Channel(int fd, pid_t id)
:_wfd(fd)
,_subid(id)
{
_name = "channel-" + std::to_string(_wfd) + "-" + std::to_string(_subid);//整型转字符串
}
//发送任务码
void Send(int code)
{
int n = write(_wfd, &code, sizeof(code));
(void)n;//避免编译报警(对于未使用变量的检查)
}
//关闭写文件描述符
void Close()
{
close(_wfd);
}
//回收子进程
void Wait()
{
pid_t rid = waitpid(_subid,nullptr,0);
(void)rid;
}
//析构
~Channel(){}
//Get方法
int Fd()
{
return _wfd;
}
pid_t SubId()
{
return _subid;
}
std::string Name()
{
return _name;
}
private:
int _wfd;//写文件描述符
int _subid;//子进程pid
std::string _name;//管道名字
};
//管道管理类(再组织)
class ChannelManager
{
public:
//构造
ChannelManager()
:_next(0)
{}
//构建管道
void Insert(int wfd, pid_t subid)
{
//Channel c(wfd,subid);
//_channels.push_back(std::move(c));
_channels.emplace_back(wfd,subid);
}
//选择管道
Channel &Select()
{
auto &c = _channels[_next];
_next++;
_next %= _channels.size();
return c;
}
//打印管道
void PrintChannel()
{
for(auto &channel : _channels)
{
std::cout << channel.Name() << std::endl;
}
}
//结束子进程
void StopSubProcess()
{
for(auto &channel : _channels)
{
channel.Close();
std::cout << "关闭: " << channel.Name() << std::endl;
}
}
//回收子进程
void WaitSubProcess()
{
for(auto &channel : _channels)
{
channel.Wait();
std::cout << "回收: " << channel.Name() << std::endl;
}
}
//析构
~ChannelManager(){}
private:
std::vector<Channel> _channels;//管道管理数组
int _next;
};
//构建管道数目
const int gdefaultnum = 5;
//进程池类
class ProcessPool
{
public:
//构造
ProcessPool(int num)
:_process_num(num)
{
//注册任务
_tm.Register(Printlog);
_tm.Register(Download);
_tm.Register(Upload);
}
//子进程工作
void Work(int rfd)
{
while(true)
{
int code = 0;
ssize_t n = read(rfd, &code, sizeof(code));
if(n > 0)
{
//如果读取任务码错误
if(n != sizeof(code))
{
//继续读取
continue;
}
std::cout << "子进程[" << getpid() << "]收到一个任务码: " << code << std::endl;
//子进程执行任务
_tm.Execute(code);
}
else if(n == 0)
{
std::cout << "子进程退出" << std::endl;
break;
}
else
{
std::cout << "读取错误" << std::endl;
break;
}
}
}
//启动进程池
bool Start()
{
for(int i = 0; i < _process_num; i++)
{
//创建管道文件(父写子读)
int pipefd[2] = {0};
int n = pipe(pipefd);
if(n < 0)
{
return false;
}
//创建子进程
pid_t subid = fork();
if(subid < 0)
{
return false;
}
else if(subid == 0)
{
//关闭子进程写
close(pipefd[1]);
//子进程工作
Work(pipefd[0]);
//关闭子进程读
close(pipefd[0]);
//子进程退出
exit(0);
}
else
{
//关闭父进程读
close(pipefd[0]);
//构建管道
_cm.Insert(pipefd[1],subid);
}
}
return true;
}
//调试
void Debug()
{
_cm.PrintChannel();
}
//运行进程池
void Run()
{
//1. 选择一个任务
int task_code = _tm.Code();
//2. 选择一个管道/子进程(负载均衡地选择子进程完成任务)
//方法一:轮询(✔)
//方法二:随机
//方法三;channel添加负载指标
auto &c = _cm.Select();
std::cout << "选择了一个子进程: " << c.Name() << std::endl;
//3. 发送任务
c.Send(task_code);
std::cout << "发送了一个任务码: " << task_code << std::endl;
}
//结束进程池
void Stop()
{
//关闭父进程所有的写文件描述符
_cm.StopSubProcess();
//回收所有的子进程
_cm.WaitSubProcess();
}
//析构
~ProcessPool(){}
private:
ChannelManager _cm;//管道管理变量
TaskManager _tm;//任务管理变量
int _process_num;//进程个数
};
#endif
- Task.hpp
#pragma once
#include <iostream>
#include <vector>
#include <time.h>
typedef void (*task_t)();
/////////////////////debug//////////////////////////
void Printlog()
{
std::cout << "我是一个打印日志的任务" << std::endl;
}
void Download()
{
std::cout << "我是一个下载的任务" << std::endl;
}
void Upload()
{
std::cout << "我是一个上传的任务" << std::endl;
}
/////////////////////debug//////////////////////////
class TaskManager
{
public:
//构造
TaskManager()
{
srand(time(nullptr));
}
//注册任务
void Register(task_t t)
{
_tasks.push_back(t);
}
//生成任务码
int Code()
{
return rand() % _tasks.size();
}
//执行任务
void Execute(int code)
{
if(code >= 0 && code < _tasks.size())
{
_tasks[code]();
}
}
//析构
~TaskManager()
{}
private:
std::vector<task_t> _tasks;
};
- Main.cc
#include "ProcessPool.hpp"
int main()
{
//1. 创建进程池类型对象
ProcessPool pp(gdefaultnum);
//2. 启动进程池
pp.Start();
//3. 自动派发任务
int cnt = 10;
while(cnt--)
{
//4. 运行进程池
pp.Run();
sleep(1);
}
//5. 回收进程池
pp.Stop();
sleep(1000);
return 0;
}
- Makefile
process_pool:Main.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f process_pool
实验现象

4.4.隐藏BUG

方案一:倒着关闭写端
最后一个进程只有父进程的一个写端,关闭后可以关闭该进程指向其他进程管道的写端
class ChannelManager
{
public:
//......
void CloseAndWait()
{
for(int i = _channels.size() - 1; i >= 0; i--)
{
_channels[i].Close();
std::cout << "关闭: " << _channels[i].Name() << std::endl;
channels[i].Wait();
std::cout << "回收: " << _channels[i].Name() << std::endl;
}
}
//......
}
方案二:让父进程单独指向每个管道的写端
通过管道管理数组,将子进程继承下来的堂兄进程写段全都关闭
class ChannelManager
{
public:
//......
void CloseAll()
{
for(auto &channel : _channels)
{
channel.Close();
}
}
void CloseAndWait()
{
for(auto &channel : channels)
{
_channels[i].Close();
std::cout << "关闭: " << _channels[i].Name() << std::endl;
channels[i].Wait();
std::cout << "回收: " << _channels[i].Name() << std::endl;
}
}
//......
}
class ProcessPool
{
public:
//......
bool Start()
{
for(int i = 0; i < _process_num ; i++)
{
//创建管道文件(父写子读)
int pipefd[2] = {0};
int n = pipe(pipefd);
if(n < 0)
{
return false;
}
//创建子进程
pid_t subid = fork();
if(subid < 0)
{
return false;
}
else if(subid == 0)
{
_cm.CloseAll();
//关闭子进程写
close(pipefd[1]);
//子进程工作
Work(pipefd[0]);
//关闭子进程读
close(pipefd[0]);
//子进程退出
exit(0);
}
else
{
//关闭父进程读
close(pipefd[0]);
//构建管道
_cm.Insert(pipefd[1],subid);
}
}
return true;
}
//......
}
五、命名管道
匿名管道:只能是具有血缘关系的进程进行进程间通信
命名管道:可以在不相关的进程直接交互数据
命名管道文件只会被打开,不会被刷新到磁盘
5.1.mkfifo命令
mkfifo命令:创建命名管道
unlink命令:删除文件

5.2.mkfifo函数
mkfifo函数:创建命名管道

unlink函数:删除文件

5.3.命名管道 VS 匿名管道
匿名管道:由pipe函数创建并打开
命名管道:由mkfifo函数创建,由open函数打开
唯一的区别在于创建和打开方式不同
5.4.命名管道通信
- comm.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PATH "."
#define FILENAME "fifo"
// 命名管道类
class NamedIpcFifo
{
public:
NamedIpcFifo(const std::string &path, const std::string &name)
: _path(path), _name(name)
{
_fifoname = _path + "/" + _name;
// 创建管道文件
umask(0); // 设置权限掩码
int n = mkfifo(_fifoname.c_str(), 0666);
if (n < 0)
{
std::cerr << "mkdir fifo error" << std::endl;
}
else
{
std::cout << "mkfifo success" << std::endl;
}
}
~NamedIpcFifo()
{
// 删除管道文件
int n = unlink(_fifoname.c_str());
if (n == 0)
{
std::cout << "remove fifo success" << std::endl;
}
else
{
std::cout << "remove fifo failed" << std::endl;
}
}
private:
std::string _path; // 管道所在目录路径
std::string _name; // 管道文件名称
std::string _fifoname; // 管道完整路径
};
// 文件操作类
class FileOper
{
public:
FileOper(const std::string &path, const std::string &name)
: _path(path), _name(name), _fd(-1)
{
_fifoname = _path + "/" + _name;
}
// 打开文件(读)
void OpenForRead()
{
// 写入方没有执行open的时候,读取方就要在open内部阻塞
// 直到有人把管道文件打开了,open才会返回
_fd = open(FILENAME, O_RDONLY);
if (_fd < 0)
{
std::cerr << "open fifo error" << std::endl;
return;
}
std::cout << "open fifo success" << std::endl;
}
// 打开文件(写)
void OpenForWrite()
{
_fd = open(FILENAME, O_WRONLY);
if (_fd < 0)
{
std::cerr << "open fifo error" << std::endl;
return;
}
std::cout << "open fifo success" << std::endl;
}
// 写文件
void Write()
{
std::string message;
int cnt = 1;
pid_t id = getpid();
while (true)
{
std::cout << "Please Enter# ";
std::getline(std::cin, message);
message += (", message number: " + std::to_string(cnt++) + "[" + std::to_string(id) + "]");
write(_fd, message.c_str(), message.size());
}
}
// 读文件
void Read()
{
while (true)
{
char buffer[1024];
int number = read(_fd, buffer, sizeof(buffer) - 1);
if (number > 0)
{
buffer[number] = 0;
std::cout << "Client Say# " << buffer << std::endl;
}
else if (number == 0)
{
std::cout << "client quit! me too!" << std::endl;
break;
}
else
{
std::cout << "read error" << std::endl;
break;
}
}
}
// 关闭文件
void Close()
{
if(_fd > 0)
{
close(_fd);
}
}
~FileOper()
{}
private:
std::string _path; // 管道所在目录路径
std::string _name; // 管道文件名称
std::string _fifoname; // 管道完整路径
int _fd; // 管道文件描述符
};
- server.cc
#include "comm.hpp"
int main()
{
// 在当前目录下创建管道文件
NamedIpcFifo fifo(PATH, FILENAME);
// 文件操作
FileOper readerfile(PATH, FILENAME);
readerfile.OpenForRead();
readerfile.Read();
readerfile.Close();
return 0;
}
- client.cc
#include "comm.hpp"
int main()
{
// 文件操作
FileOper writerfile(PATH, FILENAME);
writerfile.OpenForWrite();
writerfile.Write();
writerfile.Close();
return 0;
}
- Makefile
.PHONY:all
all:client server
client:client.cc
g++ -o $@ $^ -std=c++11
server:server.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f client server
六、System V共享内存
System V:一种模块标准,是三种进程间通信机制的总称
IPC本质:让不同的进程看到同一份资源
6.1.共享内存的原理
多组进程使用不同的共享内存进行通信
多个共享内存同时存在就需要进行管理(先描述,再组织)
共享内存由描述自己的结构体对象与物理内存
进程与共享内存的关系就是内核数据结构关系

6.2.共享内存的接口
6.2.1.shmget函数
作用:创建共享内存
参数1:共享内存的键值(不是由内核形成,而是在用户层构建并传入OS)
参数2:共享内存的大小(4KB的整数倍,向上取整)
参数3:共享内存的标志位
- IPC_CREAT:获取共享内存
- IPC_CREAT | IPC_EXCL | 权限码:创建共享内存
返回值:
- 成功:共享内存标识符
- 失败:-1

6.2.2.shmctl函数
作用:控制共享内存
参数1:共享内存标识符
参数2:共享内存设置命令
- IPC_RMID:删除共享内存
- IPC_STAT:拷贝共享内存属性
参数3:共享内存属性表


键值会被设置到共享内存的描述结构体

6.2.3.shmat函数
作用:将共享内存挂接到进程的虚拟地址空间中
参数1:共享内存标识符
参数2:采用固定地址挂接,不需要传NULL
参数3:共享内存标志位,传0使用默认设置
返回值:
- 成功:虚拟空间的起始地址
- 失败:-1

6.2.4.shmdt函数
作用:将共享内存与虚拟内存去关联,减少共享内存结构体的引用计数

6.2.5.ftok函数
作用:生成键值
参数1:任意路径名字
参数2:任意整型变量

6.2.6.ipcs命令
ipcs -m:查看共享内存
6.2.7.ipcrm命令
ipcrm -m shmid:删除共享内存
6.3.共享内存的实现
读写共享内存没有使用系统调用
共享区属于用户空间而非内核的
所以共享内存是进程通信最快的
- 映射后读写直接被对方看到
- 不需要系统调用或写入内容
但是通信双方没有保护机制,共享内存的写端只写了一部分就会被读端读取,导致数据不一致
有保护机制:
- AA
- AA BB
- AA BB CC
无保护机制:
- A
- AA
- AA B
- AA BB
可以使用命名管道的同步机制对共享内存进行局部保护
进程与进程的共享内存映射:SystemV
进程与磁盘文件的共享内存映射:mmap
- shm.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>
#include "comm.hpp"
const int gdefaultid = -1; // 错误共享内存标识符
const int gsize = 4096; // 共享内存空间
const std::string pathname = "."; // 路径名称
const int projid = 0x66; // 生成key的任意id
const int gmode = 0666; // 共享内存权限
#define CREATER "creater"
#define USER "user"
// 共享内存类
class Shm
{
private:
void CreateHelper(int flg)
{
printf("key: 0x%x\n", _key);
_shmid = shmget(_key, gsize, flg);
if (_shmid < 0)
{
ERR_EXIT("shmget");
}
printf("shmid: %d\n", _shmid);
}
// 创建一个共享内存
void Create()
{
CreateHelper(IPC_CREAT | IPC_EXCL | gmode);
}
// 关联一个共享内存
void Attach()
{
_start_mem = shmat(_shmid, nullptr, 0);
if ((long long)_start_mem < 0)
{
ERR_EXIT("shmat");
}
printf("attach success\n");
}
// 去关联一个共享内存
void Detach()
{
int n = shmdt(_start_mem);
if (n == 0)
{
printf("Detach success\n");
}
}
// 获取一个共享内存
void Get()
{
CreateHelper(IPC_CREAT);
}
// 删除一个共享内存
void Destroy()
{
Detach();
if (_usertype == CREATER)
{
int n = shmctl(_shmid, IPC_RMID, nullptr);
if (n > 0)
{
printf("shmctl delete shm: %d success!\n", _shmid);
}
else
{
ERR_EXIT("shmctl\n");
}
}
}
public:
Shm(const std::string &pathname, int projid, const std::string &usertype)
: _shmid(gdefaultid), _size(gsize), _start_mem(nullptr), _usertype(usertype)
{
_key = ftok(pathname.c_str(), projid);
if (_key < 0)
{
ERR_EXIT("ftok");
}
if (_usertype == CREATER)
{
Create();
}
else if (_usertype == USER)
{
Get();
}
else
{
}
Attach();
}
// 返回起始虚拟地址
void *VirtualAddr()
{
printf("VirtualAddr: %p\n", _start_mem);
return _start_mem;
}
// 获取共享内存大小
int Size()
{
return _size;
}
// 获取共享内存的属性
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);
}
~Shm()
{
std::cout << _usertype << std::endl;
Destroy();
}
private:
int _shmid; // 共享空间标识符
key_t _key; // key值
int _size; // 共享空间大小
void *_start_mem; // 起始的虚拟地址
std::string _usertype; // 用户类型
};
- Fifo.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "comm.hpp"
#define PATH "."
#define FILENAME "fifo"
// 命名管道类
class NamedIpcFifo
{
public:
NamedIpcFifo(const std::string &path, const std::string &name)
: _path(path), _name(name)
{
_fifoname = _path + "/" + _name;
// 创建管道文件
umask(0); // 设置权限掩码
int n = mkfifo(_fifoname.c_str(), 0666);
if (n < 0)
{
ERR_EXIT("mkfifo");
}
else
{
std::cout << "mkfifo success" << std::endl;
}
}
~NamedIpcFifo()
{
// 删除管道文件
int n = unlink(_fifoname.c_str());
if (n == 0)
{
//bug:先析构fifo后,进程直接退出,导致shm析构没有被调用
//ERR_EXIT("unlink");
}
else
{
std::cout << "remove fifo failed" << std::endl;
}
}
private:
std::string _path; // 管道所在目录路径
std::string _name; // 管道文件名称
std::string _fifoname; // 管道完整路径
};
// 文件操作类
class FileOper
{
public:
FileOper(const std::string &path, const std::string &name)
: _path(path), _name(name), _fd(-1)
{
_fifoname = _path + "/" + _name;
}
// 打开文件(读)
void OpenForRead()
{
// 写入方没有执行open的时候,读取方就要在open内部阻塞
// 直到有人把管道文件打开了,open才会返回
_fd = open(FILENAME, O_RDONLY);
if (_fd < 0)
{
ERR_EXIT("openforread");
}
std::cout << "open fifo success" << std::endl;
}
// 打开文件(写)
void OpenForWrite()
{
_fd = open(FILENAME, O_WRONLY);
if (_fd < 0)
{
ERR_EXIT("openforwrite");
}
std::cout << "open fifo success" << std::endl;
}
// 唤醒读端
void Wakeup()
{
char c = 'c';
int n = write(_fd, &c, 1);
printf("尝试唤醒: %d\n", n);
}
// 等待写端
bool Wait()
{
char c;
int number = read(_fd, &c, 1);
if (number > 0)
{
printf("读端醒来: %d\n", number);
return true;
}
return false;
}
// 关闭文件
void Close()
{
if (_fd > 0)
{
close(_fd);
}
}
~FileOper()
{
}
private:
std::string _path; // 管道所在目录路径
std::string _name; // 管道文件名称
std::string _fifoname; // 管道完整路径
int _fd; // 管道文件描述符
};
- server.cc
#include "Shm.hpp"
#include "Fifo.hpp"
int main()
{
// 创建共享内存对象
Shm shm(pathname, projid, CREATER);
// 获取共享内存属性
sleep(5);
shm.Attr();
// 创建管道文件对象
NamedIpcFifo fifo(PATH, FILENAME);
// 文件操作
FileOper readerfile(PATH, FILENAME);
readerfile.OpenForRead();
// 指向共享内存的指针
char *mem = (char *)shm.VirtualAddr();
while (true)
{
// 管道未写入时读端默认阻塞,等待写端写入
if(readerfile.Wait())
{
printf("%s\n", mem);
}
else
{
break;
}
}
readerfile.Close();
return 0;
}
- client.cc
#include "Shm.hpp"
#include "Fifo.hpp"
int main()
{
// 文件操作
FileOper writerfile(PATH, FILENAME);
writerfile.OpenForWrite();
// 获取共享内存对象
Shm shm(pathname, projid, USER);
// 指向共享内存的指针
char *mem = (char *)shm.VirtualAddr();
int index = 0;
for (char c = 'A'; c <= 'Z'; c++, index += 2)
{
sleep(2);
// 向共享内存写
mem[index] = c;
mem[index + 1] = c;
mem[index + 2] = 0;
// 唤醒读端读取
writerfile.Wakeup();
}
writerfile.Close();
return 0;
}
- comm.hpp
#pragma once
#include <cstdio>
#include <cstdlib>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
- Makefile
.PHONY:all
all:client server
client:client.cc
g++ -o $@ $^ -std=c++11
server:server.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f client server
七、System V消息队列
7.1.消息队列的原理
消息队列将该资源维护成一个队列
消息队列的生命周期随内核
消息队列提供了从一个进程向另外一个进程发送一块数据段方法
操作系统要对多个消息队列进行管理,先描述,再组织
两个进程通过约定key值保证看到同一个消息队列


消息队列的结构与内核表示
struct msqid_ds
{
struct ipc_perm msg_perm;
struct msg msg_first;
/ first message on queue, unused * /
struct msg msg_last;
/ last message in queue, unused * /
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};

7.2.消息队列的接口
7.2.1.msgget函数
作用:创建一个消息队列
参数1:消息队列的键值
参数2:消息队列的标志位
- IPC_CREAT:获取消息队列
- IPC_CREAT | IPC_EXCL | 权限码:创建消息队列
返回值:
- 成功:消息队列描述符
- 失败:-1

7.2.2.msgctl函数
作用:控制消息队列
参数1:消息队列标识符
参数2:消息队列设置命令
- IPC_RMID:删除消息队列
- IPC_STAT:获取消息队列属性
参数3:消息队列属性表



7.2.3.msgsnd函数
作用:发送数据
参数1:消息队列标识符
参数2:消息节点结构体指针
参数3:消息内容的大小
参数4:消息类型
参数5:默认设置为0
返回值:
- 成功:拷贝的字节数
- 失败:-1

消息节点结构体
- mtype:消息类型(区分进程)
- mtext:消息内容

7.2.4.msgrcv函数
作用:接收数据
参数1:消息队列标识符
参数2:消息节点结构体指针
参数3:消息内容的大小
参数4:默认设置为0

7.2.5.ftok函数
作用:生成键值
参数1:任意路径名字
参数2:任意整型变量

7.2.6.ipcs命令
ipcs -q:查看消息队列
7.2.7.ipcrm命令
ipcrm -q msqid:删除消息队列
7.3.责任链模式与消息队列
新需求:
客户端发送输入内容,时间,进程PID
服务器将收到的内容,持久化保存文件
文件过大时进行切片,保存指定目录下
行为类设计模式:责任链模式、观察者模式
将请求沿着处理者链进行传递,每个处理者对请求进行检查
如果处理者能够处理该请求,就进行处理
如果处理者不能处理该请求,就给下一个


- MsgQueue.hpp
#ifndef MSGQUEUE_HPP
#define MSGQUEUE_HPP
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define PATHNAME "/home/ubuntu/test/MsgQueue/MsgQueue.hpp"
#define PROJID 0x123
const int default_fd = -1;
const int default_size = 1024;
#define GET_MSGQUEUE (IPC_CREAT)
#define CREATE_MSGQUEUE (IPC_CREAT | IPC_EXCL | 0666)
class MsgQueue
{
struct msgbuf
{
long mtype;
char mtext[default_size];
};
public:
MsgQueue()
: _msgfd(default_fd)
{
}
// 创建消息队列
void Create(int flag)
{
// 获取键值
key_t key = ftok(PATHNAME, PROJID);
if (key == -1)
{
std::cerr << "ftok error" << std::endl;
exit(1);
}
std::cout << "Get key: " << std::hex << key << std::endl;
// 创建消息队列
_msgfd = msgget(key, flag);
if (_msgfd == -1)
{
std::cerr << "msgget error" << std::endl;
exit(2);
}
std::cout << "msgqueue created: " << _msgfd << std::endl;
}
// 发送消息
void Send(int type, std::string &text)
{
// 填充消息节点
struct msgbuf msg;
memset(&msg, 0, sizeof(msg));
msg.mtype = type;
memcpy(msg.mtext, text.c_str(), text.size());
// 发送消息节点
int n = msgsnd(_msgfd, &msg, sizeof(msg.mtext), 0);
if (n == -1)
{
std::cerr << "msgsnd error" << std::endl;
return;
}
std::cout << "send message: " << text << std::endl;
}
// 接收消息
void Recv(int type, std::string &text)
{
struct msgbuf msg;
int n = msgrcv(_msgfd, &msg, sizeof(msg.mtext), type, 0);
if (n == -1)
{
std::cerr << "msgrcv error" << std::endl;
return;
}
msg.mtext[n] = '\0';
text = msg.mtext;
type = msg.mtype;
}
// 获取消息队列中的属性
void GetAtter()
{
struct msqid_ds outbuffer;
int n = msgctl(_msgfd, IPC_STAT, &outbuffer);
if(n == -1)
{
std::cerr << "msgctl error" << std::endl;
return;
}
std::cout << "msgqueue attr: " << std::hex << outbuffer.msg_perm.__key << std::endl;
}
// 删除消息队列
void Destroy()
{
int n = msgctl(_msgfd, IPC_RMID, 0);
if (n == -1)
{
std::cerr << "msgctl error" << std::endl;
}
std::cout << "msgqueue destroyed" << std::endl;
}
~MsgQueue()
{
}
private:
int _msgfd;
};
// 定义消息类型
#define MSG_TYPE_CLIENT 1
#define MSG_TYPE_SERVER 2
class Server : public MsgQueue
{
public:
Server()
{
MsgQueue::Create(CREATE_MSGQUEUE);
std::cout << "server create msgqueue done" << std::endl;
MsgQueue::GetAtter();
}
~Server()
{
MsgQueue::Destroy();
}
private:
};
class Client : public MsgQueue
{
public:
Client()
{
MsgQueue::Create(GET_MSGQUEUE);
std::cout << "client get msgqueue done" << std::endl;
}
~Client()
{
}
private:
};
#endif
- ChainOfResp.hpp
#ifndef CHAIN_OF_RESP_HPP
#define CHAIN_OF_RESP_HPP
#include <iostream>
#include <memory>
#include <string>
#include <sstream>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include <filesystem> // C++17
#include <fstream>
#include <sys/wait.h>
class HandlerText
{
public:
virtual ~HandlerText() = default;
virtual void Excute(const std::string &text) = 0;
void SetNext(std::shared_ptr<HandlerText> next)
{
_next = next;
}
void Enable()
{
_enable = true;
}
void DisEnable()
{
_enable = false;
}
protected: // 需要被子类继承
std::shared_ptr<HandlerText> _next; // 下一个责任链节点
bool _enable = true; // 是否使用该节点
};
// 格式化文本
class HandlerTextFormat : public HandlerText
{
public:
void Excute(const std::string &text) override
{
std::string format_result = text + "\n";
if (_enable)
{
std::stringstream ss;
ss << time(nullptr) << "-" << getpid() << "-" << text << "\n";
format_result = ss.str();
std::cout << "Step 1. 格式化消息: " << text << " 结果: " << format_result << std::endl;
}
if (_next)
{
_next->Excute(format_result); // 将处理结果表现在text内部
}
else
{
std::cout << "到达责任链处理结尾, 完成责任链处理" << std::endl;
}
}
};
// 文件信息: 文件路径 文件名称
std::string defaultfilepath = "./tmp/";
std::string defaultfilename = "test.log";
// 文本保存文件
class HandlerTextSaveFile : public HandlerText
{
public:
HandlerTextSaveFile(const std::string &filepath = defaultfilepath, const std::string &filename = defaultfilename)
: _filepath(filepath), _filename(filename)
{
// 形成默认的目录名
if (std::filesystem::exists(_filepath))
{
return;
}
try
{
std::filesystem::create_directories(_filepath);
}
catch (std::filesystem::filesystem_error const &e)
{
std::cerr << e.what() << '\n';
}
}
void Excute(const std::string &text) override
{
// 保存到文件中
if (_enable)
{
std::string file = _filepath + _filename;
std::ofstream ofs(file, std::ios::app);
if (!ofs.is_open())
{
std::cerr << "open file error: " << file << std::endl;
return;
}
ofs << text;
ofs.close();
std::cout << "Step 2. 保存消息: " << text << " 到文件: " << file << std::endl;
}
if (_next)
{
_next->Excute(text); // 将处理结果表现在text内部
}
else
{
std::cout << "到达责任链处理结尾, 完成责任链处理" << std::endl;
}
}
private:
std::string _filepath;
std::string _filename;
};
const int defaultmaxline = 5;
// 检查文件内容大小, 对过大内容打包备份
class HandlerTextBackup : public HandlerText
{
private:
bool IsOutOfRange(const std::string &file)
{
std::ifstream ifs(file);
if (!ifs.is_open())
{
std::cerr << "open file error" << file << std::endl;
return false;
}
int lines = 0;
std::string line;
while (std::getline(ifs, line))
{
lines++;
}
ifs.close();
return lines > _maxline;
}
void Backup(const std::string &file)
{
// 文件重命名
std::string suffix = std::to_string(time(nullptr));
// ./tmp/test.txt.1589234234
std::string backup_file = file + "." + suffix;
// test.txt.1589234234
std::string src_file = _filename + "." + suffix;
// test.txt.1589234234.tgz
std::string tar_file = src_file + ".tgz";
pid_t pid = fork();
if (pid == 0)
{
// 子进程进行数据备份
std::filesystem::rename(file, backup_file);
std::cout << "Step 4. 备份文件: " << file << " 到文件: " << backup_file << std::endl;
// 对备份文件进行打包
std::filesystem::current_path(_filepath);
execlp("tar", "tar", "-czf", tar_file.c_str(), src_file.c_str(), nullptr);
exit(1);
}
int status;
pid_t rid = waitpid(pid, &status, 0);
if (rid > 0)
{
if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
{
// 删除备份文件
std::filesystem::remove(backup_file);
std::cout << "Step 5. 删除备份文件: " << backup_file << std::endl;
}
}
}
public:
HandlerTextBackup(const std::string &filepath = defaultfilepath, const std::string &filename = defaultfilename, const int &maxline = defaultmaxline)
: _filepath(filepath), _filename(filename), _maxline(maxline)
{
}
void Excute(const std::string &text) override
{
if (_enable)
{
std::string file = _filepath + _filename;
std::cout << "Step 3. 检查文件: " << file << " 大小是否超范围 " << std::endl;
if (IsOutOfRange(file))
{
std::cout << "目标文件超范围, 进行切片备份" << file << std::endl;
Backup(file);
}
}
if (_next)
{
_next->Excute(text); // 将处理结果表现在text内部
}
else
{
std::cout << "到达责任链处理结尾, 完成责任链处理" << std::endl;
}
}
private:
std::string _filepath;
std::string _filename;
int _maxline;
};
// 责任链入口
class HandlerEntry
{
public:
HandlerEntry()
{
// 构造责任链节点
_format = std::make_shared<HandlerTextFormat>();
_save = std::make_shared<HandlerTextSaveFile>();
_backup = std::make_shared<HandlerTextBackup>();
// 设置责任链处理顺序
_format->SetNext(_save);
_save->SetNext(_backup);
}
void EnableHandler(bool is_format, bool is_save, bool is_backup)
{
is_format ? _format->Enable() : _format->DisEnable();
is_save ? _save->Enable() : _save->DisEnable();
is_backup ? _backup->Enable() : _backup->DisEnable();
}
void Run(const std::string &text)
{
_format->Excute(text);
}
~HandlerEntry()
{
}
private:
std::shared_ptr<HandlerText> _format;
std::shared_ptr<HandlerText> _save;
std::shared_ptr<HandlerText> _backup;
};
#endif
- Client.cc
#include "MsgQueue.hpp"
int main()
{
Client client;
while(true)
{
std::string input;
std::cout << "Please input message: ";
std::getline(std::cin, input);
client.Send(MSG_TYPE_CLIENT, input);
if(input == "exit")
{
break;
}
}
return 0;
}
- Server.cc
#include "MsgQueue.hpp"
#include "ChainOfResp.hpp"
int main()
{
std::string text;
Server server;
HandlerEntry he;
he.EnableHandler(false, true, true);
while (true)
{
// 消息队列为空时阻塞等待
server.Recv(MSG_TYPE_CLIENT, text);
std::cout << "Received: " << text << std::endl;
if(text == "exit")
{
break;
}
he.Run(text);
}
return 0;
}
- Makefile
all: Client Server
Client:Client.cc
g++ -o Client Client.cc -std=c++17
Server:Server.cc
g++ -o Server Server.cc -std=c++17
.PHONY:clean
clean:
rm -f Client Server
八、System V信号量
8.1.关于并发编程的一些概念
共享内存使两个进程看到同一份资源,为通信提供前提
但是没有保护机制,数据不一致,信号量是一个具体的方案
共享资源:
多个执行流(进程)能看到的同一份公共资源
临界资源:
被保护起来的共享资源
临界区:
在进程中涉及到互斥资源的程序段(访问临界资源对应的代码)
保护临界区代码就是在保护临界资源
互斥:
任何时刻只允许一个执行流访问临界资源
类似于ATM机,每个人进入ATM机取钱之前要先开锁
进去后门会上锁,每次只能有一个人进入提款机取钱
同步:
多个执行流访问临界资源时有一定的顺序性
类似用共享内存加命名管道,通过管道文件的读端阻塞
等规定数据写入共享内存后,向管道文件写入唤醒读端

原子性:
要么做,要么不做
锁本身也要被共享,所以在申请锁的时候,必须是原子的,要么申请好了,要么不申请
8.2.信号量的理解
信号量(信号灯):一个计数器,用来表明临界资源的数量多少
作用:保护临界区
本质:对资源的预定机制

P操作:信号量本身就是共享资源,申请时要减减
P(s)
{
s.value = s.value--;
if (s.value < 0)
{
// 该进程状态置为等待状状态
// 将该进程的PCB插⼊⼊相应的等待队列s.queue末尾
}
}
V操作:进程用完资源退出后,信号量的值要加加
V(s)
{
s.value = s.value++;
if (s.value > 0)
{
// 唤醒相应等待队列s.queue中等待的⼀个进程
// 改变其状态为就绪态
// 并将其插⼊OS就绪队列
}
}
通过PV操作完成进程对资源的预定机制
多元信号量:资源不是整体使用,通过信号量进行计数
二元信号量:资源整体使用,只有1或者0两态的信号量


8.3.信号量的接口
8.3.1.semget函数
作用:创建信号量集
参数1:信号量集的键值
参数2:信号量集中信号量的个数
参数3:信号量集的标志位
- IPC_CREAT:获取信号量集
- IPC_CREAT | IPC_EXCL | 权限码:创建信号量集
返回值:
- 成功:信号量集标识符
- 失败:-1

8.3.2.semctl函数
作用:控制信号量集
参数1:信号量集标识符
参数2:指定一个信号量(下标从0开始)
参数3:信号量集设置命令
- SETVAL:设置指定信号量计数值
- IPC_RMID:删除整个的信号量集
参数4:信号量集属性表(semun联合体)
返回值:
- 成功:0
- 失败:-1



8.3.3.semop函数
作用:操作信号量集
参数1:信号量集标识符
参数2:信号量集数组
- sem_num:信号量集中信号量的下标
- sem_op:-1进行P操作,+1进行V操作
- sem_flag:默认为0
参数3:信号量集数组元素个数


操作系统需要对大量的信号量集进行管理,需要先描述,再组织


8.3.4.ftok函数
作用:生成键值
参数1:任意路径名字
参数2:任意整型变量

8.3.5.ipcs命令
ipcs -s:查看信号量集
8.3.6.ipcrm命令
ipcrm -s semid:删除信号量集
8.4.基于建造者模式的信号量
构建类设计模式:建造者模式,工厂模式,单例模式
抽象建造者(Builder):为创建一个产品对象的各个部件指定抽象的接口(基类)
具体建造者(ConcreteBuilder):实现建造者接口,构建并装配产品部件(派生类)
指挥者(Director):使用建造者接口的对象,指导建造者如何构建产品(调用接口)
产品(Product):被建造者构建的复杂对象,通常包含多个组成部分

- Sem.hpp
#ifndef SEM_HPP
#define SEM_HPP
#include <iostream>
#include <string>
#include <memory>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <vector>
#define GET_SEM (IPC_CREAT)
#define BUILD_SEM (IPC_CREAT | IPC_EXCL)
const std::string SEM_PATH = "/tmp";
const int SEM_PROJ_ID = 0x77;
const int defaultnum = 1;
// 整数转十六进制
std::string intTohex(int num)
{
char hex[64];
snprintf(hex, sizeof(hex), "0x%x", num);
return std::string(hex);
}
// 产品类
class Semaphore
{
private:
void PV(int who, int data)
{
struct sembuf sem_buf;
sem_buf.sem_num = who; // 信号量编号
sem_buf.sem_op = data; // 操作方式
sem_buf.sem_flg = SEM_UNDO; // 不关心
int n = semop(_semid, &sem_buf, 1);
if (n < 0)
{
std::cerr << "semop PV failed" << std::endl;
}
}
public:
Semaphore(int semid)
: _semid(semid)
{
}
int Id() const
{
return _semid;
}
void P(int who)
{
PV(who, -1);
}
void V(int who)
{
PV(who, 1);
}
~Semaphore()
{
if (_semid >= 0)
{
int n = semctl(_semid, 0, IPC_RMID);
if (n < 0)
{
std::cerr << "semctl IPC_RMID failed" << std::endl;
}
std::cout << "Semaphore " << _semid << " removed" << std::endl;
}
}
private:
int _semid;
};
// 抽象的建造者
class SemaphoreBuilder
{
public:
virtual ~SemaphoreBuilder() = default;
virtual void BuildKey() = 0; // 创建信号量集的键值
virtual void SetPermission(int perm) = 0; // 设置权限
virtual void SetSemNum(int num) = 0; // 设置信号量集的个数
virtual void SetInitVal(std::vector<int> initVal) = 0; // 设置信号量初值
virtual void Build(int flag) = 0; // 创建信号量集
virtual void InitSem() = 0; // 初始化信号量集
virtual std::shared_ptr<Semaphore> GetSem() = 0; // 获取信号量集
};
// 具体的建造者
class ConcreteSemaphoreBuilder : public SemaphoreBuilder
{
private:
bool Init(int semid, int num, int val)
{
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
} un;
un.val = val;
int n = semctl(semid, num, SETVAL, un);
if (n < 0)
{
std::cerr << "semctl SETVAL failed" << std::endl;
return false;
}
return true;
}
public:
ConcreteSemaphoreBuilder()
{
}
void BuildKey() override
{
_key = ftok(SEM_PATH.c_str(), SEM_PROJ_ID);
if (_key < 0)
{
std::cerr << "ftok failed" << std::endl;
exit(1);
}
std::cout << "Get Key: " << intTohex(_key) << std::endl;
}
void SetPermission(int perm) override
{
_perm = perm;
}
void SetSemNum(int num) override
{
_num = num;
}
void SetInitVal(std::vector<int> initVal) override
{
_initVal = initVal;
}
void Build(int flag) override
{
int semid = semget(_key, _num, flag | _perm);
if (semid < 0)
{
std::cerr << "semget failed" << std::endl;
exit(2);
}
std::cout << "Get semaphore id: " << semid << std::endl;
_sem = std::make_shared<Semaphore>(semid);
}
void InitSem() override
{
if (_num > 0 && _initVal.size() == _num)
{
for (int i = 0; i < _num; i++)
{
if (!Init(_sem->Id(), i, _initVal[i]))
{
std::cerr << "Init failed" << std::endl;
exit(3);
}
}
}
}
std::shared_ptr<Semaphore> GetSem()
{
return _sem;
}
private:
key_t _key; // 信号量集的键值
int _perm; // 权限
int _num; // 信号量集的个数
std::vector<int> _initVal; // 信号量初值
std::shared_ptr<Semaphore> _sem; // 产品对象
};
// 指挥者
class Director
{
public:
void Construct(std::shared_ptr<SemaphoreBuilder> builder, int flag, int perm = 0666, int num = defaultnum, std::vector<int> initVal = {1})
{
builder->BuildKey();
builder->SetPermission(perm);
builder->SetSemNum(num);
builder->SetInitVal(initVal);
builder->Build(flag);
if (flag == BUILD_SEM)
{
builder->InitSem();
}
}
private:
};
#endif
- Write.cc
#include "Sem_V2.hpp"
#include <unistd.h>
#include <ctime>
#include <cstdio>
int main()
{
// 基于抽象接口类的具体建造者
std::shared_ptr<SemaphoreBuilder> builder = std::make_shared<ConcreteSemaphoreBuilder>();
// 指挥者对象
std::shared_ptr<Director> director = std::make_shared<Director>();
// 在指挥者的指导下, 完成建造过程
director->Construct(builder, BUILD_SEM, 0600, 3, {1, 2, 3});
// 获取信号量集
auto fsem = builder->GetSem();
srand(time(0) ^ getpid());
// 要求父子进程打印的F和C成对出现(保证打印是原子的)
pid_t pid = fork();
if (pid == 0)
{
director->Construct(builder, GET_SEM);
auto csem = builder->GetSem();
while (true)
{
csem->P(0);
printf("C");
usleep(rand() % 95270);
fflush(stdout);
printf("C");
usleep(rand() % 43990);
fflush(stdout);
csem->V(0);
}
}
while (true)
{
fsem->P(0);
printf("F");
usleep(rand() % 95270);
fflush(stdout);
printf("F");
usleep(rand() % 43990);
fflush(stdout);
fsem->V(0);
}
return 0;
}
九、mmap文件映射
9.1.概述
Linux提供的系统调用
将一个进程打开的文件映射到虚拟地址空间
直接使用虚拟地址,对文件内容的增删改查
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
传统文件读写:打开文件,获取文件描述符,通过read和write系统调用进行数据的拷贝

9.2.接口介绍
mmap函数
参数1:NULL-由系统指定一个挂接的起始地址
参数2:映射的虚拟地址空间大小(单位为4KB,向上取整)
参数3:映射区域的内存保护属性(权限)
- PROT_READ:映射区域可读
- PROT_WRITE:映射区域可写
- PROT_EXEC:映射区域可执行
参数4:映射类型
- MAP_PRIVATE:私有映射(写入后不会反应磁盘文件)
- MAP_SHARED:共享映射(写入后直接同步磁盘文件)
参数5:要映射的文件的文件描述符
参数6:文件中的起始偏移量
返回值:
- 成功:虚拟空间的起始地址
- 失败:(void *) -1

9.3.写入映射
#include <iostream>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define PAGE_SIZE 4096
// write_map filename
int main(int argc, char *argv[])
{
if(argc != 2)
{
std::cout << "Usage: " << argv[0] << " filename" << std::endl;
return 1;
}
// 打开目标文件
int fd = ::open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0666);
if(fd < 0)
{
std::cout << "Failed to open file: " << argv[1] << std::endl;
return 2;
}
// 调整文件大小, 进行合法映射(默认用0填充)
if(ftruncate(fd, PAGE_SIZE) == -1)
{
std::cout << "Failed to truncate file: " << argv[1] << std::endl;
return 3;
}
// 文件映射
char *shmaddr = (char *)::mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(shmaddr == MAP_FAILED)
{
std::cout << "Failed to mmap file: " << argv[1] << std::endl;
return 4;
}
// 文件操作
for(char c = 'a'; c <= 'z'; c++)
{
shmaddr[c - 'a'] = c;
sleep(1);
}
// 关闭映射
if(::munmap(shmaddr, PAGE_SIZE) == -1)
{
std::cout << "Failed to munmap file: " << argv[1] << std::endl;
return 5;
}
// 关闭目标文件
::close(fd);
return 0;
}

9.4.读取映射
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <cstring>
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " filemame" << std::endl;
return 1;
}
// 打开目标文件
std::string filename = argv[1];
int fd = ::open(filename.c_str(), O_RDONLY);
if (fd < 0)
{
std::cerr << "open error" << std::endl;
return 2;
}
// 获取⽂件⼤⼩
struct stat st;
::fstat(fd, &st);
// 文件映射
char *mmap_addr = (char *)::mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (mmap_addr == MAP_FAILED)
{
perror("mmap");
return 3;
}
// 文件操作
std::cout << "File content: " << mmap_addr << std::endl;
// 关闭映射
::munmap(mmap_addr, st.st_size);
// 关闭目标⽂件
::close(fd);
return 0;
}

9.5.模拟实现malloc
#include <iostream>
#include <cstdio>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
// mmap分配内存
void *my_malloc(size_t size)
{
if (size > 0)
{
void *ptr = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ptr == MAP_FAILED)
{
return nullptr;
}
return ptr;
}
return nullptr;
}
// mmap释放内存
void my_free(void *start, size_t size)
{
if(start != nullptr && size > 0)
{
int ret = ::munmap(start, size);
if(ret != 0)
{
perror("munmap");
}
}
}
int main()
{
// 申请内存
char *ptr = (char *)my_malloc(1024);
if(ptr == nullptr)
{
std::cerr << "malloc failed" << std::endl;
return 1;
}
// 打印起始地址
printf("Allocated memory at address: %p\n", ptr);
// 将内存值设置为A
memset(ptr, 'A', 1024);
// 打印内存中的值
for (int i = 0; i < 1024; i++)
{
printf("%c ", ptr[i]);
fflush(stdout);
sleep(1);
}
// 释放内存
my_free(ptr, 1024);
return 0;
}


十、内核IPC资源的组织管理
共享内存,消息队列,信号量用key来区分唯一性
在操作系统中共享内存,消息队列,信号量被当作了同一种资源,叫做System V IPC
IPC资源在内核中的组织形式




6639

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



