> 文章列表 > Linux -- 进程间通信

Linux -- 进程间通信

Linux -- 进程间通信

文章目录

  • 1. vscode软件下载和使用
    • 1.1 下载
      • 1.1.1 解决下载慢问题
      • 1.1.2 推荐下载链接
    • 1.2 vscode是什么
    • 1.3 Windows本地vscode使用
    • 1.4 远程连接linux
    • 1.5 推荐插件
  • 2. 进程间通信目的
  • 3. 为什么需要通信
  • 4. 匿名管道
    • 4.1 原理
    • 4.2 代码案例
    • 4.3 玩一玩(进程池)
      • 4.3.1 模型
      • 4.3.2 代码
  • 5. 命名管道
    • 5.1 概念
    • 5.2 系统调用
    • 5.3 不同进程间通信
    • 5.4 命名管道实现进程池
  • 6. 共享内存
    • 6.1 概念
    • 6.2 原理
    • 6.3 共享内存数据结构
    • 6.4 认识接口
    • 6.5 代码

1. vscode软件下载和使用

1.1 下载

1.1.1 解决下载慢问题

链接:https://blog.csdn.net/wang13679201813/article/details/125367532

1.1.2 推荐下载链接

链接:https://vscode.cdn.azure.cn/stable/30d9c6cd9483b2cc586687151bcbcd635f373630/VSCodeUserSetup-x64-1.68.1.exe

1.2 vscode是什么

vscode(visual studio code)是一个编辑器,不是编译器,这里我们使用vscode+centos:也就是windows+linux开发,使用C++语言,这里用vscode取代Linux的vim。

1.3 Windows本地vscode使用

刚下载的桌面就是这样的:

image-20230405122018828
Linux -- 进程间通信

打开目录后按照桌面对应路径找到刚刚创建的文件,打开即可。

Linux -- 进程间通信

通过上面这个框就可以新建文件和新建目录等操作。这里不支持编译和运行。

