Linux 第二十七章

2024-05-11 12:44
文章标签 linux 第二十七章

本文主要是介绍Linux 第二十七章,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

🐶博主主页:@ᰔᩚ. 一怀明月ꦿ 

❤️‍🔥专栏系列:线性代数,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 1024int 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; // 进程idstring 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);}// fatherclose(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();// debugcout << "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// verson1for (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;elsereturn 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

 🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸 

这篇关于Linux 第二十七章的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/979573

相关文章

VScode连接远程Linux服务器环境配置图文教程

《VScode连接远程Linux服务器环境配置图文教程》:本文主要介绍如何安装和配置VSCode,包括安装步骤、环境配置(如汉化包、远程SSH连接)、语言包安装(如C/C++插件)等,文中给出了详... 目录一、安装vscode二、环境配置1.中文汉化包2.安装remote-ssh,用于远程连接2.1安装2

Linux中shell解析脚本的通配符、元字符、转义符说明

《Linux中shell解析脚本的通配符、元字符、转义符说明》:本文主要介绍shell通配符、元字符、转义符以及shell解析脚本的过程,通配符用于路径扩展,元字符用于多命令分割,转义符用于将特殊... 目录一、linux shell通配符(wildcard)二、shell元字符(特殊字符 Meta)三、s

Linux之软件包管理器yum详解

《Linux之软件包管理器yum详解》文章介绍了现代类Unix操作系统中软件包管理和包存储库的工作原理,以及如何使用包管理器如yum来安装、更新和卸载软件,文章还介绍了如何配置yum源,更新系统软件包... 目录软件包yumyum语法yum常用命令yum源配置文件介绍更新yum源查看已经安装软件的方法总结软

linux报错INFO:task xxxxxx:634 blocked for more than 120 seconds.三种解决方式

《linux报错INFO:taskxxxxxx:634blockedformorethan120seconds.三种解决方式》文章描述了一个Linux最小系统运行时出现的“hung_ta... 目录1.问题描述2.解决办法2.1 缩小文件系统缓存大小2.2 修改系统IO调度策略2.3 取消120秒时间限制3

Linux alias的三种使用场景方式

《Linuxalias的三种使用场景方式》文章介绍了Linux中`alias`命令的三种使用场景:临时别名、用户级别别名和系统级别别名,临时别名仅在当前终端有效,用户级别别名在当前用户下所有终端有效... 目录linux alias三种使用场景一次性适用于当前用户全局生效,所有用户都可调用删除总结Linux

Linux:alias如何设置永久生效

《Linux:alias如何设置永久生效》在Linux中设置别名永久生效的步骤包括:在/root/.bashrc文件中配置别名,保存并退出,然后使用source命令(或点命令)使配置立即生效,这样,别... 目录linux:alias设置永久生效步骤保存退出后功能总结Linux:alias设置永久生效步骤

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

Linux Mint Xia 22.1重磅发布: 重要更新一览

《LinuxMintXia22.1重磅发布:重要更新一览》Beta版LinuxMint“Xia”22.1发布,新版本基于Ubuntu24.04,内核版本为Linux6.8,这... linux Mint 22.1「Xia」正式发布啦!这次更新带来了诸多优化和改进,进一步巩固了 Mint 在 Linux 桌面