当前位置: 首页 > news >正文

沈阳旅游团购网站建设黑河企业网站建设公司

沈阳旅游团购网站建设,黑河企业网站建设公司,wordpress定制菜单,杭州网站关键词优化文章目录 一、原理二、方式1.管道1.1匿名管道1.1.1通信原理1.1.2接口使用 1.2命名管道 2.共享内存2.1原理2.2接口使用 3.消息队列原理 4.信号量引入原理 总结 一、原理 进程间的通信是什么#xff1f;解释#xff1a; 简单理解就是#xff0c;不同进程之间进行数据的输入输出… 文章目录 一、原理二、方式1.管道1.1匿名管道1.1.1通信原理1.1.2接口使用 1.2命名管道 2.共享内存2.1原理2.2接口使用 3.消息队列原理 4.信号量引入原理 总结 一、原理 进程间的通信是什么解释 简单理解就是不同进程之间进行数据的输入输出。 为什么要进行进程间通信呢 解释 不同进程实现数据的交互资源的共享进程控制的手段。数据交互类似与读写过程你给我发消息我收到了并对消息进行读取和处理资源共享是多个进程可以对同一块空间进行读取和写入进程控制比如一个进程给另一个进程发送kill -9 号新号结束一个进程。 如何实现进程间通信呢 解释 首先进程间是独立的这就意味着一个进程不可能直接从另一个进程中拿数据即使是父子进程因为有写时拷贝的原因在数据写入之前即使数据共享也无法完成通信因为一旦写入就会发生写时拷贝。因此进程间通信是有成本的而实现进程间通信必然免不了要靠操作系统牵线搭桥 即让操作系统开辟空间让不同的进程访问同一份资源对第三方空间进行修改自然可以完成通信。进程代表着用户而操作系统不会让程序员写的代码直接访问内核而是给用户提供系统调用接口这样既保证了内核的安全也能让内核能帮程序员干正事。因此进程间实现通信本质上是进程通过系统调用接口进而使操作系统让不同的进程看到了同一份资源。 拓展 进程通信是要有一定的标准当所有人都遵循一套标准时可以扩大标准的影响范围同时还能降低通信的成本。进程通信采取的标准为IPCSystem V 与POSIX而没有这套通信标准时通常采用管道的方式进行通信。 二、方式 1.管道 首先我们先从其名理解一下管道的意思 这样数据从一端进去然后从一端出去这样的具有单向通信的特点的我们就称之为管道。 其次管道是文件我们从文件的角度来理解管道。 如何存储数据呢我们通常都会想到文件那文件是可以多进程共享吗答案是可以的既然这样文件也可以实现通信那根据之前我们所学的文件如果文件通信具体的过程是如何的我们画图演示。 光看图比较直观但还尚需一些文字进行点缀下面我们进行补充 不同进程在进行打开文件时其实有两个缓存区其一在于C语言层次方便进行格式化的缓存区其二是操作系统自动维护的用于存数据的缓存区。在进行写入时我们首先要将内容拷贝到C语言的缓存区当中在刷新时拷贝到内核的缓存区中由缓存区再刷新到磁盘当中这其中进行了3次拷贝。而且还要刷新到磁盘当中时间的效率是比较低的。在读取时需要先让磁盘刷新完成再进行读取与写入同理也要进行3次拷贝而且由于磁盘是外设效率也是比较低的。 拓展: 由于文件打开时权限的不同因此会拥有不同的struct file但其中的其它信息基本都是一致的比如文件的innode编号。 我们看到这其中的具体过程之后发现进程通信是有一定成本的这个成本在于硬件的刷新速度以及拷贝的效率等方面那我们如何提升呢这就该思考是否存在这样的文件无需刷新到磁盘只需在用时在内存开辟空间呢答案是肯定的那就是内存文件且内存文件除了不写入磁盘外跟普通的文件一样。但是就是因为这一点内存文件当做通信的共享资源的效率是比普通文件高很多的。那管道很显然就是内存文件。再结合之前的定义管道的结构单向通信我们便可大概知道管道的基本原理。 下面我们进一步来看两种管道——匿名管道与命名管道。 1.1匿名管道 1.1.1通信原理 首先看名字便知是没有名字的管道。 通过之前的知识我们并不会感到奇怪因为文件的属性并不包含文件名只有inode编号所为唯一标识符。其文件名是放在目录下的没有文件名就代表着我们用路径 文件名的方式是找不到这个文件的。 其次没有名字的管道我们如何创建与读写。 肯定存在系统调用接口让我们创建没有名字的管道。但是对于进程来讲文件描述符便是文件/管道的名字即使没有名字我们也可以用文件描述符对文件/管道进行读写。 最后没有名字的管道如何实现共享。 首先没有名字我们看是否可以将inode传给另一个进程发现是不可行的因为我们实现的问题本身就是进行通信。那我们可以从进程的files_struct进行入手因为进程在创建时子进程会继承到files_struct,也就是父进程的文件子进程是也可以进行进行操作。因此进程的创建与files_struct的拷贝变相的实现了匿名管道的资源的共享。 因此进程通过匿名管道进行通信我们可以断定进程之间必然拥有血缘关系。 1.1.2接口使用 初识接口 头文件 #includeunistd.h 函数声明 int pipe(int pipefd[2]); 函数参数 * 一个至少有两个元素的数组实际上传参传的是数组名。 * 这里的pipefd[0]是读端pipefd[0]是写端。 返回值 * 成功返回0。 * 失败返回-1并设置合适的错误码。下面我们细讲一下为什么要设计int pipefd[2]这样的接口 从管道的定义来看管道具有单向通信的特点。这就意味着只能一端读一端写因此需要读写两个文件描述符。其次父进程打开读和写子进程继承之后假设父进程关闭读端子进程关闭写端这样父端写子端读进而达成了单向通信。 图解 为什么要单向通信呢 解释: 读端和写端可以看做出数据的口和入数据的口从而更加方便的处理数据。如果都进行写入和读取数据会杂乱无章不方便进行处理。 使用与论证 1 .论证这里我们实验验证几个问题。 说明为了验证下面的结论我们只给出读写方法因为这里的整体框架大致相同只有读写方法不同。 #includeiostream #includestring #includecstdio #includecstdlib #includeunistd.h using namespace std; #define SIZE (4096) int main() {int pipe_fd[2];int ret_pipe pipe(pipe_fd);if(ret_pipe ! 0){perror(pipe);exit(1);}int pid fork();if(pid 0){//子进程//关闭写端保留读端close(pipe_fd[1]);Read(pipe_fd[0]);exit(0);}else if(pid 0){perror(fork);exit(1);}//父进程//关闭读端保留写端close(pipe_fd[0]);Write(pipe_fd[1],pid);return 0; }既然只能进行单向通信那么我们可能会关心如下问题 读端与写端都打开如何正常运行。 验证代码 读写方法 void Read(int rfd) {while(true){char buf[SIZE];int sz read(rfd,buf,sizeof(buf));if(sz 0) break;if(sz -1){perror(read);exit(-1);}//cout sz endl;buf[sz] \0;//添上字符串结尾标识符。cout my pid is getpid() What I read from father is buf endl;} } void Write(int wfd,int pid) {while(true){string buf;cout fahter say to pid :;getline(cin,buf);int ret write(wfd,buf.c_str(),buf.size());if(ret -1){perror(write);exit(1);}sleep(1);} }代码解读首先我们写的代码意思是父进程进行写子进程进行读两个进程进行通信。 实验过程 观察现象 首先我们向子进程发送了ni hao 信息写完之后子进程进行了读取。其次我们再向子进程发送信息写完之后子进程进行读取此时上一次的信息已经没了。 实验结论 是在父进程写完之后子进程再进行读取的因此在父进程未完成写入之前子进程的读端是陷入阻塞状态的这样可以保证读取数据的完整性。再进行写入与读取时上一次的数据已经被刷新了因此再子进程进行读取时会刷新管道并且会调整文件指针写与读到开头。 拓展 原子性—— 文件执行流比如这里子进程与父进程进行的读写操作是在父进程完成写入之后子进程再进行读取的不是在父进程正在写的过程中进行的读取前面的写完再读这样进程之间再操作同一文件的互不干扰的性质我们称之为原子性。 读端与都打开写端写满之后会发生什么。 读写方法 void Read(int rfd) {while(true); } void Write(int wfd,int pid) {int cnt 0;while(true){cout cnt endl;int ret write(wfd,C,1);} }代码说明此处我们一直进行写并记录写入的字节数直到写不进看看写端会发生什么并且看看此时的已经写入的字节数。 实验过程 观察现象 我们可以看出此时写端陷入了阻塞状态并且此时写入的字节数为65536即64KB。 实验结论 管道在Linux下大概为64KB,不同平台的数据可能不一样要根据实际情况进行讨论。管道在写满时写端会陷入阻塞。 读端关闭写端会发生什么。 读写方法 void Read(int rfd) {int cnt 3;while(true){char buf[SIZE];int sz read(rfd,buf,sizeof(buf));buf[sz] \0;//添上字符串结尾标识符。cout my pid is getpid() What I read from father is buf endl;if(cnt-- 0)//读写4次之后读端进行关闭{ close(rfd);break;}} } void Write(int wfd,int pid) {while(true){string buf;cout fahter say to pid :;getline(cin,buf);int ret write(wfd,buf.c_str(),buf.size());sleep(1);} }代码解读首先我们先正常的读写三次然后关闭读端看看写端会发生什么。 实验过程 现象分析 首先这里读写四次是正常的当我们关闭读端时写端还没有异常现象此时我们再进行写入发现代码异常退出了。其次这里我们打印出退出信息发现这里的信息异常为代码异常退出的结果。最后我们分析141发现其是管道破裂信号。 实验结论 关闭读端时此时写端写是没有意义的。其次关闭读端写端不知道的情况下写会收到管道破裂信号。 写端关闭读端会发生什么。 读写方法 void Read(int rfd) {while(true){char buf[SIZE];int sz read(rfd,buf,sizeof(buf));if(sz 0){cout I read nothing endl;break;}buf[sz] \0;//添上字符串结尾标识符。cout my pid is getpid() What I read from father is buf endl;} } void Write(int wfd,int pid) { int cnt 3;while(true){string buf;cout fahter say to pid :;getline(cin,buf);int ret write(wfd,buf.c_str(),buf.size());if(--cnt 0)//读写3次之后读端进行关闭{ close(wfd);cout wfd is closed endl;break;}sleep(1);} }代码解读首先正常的读取3次之后写端进行关闭此时我们看看读端会发生什么。 实验现象 观察现象 正常读写三次之后我们将写端关闭此时读端立马从阻塞状态转换为非阻塞状态。打印出 I read nothing.此后读端break最终退出。 实验结论 写端关闭意味着无法对管道进行写入而读端从阻塞状态转换为非阻塞状态若没有break会一直读取。写端关闭读端是会收到状态转化的影响但并不会使进程陷入异常状态。 总结一下 管道读写端打开正常写入时读端陷入阻塞且读完之后会刷新缓存区和文件指针。管道读写端打开一直写不进行读取。管道写满之后写端会陷入阻塞状态。管道读端关闭写端打开。无法正常进行读写强行写会收到管道破裂信号。管道写端关闭读端打开。读端从阻塞状态变为非阻塞状态一直进行读读到文件末尾。 使用此处我们模拟实现一个进程池。 定义在没有使用进程时我们预先创建一批进程在用时使用不用再还回去。原理这就好比买一桶可乐放在身边想喝就喝和想喝可乐了再去商店里只买一瓶的道理一样。买一桶可以喝好多次方便。而现喝现买而且还只能喝一瓶不方便。 实现方法 先由父进程创建若干个进程在下面的实验中我创建十个子进程。让父进程与子进程之间产生建立管道产生通信。子进程进入读信息的死循环中父进程进入到写信息的死循环中。 对进程进行初始化 struct conduit//管道段 {conduit(){}conduit(string pipename,int pipepid,int pipefd):_pipename(pipename),_pipepid(pipepid),_pipefd(pipefd){}//1.管道名string _pipename;//2.管道的pidint _pipepid;//3.写端的文件描述符(写端)int _pipefd; }; const int PIPENUM 10; //所创建的管道进程池 vectorconduit pipe_array; void Init() {for(int i 0; i PIPENUM; i){//先创建管道int pipefd[2];int pipe_ret pipe(pipefd);if(pipe_ret -1){perror(pipe);exit(errno);}//创建子进程int pid fork();if(pid 0){//此处我们在后面还会补上一段代码。//...//子进程关闭写端close(pipefd[1]);//管道进行重定向,重定向到键盘减少传参。dup2(pipefd[0],0);//子进程从管道读信息Read();//之后会给出接口。//退出子进程exit(1);}if(pid -1){//进程创建失败,没有创建子进程perror(fork);exit(errno);}//父进程关闭读端close(pipefd[0]);//将创建的子进程放进vector中string pipe_name process_ to_string(i); pipe_array.push_back({pipe_name,pid,pipefd[1]});} }图解 可见我们在不断创建过程中子进程读端的文件描述符始终都没发生改变具体的原因图解已经很清楚了。此处还有一个小bug不过不影响我们的后续操作最后我们在清理进程时再进行详细说明。上述代码我们使用C创建了一个管道类并且使用vector容器进行管理本质上体现了先描述再组织的思想。 对进程实现控制也就是对子进程发送消息通过发送的消息控制进程。 首先我们先设计一下看看通过父进程让子进程完成什么任务此处简单设计了一个LoL的资源更新的接口。 #includefunctional #includemap #includeiostream using namespace std; void Menu() {//打印功能cout ----------------------------------- endl;cout |-----1.刷新野区--------2.刷新兵线---| endl;cout |-----3.初始化防御塔-----4.退出------| endl;cout ----------------------------------- endl; } void Exit() {exit(0); } void ClearWlidArea() {cout 野区已被刷新 endl; } void ClearSolidLine() {cout 兵线已被刷新 endl; } void InitDefenseTower() {cout 防御塔初始化 endl; } //对功能进行包装。 mapint,functionvoid() Hash {{1,ClearWlidArea},{2,ClearSolidLine},{3,InitDefenseTower} }; //... 更多功能敬请期待说明 此处使用了C的包装器对函数返回值与参数相同的函数进行了封装且使用了map对函数进行编号方便进行调用。 然后给出父进程写的接口与子进程读的接口。 void Write(vectorconduit pipe_array) {while(true){Menu();cout 请选择;int cmd;cin cmd;if(cmd 4) return;int pipe rand() % pipe_array.size(); int w_ret write(pipe_array[pipe]._pipefd,cmd,sizeof(cmd));if(w_ret -1){perror(write);exit(errno);}cout I am father, I write cmd to \ pipe_array[pipe]._pipename It Pid is \ pipe_array[pipe]._pipepid endl;sleep(1);} }void Read() {while(true){int cmd;int r_ret read(0,cmd,sizeof(cmd));if(r_ret sizeof(cmd)){cout I am child, my pid is: getpid() \, which cmd I read is cmd endl;if(cmd 1 cmd 4)Hash[cmd]();//执行任务}else if(r_ret 0)break;sleep(1);} }父进程输入任务编号向子进程发送任务让子进程进行执行。子进程收到任务编号调用任务编号对应的函数。我们这里是随机派发给一个进程当然也可以进行轮转派发给指定进程。 回收释放子进程的资源。 void DeletePipeArray(const vectorconduit pipe_array) {for(int i 0; i pipe_array.size(); i){//将子进程的写端进行关闭close(pipe_array[i]._pipefd);}for(int i 0; i pipe_array.size(); i){//等待子进程阻塞等待waitpid(pipe_array[i]._pipepid,NULL,0);} }这里我们再处理上面留下的一个小bug: 这里我们画图可以看出创建两个进程之后一个子进程的读端虽然只有一个但是在创建两个子进程之后第二个创建的子进程会从父进程那里继承第一个子进程的写端从而可以向第一个子进程里面进行写入。这样创建进程是不好的因为管道是单向通信的多个写端可能会导致数据错乱。 再来看上述删除的代码由于这里我们是一下子将所有进程的写端关闭因此不会发生错误。继续分析为什么不会发生错误因此一旦父进程的写端关闭第二个子进程就会读到0个字节然后退出第二子进程的写端就会关闭因此第一个子进程的所有写端就关闭了因此第一个子进程读到0个字节然后就会退出。是一连串的反应。那我们如何修改代码一次循环就可以删除呢 答案很简单——倒着关即可。 void DeletePipeArray(const vectorconduit pipe_array) {for(int i pipe_array.size() - 1; i 0; i--){close(pipe_array[i]._pipefd);waitpid(pipe_array[i]._pipepid,NULL,0);} }其次如果我们只要一个读写端还需要对子进程的所有的管道写端进行关闭。 因此需要在Init的接口的子进程创建后添加 for(int i 0; i pipe_array.size(); i) {close(pipe_array[i]._pipefd); }加上这个补丁之后我们再来看管道的清理不管正着删还是倒着删其实都只需一次循环。 拓展——日志 趁热打铁我们回过头看一看日志的图解顺便实现一个简单的日志类。 #includemap #includeiostream #includecstdio #includestdarg.h #includeunistd.h #includesys/types.h #includesys/stat.h #includefcntl.h #includetime.h using namespace std; #define SIZE (4096)#define EMRGE 1 #define ALERK 2 #define CRIT 3 #define ERRO 4 #define WARNNING 5 #define NOTICE 6 #define INFORE 7 #define DEBUG 8 #define NONE 9#define DEFAULTFILE 1 #define CLASSFILE 2 #define SCREAN 0 //说明一般我们在传参时一般都是以宏的方式进行传参的如果需要打印出字符串可以用KV类型进行映射转换。 mapint,string Mode {{1,EMERG},{2,ALERK},{3,CRIT},{4,ERRO},{5,WARNING},{6,NOTICE},{7,INFOR},{8,DEBUG},{9,NONE} };//分类文件处理的后缀。 mapint,string file {{1,emerg},{2,alerk},{3,crit},{4,erro},{5,warning},{6,notice},{7,infor},{8,debug},{9,none} }; class log { public:void operator()(int level,int where,const char* format,...){//将输入的字符串信息进行输出。va_list arg;va_start(arg,format);char buf[SIZE];vsnprintf(buf,SIZE,format,arg);va_end(arg);//获取时间time_t date time(NULL);struct tm* t localtime((const time_t *)date);char cur_time[SIZE] {0};snprintf(cur_time,SIZE,[%d-%d-%d %d:%d:%d],t-tm_year 1900,t-tm_mon 1,、t-tm_mday,t-tm_hour,t-tm_min,t-tm_sec);//输入再进行合并string Log [ Mode[level] ] cur_time string(buf) \n;//处理输出方向PrintClassFile(level,where,Log);}void PrintDefaultFILE(string file_name,const string mes){int fd open(file_name.c_str(),O_CREAT | O_WRONLY | O_APPEND,0666);write(fd,mes.c_str(),mes.size());close(fd);}//将文件进行分类进行输出。void PrintClassFile(int level,int where,const string mes){if(where SCREAN)cout mes;else{string file_name ./log.txt;if(where CLASSFILE)file_name (. file[level]);PrintDefaultFILE(file_name,mes);}} };1.2命名管道 顾名思义就是有名字的管道。既然有名字那就可以实现不同进程的之间的通信了。 上面提及过找到文件有两种方法一种是inode一种是路径文件名同一路径下不可能存在相同的文件名。用户常用的就是第二种。 如何实现呢涉及一条命令。一条系统调用接口。 命令 mkfifo 【管道名】系统调用接口 头文件 #includesys/type.h #includesys/stat.h 函数声明 int mkfifo(const char* pathname,mode_t mode); 函数参数 1. pathname要创建的所在路径 管道名。 2. mode:权限。 函数返回值 1.创建管道成功返回0. 2.创建管道失败返回-1并设置合适的错误码。使用——简易实现一个本地聊天程序。 聊天的基本原理 我们在QQ里面的聊天窗口的有两种基本信息。第一种是输入窗口。第二种是输出窗口。 这是每个用户都拥有的基本窗口通过输入窗口进行传递信息通过输出窗口进行发送信息。不同用户之间通过网络进行数据传输使数据显示在输出窗口中。这里由于网络还没学无法进行在网络端的数据传输因此这里我们的实现的基本原理是这样的 大体逻辑 输入端进行向共享信息区输入数据通过共享区进行数据的交互。 首先我们建立两个管道即两个输入端分别连到一个管道的写端共享信息区与两个管道的读端。 实现代码 为了便于理解先将头文件进行给出 pipe.h #pragma once #includeiostream #includecstdlib #includestring #includesys/types.h #includesys/stat.h #includefcntl.h #includeunistd.h using namespace std; const string server_name shun_hua; const string client_name 舜华; const string server_pipe server_pipe; const string client_pipe client_pipe;#define SIZE (4096) //实现管道管理//此处我们实现一个简单的窗口聊天菜单 void menu(const string username) {//获取时间time_t date time(NULL);struct tm* t localtime((const time_t *)date);char cur_time[SIZE] {0};snprintf(cur_time,SIZE,[%d:%d:%d],t-tm_hour,t-tm_min\,t-tm_sec);cout cur_time username :; } 服务端写端(serverw.cc) #includepipe.h int main() { //创建管道文件mkfifo(server_pipe.c_str(),0666);mkfifo(client_pipe.c_str(),0666);//打开写端。int fd open(server_pipe.c_str(),O_WRONLY);if(fd -1){perror(open server_pipe);exit(1);}//打开成功while(true){menu(server_name);//输入你想说的话string str;getline(cin,str);write(fd,str.c_str(),str.size());}return 0; }客户端写端clientw.cc #includepipe.h int main() { //创建管道文件//打开服务端管道的写端。int fd open(client_pipe.c_str(),O_WRONLY);if(fd -1){perror(open client_pipe);exit(1);}//打开成功while(true){menu(client_name);//输入你想说的话string str;getline(cin,str);write(fd,str.c_str(),str.size());} return 0; }服务端的读端(serverr.cc) #includepipe.h int main() { //一个打开读端一个打开写端。int fd open(server_pipe.c_str(),O_RDONLY);if(fd -1){perror(open server_pipe);exit(1);}//打开成功while(true){char buf[SIZE];int sz read(fd,buf,SIZE);buf[sz] \0;if(sz 0) break;//从client读到的话menu(client_name);cout buf endl;}return 0; }客户端读端(clientr.cc) #includepipe.h int main() { //一个打开读端一个打开写端。int fd open(client_pipe.c_str(),O_RDONLY);if(fd -1){perror(open server_pipe);exit(1);}while(true){char buf[SIZE];int sz read(fd,buf,SIZE);if(sz 0) break;buf[sz] \0;//从client读到的话menu(client_name);cout buf endl;}return 0; }此时代码写好之后我们将之编译成可执行程序。 然后我们开三个窗口根据上面的图进行运行程序。 简单的聊天效果。 我们实现的还是较为粗糙的代码有兴趣的小伙伴可以进行丰富与补充。 除此之外在读端未打开写端会陷入阻塞状态这是正常现象。与此同理写端未打开读端打开读端也会陷入阻塞状态。 拓展 推论由于管道在内存中并不会刷新到磁盘因此我们可以推断管道文件的大小为0。验证 piper.cc #includeiostream #includefcntl.h #includesys/stat.h #includesys/types.h #includeunistd.h using namespace std; char buf[4096]; int main() {int rfd open(pipe,O_RDONLY);cout open success endl;sleep(10);read(rfd,buf,sizeof(buf));cout buf;return 0; }pipew.cc #includeiostream #includefcntl.h #includesys/stat.h #includesys/types.h #includeunistd.h using namespace std; int main() {mkfifo(pipe,0666);int wfd open(pipe,O_WRONLY);cout open true endl;int cnt 10;while(cnt--){string str hello world\n;write(wfd,str.c_str(),str.size());cout writing cnt endl; sleep(1);}return 0; }实验现象 2.共享内存 2.1原理 简要说明: 此共享内存是System V 标准中的。大前提进程间要进行通信必须要看到同一份资源。 共享内存如何让进程看到同一份资源呢我们画图进行解释 我们对这张图进行深入分析 共享内存是内存若要开辟应交由操作系统进行开辟进程若要开辟必然要使用操作系统提供的系统调用接口。共享内存是映射在进程地址空间内的且位于进程地址空间的共享区。既然在进程地址空间内我们可以通过进程地址空间直接的对共享内存进行访问。不同的进程通过与共享内存建立链接可看到同一份资源从而实现通信。该共享内存是操作系统管理的且不只有一个共享内存因此共享内存也需要先描述再组织起来。且由于是操作系统进行管理的第三方资源因此不会发生类似写时拷贝的现象。 补充我们之前的动态链接其实也是看到了同一份资源但这份资源无法进行修改因此无法进行通信。 2.2接口使用 共享内存的创建 * 接口1 头文件 #includesys/ipc.h #includesys/shem.h 函数声明 int shmget((key_t key, size_t size, int shmflg); 函数参数 1. 共享内存的内核标识符。 2. 共享内存的开辟的字节数。 3. 共享内存的创建方式。其中包括1. IPC_CREAT:没有创建有则返回。2. IPC_EXIT: 有则出错返回。若要使用也得跟上方式1 返回值 4. 如果成功返回共享内存的用户标识符。 5. 如果失败返回-1并设置合适的错误码。*接口2 头文件 #includesys/ipc.h #includesys/shm.h 函数声明: key_t ftok(const char *pathname, int proj_id); 函数参数: 1. 生成路径是生成key_t类型内核共享内存表示符(这只是我这样叫的)的key. 2. 生成码,生成key_t类型的内核共享内存标识符的key不能是0.说明这两个参数都是为了生成key_t类型的内核的标识符。返回值 1. 成功返回key_t类型的内核共享内存标识符。 2. 失败返回-1其实光看这两个接口是有点懵逼的我们把这两个接口联系起来。 首先操作系统得知道key才能生成与key对应的共享内存。系统无法自动生成因为多个进程还要靠key找到同一份共享内存。如若生成进程之间无法知道同一个key。key生成需要依赖于生成路径与生成码即pathname与proj_idpathname 与proj_id相同生成的key也就相同因此多个进程 可通过相同的pathname与proj_id得到同一份key因此可找到 同一份共享内存。从而看到同一份资源进而实现通信。 补充至于shemget的返回值是为了让用户方便接下来的操作且对内核的数据做了封装更加的安全。 接下来我们简单的使用一下接口。 shm.h #includeiostream #includestring#includesys/ipc.h #includesys/shm.husing std::string;const string pathname /home/shun_hua; const int proj_id 0xFFFF; shm.cc #includeshm.husing namespace std; int main() {key_t key ftok(pathname.c_str(),proj_id);if(key -1) return;int ud shmget(key,4096,IPC_CREAT);if(ud -1) return;cout 创建成功! endl;return 0; }编译运行一下 此时由于共享内存是由操作系统进行管理的因此在没有调用对应的系统调用接口时进程退出时不会进行释放共享内存。 如何验证呢涉及一条命令 ipcs -m既然这样那如何删除呢又涉及一条命令。 ipcrm -m 【shmid】我们发现这里的权限也就是perms我们并没有进行设置因此我们还得设置一下如何设置呢其实获取共享内存时我们在第三个参数 | 上权限即可。 int ud shmget(key,4096,IPC_CREAT | 0666);共享内存的链接与取消 *产生链接 头文件 #includesys/shm.h 函数声明: void *shmat(int shmid, const void *shmaddr, int shmflg); 函数参数 1. 用户共享内存标识符。 2. 指定的进程的共享区地址若被占用失败返回-1为空系统自动分配地址。 3. 进程映射共享内存的方式。这里只介绍SHM_RDONLY,以只读方式映射共享内存区域。一般设为0。返回值 1. 成功,返回共享内存的进程空间的地址。 2. 失败,返回-1并设置合适的错误码。*取消链接 头文件 #includesys/shm.h 函数声明: int shmdt(const void *shmaddr); 函数参数 *共享内存的地址 返回值 1.成功返回0 2.失败返回-1并设置合适的错误码。简单使用 #includeshm.hpp int main() {key_t key ftok(pathname.c_str(),proj_id);if(key -1) return -1;int shmid shmget(key,4096,IPC_CREAT | 0666);if(shmid -1) return -1;cout 获取成功! endl;//产生链接char* shmptr (char*)shmat(shmid ,nullptr,0);if(shmptr ! -1) cout 链接成功 endl; sleep(5);//断开链接int ret shmdt(shmptr);if(ret 0) cout 链接已断开 endl;sleep(5);return 0; }删除共享内存 头文件: #includesys/ipc.h #includesys/shm.h 函数声明: int shmctl(int shmid, int cmd, struct shmid_ds *buf); 函数参数: 1.用户共享内存标识符。 2.选项常见的有1.IPC_STAT,获取共享内存的状态信息放在buf指向的变量中。2.IPC_SET,设置共享内存的状态需要将buf变量传进去方便修改3.IPC_RMID,删除共享内存。4.SHM_LOCK,锁定共享内存。5.SHM_UNLOCK解锁共享内存。 3.输入或者输出型参数用于存放共享内存的信息。返回值 1.成功返回0. 2.失败返回-1.并设置合适的错误码。简单使用 #includeshm.hpp int main() {key_t key ftok(pathname.c_str(),proj_id);if(key -1) return -1;int shmid shmget(key,4096,IPC_CREAT | 0666);if(shmid -1) return -1;cout 获取成功! endl;//产生链接char* shmptr (char*)shmat(shmid,NULL,0);if(shmptr ! (void*)(-1)) cout 链接成功 endl; sleep(5);//断开链接int ret shmdt(shmptr);if(ret 0) cout 链接已断开 endl;sleep(5);//上面这一坨代码我们已经循序渐进讲过了。//下面是核心代码.//将共享内存进行删除。ret shmctl(shmid,IPC_RMID,NULL);if(ret -1) cout 删除失败 endl;cout 删除成功! endl;sleep(5);return 0; }实验结果: 补充由于多个进程的共享内存的虚拟地址映射到到同一物理内存这样数据读取时与写入时直接输入输出由于管道是将准备好的数据拷贝到管道需要各自准备好一块空间因此共享内存的通信方式比管道的方式少拷贝两次因此比较高效。 3.消息队列 原理 顾名思义消息队列就是能看到消息的队列不同进程之间必须看到这个队列那是如何做到的呢同理我们先画一张图进行辅助理解 其次我们再来根据这张图进行深入。 内核区也就是由操作系统进行管理的区域且操作系统是进程的管理者进程又代表着用户因此我们这里的进程分成了两部分一部分是暴露给用户进行操作的一部分是给操作系统进行管理的。进程1与进程2通过与操作系统产生链接并分发数据因此在内核区必然要由操作系统进行操作那也就意味着操作系统要提供系统调用接口给进程。进程2与进程1都给消息队列分发数据那如何区分数据是谁的呢必然存在数据的标识符使进程1获取进程2的数据进程2获取进程1的数据。 再来谈接口具体细节我们等到后面再讲 头文件: #include sys/types.h #include sys/ipc.h #include sys/msg.h 函数声明: key_t ftok(const char *pathname, int proj_id); *此函数共享内存的创建处已经提及到过。int msgget(key_t key, int msgflg); *此函数用于消息队列的创建。 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); *此函数用于消息的发送。ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,\ int msgflg); *此函数用于消息的获取。int msgctl(int msqid, int cmd, struct msqid_ds *buf); *此函数用于消息队列信息的获取与删除。通过此类接口我们不难发现此类接口与共享内存的接口高度类似这是为什么呢? 其实很简单消息队列用的也是是System V标准的。 既然这样那操作系统是如何统一的进行管理的呢我们通过两个结构体进行分析: 共享内存struct shmid_ds 消息队列struct msqid_ds 通过查看这两个结构体的声明看是否有相同之处下面以图解的方式进行呈现 可以观察到两个结构体声明中都有 struct ipc_perm这一个结构体操作系统便可以通过指针数组对这个结构体进行管理。其次对地址进行管理变相的就对struct ipc_perm所在的结构体进行了管理因为这个struct ipc_perm位于这两个结构体的开头因此可以通过强转的方式对指向所在结构体的信息获取和管理。这种思想对应是C的多态。指针数组通过下标的形式进行管理且下标的趋势大体随着开辟的数量的增多而增大跟文件描述符类似。 4.信号量 引入 在共享内存的讲解中我们可以直接在进程中直接访问共享内存但是若有多个进程同时进行访问那就可能会出现这样的问题 数据错乱无章数据可能会紊乱。 假如有两个进程一个进程一边从进程读数据一个进程一边从进程写数据两者同时发生那就可能会导致一个进程没写完的数据被另一个进程读走了从而导致读到的数据并不完整这样的问题我们叫做数据不一致问题。 说明管道是具有原子性的因此不会出现这样的问题。 此时我们需要对资源进行加锁也就是一个进程访问时另一个进程不能进行访问。这种现象我们称之为互斥。 而有的资源只能一次被一个进程进行占用比如说显示器键盘等像这种资源我们称之为临界资源。 若我们将临界资源的访问限制在一段代码里面也就是通过代码限制临界资源的访问这样的代码我们称之为临界区。 原理 首先给出一个概念信号量的本质就是一个计数器 前面我们已经讲过像有些资源只能一次被一个进程访问占用。 如果我们在资源空闲时设置标记为1在资源被进程占用时设置标记为0。这样如果标记为0则表示资源已经被占用别的进程无法进行使用标记为1则资源没有被占用其它进程可以被占用。如此一来只有当进程在共享资源中把事情干完才会退出。其它进程才能接着使用共享资源的现象我们称之为原子性。 这里标记我们就可以看做计数器像这种只有0,1两种状态的我们称之为二元信号量。 在现实生活中像一些去ATM机里面取钱为了防止其它人干扰这个过程一般进去之后我们会自动上锁还有一些比如上厕所为了不让在蹲坑的时候被其它人看见通常都会把门锁上……诸如此类的现象都体现出了信号量的概念。 还有一些可以多执行流进行访问的资源那信号量可能就不只是0,1两种状态可能是[0,n]种状态。 比如我们去看一场电影放映厅里面的座位是固定的要想看电影就得买电影票预定座位即获取座位的在一定时间内的使用权且座位的数量是固定的这就意为电影票的数量最多与座位的数量一致。不可能出现票的数量大于座位的数量的情况。 回归到信号量 首先假设有这样的共享资源这个资源分为40份供进程进行使用。 信号量初始值为40。当共享资源被访问时信号量就减减最多被减到0。当减到0时其它资源不能被继续访问。 同时联想到电影票当我们买票成功后就代表我们可以直接使用座位了么。答案很显然不是因为我们买到票只是预定了座位的使用权同理我们进程访问使信号量减减也并不代表我们就会立马访问资源。因此进程获取的只是对资源的预定。再想到电影院包场其实就是将电影院看成一个整天进行使用。不就是信号量为1的共享资源吗 拓展 既然要申请共享资源必须要通过信号量那也就意为着信号量也是共享资源那信号量安不安全 我们把信号量看成一个整形变量的减减操作。转换成汇编语言也就是三条左右把整形变量放到寄存器中将寄存器的值减1再将寄存器的值写会到变量当中。那也就意味着在进程执行过程中很可能执行到某一句汇编就切换进程了就要保存进程的上下文进行下次接着运行。在进程在等待队列等待的过程中很可能被其它进程接着访问从而修改进程的上下文导致信号量的减减操作不安全因此信号量也需要被保护。 总结 我们围绕着进程通信的本质展开了管道共享内存消息队列的话题及其相关细节的讨论。其次我们通过对信号量的理解深刻的认识到了进程通信不仅需要传递数据还需要保持传递的同步与协调。 如果本篇对您有所帮助不妨点个赞鼓励一下吧
http://www.dnsts.com.cn/news/22087.html

