🐶博主主页:@ᰔᩚ. 一怀明月ꦿ
❤️🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C++,linux
🔥座右铭:“不要等到什么都没有了,才下定决心去做”
🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀
目录
可执行程序加载的时候,动态库也需要加载
符号表
在磁盘中,习惯叫逻辑地址:起始地址+偏移量
进程间通信
进程间通信的目的
进程间通信的本质
匿名管道通信
pipe()
进程池
to_string
function()>
匿名管道的实现
processpool.cc
task.hpp
Makefile
可执行程序加载的时候,动态库也需要加载
动态库加载的时候,会从磁盘写入内存,然后通过页表映射到进程虚拟地址空间中的共享区中
我们要做到让库在共享区的任意位置,都可以正确运行
符号表
在 Linux 中,符号表是一个记录了程序中各个函数、变量以及其他符号的名称和地址映射关系的数据结构。在编译程序时,编译器会生成符号表并将其嵌入到可执行文件中。当程序运行时,操作系统会将符号表加载到内存中,并使用它来解析程序中的符号引用
如果一个程序运行时已经加载了一个动态库 libxxx.so,然后另一个程序运行时,该程序也需使用动态库libxxx.so,那么该程序还需要加载动态库 libxxx.so吗?
不需要。因为我们动态库 libxxx.so已经加载到内存中,我们只需要使用该进程的虚拟地址空间去映射内存中的已经加载的动态库 libxxx.so
所以不管多少个进程使用同一个动态库,内存中只需要一份动态库
在磁盘中,习惯叫逻辑地址:起始地址+偏移量
当一个程序被执行时,它的可执行文件(通常是 ELF 格式)会被加载到内存中的代码段(text segment)。这个可执行文件可能会引用一些静态库,这些静态库也会被加载到内存中。
静态库是在编译时将库的代码和数据包含在可执行文件中的。当程序加载到内存时,操作系统会将静态库的内容复制到进程的地址空间中,使程序能够访问静态库中的函数和数据。这样,程序就可以直接调用静态库中定义的函数,而不需要在运行时再去查找和加载动态库。
当一个程序被执行时,它的可执行文件(通常是 ELF 格式)会被加载到内存中的代码段(text segment)。这个可执行文件可能会依赖一些动态库,这些动态库也需要被加载到内存中。
动态库是以共享对象(Shared Object)的形式存在的,它们的代码和数据并不包含在可执行文件中,而是作为独立的文件存在。当程序加载到内存时,操作系统会根据可执行文件中的信息找到并加载所依赖的动态库。加载动态库的过程包括将动态库的代码和数据复制到进程的地址空间中,并进行符号重定位等操作,使得程序能够正确地使用动态库中的函数和数据。
cpu中的指令寄存器,可以通过虚拟地址,然后通过页表,找到物理地址,然后找到内存里的命令
进程间通信
vscode的使用
1)下载vscode
2)然后下载中文插件
3)下载ssh remote插件(用于远程连接)
control+`:就是打开终端和在final shell操作一样
主要是用vscode取代vim,但是vim还是有用的,我们是远程开发,我安装的插件安装到linux下
进程间通信的目的
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
进程间通信的本质
让不同的进程先看到同一份资源(例如母亲通过孩子给父亲进行交流),由操作系统提供
匿名管道通信
我们如何让不同进程看到同一个管道文件?
通过fork创建子进程的时候实现的
pipe()
在 Linux 中,pipe() 函数是一个用于创建管道的系统调用。它允许在两个进程之间建立一个匿名的管道,实现进程间通信。
pipe() 函数的原型定义在 unistd.h 头文件中,其原型为: int pipe(int pipefd[2]); 其中,pipefd 是一个整型数组,用来存储管道的文件描述符。 pipefd[0] 用于从管道读取数据,pipefd[1] 用于向管道写入数据。
调用 pipe() 函数会创建一个管道,并将相关的文件描述符存储在 pipefd 数组中。一旦管道创建成功,就可以在父进程和子进程之间进行通信,父进程可以通过 pipefd[1] 向管道写入数据,子进程则可以通过 pipefd[0] 从管道读取数据。vscode中保存代码:cmmand+s
管道文件大小通常是64kb
1)管道四种情况
1.正常情况,如果管道里没有数据,读端必须等待,直到有数据为止(以前我们创建父子进程,让他们各自向屏幕输出东西时,他们都会各自输出自己的,不会管对方)
2.正常情况,如果管道被写满了,写端必须等待,直到有空间为止
3.写端关闭,读端一直读取,读端会读到read返回值为0,表示读到文件结尾
4.读端关闭,写端一直写入,OS会直接杀掉写端进程,通过向目标进程发送SIGPIPE(13)信号,终止目标进程2)管道五种特性
1.匿名管道,可以允许具有血缘关系的进程间通信,常用于父子,父孙也可以
2.匿名管道,默认给读写端提供同步机制
3.面向字节流的(可能读取出来的,不一定是完整的字符串)
4.管道的生命周期是随进程的
5.管道是单向通信的,半双工通信一种特殊情况(一方输出,一方接收)事例
#include <iostream> #include <unistd.h> #include <cassert> #include <cstring> #include <sys/types.h> #include <sys/wait.h> using namespace std; #define MAX 1024 int main() { // 第一步创建管道 int pipefd[2] = {0}; int n = pipe(pipefd); assert(n == 0); cout << "pipefd[0]: " << pipefd[0] << " " << "pipefd[0]: " << pipefd[1] << endl; // 第二步创建子进程 pid_t id = fork(); if (id < 0) { perror("fork:"); return 1; } // 子写父读 // 第三步,父子关闭不需要的fd,形成单向通信的管道 else if (id == 0) { // child // 因为子进程使用过写管道,需要关闭读管道 close(pipefd[0]); // w-只向管道写入,没有打印 int cnt = 10; while (cnt--) { char message[MAX]; snprintf(message, sizeof(message) - 1, "hello father, I am a child,pid:%d ,cnt:%d", getpid(), cnt); write(pipefd[1], message, strlen(message)); sleep(1); } } else { // father // 因为父进程使用过读管道,需要关闭写管道 close(pipefd[1]); char buffer[MAX]; while (true) { ssize_t n = read(pipefd[0], buffer, sizeof(buffer) - 1); if (n > 0) { buffer[n] = 0; // 防止末尾没有'\0' cout << getpid() << ": " << "child say: " << buffer << " " << "to me!" << endl; } } pid_t rid = waitpid(id, nullptr, 0); if (rid == id) cout << "wait success" << endl; } }
进程池
to_string
to_string 是 C++11 中新增加的一个函数,可以将数值类型(如 int、float、double 等)转换成字符串类型。
function<void()>
function<void()>是一个函数类型,它表示一个不接受任何参数且不返回任何值的函数。在C++中,std::function是一个通用的函数封装器类模板,可以用来包装各种可调用对象,包括函数指针、函数对象、Lambda表达式等。
具体而言,std::function<void()>表示一个可以调用的函数对象,它没有参数并且没有返回值。通过将一个不接受任何参数且不返回任何值的函数或可调用对象传递给std::function<void()>,就可以创建一个可以调用的对象,该对象可以像函数一样被调用。
以下是使用std::function<void()>的示例:
#include <iostream> #include <functional> void hello() { std::cout << "Hello, world!" << std::endl; } int main() { std::function<void()> func = hello; func(); // 调用hello函数 return 0; }
在上述示例中,我们定义了一个名为hello的函数,它不接受任何参数并且没有返回值。然后,我们创建了一个std::function<void()>对象func,将hello函数赋值给它。最后,我们通过调用func()来调用hello函数。
需要注意的是,std::function可以用于包装各种不同类型的可调用对象,只要它们的签名(参数和返回值)与std::function的模板参数匹配即可。这使得std::function非常灵活,可以在函数指针、函数对象、Lambda表达式等之间进行切换和传递。
匿名管道的实现
processpool.cc
#include <iostream> #include <cassert> #include <unistd.h> #include <string> #include <vector> #include <sys/types.h> #include <sys/wait.h> #include "task.hpp" using namespace std; const int num = 5; static int number = 1; class channel { public: channel(int fd, pid_t id) : ctrlfd(fd), workerid(id) { name = "channel-" + to_string(number++); } int ctrlfd; // 信道 pid_t workerid; // 进程id string name; // 信道名称 }; void work() { while (true) { int code = 0; ssize_t n = read(0, &code, sizeof(code)); if (n == sizeof(code)) { if (!IN1.checksafe(code)) continue; IN1.runtask(code); } else if (n == 0) { break; } else { } } cout << "子进程退出" << endl; } // 传参形式 // 1.输入参数:const & // 2.输出参数:* // 3.输入输出参数:& void printfd(const vector<int> &fds) { cout << getpid() << "close fds:"; for (auto fd : fds) { cout << fd << " "; } cout << endl; } void createchannels(vector<channel> *c) { // vector<int> tmp; // 1.定义并创建管道 int i = 0; for (i = 0; i < num; i++) { int pipefd[2]; int n = pipe(pipefd); assert(n == 0); // 2.创建进程 pid_t id = fork(); // 3.构建单向通信的信道 if (id == 0) // child { if (!tmp.empty()) { for (auto fd : tmp) { close(fd); } printfd(tmp); } close(pipefd[1]); dup2(pipefd[0], 0); work(); // sleep(1); exit(0); } // father close(pipefd[0]); c->push_back(channel(pipefd[1], id)); tmp.push_back(pipefd[1]); } } void printdebug(vector<channel> &c) { for (auto &ch : c) { cout << ch.name << " " << ch.ctrlfd << " " << ch.workerid << endl; } } void sendcommand(const vector<channel> &ch, bool flag, int num = -1) { int pos = 0; while (true) { // 开始完成任务 // 1.选择任务 int command = IN1.selecttask(); // 2.选择信道 const auto &c = ch[pos++]; pos %= ch.size(); // debug cout << "send command :" << IN1.todesc(command) << " to " << c.name << " worker is :" << c.workerid << endl; // 3.发送任务 write(c.ctrlfd, &command, sizeof(command)); // 4.判断是否要退出 if (!flag) { num--; if (num <= 0) break; } sleep(1); } cout << "发送任务完成了" << endl; } void releasechannels(vector<channel> channels) { // verson2 // verson1 for (const auto &ch : channels) { close(ch.ctrlfd); } for (const auto &ch : channels) { pid_t rid = waitpid(ch.workerid, nullptr, 0); if (rid == ch.workerid) { cout << "wait child :" << ch.workerid << " success" << endl; } } } int main() { vector<channel> channels; // 1.创建信道,创建进程 createchannels(&channels); // 2.开始发送任务 const bool g_always_loop = true; // sendcommand(channels,g_always_loop); sendcommand(channels, !g_always_loop, 10); // 3.回收资源,想让子进程退出,并且释放管道,只要关闭写端 releasechannels(channels); return 0; }
task.hpp
#pragma once #include <iostream> #include <functional> #include <vector> #include <ctime> #include <unistd.h> using namespace std; using task_t = function<void()>; // typedef function<void()> task_t;//这两种写法都一样的 void download() { cout << "我是一个下载任务" << "处理者:" << getpid() << endl; } void printlog() { cout << "我是一个打印日志任务" << "处理者:" << getpid() << endl; } void pushvideostream() { cout << "我是一个推送视频流任务" << "处理者:" << getpid() << endl; } class init { // 任务码 const int g_download_code = 0; const int g_printlog_code = 1; const int g_pushvideostream_code = 2; // 任务集合 public: vector<task_t> tasks; public: init() { tasks.push_back(download); tasks.push_back(printlog); tasks.push_back(pushvideostream); } bool checksafe(int code) { if (code >= 0 && code < tasks.size()) return true; else return false; } void runtask(int code) { tasks[code](); } int selecttask() { return rand() % tasks.size(); } string todesc(int code) { switch (code) { case 0: return "download"; break; case 1: return "printlog"; break; case 2: return "pushvideostream"; break; default: return "没有该任务"; } } }; init IN1; // 定义对象
Makefile
processpool:processpool.cc g++ -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f processpool
🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