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

【Linux】学习-进程间通信

进程间通信

介绍

进程间通信的本质

  • 进程间通信的前提,首先需要让不同的进程看到同一块“内存”
  • 此“内存”一定不属于任何进程,而应该强调共享二字

进程间通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程

  • 资源共享:多个进程之间共享同样的资源。

  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程间通信的简单理解(举例)

  • 看代码:

    #include <iostream>
    #include <unistd.h>
    using namespace std;
    int main()
    {pid_t id=fork();if(id==0){cout<<"hello i am father!"<<endl;}else{cout<<"hello i am child"<<endl;;}return 0;
    }
    
  • 运行结果:image-20231003154125046

为什么父子进程会向同一个显示器文件打印?

  • 创建子进程时对应的struct files_struct也会拷贝一份给子进程,因此里面的结构体数组:struct file*fd_array[]中的内容也是一样的,数组中存放的文件指针指向的文件也是一样的,因此通信的本质看到同一个文件也就随之实现了先让父进程打开一个文件,这样就有了一个文件描述符存放再让父进程创建子进程,这时两个进程就都指向了同一个文件,这个文件就可以作为通信渠道使父子间通信

进程间通信的分类

  • 管道

    匿名管道pipe

    命名管道

  • System V IPC

    消息队列

    共享内存

    信号量

  • POSIX IPC

    消息队列

    共享内存

    信号量

    互斥量

    条件变量

    读写锁

管道

何为管道?

  • 管道是Unix中最古老的进程间通信的形式。

  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道“

    who进程的运行结果通过标准输出将数据流入管道,wc -l 通过标准输入从管道内读取数据,处理后得到的结果再打到标准输出上让用户看到。

who命令查看当前服务器登录用户,wc -l 统计行数

匿名管道

仅限于父子进程间通信的管道文件,本质是双方进程一方打开写端关闭读端,另一端打开读端关闭写端,刻意营造单向流动的局面的一种管道

  • 图解
  • 在文件描述符的视角看
  • 在内核角度看

简明阐述

  • 父进程通过系统提供的接口创建管道文件,此文件是由操作系统管理的,其中的数据并不会被刷到磁盘上,纯内存级的文件,这样使得效率提高了,创建好文件后该接口会默认以只读和只写的模式打开此管道文件,这样就有了两个文件描述符,一个文件描述符是用来读文件的,一个文件描述符是用来写入文件的,此时父进程的file_struct内的fd_array数组也就有了两个描述符,随后父进程调用fork函数创建子进程,子进程此时是与父进程共享一份数据的,但是由于父进程需要关闭两个描述符的其中一个,会发生写时拷贝(注意:由于管道文件是系统创建并维护的,与两个进程是没有直接关系的,因此拷贝时并不会出现管道文件也被拷贝的情况,而是文件描述符表会被拷贝),此时子进程也拥有了管道文件的两个文件描述符,只需要关闭父进程关闭的那一个所相对的文件描述符,比如父进程关闭写端,那么子进程就关闭读端,这样就成功营造了单向流通的管道通信的局面!并且也符合不同进程看到同一份资源的条件!进程间通信也就完成了!这种要么在读,要么在写的通信方式也称为半双工通信

创建管道文件函数:pipe()

  • image-20231003162558322

  • image-20231003162618370

    pipefd[0]和pipefd[1] 是成功打开文件后返回的两个文件描述符,pipefd[0]对应的是读端,pipefd[1]对应的是写端

