智能模板网站建设方案用ip的网站要备案吗
- 作者: 五速梦信息网
- 时间: 2026年04月20日 03:47
当前位置: 首页 > news >正文
智能模板网站建设方案,用ip的网站要备案吗,wordpress注册没有密码,做淘宝客网站制作教程视频教程目录
进程间通讯概念的引入
意义#xff08;手段#xff09;
思维构建
进程间通信方式
管道
站在用户角度-浅度理解管道
匿名管道 pipe函数
站在文件描述符角度-深度理解管道
管道的特点总结
管道的拓展
单机版的负载均衡
匿名管道读写规则
命名管道
前言
原理…目录
进程间通讯概念的引入
意义手段
思维构建
进程间通信方式
管道
站在用户角度-浅度理解管道
匿名管道 pipe函数
站在文件描述符角度-深度理解管道
管道的特点总结
管道的拓展
单机版的负载均衡
匿名管道读写规则
命名管道
前言
原理
创建一个命名管道
用命名管道实现myServermyClient通信
匿名管道与命名管道的区别
命名管道的打开规则
system V共享内存
共享内存数据结构
共享内存的创建
key概念引入
key概念解析
基于共享内存理解信号量
总结 进程间通讯概念的引入
意义手段 在没有进程间通讯之前理论上都是单进程的那么也就无法使用并发能力更无法实现多进程协同将一个事分几个进程做。而进程间通讯就是对于实现多进程协同的手段。
数据传输一个进程需要将它的数据发送给另一个进程 资源共享多个进程之间共享同样的资源。 通知事件一个进程需要向另一个或一组进程发送消息通知它它们发生了某种事件如进程终止时要通知父进程。 进程控制有些进程希望完全控制另一个进程的执行如Debug进程此时控制进程希望能够拦截另一个进程的所有陷入和异常并能够及时知道它的状态改变。
思维构建 进程间通讯重点就在与如何让不同的进程资源的传递。而进程是具有独立性的也就是说进程相通讯会难度较大 – 因为进程间通讯的本质是先让不同的进程看见同一份资源。 融汇贯通的理解 进程的设计天然就是为了保证独立性的即进程之间无瓜葛所以深入的说所谓的同一份资源不能所属于任何一个进程更强调共享不属于任何一个进程。 进程间通信方式
管道
匿名管道pipe 命名管道
System V IPC
System V 消息队列 System V 共享内存 System V 信号量
POSIX IPC
消息队列 共享内存 信号量 互斥量 条件变量 读写锁
管道 我们把从一个进程连接到另一个进程的数据流称为一个“管道”。 当在两个命令之间设置管道 | 时管道符 | 左边命令的输出就变成了右边命令的输入。只要第一个命令向标准输出写入而第二个令是从标准输入读取那么这两个命令就可以形成一个管道。大部分的 Linux 命令都可以用来形成管道。 命令who | wc -l 用于查看当前服务器下登陆的用户人数。 补充 Linux who命令用于显示系统中有哪些使用者正在上面显示的资料包含了使用者 ID、使用的终端机、从哪边连上来的、上线时间、呆滞时间、CPU 使用量、动作等等。使用权限所有使用者都可使用。 Linux wc命令用于计算字数。在此处由于who中一个用户为一行所以此处用 -l 显示行数即登录用户个数。 其中运行起来后who命令与wc命令就是两个不同的进程。who进程作为数据提供方通过标准输入将数据写入管道wc进程再通过标准输入将数据从管道中读取出进而再将数据进行处理 -l 后以标准输出的方式将结果给用户。 站在用户角度-浅度理解管道
匿名管道 pipe函数 #include unistd.h 功能: 创建一无名管道。 原型 int pipe(int pipefd[2]); 参数 输出型参数通过调用该参数得到被打开的文件fd。 数组元素含义pipefd[0]管道读端文件描述符pipefd[1]管道写端文件描述符返回值 成功时返回0。出现错误时返回-1。 1. 父进程创建管道 2. 父进程fork出子进程 3. 父进程关闭读 / 写子进程关闭写 / 读。fork之后各自关掉不用的描述符 Note对于pipe函数创建的管道只能够进行单向通信。反之会导致读写导致管道中数据污染、混乱。我们需要对于父或子进程中的fd参数中的文件符号进行关闭。 pipe函数的使用需要结合fork函数的父子进程。
站在文件描述符角度-深度理解管道 #问如何做到让不同的进程看到同一份资源 以fork让子进程继承能够让具有“血缘关系”的进程进行进程间通讯。管道常用于父进程进程 融汇贯通的理解 fork创建子进程等于系统中多了一个子进程。而进程 内核数据结构 进程代码和数据。进程相关内核数据结构来源于操作系统进程代码和数据一般来源于磁盘。 而由于为了进程具有独立性所以创建子进程的同时需要分配对应的进程相关内核结构。对于数据被写入更改时操作系统采用写时拷贝技术进行对父子进程数据的分离。 父进程与子进程拥有自身的fd_array[]存储文件描述符fd但是其中存储的fd时相同的而文件相关内核数据并不属于进程数据结构所以并不会单独为子进程创建。于是父进程与子进程指向的是一个文件 - 这就让不同的进程看到了同一份资源。 管道本质上就是一个文件。一个具有读写功能并且无需放入磁盘的文件通道是进程进行通讯的临时内存空间无需将内容放入磁盘中保留。 tty标准输入、标准输出、标准错误
- 父进程创建管道 2. 父进程fork出子进程 3. 父进程关闭读 / 写子进程关闭写 / 读。fork之后各自关掉不用的描述符 代码实现的关键 创建管道 – 分别以读写方式打开同一个问题创建子进程 – 以fork函数创建子进程构造单向通讯的通道 – 双方进程各自关闭自己不需要的文件描述符 #include iostream #include unistd.h #include assert.h #include string #include string.h #include sys/wait.h #include sys/types.husing namespace std;int main() {//1.创建管道int pipefd[2] {0};int n pipe(pipefd);assert(n ! -1);(void)n; // 只被定义没有被使用Release下就会出现代码大量告警 – 证明使用过// 用于调试验证fd申请 #ifdef DEBUGcout pipefd[0]: pipefd[0] endl;cout pipefd[1]: pipefd[1] endl; #endif//2.创建子进程pid_t id fork();assert(id ! -1);if(id 1){// 子进程 – 只读// 3.构造单向通讯的通道, 父进程写入子进程读取// 3.1 关闭子进程不需要的fdclose(pipefd[1]);char child_buffer[1024*4];while(true){ssize_t s read(pipefd[0], child_buffer, sizeof(child_buffer) - 1);//3.2 访问控制:// a、写入的一方fd没有关闭如果有数据就读没有数据就等// b、写入的一方fd关闭, 读取的一方read会返回0表示读到了文件的结尾if(s 0){child_buffer[s] 0;cout child get a message[ getpid() ] Father# child_buffer endl;}else if(s 0){cout ———–writer quit(father), me quit!———– endl;break;}}exit(0);}// 父进程 – 只写// 3.构造单向通讯的通道, 父进程写入子进程读取// 3.1 关闭父进程不需要的fdclose(pipefd[0]);string message 我是父进程发送有效信息。;int count 0; // 传递的次数char father_buffer[1024*4];while(true){//3.2 构建一个变化的字符串snprintf(father_buffer, sizeof(father_buffer), %s[%d] : %d,message.c_str(), getpid(), count);//3.3 写入write(pipefd[1], father_buffer, strlen(father_buffer));//3.4 故意sleep凸显访问控制sleep(1);if(count 3){cout —————-father wirte quit!—————- endl;break;} }close(pipefd[1]);pid_t ret waitpid(id, nullptr, 0);assert(ret 0);(void)ret;return 0; }管道的特点总结
- 管道是用来进程具有血缘关系的进程进行进程间通讯。
- 管道具有通过让进程间通讯提供访问控制。 a、写端快读端慢写满了不能再写了。 b、写端慢读端快管道没有数据的时候读需要等待。 补充 c、写端关闭读端为0标识读到了文件结尾。 d、读端关闭写端继续写操作系统终止写端进程。 3. 管道提供的是面向流式的通信服务 – 面向字节流。
- 管道是基于文件的文件的生命周期是随进程的所以管道的生命周期是随进程的。
- 管道是单向通行的就是半双工通信的一种特殊情况。 数据的传送方式可以分为三种 单工通信(Half Duplex)是通讯传输的一个术语。一方固定为发送端另一方固定为接收端。即一方只能写一方只能读。 半双工通信(Half Duplex)是通讯传输的一个术语。指数据传输指数据可以在一个信号载体的两个方向上传输但是不能同时传输。即一段时间内只能一方写一方读。 全双工通信(Full Duplex)是通讯传输的一个术语。指通信允许数据在两个方向上同时传输它在能力上相当于两个单工通信方式的结合。即一段时间内每方能写且读。 管道的拓展
单机版的负载均衡 以循环fork函数开辟多个子进程并利用pipe函数。针对于每一个子进程开辟一个管道父进程通过管道安排其中一个子进程做某任务。 #pragma once#include iostream
#include unordered_map
#include string
#include functionaltypedef std::functionvoid() func;std::vectorfunc callbacks;
std::unordered_mapint, std::string desc;void readMySQL()
{std::cout sub process[ getpid() ]执行访问数据库的任务 std::endl;
}void executeUlt()
{std::cout sub process[ getpid() ]执行url解析\n std::endl;
}void cal()
{std::cout sub process[ getpid() ] 执行加密任务\n std::endl;
}void save()
{std::cout sub process[ getpid() ] 执行数据持久化任务\n std::endl;
}void load()
{callbacks.push_back(readMySQL);desc.insert({callbacks.size(), readMySQL: 执行访问数据库的任务});callbacks.push_back(executeUlt);desc.insert({callbacks.size(), executeUlt: 进行url解析});callbacks.push_back(cal);desc.insert({callbacks.size(), cal: 进行加密计算});callbacks.push_back(save);desc.insert({callbacks.size(), save: 执行数据持久化任务});
}// 功能展示
void showHandler()
{for(const auto iter : desc)std::cout iter.first - iter.second std::endl;
}// 具有的功能数
int handlerSize()
{return callbacks.size();
}
#include iostream
#include vector
#include unistd.h
#include cassert
#include sys/wait.h
#include sys/types.h
#include Task.hppusing namespace std;#define PROCESS_NUM 4int waitCommand(int waitfd, bool quit)
{//此处由于是父进程写入一个整数 – 用以子进程执行相关内容//规定子进程读取的数据必须是4字节uint32_t command 0;ssize_t s read(waitfd, command, sizeof(command));if(s 0){quit 1;return -1;}assert(s sizeof(uint32_t));return command;
}void wakeUp(pid_t who, int fd, uint32_t command)
{write(fd, command, sizeof(command));cout main process call: who process, execute: desc[command] , through write fd: fd endl;
}int main()
{load();// 存储子进程id父进程对应写端符fdvectorpairpid_t, int slots;//1. 创建多个进程for(int i 0; i PROCESS_NUM; i){//1.1 创建管道int pipefd[2] {0};int n pipe(pipefd);assert(n 0);(void)n;//1.2 fork创建子进程pid_t id fork();assert(id ! -1);(void)id;if(id 0){// 子进程 – 关闭写端close(pipefd[1]);while(true){// 用于判断是否bool quit 0;int command waitCommand(pipefd[0], quit);if(quit){break;}if(command 1 command handlerSize())callbackscommand - 1;elsecout error, 非法操作 endl;}exit(1);}//将父进程读端关闭close(pipefd[0]);slots.push_back(make_pair(id, pipefd[1]));}while(true){int select;int command;cout ############################################ endl;cout ## 1. show funcitons 2.command ## endl;cout ############################################ endl;cout Please Select ;cin select;if(select 1)showHandler();else if(select 2){cout Enter command endl;cin command;// 随机挑选进程int choice rand() % PROCESS_NUM;//将任务指派给指定的进程wakeUp(slots[choice].first, slots[choice].second, command);}elsecout 输入错误请重新输入 endl;}// 关闭父进程写端fd所有的子进程都会退出for(const auto slot : slots)close(slot.second);// 回收所有的子进程信息for(const auto slot : slots)waitpid(slot.first, nullptr, 0);return 0;
}
匿名管道读写规则
当没有数据可读时 O_NONBLOCK disableread调用阻塞即进程暂停执行一直等到有数据来到为止O_NONBLOCK enableread调用返回-1errno值为EAGAIN。 当管道满的时候 O_NONBLOCK disable write调用阻塞直到有进程读走数据 O_NONBLOCK enable调用返回-1errno值为EAGAIN 如果所有管道写端对应的文件描述符被关闭则read返回0 如果所有管道读端对应的文件描述符被关闭则write操作会产生信号SIGPIPE,进而可能导致write进程退出 当要写入的数据量不大于PIPE_BUF时linux将保证写入的原子性。 当要写入的数据量大于PIPE_BUF时linux将不再保证写入的原子性。 原子性要么做要么不做没有所谓的中间状态。 POSIX.1-2001要求PIPE_BUF至少为512字节。在Linux上PIPE_BUF为4096字节。 拓展 讨论原子性需要在多执行流下数据出现并发访问的时候讨论原子性才有意义。此处不深入 融会贯通的理解 匿名管道就是一个文件一个内存级别的文件并不会在磁盘上存储并不会有自身的文件名。作为基础间通讯的方式是看见同一个文件 – 通过父子进程父子继承的方式看见。 是一个只有通过具有 “血缘关系” 的进程进行使用可以称做父子进程通讯。 命名管道
前言 匿名管道只能使用于具有“亲缘关系”的进程之间通信而对于毫无关系的两个进程无法使用匿名管道通讯如果我们想在不相关的进程之间交换数据可以使用FIFO文件来做这项工作它经常被称为命名管道。命名管道是一种特殊类型的文件。
原理 当两个进程需要同时带开一个文件的时候由于为了保证进程的独立性所以两个进程会有各自的files_struct而对于文件数据并不会为每一个进程都备一份是内存的浪费此时A进程的files_struct与B进程的files_struct是不同的但是其中的文件符fd指向的是由磁盘文件加载到内存中的同一份数据空间。 命名管道就是如此其原理与匿名管道很相识。命名管道在磁盘中所以其有自己的文件名、属性信息、路径位置……但是其没有文件内容。即命名管道是内存文件其在磁盘中的本质是命名管道在磁盘中的映像且映像的大小永远为0。意义就是为了让毫无关系的基进程皆能够调用到命名管道。而管道中的数据是进程通讯时的临时数据无存储的意义所以命名管道在磁盘中为空。 创建一个命名管道
命名管道可以从命令行上创建命令mkfifo fifo 创建一个名为fifo命名管道 此时文件类型不是常用 - 与 d 而是 p 此文件的类型为管道 此时会发现处于等待状态。因为由于我们写了但是对方还没有打开于是处于阻塞状态。 此时 echo hello name_pipe进程A就是写入的进程 cat进程B就是读取的进程。这就是所谓的一个进程向另一个进程写入消息的过程通过管道写入的方式。 我们可以在命令行上使用循环的方式往管道内每隔1s写入数据。即进程A原来应向显示器文件写入的数据通过输入重定向的方式将数据写入管道中再将管道中数据通过输出重定向通过进程B将数据写入到显示器文件中。如此以毫无相关的进程A与进程B通过命名管道进行数据传输 - 进程间通信。 此时我们通过终止读取进程方导致写入端向管道写入的数据无意义了无读取端此时作为写入端的进程就应该被操作系统杀掉。此时需要注意echo是内置命令所以是bush本身自行执行的命令所以此时杀掉写入端的进程无疑就是杀掉bush。于是bush被操作系统杀死云服务器即退出。
内置命令让父进程myshell自己执行的命令叫做内置命令内建命令。 命名管道可以从程序里创建#include sys/types.h #include sys/stat.h int mkfifo(const char pathname, mode_t mode); 参数 pathname创建的命名管道文件。 路径的方式给出。会在对应路径下创建文件名的方式给出。默认当前路径下创建 mode创建命名管道文件的默认权限。 我们创建的文件权限会被umask(文件默认掩码)进行影响umask的默认值0002而实际创建出来文件的权限为mode(~umask)。于是导致我们创建的权限未随我们的想法如0666 - 0664。需要我们利用umask函数更改默认。umask(0); //将默认值设为 0000返回值 命名管道创建成功返回0。 命名管道创建失败返回-1。 用命名管道实现myServermyClient通信 利用命名管道实现服务端myServer与客户端myClient之间进行通讯。将服务端myServer运行起来并用mkfifo函数开辟一个命名管道。而客户端myClient中利用open打开命名管道命名管道本质为文件以write向管道中输入数据。以此服务端myServer利用open打开命名管道以read从管道中读取数据。
comm.hpp 所展开的头文件集合。
#ifndef _COMMH
#define _COMMH#include iostream
#include string
#include cstring
#include cstdlib
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include sys/wait.hstd::string ipcPath ./fifo.ipc;#endif
Log.hpp 编程的日志就是当前程序运行的状态。
#ifndef _LOGH
#define _LOGH#include iostream
#include ctime#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3std::string msg[] {Debug,Notice,Warning,Error
}std::ostream Log(std::string message, int level)
{std::cout | (unsigned)time(nullptr) | msg[level] | message;
}#endif
myServer.cc
细节 mkfifo的第二个参数传入权限0666之前需要以umask(0)对于服务端因为只需要在命名管道中读取数据所以以只读的方式O_RDONLYopen管道文件后序以fork开辟子进程让子进程read读取即可同时也需要注意C语言的字符串结尾必须是 \0读取大小sizeof(buffer) - 1。 由于我们让子进程执行读取工作所以需要以waitpid等在子进程此处我们让nums个子进程进行所以waitpid的第一个参数为 -1 等待任意一个子进程。 由于open打开了管道类型的文件所以需要以close(fd)关闭文件由于mkfifo开辟了管道所以需要以unlink删除管道文件。
#include comm.hpp// 管道文件创建权限umask 0
#define MODE 0x0666// 读取数据大小
#define READ_SIZE 64// 从管道文件读取数据
static void getMessage(int fd)
{char buffer[READ_SIZE];while(true){memset(buffer, \0, sizeof(buffer));ssize_t s read(fd, buffer, sizeof(buffer) - 1); // C语言字符串需要保证结尾为\0if(s 0){std::cout [ getpid() ] myClient say buffer std::endl;}else if(s 0){// 写端关闭 - 读到文件结尾std::cerr [ getpid() ] read end of file, clien quit, server quit too! std::endl;}else{// 读取错误perror(read);exit(3);}}
}int main()
{//1. 创建管道文件umask(0);if(mkfifo(ipcPath.c_str(), MODE) 0){perror(mkfifo);exit(1);}#ifdef DEBUGLog(创建管道文件成功, Debug) step 1 std::endl;#endif//2. 正常的文件操作int fd open(ipcPath.c_str(), O_RDONLY);if(fd 0){perror(open);exit(2);}#ifdef DEBUGLog(打开管道文件成功, Debug) step 2 std::endl;#endifint nums 3;// 创建3个子进程for(int i 0; i nums; i){pid_t id fork();if(fd 0){// 子进程 - 读取管道数据getMessage(fd);exit(1);}}// 父进程 - 等待子进程for(int i 0; i nums; i){waitpid(-1, nullptr, 0);}// 4. 关闭管道文件close(fd);#ifdef DEBUGLog(关闭管道文件成功, Debug) step 3 std::endl;#endifunlink(ipcPath.c_str()); // 通信完毕就删除管道文件#ifdef DEBUGLog(删除管道文件成功, Debug) step 4 std::endl;#endifreturn 0;
}
myClient.cc
细节 对于客户端因为只需要在命名管道中写入数据所以以只写的方式O_WRONLYopen管道文件后序write即可。
#include comm.hppint main()
{//1. 获取管道文件 - 以写的方式打开命名管道文件int fd open(ipcPath.c_str(), O_WRONLY);if(fd 0){perror(open);exit(1);}//2. ipc过程std::string buffer; //用户级缓冲区while(true){std::cout Please Enter Message Line : ;std::getline(std::cin, buffer);write(fd, buffer.c_str(), buffer.size());}//3. 通信完毕关闭命名管道文件close(fd);return 0;
} 由于命名管道的创建是在服务端myServer中所以需要先运行myServer。 服务端myServer进程运行起来我们就能看到创建的命名管道文件。此时服务端myServer处于阻塞状态也是管道文件的特性写入端未开辟读取端需要等待写入端开辟。 可以通过 ps 命令查看进程是否相关 从此可以看出myServer与myClient是毫无相关的进程即myServer的三个子进程与myClient也是毫无相关的进程。
匿名管道与命名管道的区别
匿名管道由pipe函数创建并打开。 命名管道由mkfififo函数创建打开用open FIFO命名管道与pipe匿名管道之间唯一的区别在它们创建与打开的方式不同一但这些工作完成之后它们具有相同的语义。
命名管道的打开规则
如果当前打开操作是为读而打开FIFO时 O_NONBLOCK disable阻塞直到有相应进程为写而打开该FIFO O_NONBLOCK enable立刻返回成功 如果当前打开操作是为写而打开FIFO时 O_NONBLOCK disable阻塞直到有相应进程为读而打开该FIFO O_NONBLOCK enable立刻返回失败错误码为ENXIO system V共享内存 system V共享内存是与管道不同的管道基于操作系统已有的文件操作。文件部分无论有没有通讯的需求这个文件都需要维护有没有通讯都需要和指定进程建立关联通不通讯都会有。 而共享内存是不用来通讯操作系统就不用进行管理只有需要使用时操作系统才提供 - 有通讯才会有共享内存。共享内存是操作系统单独设立的内核模块专门为进程间通讯设计 – 这个内核模块就是system V。 即前面的匿名管道、命名管道通讯是恰好使用文件方案可以实现。而共享内存是操作系统专门为了通讯设计。 共享内存的建立 共享区共享内存、内存映射和共享库保存位置。 共享内存数据结构 共享内存的提供者是操作系统。 大量的进程进行通讯 - 共享内存是大量的。所以操作系统对于共享内存需要进行管理需要管理 - 先描述再组织 - 重新理解共享内存 共享内存块 对应的共享内存的内核数据结构。 共享内存的数据结构 shmid_ds 在 /usr/include/linux/shm.h 中定义 cat命令即可 struct 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 current attaches / unsigned short shm_unused; / compatibility */ void shm_unused2; / ditto - used by DIPC */ void shm_unused3; / unused */}; 此处首先提一下key值后面共享内存的建立引入其是在上面的共享内存的第一个参数struct ipc_perm类型的shm_perm变量中的一个变量。 在 /usr/include/linux/ipc.h 中定义 struct ipc_perm { kernel_key_t key; kernel_uid_t uid; kernel_gid_t gid; kernel_uid_t cuid; kernel_gid_t cgid; kernel_mode_t mode; unsigned short seq; }; 共享内存的创建 #include sys/ipc.h #include sys/shm.h // 用来创建共享内存 int shmget(key_t key, size_t size, int shmflg); 参数 key这个共享内存段名字。 size共享内存大小。 大小建议为4096的整数倍。原因使用时讲解 shmflg由九个权限标志构成它们的用法和创建文件时使用的mode模式标志是一样的。 组合方式作用IPC_CREAT创建共享内存如果底层已经存在获取之并且返回。如果底层不存在创建之并且返回。IPC_EXCL没有意义IPC_CREAT | IPC_EXCL创建共享内存如果底层不存在创建之并且返回。如果底层存在出错返回。IPC_CREAT | IPC_EXCL意义可以保证放回成功一定是一个全新的共享内存shm。 此外创建需要权限的初始化 如IPC_CREAT | IPC_EXCL | 0666 返回值 成功返回一个非负整数即该共享内存段的标识码用户层标识符失败返回-1。 key概念引入 进程间通讯首先需要保证的看见同一份资源。 融会贯通的理解 匿名管道通过pipe函数开辟内存级管道 – 本质是文件 – 通过pipe函数的参数文件符fd– 看见同一份资源。命名管道通过mkfifo函数根据路径开辟管道文件可以从权限p看出– 本质是开辟一个文件可以从第二个参数需要初始化权限看出– 利用open、write、read、close文件级操作 – 看见同一份资源。管道 – 内存级文件 – 恰巧利用文件操作。前面已有所提system V共享内存是操作系统为进程间通讯专门设计 并无法利用类似于管道利用文件实现。于是便有了key。 key概念解析 key其实就是一个整数是一个利用算法实现的整数。我们可以将key想象为一把钥匙而共享内存为一把锁。 更像是同心锁和一对对情侣情侣拿着同样的钥匙只可解一堆锁中的一把锁。 如同一把钥匙会按照固定的形状制造。其会使用同样的算法规则形成一个唯一值key同时再创建共享内存时会将key值设置进其中此时两个毫无关系的进程就可以通过key值用共享内存进行通讯一方创建共享内存一方获取共享内存。 制造唯一值key的算法 #include sys/types.h #include sys/ipc.hkey_t ftok(const char *pathname, int proj_id); 其不进行任何系统调用其内部是一套算法该算法就是将两个参数合起来形成一个唯一值就可以数值是几不重要。对于第一个参数ftok是拿带文件的inode标号所以路径可以随意写但必须保证具体访问权限proj_id(项目id)随意写即可一般是0~255之间可以随便写因为超了其也会直接截断。 返回值 成功后返回生成的key_t值。失败时返回-1。 note 终究就是个简易的算法所以key值可能会产生冲突于是可以对传入ftok函数的参数进行修改。需要保证需要通讯的进程使用的 pathname 与 proj_id 相同如此才能保证生成的是同一个key值。简易的使用shmget函数结合ftok函数 其不进行任何系统调用其内部是一套算法该算法就是将两个参数合起来形成一个唯一值就可以数值是几不重要。对于第一个参数ftok是拿带文件的inode标号路径可以随意写但必须保证具体访问权限 两个进程要通讯就要保证两个看见统一个共享内存本质上保证两个看到同一个key。 与文件不同文件是打开了最后进程退出文件没有进程与其关联文件就会自动释放。 操作系统为了维护共享内存就需要先描述再组织。所以共享内存在内核里处理共享内存的存储内存空间也需要存储对其描述信息的数据结构。所以为了设置或获取其的属性就通过第三个参数。当只需要删除的时候第三个参数设为nullptr即可 操作系统管理物理内存的时候页得大小是以4KB为单位。也就是4096byte如果我们用4097byte就多这1byte操作系统就会在底层直接创建4096 * 2byte的空间此时多余的4095byte并不会使用就浪费了。 此处我们以4097byte申请操作系统开辟了4096 * 2byte但是查询下是4097byte因为操作系统分配了空间但是并不代表对所有都有权利访问我们要的是4097byte那操作系统只会给对应的权限。所以建议配4096byte的整数倍。 prems权限。此处为0 代表任何一个人包括我们都没有权力读写共享内存此时创建共性内存也就没了意义。于是我们需要再加一个选项设置权限。 nattchn标识个数attch表示关联。表示有多少个进程与该共享内存关联。 需要将指定的共享内存挂接到自己的进程的地址空间。 参数 要挂接的共享内存的用户管理的对应的id。获取共享内存时的id我们需要指定的虚拟地址。共享内存挂接时可将其挂接到指定的虚拟地址。一般不推荐因为虚拟地址的使用情况我们并不是十分的清楚。即使我们能获取到设置为nullptr让操作系统自行挂接即可。挂接方式。设置为0即可默认会以读写的方式挂好。 · 范围值共享内存的起始地址。 文件描述符文件有其对应的文件指针可用户从来不会用文件指针用的全是文件描述符它们都可以用来标定一个文件。同样的道理shmid与key它们都可以用来标定共享内存的唯一性。key标定共享内存在系统级别上的唯一性。shmid标定共享内存的用户级别上的唯一性。所以我们在用的时候全部都是shmid。只要是指令编写的时候就是在用户层次的所以ipcs等用的是shmid。 system V IPC资源生命周期随内核与之相对的是生命周期随进程。即操作系统会一直保存这个资源除非用户用手动命令删除否则用代码删除。 共享内存由操作系统提供并对其进行管理先描述再组织 - 共享内存 共享内存块 对应的共享内存的内核数据结构。 融会贯通的理解 一个内存为4G的地址空间0~3G属于用户3~4G属于内核。所谓的操作系统在进行调度的时候执行系统调用接口、库函数。本质上都是要将代码映射到地址空间当中所以我们的代码无论是执行动态库还是执行操作系统的代码。都是在其地址空间中完成的。所以对于任何进程3~4G都是操作系统的代码和数据所以无论进程如何千变万化操作系统永远都能被找到。 堆栈之间的共享区是用户空间该空间拿到了无需经过系统调用便可直接访问。 – 共享内存是不用经过系统调用直接可以进行访问双方进程如果要通讯直接进行内存级的读和写即。 融会贯通的理解 前面所说的匿名管道pipe、命名管道fifo。都需要通过read、writeIO系统调用来进行通讯。因为这两个属于文件而文件是在内核当中的特定数据结构所以其是操作系统维护的 – 其是在3~4G的操作系统空间范围中。无权访问必须使用系统接口 共享内存在被创建号之后默认被清成全0所以打印字符是空串。 共享内存就是天然的为了让我们可以快速访问的机制所以其内部没有提供任何的控制策略。共享内存中有数据读端读没数据读端也读。甚至客户端写入端不在了其也读。更直接的说写入端和读取端根本不知道对方的存在。 缺乏控制策略 – 会带来并发的问题。 拓展 并发的问题如 客户端想让一个进程处理一个完整的数据内容然而客户端在未完全写入共享内存时读取方就将不完整的数据读取并处理此时处理结果为未定义。 – 数据不一致问题 基于共享内存理解信号量 根据前面的学习 匿名管道通过派生子进程的方式看见同一份资源。命名管道通过路径的方式看见同一份资源。共享内存通过key值得方式看见同一份资源。所以为了让进程间通讯 - 让不同的进程之间看见同一份资源 - 本质让不同的进程看见同一份资源。 通过前面得到学习我们会发现如共享进程其并没有访问控制即独断读取的时机是不确定的这也就带来了一些时序问题 —— 照成数据的不一致问题。 引入两个概念 临界资源我们把多个进程执行流看到的公共的一份志愿称作临界资源。临界区我们把自己的进程访问的临界资源的代码称作临界区。所以多个进程执行流互相运行的时候互相干扰主要是我们不加以保护的访问了相同的资源临界资源在非临界区多个进程执行流互相是不干扰的。 而为了更好的进行临界资源的保护可以让多个进程执行流在任何时刻都只能有一个进程进入临界区 —— 互斥 。 互斥的理解 我们可以将一个执行流人临界区电影院(一个位置的电影院)。 看电影一定要有位置电影院中的唯一位子。当前一个人在其中看电影那么其他人必须等待他看完才可进入观看。并且电影院中此唯一的位置是并不属于观影人的而是买票只要买了票即在你进去看完电影之前就拥有了这个位置。买票就是对座位的 预定 机制。 同样的道理进程想进入临界资源访问临界资源不能让进程直接去使用临界资源不能让用户直接去电影院内部占资源需要先申请票 —— 信号量 。 信号量 的存在是等于一张票。票的意义是互斥而互斥的本质是串形化互斥就是一个在跑另一个就不能跑需要等待跑完才能跑。其必须串形的去执行。但是一旦串形的去执行多并发的效率就差了。所以 当有一份公共资源只要有多个执行流访问的是这个公共资源的不同区域这个时候可以允许多个执行流同时进入临界区。这个时候可以根据区域的数量如同电影院座位的个数 - 允许观影的人数可以让对应的进程个数并发的执行自己临界区的代码看电影的自行观影。 信号量本质上就是一个计数器类似于int count nn张票。 申请信号量 申请信号量的本质让信号量计数器 – 。释放信号量的本质让信号量计数器。信号量申请成功临界资源内部就一定会预留所需要的资源 —— 申请信号量本质其实是对临界资源的一种“ 预定 ”机制。只要申请信号量成功 ……只要申请成功一定在临界区中有一个资源对应提供的。 换句话说首先我们要进行访问信号量计数器要每一个线程访问计数器必须保证信号量本身的 –操作 以及 操作 是原子的。否者很难保护临界资源。其次信号量需要是公共的能被所有进程能看到的资源叫做临界资源 —— 而信号量计数器存在的意义就是保护临界资源但是其有又成了临界资源所以其必须保证自己是安全的才能保证临界资源的安全。 #如果用一个整数表示信号量。假设让多个进程整数n在共享内存里看见同一个全局变量都可以进行申请信号量 —— 不可以的。 CPU执行指令的时候 将内存中的数据加载到CPU内的寄存器中读指令。n–分析 执行指令。将CPU修改完的数据n写回到内存写回结果。复习 执行流在执行的时候在任何时刻都可能被切换。 切换的本质CPU内的寄存器是只有一份的但是寄存器需要存储的临时数据上下文是多份的分别对应不同的进程 我们知道每一个进程的上下文是不一样的寄存器只有一份那么根据并发为下一个进程让出位置。并且由于上下文数据绝而对不可以被抛弃 当进程A暂时被切下来的时候需要进程A顺便带走直接的上下文数据带走暂时保存数据的是为了下一次回来的时候能够恢复上去以此继续按照之前的逻辑继续向后运行就如同没有中断过一样。 由于寄存器只有一套被所有的执行流共享但是寄存器里面的数据属于一个执行流属于该执行流的上下文数据。所以对应的执行流需要将上下文数据进行保护方便与上下文数据恢复重新回到CPU更具上下文数据继续执行。 当myClient执行的时候重点在于n–到n因为时序的问题会导致n有中间状态。切换为myServer执行的时候中间状态会导致数据不一致。 即CPU执行myClient中的写入数据到共享内存时就被替换了 CUP执行到n的中间状态 myClient被切换为myServer myServer信号量执行完了并将n写回 myCilent带着自己的上下文数据并将n写回 此时1 - 2凸显了信号量操作必须是原子性的只有原子性才不会怕因时序导致的数据不一致问题。 总结 申请信号量 - 计数器– - P操作 - 必须是原子的申请信号量 - 计数器 - V操作 - 必须是原子的总结 所以由于信号量的思想也是让我们看见同一份资源所以其本质与上面的管道、共享内存没有太大的区别。所以信号量被纳入进程间通讯的范畴。 信号量是为了保证特定的临界资源不被受侵害保证临界资源数据一致性。前面所讲信号量也是一个临界资源所以首先其需要保证自己的安全性 —— 提出信号量操作需是原子性的。 而信号量理论的提出是由于临界区、临界资源的 互斥 当多个执行流进程才会真正的凸显出来所以此处由于是进程间通讯 —— 需要提出信号量但作用凸显在多线程 —— 多线程再深入讲解信号量。
- 上一篇: 智能建站系统怎么更换网站模板公司招牌制作价格
- 下一篇: 智能手机网站开发网站图片加载优化
相关文章
-
智能建站系统怎么更换网站模板公司招牌制作价格
智能建站系统怎么更换网站模板公司招牌制作价格
- 技术栈
- 2026年04月20日
-
智能建站系统 网站建设的首选仓库管理系统界面
智能建站系统 网站建设的首选仓库管理系统界面
- 技术栈
- 2026年04月20日
-
智能建站模版生态网站模板
智能建站模版生态网站模板
- 技术栈
- 2026年04月20日
-
智能手机网站开发网站图片加载优化
智能手机网站开发网站图片加载优化
- 技术栈
- 2026年04月20日
-
智能锁网站建设关键词域名解析错误是网络问题还是电脑问题
智能锁网站建设关键词域名解析错误是网络问题还是电脑问题
- 技术栈
- 2026年04月20日
-
智能网站建设公司排名wordpress标签图片
智能网站建设公司排名wordpress标签图片
- 技术栈
- 2026年04月20日