相关文章:

  • 网站开发的前后台的步骤分别为成都有什么好玩的地方 景点
  • 宁波专业网站推广平台便宜网站服务器怎么配置
  • 网站设计注意事项零食网站策划书
  • 江门网站建设硬件设备邯郸伟域网络科技有限公司
  • 义乌大岳网络科技有限公司廊坊优化软件
  • 古典网站建设欣赏模板做图 网站有哪些
  • 重庆卓光网站建设如何做视频网站 需要注意的地方
  • 代做ppt网站好wordpress 修改模板
  • 中企动力云邮箱关键词首页排名优化
  • html5做网站链接范例济南建设工程信息网站
  • 网站开发计划书网站技术解决方案华润置地建设事业部网站
  • wordpress镶嵌网页seo关键词推广
  • 公司网站怎么发布文章做网站利用自己电脑
  • 开江网站建设江苏专业的网站建设
  • 网站建设图标合集专业网站开发哪家专业
  • 建立一个网站怎么做电商网站开发主要的三个软件
  • 长丰县建设局网站网站 接入微信
  • 钓鱼网站的类型网站权重是什么
  • 织梦网站怎么做安全措施网站建设视频教程推荐
  • 多用户商城网站方案成都灯箱广告制作公司
  • 公司网站建设一年多少钱杭州91网站建设
  • 建设法律法规文本查询网站做网站关键字
  • 小网站 收入网站建没有前景
  • 写网站软件手机版网站建设
  • 晋城网站设计云浮哪有做网站公司
  • 免费网站制作视频教程苏州新闻最新消息今天
  • 网站建设与管理是干嘛的咨询邯郸网站建设
  • 太原模板建站系统教育类网站框架
  • 沅江网站设计公司五屏网站建设代理商
  • 自己的网站怎样做优化手机壳定制网站制作