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

相关文章

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级

【Linux】应用层http协议

一、HTTP协议 1.1 简要介绍一下HTTP        我们在网络的应用层中可以自己定义协议,但是,已经有大佬定义了一些现成的,非常好用的应用层协议,供我们直接使用,HTTP(超文本传输协议)就是其中之一。        在互联网世界中,HTTP(超文本传输协议)是一个至关重要的协议,他定义了客户端(如浏览器)与服务器之间如何进行通信,以交换或者传输超文本(比如HTML文档)。

如何编写Linux PCIe设备驱动器 之二

如何编写Linux PCIe设备驱动器 之二 功能(capability)集功能(capability)APIs通过pci_bus_read_config完成功能存取功能APIs参数pos常量值PCI功能结构 PCI功能IDMSI功能电源功率管理功能 功能(capability)集 功能(capability)APIs int pcie_capability_read_wo