1.4 远程连接linux

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VmDdkau8-1681550357620)(https://jinyinhan.oss-cn-beijing.aliyuncs.com/QQ截图20230405132005.png)]

Linux -- 进程间通信

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EYMrX7wf-1681550357621)(https://jinyinhan.oss-cn-beijing.aliyuncs.com/QQ截图20230405132821.png)]

重启后就可以看到对应的SSH下就有对应的主机。后续就是连接新的Window,然后选择Linux,输入密码就OK了。连接成功后,Linux主机名会显示一个绿色的勾勾。后续操作按照对应的来就OK了,不演示了。

远程运行直接使用快捷方式:ctrl+s,或者点击最上面框架中的Terminal终端,new一个新的终端即可。

1.5 推荐插件

  1. C/C++
  2. C/C++ Extension Pack
  3. C/C++ Themes
  4. Chinese
  5. vscode-icons
  6. file-size
  7. GBK to UTF8 for vscode
  8. GDB Dubug(不建议使用vscode搭建的远程调试),这里最好使用Linux的gdb调试

2. 进程间通信目的

  1. 数据传输(一个进程需要把自己的数据传输给另外一个进程)
  2. 资源共享(多个进程之间共享同样的资源)
  3. 通知事件(一个进程需要向另外一个或多个进程发送消息,通知它们发生某种事件)
  4. 进程控制(有些进程需要完全控制另外一个进程的执行)

3. 为什么需要通信

进程具有独立性,需要让独立进程通信就需要成本。不能让一个进程直接访问另外一个进程的"资源",不能违背进程具有独立性,所以让两个程序通信前提条件是:先让两个进程看到同一份“资源”,不直接访问,"资源"就由操作系统来提供!

4. 匿名管道

管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

[jyh@VM-12-12-centos study17]$ who //查看当前正在登陆的用户
jyh      pts/0        2023-04-05 16:30 (171.113.60.75) 
[jyh@VM-12-12-centos study17]$ who | wc -l //wc是用来统计个数
1
[jyh@VM-12-12-centos study17]$ 

上述who是一个命令,本质它是一个进程,一个bash的子进程,wc同样是一个bash的子进程,通过管道的方式把who进程的数据也让wc进程看到。

Linux -- 进程间通信

如何理解呢?首先Linux下一切皆文件,管道也是文件,who进程一般都是从标准输出中获取的信息,wc进程一般都是从标准输入中获取信息的,将who进程的标准输出重定向到管道这个文件中,wc进程则将标准输入重定向到管道文件中,所以这样就可以实现两个进程看到同一份“资源”。

4.1 原理

Linux -- 进程间通信

创建子进程,只会复制进程相关的数据结构对象,并不会拷贝父进程的文件对象。所以上述两个进程中文件描述符表中每个指针的指向的都是同一个文件对象。这让就可以解析一个现象:fork之后,父子进程会向同一个显示器打印数据的原因。这里OS提供的内存文件就是管道文件,这样就可以达成共享同一"资源"。(这里管道文件不是放在磁盘中的,是一个内存文件,它只是支持单向通信)。另外管道文件是确定数据流向的,关闭不需要的fd,也就是当who进程进行write的时候,wc进程进行read的时候,此时who进程就会关闭对应的read对应的描述符,wc进程就会关闭对应的write对应的描述符,就可以解析清楚上一个管道的案例的现象了。(这里的这个管道也就是匿名管道,不知道它对应的路径,也不知道对应的文件名等等。

4.2 代码案例

选项 pipe()系统调用
声明 int pipe(int pipefd[2]);
头文件 #include <unistd.h>
作用 创建管道
详细描述 Pipe()创建一个管道,这是一个单向数据通道,可用于进程间通信。数组pipefd用于返回2引用管道两端的文件描述符。Pipefd[0]表示管道的读端Pipefd[1]表示写端管道。写入管道写端的数据被内核缓冲,直到从管道的读端读取。
返回值 如果成功,则返回0。如果出现错误,则返回-1,并适当地设置errno。
#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <cstdlib>
#include <string>
#include <stdio.h>
#include <sys/types.h>
using namespace std;
int main()
{//1. 创建管道int pipefd[2] = {0};int fd_result = pipe(pipefd);if(fd_result == -1){cout << "call pipe() is failed" << "errno=" << errno << ":" << strerror(errno) << endl;}cout << "pipefd[0]:" << pipefd[0] << endl;  //fd:3 -> 读端 cout << "pipefd[1]:" << pipefd[1] << endl;  //fd:4 -> 写端//2. 创建子进程pid_t id = fork();assert(id != -1);if(id == 0) //child(写入){close(pipefd[0]); //关闭读端//4. 开始通信const string name_str = "hello, i am child";int cnt = 1;char buffer_child[1024];while(true){snprintf(buffer_child, sizeof(buffer_child), "name:%s, cnt:%d, my_pid:%d", name_str.c_str(), cnt++, getpid());write(pipefd[1], buffer_child, strlen(buffer_child));sleep(1);}exit(0);    }//parent(读取)://3. 关闭不需要的fd(父进程读取,子进程写入)close(pipefd[1]); //关闭写端char buffer_parent[1024];while(true){int n = read(pipefd[0], buffer_parent, sizeof(buffer_parent) - 1);if(n == -1){cout << "call read() is failed" << "errno=" << errno << ":" << strerror(errno) << endl;}buffer_parent[n] = '\\0';cout << "i am parent, child give me message:" << buffer_parent << endl;}return 0;
}

五个特点

  1. 管道是单向通信
  2. 管道本质是文件,文件描述符声明周期是随进程的,管道的声明周期是随进程的
  3. 不仅父子进程可以通信,爷孙进程也可以通信,兄弟进程也可以通信,所以管道通信通常用来具有"血缘关系"的进程进行进程间通信。(pipe()打开管道并不清楚管道名字 --> 匿名管道)
  4. 写入的次数和读取的次数不是严格匹配的,可能一次写的东西分很多次读,可能多次写的东西一次读取,读写没有强相关
  5. 如果一个进程read端读取完管道所有数据,对方如果不发,那么只能等待;如果write端写满管道了,不能够写了;管道具有一定的协同能力,让读端和写端能够按照顺序通信(自带同步机制)

两个场景

  1. 关闭写端,读端没有关闭
#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <cstdlib>
#include <string>
#include <stdio.h>
#include <sys/types.h>
using namespace std;
int main()
{//1. 创建管道int pipefd[2] = {0};int fd_result = pipe(pipefd);if(fd_result == -1)
{cout << "call pipe() is failed" << "errno=" << errno << ":" << strerror(errno) << endl;}cout << "pipefd[0]:" << pipefd[0] << endl;  //fd:3 -> 读端 cout << "pipefd[1]:" << pipefd[1] << endl;  //fd:4 -> 写端//2. 创建子进程pid_t id = fork();assert(id != -1);if(id == 0) //child(写入){close(pipefd[0]); //关闭读端//4. 开始通信int cnt = 0;while(true){char x = 'A';write(pipefd[1], &x, 1);cout << "cnt:" << cnt << endl;sleep(3);break;}close(pipefd[1]);exit(0);    }//parent(读取)://3. 关闭不需要的fd(父进程读取,子进程写入)close(pipefd[1]); //关闭写端char buffer_parent[1024];while(true){int n = read(pipefd[0], buffer_parent, sizeof(buffer_parent) - 1);if(n == -1){cout << "read is anomaly" << endl;}if(n == 0){cout << "read end of file" << endl;break;}buffer_parent[n] = '\\0';cout << "i am parent, child give me message:" << buffer_parent << endl;}close(pipefd[0]);return 0;
}
//输出结果:
//pipefd[0]:3
//pipefd[1]:4
//i am parent, child give me message:A
//cnt:0
//read end of file

所以,关闭写端,读端没有关闭,读端再去读取数据,read就会返回0,那么就读到了文件结尾。

  1. 关闭读端,写端没有关闭
#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <cstdlib>
#include <string>
#include <stdio.h>
#include <sys/types.h>
using namespace std;
int main()
{//1. 创建管道int pipefd[2] = {0};int fd_result = pipe(pipefd);if(fd_result == -1)
{cout << "call pipe() is failed" << "errno=" << errno << ":" << strerror(errno) << endl;}cout << "pipefd[0]:" << pipefd[0] << endl;  //fd:3 -> 读端 cout << "pipefd[1]:" << pipefd[1] << endl;  //fd:4 -> 写端//2. 创建子进程pid_t id = fork();assert(id != -1);if(id == 0) //child(写入){close(pipefd[0]); //关闭读端//4. 开始通信int cnt = 0;while(true){char x = 'A';write(pipefd[1], &x, 1);cout << "cnt:" << cnt << endl;sleep(1);}close(pipefd[1]);exit(0);    }//parent(读取)://3. 关闭不需要的fd(父进程读取,子进程写入)close(pipefd[1]); //关闭写端char buffer_parent[1024];while(true){//sleep(10);int n = read(pipefd[0], buffer_parent, sizeof(buffer_parent) - 1);if(n == -1){cout << "read is anomaly" << endl;break;}if(n == 0){cout << "read end of file" << endl;break;}buffer_parent[n] = '\\0';cout << "i am parent, child give me message:" << buffer_parent << endl;sleep(1);break;}close(pipefd[0]);return 0;
}
//输出结果:
//pipefd[0]:3
//pipefd[1]:4
//i am parent, child give me message:A
//cnt:0

关闭读端,写端没有关闭,这样就没有意义,操作系统不会维护不会浪费资源,OS会通过信号直接kill掉一直在写入的进程。

4.3 玩一玩(进程池)

4.3.1 模型

Linux -- 进程间通信

4.3.2 代码

  • processPool.hpp
#include <iostream>
#include <assert.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>class manage_child;
class Task;#define CHILD_PROCESS_NUM 3void create_frame(std::vector<manage_child>& manage);
void control_child_process(const std::vector<manage_child>& manage);
void recycle_process(const std::vector<manage_child>& manage);class manage_child
{
public:manage_child(int fd, int id):_write_fd(fd),_child_id(id){}~manage_child(){}
public:int _write_fd;pid_t _child_id;
};typedef void(*fun_p)();
#define GET_NET 1
#define POLL_SERVER 2
#define PUSH_SOURCE 3
void get_net();
void poll_server();
void push_source();
class Task
{
public:Task(){funcs.push_back(get_net);funcs.push_back(poll_server);funcs.push_back(push_source);}void execute_task(int option){assert(option >= 1 && option <= 3);funcs[option - 1]();}~Task(){}
private:std::vector<fun_p> funcs;
};
  • main.cc
#include "processPool.hpp"int main()
{//创建框架:一个父进程控制多个子进程(父进程写,子进程读)std::vector<manage_child> manage; create_frame(manage);//父进程控制任意子进程执行任务control_child_process(manage);//回收子进程并关闭管道recycle_process(manage);return 0;
}
  • processPool.cc
#include "processPool.hpp"void get_net()
{std::cout << "子进程:PID:" << getpid() <<  "获取网络资源中........." << std::endl;
}
void poll_server()
{std::cout << "子进程:PID:" << getpid() <<  "下载服务资源中........" << std::endl;
}
void push_source()
{std::cout << "子进程:PID:" << getpid() <<  "发送服务资源中........" << std::endl;
}void execute_command(int read_fd)
{Task task;while(true){int command = 0;int read_return = read(read_fd, &command, sizeof(int));if(read_return == sizeof(int)){//执行任务task.execute_task(command);}else{break;}}
}void create_frame(std::vector<manage_child>& manage)
{std::vector<int> fds;for(int i = 0; i < CHILD_PROCESS_NUM; ++i){ //创建管道int pipefd[2];int pipe_return = pipe(pipefd);assert(pipe_return == 0);(void)pipe_return;//创建子进程pid_t id = fork();assert(id != -1);if(id == 0){ //子进程 - 读for(auto& fd : fds) close(fd); //关闭子进程拷贝父进程的写端close(pipefd[1]);//dup2(pipefd[0], 0);execute_command(pipefd[0]);close(pipefd[0]);exit(0);}//父进程close(pipefd[0]);//父进程对子进程和写端组织管理manage.push_back(manage_child(pipefd[1], id)); fds.push_back(pipefd[1]);}
}void show_option(){std::cout << "-----------------------------------------" << std::endl;std::cout << "---  1. 获取网络资源   2. 下载服务资源  ---" << std::endl;std::cout << "---  3. 发送服务资源   4. 退出服务系统  ---" << std::endl;std::cout << "-----------------------------------------" << std::endl;
}void control_child_process(const std::vector<manage_child>& manage)
{while(true){//选择任务show_option();  std::cout << "请选择->";int command = 0;std::cin >> command;if(command == 4) break;if(command < 1 || command > 3) continue;//选择进程int rand_index = rand() % manage.size();std::cout << "被选中的子进程:" << manage[rand_index]._child_id << std::endl;//分发任务write(manage[rand_index]._write_fd, &command, sizeof(command));sleep(1);}   
}void recycle_process(const std::vector<manage_child>& manage)
{for(int i = 0; i < manage.size(); ++i){std::cout << "父进程让子进程退出" << manage[i]._child_id << std::endl;close(manage[i]._write_fd);waitpid(manage[i]._child_id, nullptr, 0); //阻塞式等待std::cout << "父进程回收子进程:" << manage[i]._child_id << std::endl;}sleep(5); //父进程退出
}

5. 命名管道

5.1 概念

命名管道(named pipe)又被称为先进先出队列(FIFO),是一种特殊的管道,存在于文件系统中。命名管道与管道非常类似,但是又有自身的显著特征:

  • 命名管道可以用于任何两个进程间的通信,而不限于同源的两个进程。
  • 命名管道作为一种特殊的文件存放在文件系统中,而不是像匿名管道那样存放在内核中。当进程对命名管道的使用结束后,命名管道依然存在于文件系统中,除非对其进行删除操作,否则该命名管道不会自行消失。

5.2 系统调用

选项 mkdifo()系统调用
声明 int mkfifo(const char *pathname, mode_t mode);
头文件 <sys/types.h>、 <sys/stat.h>
作用 创建一个命名管道
返回值 成功返回0,失败返回-1
选项 unlink()系统调用
声明 int unlink(const char *pathname);
头文件 <unistd.h>
作用 删除一个指向这个文件名的文件
返回值 成功返回0,失败返回-1

5.3 不同进程间通信

  • common.hpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
#include <strings.h>const std::string path_name = "./fifo";
#define MODE 0664
  • client.cc
#include "common.hpp"int main()
{int open_fd = open(path_name.c_str(), O_WRONLY);assert(open_fd != -1);while(true){char buffer[1024];if(strcasecmp(buffer, "quit") == 0){break;}std::cout << "请输入:>";char* message = fgets(buffer, sizeof(buffer), stdin);buffer[strlen(buffer) - 1] = '\\0';assert(message != nullptr);(void)message;write(open_fd, buffer, strlen(buffer));}close(open_fd);return 0;
}
  • serve.cc
#include "common.hpp"int main()
{//创建命名管道umask(0);int mkfifo_ret = mkfifo(path_name.c_str(), MODE);assert(mkfifo_ret != -1);//打开并读取int open_fd = open(path_name.c_str(), O_RDONLY);assert(open_fd != -1);while(true){char buffer[1024];ssize_t size = read(open_fd, buffer, sizeof(buffer) - 1);if(size > 0){buffer[size] = '\\0';std::cout << buffer << std::endl;}else{break;}}close(open_fd);//关闭命名管道int unlink_ret = unlink(path_name.c_str());assert(unlink_ret != -1);return 0;
}

5.4 命名管道实现进程池

  • control_center.cc
#include "namepipe_pool.hpp"void create_namepipe()
{int fifo_net_request_ret = mkfifo(fifo_net_request.c_str(), MODE);assert(fifo_net_request_ret != -1);int fifo_dispose_data_ret = mkfifo(fifo_dispose_data.c_str(), MODE);assert(fifo_dispose_data_ret != -1);int fifo_manage_system_ret = mkfifo(fifo_manage_system.c_str(), MODE);assert(fifo_manage_system_ret != -1);
}Manage_fifo manage_namepipe()
{std::cout << "打开fifo_net_request......" << std::endl;sleep(1);int fifo_net_request_fd = open(fifo_net_request.c_str(), O_RDWR);assert(fifo_net_request_fd != -1);std::cout << "打开fifo_dispose_data......." << std::endl;sleep(1);int fifo_dispose_data_fd = open(fifo_dispose_data.c_str(), O_RDWR);assert(fifo_dispose_data_fd != -1);std::cout << "打开fifo_manage_system........" << std::endl;sleep(1);int fifo_manage_system_fd = open(fifo_manage_system.c_str(), O_RDWR);assert(fifo_manage_system_fd != -1);Manage_fifo center(   fifo(fifo_net_request_fd, fifo_net_request), fifo(fifo_dispose_data_fd,fifo_dispose_data), fifo(fifo_manage_system_fd,fifo_manage_system));std::cout << "命名管道全部打开!" << std::endl;return center;
}void task_menu()
{std::cout << "----------------------------------------------" << std::endl;std::cout << "-- 1.网络请求                     2.数据处理 --" << std::endl;std::cout << "-- 3.系统管理                     4.退出中控 --" << std::endl;std::cout << "----------------------------------------------" << std::endl;
}void write_fifo(int& command, Manage_fifo& center)
{int buf = command;for(int i = 0; i < PROCESS_NUM; ++i){if(command - 1 == i){write(center._fifos[i]._fifo_fd, &buf, sizeof(buf));break;}}
}void close_fifo(Manage_fifo& center)
{for(int i = 0; i < PROCESS_NUM; ++i){close(center._fifos[i]._fifo_fd);unlink(center._fifos[i]._fifo_name.c_str());}
}//主控进程管理命令管道写入->任务进程读取执行
int main()
{//1. 创建管道文件create_namepipe(); //2. 打开文件做管理Manage_fifo center;try{center = manage_namepipe(); //TODO(拷贝构造)}catch(const std::exception& e){std::cerr << e.what() << '\\n';}std::cout << "开始执行任务" << std::endl;//3. 通过选择执行任务while(true){//1. 选择执行任务// > 列出菜单task_menu();// > 输入int command = 0;std::cout << "请求输入#";std::cin >> command;if(command == 4){break;}if(command < 1 || command > 4){std::cout << "输入错误,请重新输入!";continue;}//2. 执行选择写入对应进程write_fifo(command, center);sleep(1);}//3. 关闭所有命名管道close_fifo(center);(void)center;return 0;
}
  • namepipe_pool.hpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <vector>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#include <assert.h>//管道名
std::string fifo_net_request = "./fifo_net_request";
std::string fifo_dispose_data = "./fifo_dispose_data";
std::string fifo_manage_system = "./fifo_manage_system";
#define MODE 0664
#define FIFO_NET_REQUEST 1
#define FIFO_DISPOSE_DATA 2
#define FIFO_MANAGE_SYSTEM 3
#define PROCESS_NUM 3class fifo
{
public:fifo(const int& fd, std::string& name):_fifo_fd(fd),_fifo_name(name){}fifo(const fifo& x){_fifo_fd = x._fifo_fd;_fifo_name = x._fifo_name;}~fifo(){}
public:int _fifo_fd;std::string _fifo_name;
};class Manage_fifo
{
public:Manage_fifo(){}Manage_fifo(const fifo fd1, const fifo fd2, const fifo fd3){_fifos.push_back(fd1);_fifos.push_back(fd2);_fifos.push_back(fd3);}// Manage_fifo(const Manage_fifo& center)// {// }~Manage_fifo(){}
public:std::vector<fifo> _fifos;
};
  • net_request.cc
#include "namepipe_pool.hpp"int main()
{int fd = open(fifo_net_request.c_str(), O_RDWR);assert(fd != -1);int buf = 0;ssize_t size = read(fd, &buf, sizeof(buf));assert(size > 0);if(buf == FIFO_NET_REQUEST){int cnt = 5;while(cnt > 0){printf("网络正在请求中..............(请等待<%d>秒钟)\\n", cnt--);sleep(1);}}    if(size == -1){std::cout << "net_request进程读取错误";exit(0);}return 0;
}
  • dispose_data.cc
#include "namepipe_pool.hpp"int main()
{int fd = open(fifo_dispose_data.c_str(), O_RDWR);assert(fd != -1);int buf = 0;ssize_t size = read(fd, &buf, sizeof(buf));assert(size >= 0);if(buf == FIFO_DISPOSE_DATA){int cnt = 5;while(cnt > 0){printf("数据库正在处理数据中..............(请等待<%d>秒钟)\\n", cnt--);sleep(1);}}    if(size == -1){std::cout << "dispose_data进程读取错误";exit(0);}return 0;
}
  • manage_system.cc
#include "namepipe_pool.hpp"int main()
{int fd = open(fifo_manage_system.c_str(), O_RDWR);assert(fd != -1);int buf = 0;while(true){ssize_t size = read(fd, &buf, sizeof(buf));assert(size >= 0);if(buf == FIFO_MANAGE_SYSTEM){int cnt = 5;while(cnt > 0){printf("系统正在管理各个子系统中..............(请等待<%d>秒钟)\\n", cnt--);sleep(1);}break;} if(size == -1){std::cout << "manage_system进程读取错误";exit(0);} }return 0;
}
  • makefile
.PHONY:all
all:control_center manage_system net_request dispose_datacontrol_center:control_center.ccg++ -o $@ $^ -std=c++11 -g
manage_system:manage_system.ccg++ -o $@ $^ -std=c++11
net_request:net_request.ccg++ -o $@ $^ -std=c++11 -g
dispose_data:dispose_data.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f control_center manage_system net_request dispose_data rm -f fifo_*

6. 共享内存

6.1 概念

共享内存是System V版本的最后一个进程间通信方式,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

注意:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取,所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量。

6.2 原理

Linux -- 进程间通信

当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。但是,我们要确保一个进程在写的时候不能被读,因此我们使用信号量来实现同步与互斥。

6.3 共享内存数据结构

共享内存可能被多个进程使用会有多份共享内存,那么OS就需要对共享内存做管理,就需要对应的描述(结构体)来组织进行管理

ipcrm -m [shmid]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 */
};

6.4 认识接口

  • 命令
    1. ipcs -m :显示共享内存段
    2. ipcrm -m [shmid] : 删除共享内存段
选项 shmget()系统调用
声明 int shmget(key_t key, size_t size, int shmflg);
头文件 <sys/ipc.h> <sys/shm.h>
作用 分配System V共享内存段
返回值 成功返回标识符,失败返回-1
选项 ftok()系统调用
声明 key_t ftok(const char *pathname, int proj_id);
头文件 <sys/types.h> <sys/ipc.h>
作用 ftok()函数使用给定路径名(必须指向一个现有的、可访问的文件)和最低有效值8个比特位的文件的标识符位的proj_id(必须非零)来生成一个key_t类型的System V IPC密钥
返回值 成功返回生成的key值,失败返回-1
选项 shmctl()系统调用
声明 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
头文件 <sys/ipc.h> <sys/shm.h>
作用 控制共享内存
返回值 失败返回-1
选项 shmdt()系统调用
声明 int shmdt(const void *shmaddr);
头文件 <sys/types.h> <sys/shm.h>
作用 去掉进程和该共享内存的关联
返回值 成功返回0,失败返回-1
选项 shmat()系统调用
声明 void *shmat(int shmid, const void *shmaddr, int shmflg);
头文件 <sys/types.h> <sys/shm.h>
作用 进程和该共享内存关联
返回值 成功返回共享内存起始虚拟地址,失败返回-1

6.5 代码

  • share_memory.hpp
#ifndef __SHARE_MEMORY_HPP__
#define __SHARE_MEMORY_HPP__#include <iostream>
#include <assert.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <cstring>
#include <string>
#include <cstdio>
#include <sys/stat.h>
#include <sys/shm.h>
#include <unistd.h>#define PATH_NAME "."
#define PRO_ID 0xff
#define MODE 0664const int size = 4096;key_t get_key()
{key_t key = ftok(PATH_NAME, PRO_ID);if(key == -1){printf("errno:%d, error:%s\\n", errno, strerror(errno));exit(1);}return key;
}std::string to_16Hex(int x)
{char buf[64];snprintf(buf, sizeof(buf), "0x%x", x);return buf;
}int select_shm(key_t& key, const int& size, int flags)
{//创建shmint shm_id = shmget(key, size, flags);if(shm_id == -1){printf("errno:%d, error:%s\\n", errno, strerror(errno));exit(2);}return shm_id;
}int create_shm(key_t& key, const int& size)
{umask(0);return select_shm(key, size, IPC_CREAT | IPC_EXCL | MODE);
}int get_shm(key_t& key, const int& size)
{return select_shm(key, size, IPC_CREAT);
}void delete_shm(int& shm_id)
{int ret = shmctl(shm_id, IPC_RMID, nullptr);assert(ret != -1);
}char* attach_shm(int shm_id)
{char* start = (char*)shmat(shm_id, nullptr, 0);assert(start != nullptr);printf("该进程%d成功和shm:%d关联!\\n", getpid(), shm_id);return start;
}void nattach_shm(char* start, int& shm_id)
{int ret = shmdt(start);assert(ret != -1);printf("该进程%d成功和shm:%d去关联!\\n", getpid(), shm_id);
}#define SERVE 1
#define CLIENT 2class share_memory
{
public:share_memory(int id):_identity(id),_start(nullptr),_shm_id(-1){key_t key =  get_key();if(_identity == SERVE){_shm_id = create_shm(key, size);}else{_shm_id = get_shm(key, size);}_start = attach_shm(_shm_id);}char* get_address(){return _start;}~share_memory(){nattach_shm(_start, _shm_id);if(_identity == SERVE){delete_shm(_shm_id);}}
private:char* _start;int _identity;int _shm_id;
};#endif
  • serve.cc
#include "share_memory.hpp"//服务端读 -- 客户端写
int main()
{share_memory serve(SERVE);char* start = serve.get_address();int cnt = 0;while(cnt <= 26){std::cout << "client -> serve#" << start << std::endl;sleep(1);++cnt;}return 0;
}
  • client.cc
#include "share_memory.hpp"int main()
{share_memory client(CLIENT);char* start = client.get_address();char c = 'A';while(c <= 'Z'){start[c - 'A'] = c;++c;start[c - 'A'] = '\\0';sleep(1);}return 0;
}
  • makefile
.PHONY:all
all: client serveclient:client.ccg++ -o $@ $^ -std=c++11
serve:serve.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f client serve