使用pipe()完成进程间通信:提供框架,具体自行测试

  • // 半双工,要么在读要么在写
    int main()
    {int pipefd[2] = {0};int n = pipe(pipefd);assert(n != -1);(void)n;pid_t id = fork();if (id == 0){// child:readclose(pipefd[1]);    while (true){//读操作}close(pipefd[0]);exit(1);}else{// father:writeclose(pipefd[0]);while (true){//写操作}close(pipefd[1]);pid_t ret = waitpid(id, nullptr, 0);assert(ret > 0);(void)ret;}return 0;
    }
  • 写操作示例:

    // father:writeclose(pipefd[0]);char send_buffer[1024 * 8]; // 缓冲区while (true){fgets(send_buffer, sizeof send_buffer - 1, stdin);ssize_t s = write(pipefd[1], send_buffer, strlen(send_buffer));}
    
  • 读操作示例:

    // child:readclose(pipefd[1]);char buffer[1024 * 8];while (true){// sleep(5);ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;if (strcmp(buffer, "quit") == 0){cout << "ready to close child" << endl;break;}cout << "copy that:[" << getpid() << "] " << buffer;}else{// 读不到东西了,写端关闭会走到这里cout << "writing quit, reading quit!" << endl;break;}
    
  • 运行结果:

    image-20231003175441079

注意

  • 子进程一次读的内容,可能是父进程进行无数次写入的内容,这就叫流式服务,通俗点理解就是,有一端在写的时候,另一端读端会被挂起阻塞,没有在读,等待写端完毕后,读端才会被唤醒(这个概念又称为互斥
  • 一个写端可以有多个读端,也就是父进程只有一个,而子进程有多个,但此时可以让所有的管道文件的写端文件描述符由父进程控制,而读端就由不同的子进程进行,这就是进程池

管道读写规则

  • 没有数据可读时:
    • O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
    • O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
  • 当管道满时
    • O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
    • O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
  • 如果所有管道写端对应的文件描述符被关闭,则read返回0,也就是直接标志着读到文件末尾了
  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
  • PIPE_BUF:image-20231003194343744
  • image-20231003194516911

何为原子性?

  • 简单来说,诸如管道通信的特点中,半双工通信时,要么处于读的状态,要么处于写的状态,写端在写时,那么读端就不读,一直阻塞,写端就一直写,读端读时,写端就不写,一直阻塞,读端就一直读,这种要么做,要么就不做,不存在你边读我边写的中间状态就称为原子性。

管道的特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
  • 管道提供流式服务
  • 一般而言,进程退出,管道释放,所以管道的生命周期随进程
  • 一般而言,内核会对管道操作进行同步与互斥
  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

何为互斥与同步?

  • 首先我们需要知道什么是临界资源?临界资源是一次仅允许一个进程独占使用的不可剥夺的资源,相应的,临界区就是进程访问临界资源的那段程序代码。一次仅允许一个进程在临界区中执行

  • 互斥:当一个进程正在临界区中访问临界资源时,其他进程不能进入临界区

  • 同步:合作的并发进程需要按先后次序执行,例如:一个进程的执行依赖于合作进程的消息或者信号,当一个进程没有得到来自于合作进程的消息或者信号时需要阻塞等待,直到消息或者信号到达后才被唤醒

  • 以前面所提到的进程池为例,多个管道,但写端都是父进程,而读端是由父进程所创建的多个子进程,那么父进程向管道写进资源时,此时多个读端都会处于堵塞状态,等待父进程写完毕,这就体现了同步过程,一旦写完毕,多个子进程便会争相去读取这份临界资源,但每次最多只能有一个进程读取此时的管道数据,这就体现了互斥,当然这只是冰山一角,更深层次的还有待探讨。

再次理解管道读写规则的四种特殊情况

  • 写端速度快于读端,写端写满了不能再写了,于是写端进入阻塞,等待读端唤醒读走数据(基于同步机制)
  • 写端速度慢于读端,那么当管道没有数据时,读端必须进入阻塞状态等待写端(基于同步机制)
  • 写端关闭,则管道内永远不会出现数据,则标志着读端读到了文件末尾,即read的返回值=0
  • 读端关闭,写端如果再继续往管道内写数据,不会再被读走了,因此系统会直接发送SIGPIPE信号终止掉进程,导致写端进程退出

进程池代码举例

  • processpool.cc文件:
// 进程池:父进程派发任务让多个子进程执行
#include <iostream>
#include <cstdlib>
#include <vector>
#include <unistd.h>
#include <cassert>
#include <sys/wait.h>
#include <sys/types.h>
#include <ctime>
#include "Task.hpp"#define PROCESS_NUM 5int waitCommand(int waitFd, bool &quit)
{// waiting for father's writing, now is blockingint command = 0;ssize_t s = read(waitFd, &command, sizeof(command));if (s == 0) // writing's closing{quit = true;return -1;}// promise of correct commandassert(s == sizeof(uint32_t));return command;
}
void distriAndWakeUp(pid_t id, int fd, uint32_t command)
{write(fd, &command, sizeof(command));std::cout << "main process: call procesee:[" << id << "] execute-> " << desc[command] << " through " << fd << std::endl;
}
int main()
{load();std::vector<std::pair<pid_t, int>> slots;for (int i = 0; i < PROCESS_NUM; ++i){int pipefd[2] = {0};int n = pipe(pipefd);assert(n != -1);(void)n;pid_t id = fork();assert(id != -1);if (id == 0){// exit in the process, ineffect of father// child:read// turn down writeclose(pipefd[1]);while (true){// wait commandbool quit = false;int command = waitCommand(pipefd[0], quit);if (quit)break;// coduct commandif (command >= 0 && command < handlerSize()){callbacks[command]();}else{std::cout <<"error command"<< command << std::endl;}}exit(1);}// father:writeclose(pipefd[0]);slots.push_back(std::pair<pid_t, int>(id, pipefd[1]));}// dispatch order//more randomsrand((unsigned long)time(nullptr) ^ getpid());//srand((unsigned long)time(nullptr) ^ getpid() ^ 23323123123L); // 让数据源更随机while (true){// choose a taskint command = rand() % handlerSize();// choose a processint choice = rand() % slots.size();// distribute to a pointed processdistriAndWakeUp(slots[choice].first, slots[choice].second, command);sleep(1);}// close fdfor (const auto &slot : slots){close(slot.second);}// recycle informationfor (const auto &slot : slots){waitpid(slot.first, nullptr, 0);}return 0;
}
  • Task.hpp文件:

    //.hpp include func implementation
    #pragma once#include <iostream>
    #include <unistd.h>
    #include <unordered_map>
    #include <string>
    #include <vector>
    #include <functional>typedef std::function<void()> func;std::unordered_map<int, std::string> desc;
    std::vector<func> callbacks;void readMySQL()
    {std::cout << "sub process[ " << getpid() << " ]Database Access task!\n"<< std::endl;
    }void AnalyseURL()
    {std::cout << "sub process[ " << getpid() << " ]URL Analysis task!\n"<< std::endl;
    }void cal()
    {std::cout << "sub process[ " << getpid() << " ]Encryption task!\n"<< std::endl;
    }void save()
    {std::cout << "sub process[ " << getpid() << " ]Data Persistence task!\n"<< std::endl;
    }void load()
    {// load taskdesc.insert(std::make_pair(callbacks.size(), "readMySQL:Database Access task\n"));callbacks.push_back(readMySQL);desc.insert(std::make_pair(callbacks.size(), "URL Analysis task!\n"));callbacks.push_back(AnalyseURL);desc.insert(std::make_pair(callbacks.size(), "URL Analysis task!\n"));callbacks.push_back(cal);desc.insert(std::make_pair(callbacks.size(), "Data Persistence task!\n"));callbacks.push_back(save);
    }
    // Preview task
    void showHandler()
    {for (const auto &dc : desc){std::cout << dc.first << "\t" << dc.second << std::endl;}
    }
    // task number
    int handlerSize()
    {return callbacks.size();
    }

命名管道

匿名管道是仅限与父子进程通信的渠道,而让没有关系的两个之间通信,可以使用命名管道

  • 命名管道是一种特殊类型的文件,又叫FIFO文件

  • 这种文件不具有文件内容,但具有文件属性,也就是是实实在在存在于磁盘上的文件,但又和匿名管道一样,是内存级的文件,并且不会将数据刷到磁盘上

创建命名管道

  • 命名管道可以从命令行上创建,命令行方法是使用下面这个命令:

    mkfifo filename
    

    image-20231003233654059

    image-20231003233341958

  • 可以从程序创建,相关函数:

    int mkfifo(const char* filename,mode_t mode);
    

    image-20231003233903817

    image-20231003234401601

    mode为文件的默认权限,会受到umask掩码的影响,因此在一个进程中可以将默认掩码设置为0

    命名管道的打开规则

    • 如果当前打开操作是为读而打开FIFO时
      • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
      • O_NONBLOCK enable:立刻返回成功
    • 如果当前打开操作是为写而打开FIFO时、
      • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
      • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

匿名与命名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

用命名管道实现server/client间通信:

  • commu.hpp 文件代码

    #pragma once#include <iostream>
    #include <unistd.h>
    #include <assert.h>
    #include <string>
    #include <string.h>
    #include <sys/wait.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include "Log.hpp"#define SIZE 128
    #define FIFO_MODE 0666 std::string ipcPath="./fifo.ipc";
  • Log.hpp文件代码

    #pragma once#include <iostream>
    #include <time.h>#define DEBUG 0
    #define NOTICE 1
    #define WARNING 2
    #define ERROR 3const std::string msg[] ={"DEBUG","NOTICE","WARNING","ERROR"};std::ostream &Log(const std::string message, int leval)
    {std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[leval] << " | " << message << std::endl;
    }
    
  • client.cc文件代码

    //open fifo -> write message to server
    #include "commu.hpp"int main()
    {//open fifoint fd=open(ipcPath.c_str(),O_WRONLY);assert(fd!=-1);//ipcstd::string buffer;while(true){std::cout<<"Please input the message :> ";std::getline(std::cin,buffer);write(fd,buffer.c_str(),buffer.size());}//close fifoclose(fd);return 0;
    }
    
  • server.cc文件代码

    // make fifo -> open fifo -> read client
    #include "commu.hpp"void getMessage(int fd)
    {char buffer[SIZE];while (true){memset(buffer, 0, sizeof(buffer));ssize_t n = read(fd, buffer, sizeof(buffer) - 1);if (n > 0){if(strcmp(buffer,"quit")==0)break;std::cout << "[" << getpid() << "]"<< "client say: " << buffer << std::endl;}else if (n == 0){std::cout << "[" << getpid() << "]"<< "End of the File, client quit, server quit,too! " << std::endl;break;}else{perror("error");break;}}
    }
    int main()
    {// make fifoint n = mkfifo(ipcPath.c_str(), FIFO_MODE);assert(n != -1);Log("Creat fifo successfully!", DEBUG);// open fifoint fd = open(ipcPath.c_str(), O_RDONLY);assert(fd != -1);Log("Open fifo successfully!", DEBUG);// ipcint nums = 3;for (int i = 0; i < nums; ++i){pid_t id = fork();assert(id != -1);if (id == 0){// child:getMessage(fd);exit(1);}}for (int i = 0; i < nums; ++i){waitpid(-1, nullptr, 0);}// close fifoclose(fd);Log("close fifo successfully!", DEBUG);// delete fifounlink(ipcPath.c_str());Log("delete fifo successfully!", DEBUG);return 0;
    }
  • 运行展示:fifo

由于我设置了三个子进程同时接收,因此收到quit命令时,由于管道是临界资源,只有其中一个进程收到退出命令,其他进程依旧存在,所以需要quit三次才能将服务端退出。也作为一个验证的调试程序,可以自行根据要求修改代码。

用命名管道实现文件拷贝

  • 整体代码只需要对ipc过程进行修改,因此只展示ipc部分代码:

  • server.cpp:

    // ipcint fd_copy=open("test_copy.txt",O_WRONLY | O_CREAT,0666);assert(fd_copy);char msg[SIZE];ssize_t s=read(fd,msg,sizeof(msg)-1);if(s>0){write(fd_copy,msg,s);}
    
  • client.cpp:

    //ipcchar buffer[SIZE];int fd_sorce=open("test.txt",O_RDONLY);assert(fd_sorce);while(true){ssize_t s =read(fd_sorce,buffer,sizeof(buffer)-1);if(s>0){write(fd,buffer,s);}else{//DEBUGbreak;}}
    

image-20231005155144167

客户端运行后,服务端执行完后就立马退出了,而此时对应文件就已经拷贝完成

SystemV共享内存

除了使用管道文件让不同进程间看到同一份资源外,操作系统还专门设计有一种通信方式:System V IPC,其中System V共享内存就是我们要学习的一种临界资源。

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。通俗点理解,使用管道文件时,我们还需要用的系统调用接口来建立管道与使用管道,但共享内存是操作系统已经设计好的一种具有内存块和数据结构的资源,不再需要使用系统调用接口。

  • 共享内存数据结构:

    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 */
    };
    

    可以理解成临界资源从文件转到了内存里。

想要使用共享内存,我们需要经过以下步骤:

  • 创建共享内存
  • 将共享内存段链接到进程地址空间,通信的双方进程都要链接
  • 通信过程
  • 通信结束后,想要回收资源,首先要将共享内存段与当前进程脱离
  • 脱离后回收共享内存段资源

image-20231004154433137

共享内存函数

shmget函数

  • 功能:创建共享内存
int shmget(key_t key,size_t size, int shmflg);
  • 参数:key

    key:不同进程找到相同共享内存段的键值,也就是标识共享内存段的特殊值

    相当于有一扇门,叫做共享内存,而不同进程想要实现通信,就得打开这扇门,而打开这扇门的唯一密码就是key值,其中一个进程设定好key值后,并申请好共享内存空间,另一个进程想要通信,就得拥有相同的键值。键值一般通过算法来转化,我们使用ftok函数来转化获取key

    • key_t ftok(const char* pathname,int proj_id);
      

      image-20231004232418294功能:用一个已存在的可访问的文件的路径名和一个非0的八比特位的整型通过特殊算法转化成IPC键值key

  • 参数:size

    size:共享内存大小,且大小最好为页的整数倍!页的大小:4096字节

  • 参数:shmflg

    shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的

    image-20231004234633077

    • IPC_CREAT单独使用时:创建共享内存时,如果底层已经存在,则获取它,若不存在,则创建它
    • IPC_EXCL单独使用时没用意义
    • IPC_CREAT | IPC_EXCL:一起使用时,如果底层不存在,则创建它,若存在,则出错返回,因此一起使用时并且成功返回时,必定是全新的共享内存
  • 返回值:成功返回一个非负整数,即该共享内存段的标识码,失败返回-1。类比文件成功打开时的文件描述符fd!

shmat函数

  • 功能:将共享内存段连接到进程地址空间
void *shmat(int shmid,const void* shmaddr,int shmflg);
  • 参数:shmid

    shmid:共享内存标识,即shmget函数的返回值,旨在告诉编译器想要链接哪一块被申请的共享内存

  • 参数:shmaddr

    shmaddr:指定连接的地址

    • shmaddr为NULL,核心自动选择一个地址
    • shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
    • shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
    • shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

    说明:一般都为NULL,让系统自由挂接合适的位置

  • 参数:shmflg

    shmflg:它的两个可能取值是SHM_RND和SHM_RONLY

    • image-20231005000003645
    • SHM_RONLY:以只读方式挂接
    • SHM_RND: 若设置了此选项且shmaddr不为NULL,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
    • 0:默认以读写方式挂接
  • 返回值:成功返回一个指针,指向共享内存的第一个节,失败返回-1

shmdt函数

  • 功能:将共享内存段与当前进程脱离,又叫去关联

    int shmdt(const void* shmaddr);
    
  • 参数:shmaddr

    shmaddr:由shmat所返回的指针

  • 返回值:成功返回0;失败返回-1

  • 注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

  • 功能:用于控制共享内存
int shmctl(int shmid,int cmd.struct shmid_ds *buf);
  • 参数:shmid

    shmid:由shmget返回的共享内存标识码

  • 参数:cmd

    cmd:将要采取的动作->三个可取值

  • 参数:buf

    buf:指向一个保存着共享内存模式状态和访问权限的数据结构,若cmd设置为IPC_RMID即删除共享内存段时,buf设为nullptr

  • 返回值:成功返回0;失败返回-1

共享内存的使用

使用共享内存进行server/client 进行ipc的大致框架

  • server.cpp:
// creat shared memory -> link to shared memory -> ipc -> unlink -> delete
int main()
{// creat key for shmkey_t key = ftok(PATH_NAME, PROJ_ID);if (key == -1){exit(1);}// creat shared memoryint shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | MODE);if (shmid == -1){exit(1);}// link to shared memorychar *shmadd = (char *)shmat(shmid, nullptr, 0);if (shmadd == (void *)-1){exit(1);}// ipcwhile (true){//举例//Wait(fd);// printf("%s\n", shmadd);//sleep(1);//if (strcmp(shmadd, "quit") == 0)//   break;}// unlinkint n = shmdt(shmadd);if (n == -1){exit(1);}// removen = shmctl(shmid, IPC_RMID, nullptr);if (n == -1){exit(1);}Closefifo(fd);return 0;
}
  • client.cpp:
// get shared memory -> link to -> unlink
int main()
{// get shared memorykey_t key = ftok(PATH_NAME, PROJ_ID);if (key == -1){exit(1);}int shmid = shmget(key, SHM_SIZE, 0);if (shmid == -1){exit(1);}// link tochar *shmadd = (char *)shmat(shmid, nullptr, 0);if (shmadd == (void *)-1){exit(1);}// ipcwhile (true){//举例// ssize_t s = read(0, shmadd, SHM_SIZE - 1);// if (s > 0)// {//      shmadd[s - 1] = 0;//     Signal(fd);//     if (strcmp(shmadd, "quit") == 0)//         break;// }}Closefifo(fd);// unlinkint n = shmdt(shmadd);if (n == -1){exit(1);}return 0;
}

整体框架就是如此,具体ipc过程可根据需求测试。

命令ipcs -m 可以用来查看此时系统内被申请的共享内存的属性状态

image-20231005002116707

命令ipcrm +shmid也可以用来删除共享内存,但此操作并不会去关联

image-20231005002406748

共享内存解释几个结论

结论1:

  • 只要是通信双方使用shm,一方直接向共享内存中写入数据,另一方,就可以立马看到,因此共享内存是所有进程间通信速度最快的!

    原因:

    • 共享内存进行通信时,中间的拷贝是最少的:下面为管道的拷贝次数

      image-20231005161541739

      在此简单io中,相较于管道,若使用共享内存能减少2次拷贝

结论2:

  • 共享内存缺乏访问控制,会带来并发问题
    相比于管道文件通信方式,管道文件自带同步与互斥机制,因此能够有条不紊的进行,但由于共享内存专注于速度,少了访问控制,因此当多个进程一起看到同一份临界资源时,一旦有数据在临界资源里,这份数据将遭到哄抢,有可能会造成数据丢失或数据不一。

相关文章:

【Linux】学习-进程间通信

进程间通信 介绍 进程间通信的本质 进程间通信的前提&#xff0c;首先需要让不同的进程看到同一块“内存”此“内存”一定不属于任何进程&#xff0c;而应该强调共享二字 进程间通信的目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程 资源共享&#xff1a;…...

45. C++ 字符指针和字符串

一、字符指针可以指向一个字符串。 我们可以用字符串常量对字符指针进行初始化。例如&#xff0c;有说明语句&#xff1a; char *str "This is a string.";是对字符指针进行初始化。此时&#xff0c;字符指针指向的是一个字符串常量的首地址&#xff0c;即指向字符…...

如何制作一款3D FPS游戏

制作一款3D FPS游戏是一个复杂的过程&#xff0c;需要涵盖多个方面&#xff0c;包括游戏设计、游戏引擎选择、模型制作、音效制作、关卡设计等。下面是一个关于如何制作一款3D FPS游戏的超长文章。 游戏设计 首先&#xff0c;你需要确定游戏的整体设计和核心玩法。这包括游戏的…...

人工智能|深度学习——使用多层级注意力机制和keras实现问题分类

代码下载 使用多层级注意力机制和keras实现问题分类资源-CSDN文库 1 准备工作 1.1 什么是词向量? ”词向量”&#xff08;词嵌入&#xff09;是将一类将词的语义映射到向量空间中去的自然语言处理技术。即将一个词用特定的向量来表示&#xff0c;向量之间的距离&#xff08;例…...

C语言常见面试题:C语言中如何进行网页开发编程?

在C语言中进行网页开发通常不是一个直接的过程&#xff0c;因为C语言主要用于系统级编程&#xff0c;而不是Web开发。传统的Web开发主要使用高级语言如JavaScript、Python、Ruby、PHP等&#xff0c;以及与Web相关的技术&#xff0c;如HTML、CSS和数据库。 然而&#xff0c;如果…...

DevOps落地笔记-20|软件质量:决定系统成功的关键

上一课时介绍通过提高工程效率来提高价值交付效率&#xff0c;从而提高企业对市场的响应速度。在提高响应速度的同时&#xff0c;也不能降低软件的质量&#xff0c;这就是所谓的“保质保量”。具备高质量软件&#xff0c;高效率的企业走得更快更远。相反&#xff0c;低劣的软件…...

政安晨:梯度与导数~示例演绎《机器学习·神经网络》的高阶理解

这篇文章确实需要一定的数学基础&#xff0c;第一次接触的小伙伴可以先看一下我示例演绎这个主题的前两篇文章&#xff1a; 示例演绎机器学习中&#xff08;深度学习&#xff09;神经网络的数学基础——快速理解核心概念&#xff08;一&#xff09;&#xff1a; 政安晨&#…...

CTFSHOW命令执行web入门29-54

description: >- 这里就记录一下ctfshow的刷题记录是web入门的命令执行专题里面的题目,他是有分类,并且覆盖也很广泛,所以就通过刷这个来,不过里面有一些脚本的题目发现我自己根本不会笑死。 如果还不怎么知道写题的话,可以去看我的gitbook,当然csdn我也转载了我自己的…...

探索ChatGPT4:新一代人工智能语言模型的突破

ChatGPT4&#xff0c;作为最新一代的语言处理模型&#xff0c;代表了人工智能在自然语言理解和生成方面的最新突破。本文将深入介绍ChatGPT4的新特性&#xff0c;探讨其在各个领域的潜在应用。 ChatGPT4概述 在继承了前一代模型的强大基础之上&#xff0c;ChatGPT4引入了多项…...

PVST详解

PVST&#xff08;Per-VLAN Spanning Tree&#xff09;是Cisco公司的一种扩展的Spanning Tree协议&#xff0c;允许在每个VLAN中独立运行一个Spanning Tree实例&#xff0c;从而提高网络的可靠性和性能。 PVST协议在每个交换机中维护多个Spanning Tree实例&#xff0c;每个实例…...

c++ 子进程交互 逻辑

目录 一、主进程逻辑 1、创建子进程时候,写入自己的HWND 2、响应子进程消息...

C#实现矩阵乘法

目录 一、使用的方法 1.矩阵 2.矩阵的乘法原理 二、实例 1.源码 2.生成效果 一、使用的方法 矩阵相当于一个数组&#xff0c;主要用来存储一系列数&#xff0c;例如&#xff0c;mn矩阵是排列在m行和n列中的一系列数&#xff0c;mn矩阵可与一个np矩阵相乘&#xff0c;结果…...

Objective-C 中的SEL

在 Objective-C 中&#xff0c;SEL&#xff08;Selector&#xff09;是一种用来表示方法的类型。 它实际上是一个指向方法的指针&#xff0c;用于在运行时动态调用方法。 下面是一个使用 SEL 的代码示例&#xff1a; #import <Foundation/Foundation.h>interface MyCl…...

使用 Docker 镜像预热提升容器启动效率详解

概要 在容器化部署中,Docker 镜像的加载速度直接影响到服务的启动时间和扩展效率。本文将深入探讨 Docker 镜像预热的概念、必要性以及实现方法。通过详细的操作示例和实践建议,读者将了解如何有效地实现镜像预热,以加快容器启动速度,提高服务的响应能力。 Docker 镜像预热…...

锁(二)队列同步器AQS

一、队列同步器AQS 1、定义 用来构建锁或者其他同步组件的基础框架&#xff0c;它使用了一个int成员变量表示同步状态&#xff0c;通过内置的FIFO队列来完成资源获取线程的排队工作。是实现锁的关键。 2、实现 同步器的设计是基于模板方法模式的&#xff0c;也就是说&#…...

【知识整理】招人理念、组织结构、招聘

1、个人思考 几个方面&#xff1a; 新人&#xff1a;选、育、用、留 老人&#xff1a;如何甄别&#xff1f; 团队怎么演进&#xff1f; 有没有什么注意事项 怎么做招聘&#xff1f; 2、 他人考虑 重点&#xff1a; 1、从零开始&#xff0c;讲一个搭建团队的流程 2、标…...

监控概述、安装zabbix、配置zabbixagent、添加被控端主机、常用监控指标、自定义监控项

目录 监控概述 监控命令 zabbix 安装zabbix 6.0 配置zabbix监控web1服务器 在web1上安装agent 在web页面中添加对web1的监控 常用监控指标 自定义监控项 实现监控web1用户数量的监控项 在被控端创建key 创建模板 应用模板到主机 查看结果 监控概述 对服务的管理&am…...

恒创科技:香港 BGP 服务器网络连通性如何测试?

随着互联网的快速发展&#xff0c;网络连通性测试变得越来越重要。网络连通性测试的目的是确定网络设备之间的连接是否正常&#xff0c;以及数据包是否能够在网络中顺利传输。本文将介绍一种简单易行的香港 BGP 服务器网络连通性的测试方法&#xff0c;利用tracer测试工具。这里…...

《动手学深度学习(PyTorch版)》笔记7.6

注&#xff1a;书中对代码的讲解并不详细&#xff0c;本文对很多细节做了详细注释。另外&#xff0c;书上的源代码是在Jupyter Notebook上运行的&#xff0c;较为分散&#xff0c;本文将代码集中起来&#xff0c;并加以完善&#xff0c;全部用vscode在python 3.9.18下测试通过&…...

Quicker读取浏览器的书签(包括firefox火狐)

从edge换了火狐&#xff0c;但是quicker不能读取本地的bookmarks文件了&#xff0c;就研究了一下。 方法1&#xff1a;读取本地Bookmarks文件&#xff08;仅谷歌内核浏览器&#xff09; 谷歌内核的浏览器本地会有Bookmarks文件&#xff0c;放了所有的书签数据&#xff0c;直接…...

【数学建模】【2024年】【第40届】【MCM/ICM】【B题 搜寻潜水器】【解题思路】

一、题目 &#xff08;一&#xff09;赛题原文 2024 MCM Problem A: Resource Availability and Sex Ratios Maritime Cruises Mini-Submarines (MCMS), a company based in Greece, builds submersibles capable of carrying humans to the deepest parts of the ocean. A …...

深入探索Redis:如何有效遍历海量数据集

深入探索Redis&#xff1a;如何有效遍历海量数据集 Redis作为一个高性能的键值存储数据库&#xff0c;广泛应用于各种场景&#xff0c;包括缓存、消息队列、排行榜等。随着数据量的增长&#xff0c;如何高效地遍历Redis中的海量数据成为了一个值得探讨的问题。在本篇博客中&am…...

贪心算法之田忌赛马,多种语言实现

目录 题目描述: 输入: 样例输入: 样例输出: c代码实现: c++代码实现: python代码实现: Java代码实现: 题目描述: 这是中国历史上一个著名的故事。 “那是大约2300年前的事了。田骥将军是齐国的高级官员。他喜欢和国王和其他人一起赛马。 “田和王都有三匹不同等级…...

C++ static 修饰全局变量时的作用探究

C static 修饰全局变量时的作用探究 作为一个c开发者&#xff0c;我们面试时经常被问到 static 变量的作用&#xff0c;其中有一个问题是&#xff0c;static 修饰全局变量时起什么作用。 通常我们会回答&#xff0c;“static 修饰全局变量时代表限制这个变量为此源文件可见&a…...

Git的基础操作指令

目录 1 前言 2 指令 2.1 git init 2.2 touch xxx 2.3 git status 2.4 git add xxx 2.5 git commit -m xxxx 2.5 git log及git log --prettyoneline --all --graph --abbrev-commit 2.6 rm xxx 2.7 git reset --hard xxx(含小技巧) 2.8 git reflog 2.9 mv xxx yyy 1…...

前端开发:(四)JavaScript入门

JavaScript是一种强大的脚本语言&#xff0c;用于在网页中实现交互性和动态性。它的发展历史可以追溯到1995年&#xff0c;由Netscape公司的Brendan Eich设计开发而成。JavaScript的重要性在于它能够让网页实现丰富的功能和用户体验&#xff0c;成为Web开发的核心技术之一。 1…...

js文件忽略ESLint语法检查

1. 整个文件忽略 /* eslint-disable */ // 代码开始位置2. 临时禁止 /* eslint-disable */ console.log(hello); /* eslint-enable */3. 对指定规则忽略 /* eslint-disable no-alert, no-console */ alert(foo); console.log(bar); /* eslint-enable no-alert, no-console …...

【深度学习】:实验6布置,图像自然语言描述生成(让计算机“看图说话”)

清华大学驭风计划 因为篇幅原因实验答案分开上传&#xff0c;深度学习专栏持续更新中&#xff0c;期待的小伙伴敬请关注 实验答案链接http://t.csdnimg.cn/bA48U 有任何疑问或者问题&#xff0c;也欢迎私信博主&#xff0c;大家可以相互讨论交流哟~~ 案例 6 &#xff1a;图像自…...

内网安全-内网穿透

目录 内网渗透 Nc使用详解 Nc监听和探测 Nc传文件 termite内网穿透工具 ssh代理内网穿透 ssh配置socket代理 MSF多级网络穿透 内网渗透 Nc使用详解 Nc监听和探测 Nc传文件 termite内网穿透工具 1、termite 之前叫ew &#xff08;可以进行正向连接&#xff0c;可以…...

【Make编译控制 01】程序编译与执行

目录 一、编译原理概述 二、编译过程分析 三、编译动静态库 四、执行过程分析 一、编译原理概述 make&#xff1a; 一个GCC工具程序&#xff0c;它会读 makefile 脚本来确定程序中的哪个部分需要编译和连接&#xff0c;然后发布必要的命令。它读出的脚本&#xff08;叫做 …...

MySQL如何定位慢查询

MySQL中定位慢查询通常涉及到以下几个步骤&#xff1a; 1. 慢查询日志 开启慢查询日志是识别慢查询的第一步。通过设置slow_query_log变量为1&#xff0c;MySQL会记录所有执行时间超过long_query_time秒的查询。 -- 开启慢查询日志 SET GLOBAL slow_query_log ON;-- 设置慢…...

npm 上传一个自己的应用(4) 更新自己上传到NPM中的工具版本 并进行内容修改

前面 npm 上传一个自己的应用(2) 创建一个JavaScript函数 并发布到NPM 我们讲了将自己写的一个函数发送到npm上 那么 如果我们想到更好的方案 希望对这个方法进行修改呢&#xff1f; 比如 我们这里加一个方法 首先 我们还是要登录npm npm login然后 根据要求填写 Username 用…...

Linux开发:PAM1 介绍

PAM(Pluggable Authentication Modules )是Linux提供的一种通用的认证方式,他可以根据需要动态的加载认证模块,从而减少认证开发的工作量以及提供认证的灵活度。 1.PAM的框架 PAM的框架由一下几个部分构成 1)应用程序,即需要使用认证服务的程序,这些应用程序是使用抽象…...

Leetcode 3036. Number of Subarrays That Match a Pattern II

Leetcode 3036. Number of Subarrays That Match a Pattern II 1. 解题思路2. 代码实现 3036. Number of Subarrays That Match a Pattern II 1. 解题思路 这一题其实有点水&#xff0c;因为本质上还是一道套路题目&#xff0c;和前两周的两道题目一样&#xff0c;都是考察的…...

华为环网双机接入IPTV网络部署案例

环网双机接入IPTV网络部署案例 组网图形 图2 环网双机场景IPTV基本组网图 方案简介配置注意事项组网需求数据规划配置思路操作步骤配置文件 方案简介 随着IPTV业务的迅速发展&#xff0c;IPTV平台承载的用户也越来越多&#xff0c;用户对IPTV直播业务的可靠性要求越来越高。…...

“智能检测,精准把控。温湿度检测系统,为您的生活带来全方位的健康保障。”#非标协议项目【上】

“智能检测&#xff0c;精准把控。温湿度检测系统&#xff0c;为您的生活带来全方位的健康保障。”#非标协议项目【上】 前言预备知识1温湿度检测系统需求2.代码整合2.1找到编程实现LCD1602显示一行工程&#xff0c;打开代码文件&#xff0c;将所需的LCD1602驱动代码拷贝到温湿…...

牛客网SQL进阶137:第二快/慢用时之差大于试卷时长一半的试卷

官网链接&#xff1a; 第二快慢用时之差大于试卷时长一半的试卷_牛客题霸_牛客网现有试卷信息表examination_info&#xff08;exam_id试卷ID, tag试卷类别,。题目来自【牛客题霸】https://www.nowcoder.com/practice/b1e2864271c14b63b0df9fc08b559166?tpId240 0 问题描述 试…...

CVE-2022-0760 漏洞复现

CVE-2022-0760 NSS [HNCTF 2022 WEEK2]ohmywordpress 【CVE-2022-0760】 题目描述&#xff1a;flag在数据库里面。 开题&#xff1a; 顺着按钮一直点下去会发现出现一个按钮叫安装WordPress 安装完之后的界面&#xff0c;有一个搜索框。 F12看看network。 又出现了这个Wor…...

WordPress突然后台无法管理问题

登录WordPress后台管理评论&#xff0c;发现点击编辑、回复均无反应。 尝试清除缓存、关闭CF连接均无效。 查看插件时发现关闭wp-china-yes插件可以解决问题。 后来又测试了下发现加速管理后台这项&#xff0c;在启用时会发生点击无效问题&#xff0c;禁用就好了&#xff0c;不…...

STM32F1 - 标准外设库_规范

STM32F10x_StdPeriph_Lib_V3.6.0 1> 头文件包含关系2> .c文件内部结构3> 宏定义位置4> 位掩码bit mask5> .c文件中定义私有变量6> 枚举类型定义 1> 头文件包含关系 1个头文件stm32f10x.h 就把整个MCU以及标准外设库&#xff0c;就管理了&#xff1b; 2>…...

推荐系统|召回04_离散特征处理

离散特征处理 离散特征是什么 怎么处理离散特征 One-hot编码 Embedding嵌入 从one-hot到Embedding&#xff0c;已经节省了很多的存储空间&#xff0c;但当数据量大的时候&#xff0c;还是占空间&#xff0c;所以工业界仍会对Embedding进行优化 而一个物品所对应的Embedding参数…...

一个查看armv8系统寄存器-值-含义的方式

找到解压后的SysReg_xml_v86A-2019-12目录 wget https://developer.arm.com/-/media/developer/products/architecture/armv8-a-architecture/2019-12/SysReg_xml_v86A-2019-12.tar.gz wget https://developer.arm.com/-/media/developer/products/architecture/armv8-a-archi…...

LLMs之miqu-1-70b:miqu-1-70b的简介、安装和使用方法、案例应用之详细攻略

LLMs之miqu-1-70b&#xff1a;miqu-1-70b的简介、安装和使用方法、案例应用之详细攻略 目录 miqu-1-70b的简介 miqu-1-70b的安装和使用方法 1、安装 2、使用方法 miqu-1-70b的案例应用 miqu-1-70b的简介 2024年1月28日&#xff0c;发布了miqu 70b&#xff0c;潜在系列中的…...

npm 下载报错

报错信息 : 证书过期 (CERT_HAS_EXPIRED) D:\Apps\nodejs-v18.16.1\npx.cmd --yes create-next-app"latest" . --ts npm ERR! code CERT_HAS_EXPIRED npm ERR! errno CERT_HAS_EXPIRED npm ERR! request to https://registry.npm.taobao.org/create-next-app failed…...

GPT-4登场:多模态能力革新,提升ChatGPT与必应体验,开放API助力游戏革新

GPT-4登场&#xff1a;多模态能力革新&#xff0c;提升ChatGPT与必应体验&#xff0c;开放API助力游戏革新 引言 在人工智能领域&#xff0c;GPT-4的发布标志着一个新时代的到来。这一多模态大模型不仅在技术性能上实现了飞跃&#xff0c;更在功能层面带来全新的突破。GPT-4的…...

【芯片设计- RTL 数字逻辑设计入门 11.1 -- 状态机实现 移位运算与乘法 1】

文章目录 移位运算与乘法状态机简介SystemVerilog中的测试平台VCS 波形仿真 阻塞赋值和非阻塞赋值有限状态机&#xff08;FSM&#xff09;与无限状态机的区别 本篇文章接着上篇文章【芯片设计- RTL 数字逻辑设计入门 11 – 移位运算与乘法】 继续介绍&#xff0c;这里使用状态机…...

MongoDB系列:管道操作:聚合阶段操作符(二)

MongoDB系列&#xff1a;管道操作&#xff1a;聚合阶段操作符&#xff08;二&#xff09; 聚合阶段操作符介绍 本节只编写了个人认为可能用到的操作符&#xff0c;详细更多的操作符以及使用注意事项请前往MongoDB官网。 $match 过滤匹配数据。 // 插入数据 db.orders.inse…...

C++ //练习 5.12 修改统计元音字母的程序,使其能统计以下含有两个字符的字符序列的数量:ff、fl和fi。

C Primer&#xff08;第5版&#xff09; 练习 5.12 练习 5.12 修改统计元音字母的程序&#xff0c;使其能统计以下含有两个字符的字符序列的数量&#xff1a;ff、fl和fi。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /****…...

C语言-----自定义类型-----结构体枚举联合

结构体和数组一样&#xff0c;都是一群数据的集合&#xff0c;不同的是数组当中的数据是相同的类型&#xff0c;但是结构体中的数据类型可以不相同&#xff0c;结构体里的成员叫做成员变量 结构体类型是C语言里面的一种自定义类型&#xff0c;我们前面已经了解到过int,char,fl…...

elasticsearch下载及可视化工具下载使用

elasticsearch下载及配置、启动 一、下载 Download Elasticsearch | Elastic 二、启动 双击bat即可。 出现如下说明启动成功&#xff1a; 访问测试&#xff1a; 三、注意 &#xff08;1&#xff09;因为es启动默认端口是&#xff1a;9200,所以需要检查此端口是否被占用。…...