做网站3年,网站设计师薪资,岳阳网络推广公司,wordpress静态化经验Linux学习笔记---013 Linux的管道文件1、进程间通信1.1、进程为什么要通信#xff1f;1.2、进程如何通信#xff1f;1.3、进程通信的方式#xff1f; 2、匿名管道2.1、理解一种现象2.2、基本概念和管道原理 3、管道的使用3.1、代码样例3.2、如何使用管道通信呢#xff1f;3… Linux学习笔记---013 Linux的管道文件1、进程间通信1.1、进程为什么要通信1.2、进程如何通信1.3、进程通信的方式 2、匿名管道2.1、理解一种现象2.2、基本概念和管道原理 3、管道的使用3.1、代码样例3.2、如何使用管道通信呢3.3、管道的4种情况3.4、管道的5种特征3.5、管道的应用场景 4、命名管道4.1、原理4.2、创建命名管道函数的使用 5、system V的共享内存5.1、原理5.2、代码理解5.3、共享内存的理解5.4、共享内存的相关接口 6、消息队列6.1、基本概念6.2、涉及的常用接口 7、信号量7.1、5个概念7.2、对于信号量的理论理解7.3、原子操作7.4、常用信号量的指令 Linux的管道文件
前言 前篇开始进行了解学习Linux的磁盘文件等相关知识内容接下来学习关于Linux的管道文件、共享内存、消息队列和信号量的基本知识深入地了解这个强大的开源操作系统。 /知识点汇总/
1、进程间通信
1.1、进程为什么要通信
进程间也是需要某种协同的如何协同的前提条件就是通信。 数据是有类别的通知就绪的单纯的要传递的信息以及控制信息。 事实进程是具有独立性的。 进程 内核的数据结构 代码和数据 1.2、进程如何通信
a、进程间通信成本可能会稍微高一些。(因为进程是独立的) 比如进程a把数据给进程b进程具有独立性所以数据无法直接传递的。(父子进程fork的方式只是处于只读传递信息和一直可以传递信息是有区别的所以frok是处于可以传递信息但不能一直传递因为是基于写时拷贝的) b、进程间通信的前提先让不同的进程看到同一份(操作系统的)资源“一段内存” 因为进程a和进程b进程间具有独立性相互之间的空间和数据等资源无法共享就通过操纵系统实现让它们能够在“一段内存中交换和访问数据”。 那么操作系统怎么知道什么时候创建共享区域呢 1.一定是某一个进程先需要通信让OS创建一个共享资源。 2.OS必须提供很多的系统调用。 – OS创建的共享资源不同系统调用接口不同 ---- 进程间通信的方式就会存在不同。 1.3、进程通信的方式
a、存在一定约定的标准专利 b、消息队列、共享内存、信号量
直接复用内核代码直接通信呢 进程间独立对于文件系统无关。引出管道 1.命名管道 2.匿名管道 2、匿名管道
2.1、理解一种现象
为什么父子进程会向同一个显示器终端打印数据。 因为父子进程中子进程会继承父进程的文件描述符表进而指向同一个显示器文件用同一个进程inode也就把数据写入同一个缓冲区里所以系统刷新时就刷新到同一个显示器中。 进程默认会打开三个标准输入/输出012怎么做到的呢 都属于bash的子进程所以是bash打开了。 进程默认也就打开了我们只要约定好即可。 close():为什么我们子进程主动close(0/1/2)不影响父进程继续使用显示器文件呢 本质是由于之前了解到的引用计数通过引用计数能够知道有多少文件指针指向它那么就通过引用计数的指针依次释放指定的次数。 2.2、基本概念和管道原理 那么在通过操作系统基于文件系统上在内存中建立的“一段共享内存”就称为管道资源。 – 管道文件 注意 1.管道只允许单向通信 — 半双工通信 2.管道与文件的操作区别就在于不用刷新到磁盘了。 既然父子进程会关闭不需要的fd那么为什么在创建父子进程时要默认打开呢可以选择不关闭吗 答为了让子进程继承下去(父进程只有那么子进程就只有读父进程有读/写那么子进程就继承读/写)。 可以不关闭建议关闭防止误读或误写以及系统资源的浪费。 既然管道不用再刷新到磁盘中那么需要重新设计通信接口吗 答创建管道的系统调用底层实际就是open只是不用了磁盘部分。 int pipe(int pipefd[2]); 不需要文件路径和文件名其次也被称为匿名文件 – 匿名管道 管道只能实现单向通信。我实际就想要实现双向通信呢 答就创建两个管道。 为什么管道是单向通信的呢 答a.方便复用代码减少开发成本。 b.数据易混淆涉及数据的区分等复杂的操作所以不采用双向只需要满足传输数据。单向即可满足。 3、管道的使用
3.1、代码样例
测试代码 子进程交给父进程的通信
//管道的使用#include iostream
#include unistd.h
//c版本的errno.h和c版本的string.h
#include cerrno
#include cstring
#include sys/types.h
#include sys/stat.h
#include sys/wait.h
#include stringusing namespace std;const int Size 1024;string getOtherMessage()
{static int cnt 0;string messageid to_string(cnt);cnt;pid_t self_id getpid();string stringpid to_string(self_id);//拼接string message messageid: ;message messageid;message my pid is : ;message stringpid;return message;
}//子进程写入
void SubProcessWrite(int wfd)
{string message father,I am your son process!;while (true){string info message getOtherMessage();//拼接得到子进程写入管道的信息write(wfd, info.c_str(), info.size());//写入管道时用的是系统调用write没有写入\0也没有必要不使用时一同写入‘\0’sleep(5);//情况2管道满64kb,ubantu 20.02版本char c A;write(wfd, c, 1);cout pipesize pipesize endl;break;}cout child quit ... endl;
}//父进程读取
void FatherProcessRead(int rfd)
{char inbuffer[Size];//while (true){//sleep(5);ssize_t n read(rfd, inbuffer, sizeof(inbuffer) - 1);//因为没有写入\0,所以sizeof读取到时要减1if (n 0){inbuffer[n] 0;//所以需要时要手动添加\0cout father get message inbuffer endl;}cout father get return val: n endl;}
}int main()
{//1.创建管道int pipefd[2];int n pipe(pipefd);//pipe的参数属于输出型参数rfd和wfdif (n ! 0){cerr errno errno : srrstring : strerror(errno) endl;return 1;}//打印文件描述符预测是3和4,因为文件描述符默认代开三个012.cout pipefd[0]: pipefd[0] , pipefd[1]: pipefd[1] endl;sleep(1);//得到的是管道的读写端//pipefd[0] -- 0 --r//pipefd[1] -- 1 --w//2.创建子进程//3.关闭不需要的文件描述符以子进程写父进程读为例pid_t id fork();if (id 0){cout 子进程关闭不需要的fd准备开始发消息 endl;sleep(1);//子进程 -- write//....//关闭不需要的文件描述符close(pipefd[0]); // 关闭读//写入SubProcessWrite(pipefd[1]);close(pipefd[1]); // 写完后关闭写exit(0);}cout 父进程关闭不需要的fd准备开始收消息 endl;sleep(1);//父进程 -- read//....//关闭不需要的文件描述符close(pipefd[1]); // 关闭写//读取FatherProcessRead(pipefd[0]);close(pipefd[0]); // 读完后关闭读//到此仍然没有进行通信只是在建议一个共享的内存空间 --管道//即让不同的进程看到同一块资源。//以上那么多操作也就说明了进程间的通信是需要一定的成本的。因为进程间具有独立性//4.进程间通信//SubProcessWrite() //FatherProcessRead()//5.防止僵尸进程pid_t rid waitpid(id, nullptr, 0);if (rid 0){cout wait child process done endl;}return 0;
}3.2、如何使用管道通信呢 int pipe(int pipefd[2]); — 参数int pipefd[2]属于输出型参数表示管道的输入或输出的端口 既然管道也是文件那么文件的操作依然通用于管道文件。 read / write 根据之前的知识知道的fork之后子进程是拿到父进程的数据的,是属于通信吗 严格意义上讲并不是属于通信对于子进程来讲它是只读的无法修改无法阻止接收通信只能父进程交给子进程完全是由父进程决定得到的资源是单向的数据。 所以再结合写时拷贝对方是看不见通信信息的。 所以简单的通过全局变量的缓冲区使得双方获取对方数据是行不通的。 代码验证测试代码
#include iostream
#include unistd.h
//c版本的errno.h和c版本的string.h
#include cerrno
#include cstring
#include sys/types.h
#include sys/stat.h
#include sys/wait.h
#include stringusing namespace std;const int Size 1024;string getOtherMessage()
{static int cnt 0;string messageid to_string(cnt);cnt;pid_t self_id getpid();string stringpid to_string(self_id);//拼接string message messageid: ;message messageid;message my pid is : ;message stringpid;return message;
}//子进程写入
void SubProcessWrite(int wfd)
{string message father,I am your son process!;char c A;while (true){//情况5cerr endl;string info message getOtherMessage();//拼接得到子进程写入管道的信息write(wfd, info.c_str(), info.size());//写入管道时用的是系统调用write没有写入\0也没有必要不使用时一同写入‘\0’//sleep(5);cerr info endl;//情况2管道满64kb,ubantu 20.02版本// write(wfd, c, 1);// cout pipesize pipesize write charctor c endl;// c;// if (c G) break;// sleep(1);}cout child quit ... endl;
}//父进程读取
void FatherProcessRead(int rfd)
{char inbuffer[Size];//while (true){sleep(2);cout -------------- endl;ssize_t n read(rfd, inbuffer, sizeof(inbuffer) - 1);//因为没有写入\0,所以sizeof读取到时要减1if (n 0){inbuffer[n] 0;//所以需要时要手动添加\0cout father get message inbuffer endl;}else if (n 0){cout client quit,father get return vsl: n father quit too! endl;break;}else if (n 0){cerr read error endl;break;}//情况5sleep(1);break;}
}int main()
{//1.创建管道int pipefd[2];int n pipe(pipefd);//pipe的参数属于输出型参数rfd和wfdif (n ! 0){cerr errno errno : srrstring : strerror(errno) endl;return 1;}//打印文件描述符预测是3和4,因为文件描述符默认代开三个012.cout pipefd[0]: pipefd[0] , pipefd[1]: pipefd[1] endl;sleep(1);//得到的是管道的读写端//pipefd[0] -- 0 --r//pipefd[1] -- 1 --w//2.创建子进程//3.关闭不需要的文件描述符以子进程写父进程读为例pid_t id fork();if (id 0){cout 子进程关闭不需要的fd准备开始发消息 endl;sleep(1);//子进程 -- write//....//关闭不需要的文件描述符close(pipefd[0]); // 关闭读//写入SubProcessWrite(pipefd[1]);close(pipefd[1]); // 写完后关闭写exit(0);}cout 父进程关闭不需要的fd准备开始收消息 endl;sleep(1);//父进程 -- read//....//关闭不需要的文件描述符close(pipefd[1]); // 关闭写//读取FatherProcessRead(pipefd[0]);cout 5s, father close rfd endl;sleep(5);close(pipefd[0]); // 读完后关闭读//到此仍然没有进行通信只是在建议一个共享的内存空间 --管道//即让不同的进程看到同一块资源。//以上那么多操作也就说明了进程间的通信是需要一定的成本的。因为进程间具有独立性//4.进程间通信//SubProcessWrite() //FatherProcessRead()//5.防止僵尸进程int status 0;pid_t rid waitpid(id, nullptr, 0);if (rid 0){cout wait child process done,exit sig: (status0x7f) endl;cout wait child process done,exit code(ign): ((status8)0xFF) endl;}return 0;
}3.3、管道的4种情况 1.可能会存在被多个进程同时访问的情况(并发)数据都不一致问题。 2.如果管道内部是空的 write fd 没有关闭读取条件不具备读进程会被阻塞 — wait — 读取条件具备再写入数据。 3.管道被写满了 read fd 不读且没有关闭管道被写满写进程会被阻塞(管道被写满 – 写条件不具备) – wait — 写条件具备 --》读取数据管道一直在读 写端关闭了wfd读端read返回值读到了0表示读到了文件结尾。 5.rfd直接关闭写端wfd一直再进行写入处于水管出口堵塞了还一直灌水属于无用功。对于操作系统不会做这种浪费时间浪费空间的事情没有意义。操作系统会直接杀掉马这种坏管道。 所以对于此类出异常的管道操作系统会主动发送信号kill SIGPIPE杀掉该管道。 3.4、管道的5种特征 1.匿名管道只限于具有血缘关系的进程之间进行通信常用于父子进程之间的通信。因为父子进程有一个“天生的”前提条件都能看到同一份(操作系统的)资源“一段内存” 2.管道内部自带进程之间同步的机制。(多执行流执行代码时具有明显的顺序性) 3.管道文件的生命周期是随进程的。 4.管道文件在通信的时候是面向字节流的有些挑战 面向字节流最典型的特点就是 write的次数与读取的次数不是一一匹配的。 5.管道通信的模式是一种特殊的半双工模式。 补充PIPE_BUF 因为管道通信属于特殊的半双工所以有关于管道大小的两点 1.写入的字节大小小于PIPE_BUF的大小时会被认为是原子的也就是小于规定的范围的或者说属于一个单元的即这种情况下是安全的不会出现写到一半被读取走。 2.PIPE_BUF的大小通常是512byte,而Linux中是4096byte. 3.5、管道的应用场景 1.命令行中的|就是匿名管道的应用。 2.进程池 比如提前创建fork一批子进程有任务就通过每一个管道对接每一个子进程 从而父进程对接每一个管道的写端每一个子进程对应与其对应的读端。 这种提前创建好进程的方式就是进程池大大节约了成本使其不用单独创建单独的进程了直接通过各个管道派遣任务就行了。 并且管道里没有数据时各个子进程(work进程)就处于阻塞等待等待分配的任务 所以父进程(master)向哪一个管道写入就会唤醒哪一个进程来处理任务。进程间管道就处于的概念就是进程的协同 其中父进程最好要将任务均衡的划分给每一个子进程就称为负载均衡。 测试代码
#include iostream
#include sys/types.h
#include string
#include unistd.h
#include vector
#include Task.hpp
#include sys/wait.husing namespace std;class Channel
{
public:Channel(int wfd, pid_t id, const string name):_wfd(wfd), _subprocessid(id),_name(name){}int GetWfd(){return _wfd;}pid_t GetProcessId(){return _subprocessid;}string GetName(){return _name;}void CloseChannel(){close(_wfd);}void Wait(){pid_t rid waitpid(_subprocessid,nullptr,0);if (rid 0){cout wait rid success endl;}}~Channel(){}
private:int _wfd;pid_t _subprocessid;string _name;
};void work(int rfd)
{while (true){int command 0;int n read(rfd, command, sizeof(command));if (n sizeof(int)){cout pid is: getpid() handler task endl;ExcuteTask(command);}else if (n 0){cout sub process: getpid() quit endl;break;}}
}//创建信道和子进程
/**/
void test_pipepool(int argc, char* argv[])
{if (argc ! 2){cerr Usage: argv[0] processnum endl;return ;}int num stoi(argv[1]);vectorChannel channels;//创建信道和子进程for (int i 0; i num; i){//1.创建管道int pipefd[2] { 0 };int n pipe(pipefd);if (n 0) exit(1);//3.创建子进程pid_t id fork();if (id 0){//child --- read 处理任务close(pipefd[1]);//关闭写端work(pipefd[0]);close(pipefd[0]);//读完读端exit(0);}//3.构建一个channel1名称string channel_name Channel- to_string(i);//father --- writeclose(pipefd[0]);//关闭读端//a、子进程的Pid, b、父进程关心的管道的写端channels.push_back(Channel(pipefd[1], id, channel_name));}//for testfor (auto channel : channels){cout endl;cout channel.GetName() endl;cout channel.GetProcessId() endl;cout channel.GetWfd() endl;}
}//优化
/**/
//形参类型和命名规范
//const --- 只读型参数
//const -- 输出型参数
// --- 输入输出型参数
//* --- 输出型参数
void CreateChannelAndSub(int num, vectorChannel* channels)
{//创建信道和子进程for (int i 0; i num; i){//1.创建管道int pipefd[2] { 0 };int n pipe(pipefd);if (n 0) exit(1);//3.创建子进程pid_t id fork();if (id 0){//child --- read 处理任务close(pipefd[1]);//关闭写端work(pipefd[0]);close(pipefd[0]);//读完读端exit(0);}//3.构建一个channel1名称string channel_name Channel- to_string(i);//father --- writeclose(pipefd[0]);//关闭读端//a、子进程的Pid, b、父进程关心的管道的写端channels-push_back(Channel(pipefd[1], id, channel_name));}}//轮询方案
int NextChannel(int channelnum)
{static int next 0;int channel next;next;next % channelnum;return channel;
}void SendTaskCommand(Channel channel, int taskcommand)
{write(channel.GetWfd(), taskcommand, sizeof(taskcommand));
}//优化1
void test_pipepool2(int argc, char* argv[])
{if (argc ! 2){cerr Usage: argv[0] processnum endl;return ;}int num stoi(argv[1]);LoadTask();//装载任务vectorChannel channels;//1.创建信道和子进程CreateChannelAndSub(num, channels);//2.通过channel控制子进程while (true){sleep(1);//a、选择一个任务int taskcommand SelectTask();//b、选择一个信道和进程int channel_index NextChannel(channels.size());//c、发送任务SendTaskCommand(channels[channel_index], taskcommand);cout ----------------- endl;cout taskcommand: taskcommand channel: channels[channel_index].GetName() sub process: channels[channel_index].GetProcessId() endl;}//3.回收管道和子进程
}//优化2
void ctrlProcessOnce(vectorChannel channels)
{sleep(1);//a、选择一个任务int taskcommand SelectTask();//b、选择一个信道和进程int channel_index NextChannel(channels.size());//c、发送任务SendTaskCommand(channels[channel_index], taskcommand);cout ----------------- endl;cout taskcommand: taskcommand channel: channels[channel_index].GetName() sub process: channels[channel_index].GetProcessId() endl;
}void ctrlProcess(vectorChannel channels, int times -1)
{if (times 0){while (times--){ctrlProcessOnce(channels);}}else{while (true){ctrlProcessOnce(channels);}}
}void CleanUpChannel(vectorChannel channels)
{//关闭管道for (auto channel : channels){channel.CloseChannel();}//注意防止僵尸进程//回收子进程for (auto channel : channels){channel.Wait();}
}void test_pipepool3(int argc, char* argv[])
{if (argc ! 2){cerr Usage: argv[0] processnum endl;return;}int num stoi(argv[1]);LoadTask();//装载任务vectorChannel channels;//1.创建信道和子进程CreateChannelAndSub(num, channels);//2.通过channel控制子进程ctrlProcess(channels);//3.回收管道和子进程//a、关闭所有的写端返回值为0 --》子进程就自动退出最后回收即可//b、回收子进程CleanUpChannel(channels);
}//先描述再组织
int main(int argc, char* argv[])
{//创建信道和子进程test_pipepool(argc, argv);//优化test_pipepool2(argc, argv);//优化test_pipepool3(argc, argv);return 0;
}
通过函数指针数组管理任务码分配子进程完成任务功能. 可规定一个固定长度的4字节数组下标写和读都以4字节为单位识别。 – 任务码 测试代码 .hpp默认属于开源程序因为声明和定义是写在一起的 //.hpp默认属于开源程序因为声明和定义是写在一起的
#pragma once#include iostream
#include ctime
#include stdlib.h
#include sys/types.h
#include unistd.husing namespace std;#define TaskNum 3typedef void (*task_t)();//task_t 函数指针void Print()
{cout T am a print task endl;
}void Douwnload()
{cout T am a download task endl;
}void Flush()
{cout T am a flush task endl;
}task_t tasks[TaskNum];void LoadTask()
{srand(time(nullptr) ^ getpid());tasks[0] Print;tasks[1] Douwnload;tasks[2] Flush;
}void ExcuteTask(int number)
{if (number 0 || number 2)return;tasks[number]();
}int SelectTask()
{return rand() % TaskNum;
}//回调函数 --- work本质也是任务
void work()
{while (true){int command 0;int n read(0, command, sizeof(command));//重定向到标准输入去读取了if (n sizeof(int)){cout pid is: getpid() handler task endl;ExcuteTask(command);}else if (n 0){cout sub process: getpid() quit endl;break;}}
}4、命名管道
4.1、原理
两个进程毫无关系怎么建立通信呢 通过命名管道一方写另一方读 那么怎么保证两个不相关的进程能够准确打开同一个文件呢 答每一个文件都有一个唯一路径(具有唯一性) mkfifo命令 用于创建一个命名管道 mkfifo myfifo得到一个p管道文件 建立一次进程通信 echo “hello named pipe” myfifo cat myfifo 循环通信 while :;do sleep 1;echo “hello named pipe” myfifo; done cat myfifo 4.2、创建命名管道函数的使用 创建管道文件 #include sys/types.h #include sys/stat.h int mkfifo(const char* pathname.mode_t mode); 删除指定的管道文件 #include unistd.h int unlink(const char* pathname); 测试代码 client.cc
#include namedPipe.hpp// write
int main()
{NamePiped fifo(comm_path, User);if (fifo.OpenForWrite()){std::cout client open namd pipe done std::endl;while (true){std::cout Please Enter ;std::string message;std::getline(std::cin, message);fifo.WriteNamedPipe(message);}}return 0;
}server.cc
#include namedPipe.hpp// server read: 管理命名管道的整个生命周期
int main()
{NamePiped fifo(comm_path, Creater);// 对于读端而言,如果我们打开文件但是写还没来我会阻塞在open调用中直到对方打开// 进程同步if (fifo.OpenForRead()){std::cout server open named pipe done std::endl;sleep(3);while (true){std::string message;int n fifo.ReadNamedPipe(message);if (n 0){std::cout Client Say message std::endl;}else if (n 0){std::cout Client quit, Server Too! std::endl;break;}else{std::cout fifo.ReadNamedPipe Error std::endl;break;}}}return 0;
}namedPipe.hpp
#pragma once#include iostream
#include cstdio
#include cerrno
#include string
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.hconst std::string comm_path ./myfifo;
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096class NamePiped
{
private:bool OpenNamedPipe(int mode){_fd open(_fifo_path.c_str(), mode);if (_fd 0)return false;return true;}public:NamePiped(const std::string path, int who): _fifo_path(path), _id(who), _fd(DefaultFd){if (_id Creater){int res mkfifo(_fifo_path.c_str(), 0666);//创建命名管道并配置权限if (res ! 0){perror(mkfifo);}std::cout creater create named pipe std::endl;}}bool OpenForRead(){return OpenNamedPipe(Read);}bool OpenForWrite(){return OpenNamedPipe(Write);}// const : const std::string XXX// * : std::string * //输出型// : std::string //输入输出型int ReadNamedPipe(std::string* out){char buffer[BaseSize];int n read(_fd, buffer, sizeof(buffer));if (n 0){buffer[n] 0;*out buffer;}return n;}int WriteNamedPipe(const std::string in){return write(_fd, in.c_str(), in.size());}~NamePiped(){if (_id Creater){int res unlink(_fifo_path.c_str());if (res ! 0){perror(unlink);}std::cout creater free named pipe std::endl;}if (_fd ! DefaultFd) close(_fd);}private:const std::string _fifo_path;int _id;int _fd;
};
5、system V的共享内存 共享内存是最快的IPC形式、一旦这样的内存映射到共享它的进程的地址空间这些进程间数据传递不在涉及到内核也就是说进程不再通过执行进入内核的系统调用来传递彼此的数据。 5.1、原理 1.所有说到的操作都是由OS完成的 2.OS提供上面1,2步骤的系统调用供用户进程A,B来进行调用 – 系统调用 3.ABCD,EF…共享内存在系统中可以同时存在多份每份可不同个数不同对的进程同时进行通信。 4.OS注定了要对共享内存进行管理–》先描述再组织 --》共享内存不是简单的一段内存空间也要有描述并管理共享内存的数据结构匹配的算法。 5.共享内存 内存空间(放数据) 共享内存的属性 5.2、代码理解
测试代码 shm目录 – 共享内存 Shm.hpp
#ifndef __SHM_HPP__
#define __SHM_HPP__#include iostream
#include string
#include cerrno
#include cstdio
#include cstring
#include sys/ipc.h
#include sys/shm.h
#include unistd.hconst int gCreater 1;
const int gUser 2;
const std::string gpathname /home/whb/code/111/code/lesson22/4.shm;
const int gproj_id 0x66;
const int gShmSize 4097; // 4096*nclass Shm
{
private:key_t GetCommKey(){key_t k ftok(_pathname.c_str(), _proj_id);if (k 0){perror(ftok);}return k;}int GetShmHelper(key_t key, int size, int flag){int shmid shmget(key, size, flag);if (shmid 0){perror(shmget);}return shmid;}std::string RoleToString(int who){if (who gCreater)return Creater;else if (who gUser)return gUser;elsereturn None;}void* AttachShm()//挂接{if (_addrshm ! nullptr)DetachShm(_addrshm);void* shmaddr shmat(_shmid, nullptr, 0);if (shmaddr nullptr){perror(shmat);}std::cout who: RoleToString(_who) attach shm... std::endl;return shmaddr;}void DetachShm(void* shmaddr){if (shmaddr nullptr)return;shmdt(shmaddr);std::cout who: RoleToString(_who) detach shm... std::endl;}public:Shm(const std::string pathname, int proj_id, int who): _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr){_key GetCommKey();if (_who gCreater)GetShmUseCreate();else if (_who gUser)GetShmForUse();_addrshm AttachShm();std::cout shmid: _shmid std::endl;std::cout _key: ToHex(_key) std::endl;}~Shm(){if (_who gCreater){int res shmctl(_shmid, IPC_RMID, nullptr);}std::cout shm remove done... std::endl;}std::string ToHex(key_t key){char buffer[128];snprintf(buffer, sizeof(buffer), 0x%x, key);return buffer;}bool GetShmUseCreate(){if (_who gCreater){_shmid GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);if (_shmid 0)return true;std::cout shm create done... std::endl;}return false;}bool GetShmForUse(){if (_who gUser){_shmid GetShmHelper(_key, gShmSize, IPC_CREAT | 0666);if (_shmid 0)return true;std::cout shm get done... std::endl;}return false;}void Zero(){if (_addrshm){memset(_addrshm, 0, gShmSize);}}void* Addr(){return _addrshm;}void DebugShm(){struct shmid_ds ds;int n shmctl(_shmid, IPC_STAT, ds);if (n 0) return;std::cout ds.shm_perm.__key : ToHex(ds.shm_perm.__key) std::endl;std::cout ds.shm_nattch: ds.shm_nattch std::endl;}private:key_t _key;int _shmid;std::string _pathname;int _proj_id;int _who;void* _addrshm;
};#endifshmnamedPipe.hpp
#pragma once#include iostream
#include cstdio
#include cerrno
#include string
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.hconst std::string comm_path ./myfifo;
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096class NamePiped
{
private:bool OpenNamedPipe(int mode){_fd open(_fifo_path.c_str(), mode);if (_fd 0)return false;return true;}public:NamePiped(const std::string path, int who): _fifo_path(path), _id(who), _fd(DefaultFd){if (_id Creater){int res mkfifo(_fifo_path.c_str(), 0666);if (res ! 0){perror(mkfifo);}std::cout creater create named pipe std::endl;}}bool OpenForRead(){return OpenNamedPipe(Read);}bool OpenForWrite(){return OpenNamedPipe(Write);}// const : const std::string XXX// * : std::string *// : std::string int ReadNamedPipe(std::string* out){char buffer[BaseSize];int n read(_fd, buffer, sizeof(buffer));if (n 0){buffer[n] 0;*out buffer;}return n;}int WriteNamedPipe(const std::string in){return write(_fd, in.c_str(), in.size());}~NamePiped(){if (_id Creater){int res unlink(_fifo_path.c_str());if (res ! 0){perror(unlink);}std::cout creater free named pipe std::endl;}if (_fd ! DefaultFd) close(_fd);}private:const std::string _fifo_path;int _id;int _fd;
};shmclient.cc
#include Shm.hpp
#include shmnamedPipe.hppint main()
{// 1. 创建共享内存Shm shm(gpathname, gproj_id, gUser);shm.Zero();char* shmaddr (char*)shm.Addr();sleep(3);// 2. 打开管道NamePiped fifo(comm_path, User);fifo.OpenForWrite();// 当成stringchar ch A;while (ch Z){shmaddr[ch - A] ch;std::string temp wakeup;std::cout add ch into Shm, wakeup reader std::endl;fifo.WriteNamedPipe(temp);sleep(2);ch;}return 0;
}shmserver.cc
include Shm.hpp
#include shmnamedPipe.hppint main()
{// 1. 创建共享内存Shm shm(gpathname, gproj_id, gCreater);char* shmaddr (char*)shm.Addr();shm.DebugShm();// // 2. 创建管道// NamePiped fifo(comm_path, Creater);// fifo.OpenForRead();// while(true)// {// // std::string temp;// // fifo.ReadNamedPipe(temp);// std::cout shm memory content: shmaddr std::endl;// }sleep(5);return 0;
}5.3、共享内存的理解
申请一个systeam V版本的动态内存 int shmget(key_t key,size_t size,int shmflg); 参数 1.size_t size — 创建的共享内存大小 2.int shmflg — 标志位(常用IPC_CREAT 和 IPC_EXEL) – 可以位图的形式传参 IPC_CREAT:如果你要创建的共享内存不存在就创建如果存在获取该共享内存并返回。总能获取到 IPC_EXEL单独使用没有意义只有和IPC_CREAT组合使用才有意义。 IPC_CREAT | IPC_EXEL如果你要创建的共享内存不存在就创建否则出错返回。获取的是全新的shm 3.key_t key — 由用户自定义的key值设置为唯一标识符只要具备唯一性即可 a、key_t key是什么是由用户自定义的key值用于设置为唯一标识符 b、为什么因为让不同的进程看到同一个共享内存 c、怎么办利用ftok随机设置key值便于用户使用 返回值 返回唯一的标识符 #include sys/typse.h #include sys/ipc.h key_t ftok(const char* pathname, int proj_id); 我们怎么确定OS内的共享内存是否存在了呢 答struct Shm中会有一个标识共享内存的唯一性标识符 能让OS自动生成标识符呢 答不能 共享内存不随着进程的结束而自动释放需要手动释放。或系统调用释放 共享内存的生命周期 共享内存生命周期随内核文件生命周期随进程 查看共享内存的信息 ipcs -m 删除/释放指定的共享内存 ipcrm -m shmid返回给用户的标识符 补充IPC的知识 key VS shmid key属于用户形成的属于内核使用的一个特定字段具有唯一性用户不能使用key来对shm进行管理内核进行区分shm的唯一性(struct file*) shmid内核给用户返回的一个标识符用来进行用户级对共享内存进行管理的id值(fd). 5.4、共享内存的相关接口
a、shmctl #include sys/ipc.h #include sys/shm.h int shmctl(int shmid, int cmd, struct shmod_ds *buf); 功能 共享内存的控制增删改查… 参数 int shmid内核给用户返回的id值 int cmd对共享内存要执行的操作(常用IPC_RMID,删除/释放当前共享内存) struct shmod_ds *buf共享内存结构体的属性成员 b、shmat #include sys/typse.h #include sys/shm.h void* shmat(int shmid, const void* shmaddr, int shmflg); 功能将对应的地址空间挂接到共享内存中。 返回值 地址空间中共享内存的起始地址 c、shmdt int shmdt(const void* shmaddr); 功能取消挂接关联 6、消息队列
6.1、基本概念 一个进程向另一个进程发送有类型的数据块的方式。结合之前的理解msg_queue自带属性信息。 消息队列的生命周期也是随内核的不随进程。 6.2、涉及的常用接口 消息队列常用接口 #include sys/types.h #include sys/ipc.h #include sys/msg.h int msgget(key_t key, int msgflg); key_t ftok(const char* pathname,int proj_id); int msgget(key_t key, int msgflg); int msgctl(int msgid,int cmd,struct msgid_ds* buf); int msgsnd(int msgid, const void* msgp, size_t msgsz, int msgflg); int mshrcv(int msgid, void* msgp,size_t msgsz, long msgtyp,int msgflg); 7、信号量
7.1、5个概念 1.多个执行流(进程)都能看到的一份资源称为共享资源 2.被保护起来的资源 – 称为临界资源 – 同步和互斥 3.互斥任何时刻只能有一个进程访问共享资源。 4.资源 — 要被程序员访问 – 资源被访问简单理解就是就是通过代码访问代码 访问共享资源的代码(临界区) 不访问共享资源的代码(非临界区) 5.所谓的对共享资源进行保护 – 临界资源 – 本质是对访问共享资源的代码进行代码。 7.2、对于信号量的理论理解 临界区 加锁/解锁 非临界区 1.用于保护临界资源本质是一个计数器。 信号量的计数数量标志对共享资源的预定机制。 担心超出资源量的个数管理属性资源总数限制资源不被多余预定。 类比电影院系统 电影院共享资源(临界资源) 买票申请信号量 票数信号量的初始值 申请信号量的本质就是对公共资源的一种预定机制 申请信号量 访问共享资源 释放信号量 对共享资源整体的使用其实不就是资源只有一个么 1/0二元信号量互斥 既然信号量是一个计数器可以使用一个全局的变量如gcongt来充当对共享资源的保护吗 答不能 1.因为全局变量不能被所有进程能够看到。 2.并且gcount,不是原子的。 7.3、原子操作
所以IPC信号量 1.与共享内存一样使不同的进程之间都能看到同一个信号量计数器控制不同进程的同步或互斥 2.意味着信号量本身也属于共享资源 3.既然本身也属于临界资源却要保护别的临界资源安全前提是不是需要自己肯定是安全的呢—提出原子操作 4.允许用户一次性申请多个信号量集 – 用数组来维护的。 步骤 1.申请信号量 2.访问公共资源(共享内存) 3.释放信号量 对信号量(计数器)的操作就被设置为原子操作 P和V操作 – 安全的 – 原子性 – —》本身是安全的 P —》本身是安全的 V 7.4、常用信号量的指令 #include sys/types.h #include sys/ipc.h #include sys/sem.h int semget(key_t key,int nsems, int semflg); int semctl(int semid,int semnum,int cmd,…); int semop(int semid,struct sembuf* sops,size_t nsops); 查看信号量指令 incs -s 删除指定信号量 ipcrn -s semid