【Linux初阶】进程地址空间 | CUP与可执行程序的交互原理

news2024/11/28 23:32:04

 🌟hello,各位读者大大们你们好呀🌟

🍭🍭系列专栏:【Linux初阶】

✒️✒️本篇内容:计算机空间初识(子进程变量修改实验),感性理解进程虚拟地址空间,进程地址空间基础(概念、区域划分与调整、程序对内存数据的修改、按需分配虚拟地址空间),解答为什么存在虚拟地址空间(防止非法越界、方便解耦、保证进程独立性、统一编译方便使用),其中重点讲解统一编译中CPU与可执行程序的交互原理

🚢🚢作者简介:本科在读,计算机海洋的新进船长一枚,请多多指教( •̀֊•́ ) ̖́-

目录

一、你真的了解计算机数据空间分布吗?

二、感性理解进程虚拟地址空间

三、进程地址空间基础

1.相关的基础概念

2.区域划分与调整

3.程序是如何更改内存中的数据的?

4.操作系统会根据进程的需求分配虚拟地址空间

四、为什么存在地址空间 

1.防止非法越界访问

2.方便进程解耦,保证进程独立性

3.统一编译,方便使用

(1)可执行文件中有地址吗?

(2)编译器的编码方式

(3)物理地址的来源

(4)CPU与可执行程序交互的原理

(5)逻辑地址知识补充

结语


一、你真的了解计算机数据空间分布吗?

在以前的学习中,大家可能很早已经见过类似下面这样的空间分布图了

可是我们很多同学对它并没有深入理解,很多同学认为这个空间实际上就是我们的内存,但是真的是这样吗?

我们直接通过一段代码来验证一下

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{
	pid_t id = fork();
	if (id < 0) {
		perror("fork");
		return 0;
	}

	else if (id == 0) { //child
		printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
	}
	else { //parent
		printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
	}
	sleep(1);
	return 0;
}

输出

//与环境相关,观察现象即可
parent[2995]: 0 : 0x80497d8
child[2996] : 0 : 0x80497d8

我们发现,输出出来的变量值和地址是一模一样的,很好理解呀,因为子进程按照父进程为模版,父子并没有对变量进行进行任何修改。可是将代码稍加改动:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int g_val = 0;

int main()
{
	pid_t id = fork();
	if (id < 0) {
		perror("fork");
		return 0;
	}
	else if (id == 0) { //child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取
		g_val = 100;
		printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
	}
	else { //parent
		sleep(3);
		printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
	}
	sleep(1);
	return 0;
}

输出结果:

//与环境相关,观察现象即可
child[3046]: 100 : 0x80497e8
parent[3045] : 0 : 0x80497e8

我们发现,父子进程,输出地址是一致的,但是变量内容不一样!能得出如下结论:

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量。
  • 但地址值是一样的,说明,该地址绝对不是物理地址!
  • 曾经我们学习语言的基本地址(指针),并不是对应的物理地址。
  • 在Linux地址下,这种地址叫做 虚拟地址
  • 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理。

注意:OS必须负责将 虚拟地址 转化成 物理地址


二、感性理解进程虚拟地址空间

进程会认为自己是独占系统资源的(实际上不是)

这里我举一个通俗易懂的例子:

peter是美国一个拥有十亿美元的大富翁,他拥有三个私生子,且这三个私生子互不知道其他私生子的存在,三个儿子分别是工厂老板、证券交易员、学生。每个儿子都认为自己是peter十亿美金的继承者。

在他们工作的过程中,需要向peter索取金钱(工厂老板为维持家族企业正常运转,发放工人工资,结清货款;证券交易员维持正常投资,管理部分家族现金资产;学生为缴纳学费等),他们向peter索要的金钱有多有少,但是都不会提出过分的要求(比如直接索要十亿美金),如果他们提出过分要求,会被peter拒绝。

peter在三个儿子工作的时候,会给儿子们“画饼”,比如他会对第一个儿子说:你好好干,只要你管理好我们的家族企业,提高我们的工厂效益,以后我的十亿美金就都是你的了。

其中大富翁peter就相当于我们的操作系统,十亿美金就相当于操作系统管理的内存资源。其中儿子就相当于我们平时 malloc、new等向系统索要资源的进程。儿子们获取的金钱就相当于内存或者在语言上称为对象空间(内存)。peter给三个儿子画的“大饼”就相当于进程地址空间。计算机通过软件,给进程“画饼”。

 ———— 我是一条知识分割线 ————

那实际上如何“画饼”呢?在此之前,我们要知道“饼”是什么,画饼的本质:在你大脑中的蓝图 - 数据结构对象地址空间)。

每个儿子(进程)都可以根据peter(操作系统)为自己画的“饼”(数据结构对象/地址空间)向peter要钱(内存资源)

 

———— 我是一条知识分割线 ———— 

 我们已经知道“饼”是什么,那么问题又来了,我们都知道儿子(进程)需要被管理,那么peter(操作系统)为儿子(进程)提供的“饼”(数据结构对象/地址空间)需要被管理吗?答案是:要的!因为不能将给大儿子的承诺许错给二儿子或三儿子,所以“饼”也要被管理。

如果进程中有500个进程,操作系统需要给每个进程创建地址空间,进程会被PCB管理,地址空间需要怎么管理呢?答案是:先描述,在组织。地址空间的本质:是内核的一种数据结构(mm_struct)。简单来说,我们的虚拟地址空间会被一个名为 struct mm_struct的数据结构管理起来


三、进程地址空间基础

1.相关的基础概念

注意:本次讲解以32位计算机平台为基础

 在正式深入学习进程地址空间之前,我们需要先了解一些相关的基础概念:

  1. 进程/内存空间描述的基本空间大小为字节;
  2. 32位下计算机最多可以形成 2^32个地址;
  3. 每个地址标识一个字节,所以我们能表示的地址空间范围最大为 4G;
  4. 每个进程都要有唯一的地址,假设我们需要表示 2^32个地址,我们需要保持每个地址的唯一性;
  5. 我们可以通过 32位的数据(32个比特位的数据)来表示一个地址。

进程地址空间图例如下 


2.区域划分与调整

这里我们举一个例子来理解区域划分和区域调整。

假设初中有一对男女同桌,由于男生太贪玩,经常打扰到旁边的女生学习,所以女生给桌子画了一条线,将桌子分成了两个区域,假设桌子有100cm,她将区域划分为[1-50, 51-100],这就是区域划分。

而后小男孩还是不在意,经常越线,因此小女孩调整了桌子上线的位置,桌子有100厘米,但是女孩只给了小男孩30厘米的活动范围,[1-30, 31-100],这种在划分后调整各自区域的行为称为区域调整。 

通过观察下图我们可以发现,只要我们调整不同对象的 start、end,就可以实现区域调整(扩大 or 缩小)。

我们可以通过搜索不同的地址位置,找到数据所在。还是上面的例子来解释,可能1-10cm中放有小男孩的笔袋,12-16cm放有小男孩的笔记本。

———— 我是一条知识分割线 ———— 

之前文章中提到,我们可以通过 32位的数据(32个比特位的数据)来表示地址,我们的虚拟地址空间会被一个名为 struct mm_struct的数据结构管理起来。在32位计算机下,通常我们采用 int类型的数据来记录一个地址,因为 int在刚好在32位计算机下占32个比特位。在 struct mm_struct的数据结构中包含着不同区域地址信息,其中就包含有堆的区域起始地址、区域结束地址等

这些在不同的区域范围内、可以供我们使用的地址称为虚拟地址。也就是说在地址空间中的 2^32个地址都是虚拟地址。

所以,进程地址空间也叫做进程虚拟地址空间,它的本质实际上是一个存储虚拟地址及其相关数据的集合。

———— 我是一条知识分割线 ————

在地址空间中,代码区(已初始化、未初始化)的大小是固定的,也就是说代码区的起始和结束地址不可被调整;但是堆区和栈区不同,堆区可以通过 malloc等手段调整它的大小,栈区也可以定义相应的局部变量,也就是说,堆区和栈区是可以被调整的!

heap & stack的调整,本质上就是调整两个区域的 start和end。在我们的编程过程中,定义局部变量,malloc、new对象实际上就是扩大堆区或者栈区。当我们 free的时候,实际上就是在缩小堆区或者栈区。

3.程序是如何更改内存中的数据的?

在第一节中我们验证过,父子进程,输出地址是一致的,但是变量内容不一样,这到底是怎么做到的呢?在解答这个问题之前,我们先要知道,程序是如何更改内存中的数据的。接下来这一部分的知识主要就是向大家讲解,程序是如何更改内存中的数据的

文章第二节讲到,操作系统会为进程画“大饼”,这里的大饼就是地址空间(mm_struct),因此相应的,tack_struct(PCB,进程控制块)会有相应的指针指向进程地址空间。

———— 我是一条知识分割线 ———— 

在硬件方面(内存和磁盘),假设有文件 my.exe存在于磁盘之中,当文件加载到内存中,占用内存 1k字节的空间,每个字节我们都可将它视作一个地址。

内存在使用时,它会和我们对应的磁盘进行输入输出数据,这个工作我们称为 IO,其中传输数据的基本单位为 4KB(1KB = 1024字节)。在此基础上,内存可以被我们划分为一个个 4KB大小空间块,这个空间块我们称之为 page(页),如果我们将内存想象为一个大数组,则内存大小或者说对应的页有:struct page men[4GB/4KB]。只要我们知道某一页的起始地址,再加上对应的偏移量,我们就可以找到内存的任意一个地址。

———— 我是一条知识分割线 ———— 

页表简介:可以用于建立虚拟地址和物理地址之间的映射关系,实现虚拟地址和物理地址的转换。页表内部不是简单的对应空间结构,因为如果这样设计需要占用 4G*8 = 32G。左式中8的含义 - 假设空间为1-1对应结构,左边和右边各一个数据,一个数据需要4个字节表示(int),加起来则需要8个字节。因此,页表内部使用的是多级页表的结构,就是页目录为根,对应多个二级、三级索引这样的结构,这里就不展开细说了。

下图中以浅蓝色标记为例,虚拟地址0x123445678可以通过页表映射找到对应的物理地址 0x11112222,物理空间中储存着 val变量的值为10.

———— 我是一条知识分割线 ———— 

 以下述代码为例讲述程序是如何更改内存中的数据的?

char val = 100;

首先我们要明确,val的地址是虚拟地址(在第一节中验证过),当代码运行起来时,操作系统拿着 val的虚拟地址,去查找页表,找到对应的物理地址,将物理地址对应内存空间中的数值修改/存储为100。至此我们已经可以充分认识到程序是如何更改内存中的数据的原理了。

注意:这个过程都是由操作系统完成的!

4.操作系统会根据进程的需求分配虚拟地址空间

进程无法直接访问物理内存,进程只能通过虚拟地址空间来访问对应的数据,其中我们可以认为操作系统给进程画了一个“大饼”(红色正方形框框),每个进程都以为自己能占有 2^32个地址空间(内存大小)。

而实际上,我们通过观察,发现堆和栈之间还有很大部分的空缺,不难推断出操作系统会根据进程对应的需求给进程分配地址空间,而不会真的将所有空间都给一个进程。相反,如果进程的需求不合理,操作系统会拒绝进程的对应访问


四、为什么存在地址空间 

1.防止非法越界访问

如果进程直接访问物理内存,会存在越界非法操作的风险(比如将储存有别的数据的page覆盖)

地址空间的存在,使程序员在应用层只能通过页表映射来访问物理地址,期间操作系统可以帮我们甄别相应指令,接收正确指令,拒绝错误访问。

2.方便进程解耦,保证进程独立性

进程地址空间的存在,可以更方便的让进程与进程之间的数据进行解耦,保证进程独立性

进程具有独立性,一个进程对被共享的数据进行修改,不能影响其他进程

操作系统为了保证进程独立性,当物理内存中的一个共享数据需要被修改时,操作系统会在物理内存中开辟一个新的空间,数据拷贝一份放到新的空间中,然后会将页表原来对应共享数据空间物理地址改为新空间的地址(更改映射)。这样,在进程改变某个值的时候,就只和这个进程本身有关,和其他进程无关,实现了进程之间的相互独立。

任何一方尝试写入,OS先进行数据拷贝,更改页表映射,然后再让进程进行修改,我们把这样的行为称为写时拷贝。在我们修改数据时,操作系统会帮我们自动完成写时拷贝。

写时拷贝,帮我们实现了数据间的分离。以下图为例,在数据不做修改时,父子进程可以共用同一块物理内存空间,当子进程对数据进行修改,操作系统会通过写时拷贝,实现数据分离,即父进程的 g_val变量和子进程 g_val变量存储于不同的物理地址空间中。

每个进程都有自己独立的内核数据结构,包括独立的PCB、独立的地址空间、独立的页表。

结论:进程 = 进程的内核数据结构 + 进程对应的代码和数据,当进程内核数据结构和对应数据都相互独立时,这个进程也就和其他进程相互独立了。也就是说,地址空间的存在,使进程可以拥有独立的内核数据结构,可以更方便的让进程与进程之间的数据进行解耦,保证进程独立性。

3.统一编译,方便使用

让进程可以以统一的视角,来看待代码和数据的各个区域,方便使用;方便编译器也以统一的视角来编译代码,编完可以直接用。

(1)可执行文件中有地址吗?

我们的可执行文件中,有没有地址呢?答案是有的,我们的可执行文件中早就有地址了!这种在程序内部使用的地址,我们称它们为 - 逻辑地址程序可以通过逻辑地址实现内部之间的相互跳转,实现函数调用等功能

(2)编译器的编码方式

编译器在编译我们的代码和数据时,会按照虚拟地址空间的方式给我们的代码和数据进行编址,也就是它也会按照32位的方式进行编址。

只有栈空间、堆空间对应的数据没有进行编址,因为它们是需要在内存中动态申请的。其他的代码都会完成编址。

(3)物理地址的来源

程序内部使用的地址(逻辑地址),在程序加载到内存时,会天然具有物理地址。因为逻辑地址和物理地址的表示方法相同,所以逻辑地址就是物理地址,不用改变。只不过我们为程序内部的地址起了一个名称 - 逻辑地址,加以辨识罢了。

注意通常情况下,逻辑地址、物理地址、虚拟地址的表示方式是一样的,都采用32位的数据进行记录。这也就是为什么编译器可以以统一的视角来编译代码,编完可以直接用的原因所在。

———— 我是一条知识分割线 ————

(4)CPU与可执行程序交互的原理

代码编译好加载到内存后,会生成对应的虚拟地址,且在内存中的代码天然具有物理地址。操作系统会用 main函数入口和结束处的虚拟地址等数据,初始化进程虚拟地址空间(主要是代码区)和进程对应的页表,再将虚拟地址空间初始化好的地址加载到CPU的寄存器中。

CPU通过虚拟地址,经过页表映射,找到对应物理地址中数据并进行获取CPU读进来的都是指令,指令中包含地址,这里的地址是虚拟地址。

CPU处理完数据之后,再将数据通过虚拟地址,经过页表映射,写入到对应物理地址的内存中

具体图示如下,下图中的地址都是虚拟地址(物理内存中的虚拟地址来源于代码编译生成的虚拟地址)

所以,在整个访问过程中,CPU输入输出的都是虚拟地址,CPU压根儿就没有见到物理地址,也即是说CPU不会对物理地址进行直接访问

在我们对代码进行 debug(调试)时,程序已经运行起来了,此时我们通过监视窗口看到寄存器相关的地址都是虚拟地址。

总结:虚拟进程地址的使用,让进程可以以统一的视角,来看待代码和数据的各个区域,方便使用;

———— 我是一条知识分割线 ————

(5)逻辑地址知识补充

注意:Linux下我们编译可执行程序的格式的编码格式为 ELF

逻辑地址有两种常见的编址方式,一种是线性编址方式,另一种是区域编址方式。这两种生成的都是逻辑地址,但是因为线性编址编出来的地址和物理地址的表示方法相同,因此当程序内部使用线性编址时,在程序加载到内存之后,该程序就天然具有了物理地址

  • 线性编址(常用)

地址从0开始,假设普通代码占了100个字节(每个字节一个地址),那么其他数据代码从101个地址开始,继续向下呈线性编址

  • 区域编址(不常用 - 了解)

区域编址,采用的是区域起始地址加偏移量的方式,当代码加载到内存时,每一个地址都要做修改才能转换成对应的物理地址。因为这种编制方式,使得地址时一块块区域区分开来的,我们可以使用物理地址的起始地址+对应的偏移量,找到对应逻辑地址。

地址从0开始,代码区的普通代码编址完成后,数据区的地址重新从0开始编址,呈现了区域性。


结语 

 🌹🌹Linux进程地址空间 的知识大概就讲到这里啦,博主后续会继续更新更多Linux操作系统的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/524164.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Web自动化测试工具大对决:细致比较Selenium、Protractor和Cypress

目录 前言&#xff1a; Selenium Protractor Cypress 结论 Web自动化测试步骤结构图&#xff1a; 前言&#xff1a; 随着Web应用程序的广泛使用&#xff0c;Web自动化测试工具的需求也越来越高。Web自动化测试工具可以模拟用户在Web浏览器中的行为&#xff0c;并且可以快…

NICE-SLAM学习

NICE-SLAM简介&#xff1a; 是浙大和ETH提出的一种神经隐式SLAM方法主要创新点在于解决了基于传统nerf的方案如imap无法应用在较大场景的问题。 关于该工程的前身和当前领域的主要研究现状见&#xff1a;&#xff08;知乎&#xff1a;秦通&#xff09;基于神经辐射场的&#…

接口自动化测试-Pytest核心固件Fixture详解以及应用实战

目录 前言&#xff1a; 一、Fixture概述 二、Fixture分类 在Pytest中&#xff0c;Fixture可以分为4类&#xff1a; 三、Fixture使用 下面结合实例说明如何使用Fixture。 3.1 session级别的Fixture 3.2 module级别的Fixture 3.3 function级别的Fixture 3.4 class级别的F…

2023.5.13前端设计

SpringbootVue架构设计&#xff08;一&#xff09; 项目中的文件来自B站视频&#xff08;程序员青戈&#xff09; https://www.bilibili.com/video/BV1U44y1W77D 前端设计 assets 文件夹通常用于存放静态资源文件&#xff0c;例如图像、CSS、字体等。components 文件夹通常用…

2023.5.14数据库设计

SpringbootVue架构设计&#xff08;二&#xff09; 项目中的文件来自B站视频&#xff08;程序员青戈&#xff09; https://www.bilibili.com/video/BV1U44y1W77D 数据库设计 1、article 在关系型数据库中&#xff0c;text类型和varchar类型都是用来存储字符串的数据类型。它…

【ShaderToy中图形效果转译到UnityShaderlab案例分享_Flame】

ShaderToy内的源码与效果图如下: float noise(vec3 p) //Thx to Las^Mercury {vec3 i = floor(p);vec4 a = dot(i

电动力学专题:电磁波传播时的色散效应

本文中假定波动方程的性质足够好&#xff0c;不要杠&#xff0c;杠你就输了 数学分析中的问题和XX 波动方程&#xff1a;\frac{\partial^2 u}{\partial t^2} - a^2 \frac{\partial^2 u}{\partial x^2} 0......(1) 波的色散 波动方程描写的是以恒定速度a传播的非衰减波。方程&…

【电厂用 交流三相电流继电器HJL-F93/AY 导轨安装 JOSEF约瑟供应 】

名称&#xff1a;交流三相电流继电器&#xff0c;品牌&#xff1a;JOSEF约瑟&#xff0c;型号&#xff1a;HJL-F93/AY&#xff0c;功率消耗&#xff1a;≤5W&#xff0c;触点容量&#xff1a;250V/5A&#xff0c;额定电压&#xff1a;58、100、110、220V 上海约瑟供应 交流三相…

第十四届蓝桥杯青少组选拔赛Python真题 (2022年10月30日),包含答案

目录 一、选择题 二、编程题 第十四届蓝桥杯青少组选拔赛Python真题 (2022年10月30日) 一、选择题 第 1 题 单选题 执行print (5%3) 语句后,输出的结果是 ( ) 答案:C 第2题单选题 以下选项中,哪一个是乘法运算符?() 答案:C 第3题 单选题 已知x-3,求x//2+x”2的…

PCIE结构拓扑(RC、EP、SWITCH)介绍

1、PCIE典型结构拓扑 (1)个人理解&#xff1a;红色方框部分一般是在芯片内部集成&#xff0c;对外可提供多个PCIE接口&#xff1b; (2)芯片提供的PCIE接口&#xff0c;可以接EP设备、PCI桥、Switch设备&#xff1b; 2、RC&#xff08;Root Complex&#xff09; 个人理解&#x…

【一文吃透归并排序】基本归并·原地归并·自然归并 C++

目录 1 引入情境基本归并排序实现 C 2 原地归并排序2-1 死板的解法2-2 原地工作区2-3 链表归并排序 3 自底向上归并排序4 两路自然归并排序4-1 形式化描述4-2 代码实现 1 引入情境 归并思想&#xff1a;假设有两队小孩&#xff0c;都是从矮到高排序&#xff0c;现在通过一扇门后…

微服务#3 Docker常用命令

Docker 一.初识docker Docker如何解决大型项目依赖关系复杂&#xff0c;不同组件依赖的兼容性问题? Docker允许开发中将应用、依赖、函数库、配置一起打包&#xff0c;形成可移植镜像Docker应用运行在容器中&#xff0c;使用沙箱机制&#xff0c;相互隔离 Docker如何解决开…

在树莓派上搭建WordPress博客网站,并内网穿透发布到公网

✨个人主页&#xff1a;bit me&#x1f447; 目 录 &#x1f43e;概述&#x1f490;安装 PHP&#x1f338;安装MySQL数据库&#x1f337;安装 Wordpress&#x1f340;设置您的 WordPress 数据库&#x1f339;设置 MySQL/MariaDB&#x1f33b;创建 WordPress 数据库 &#x1f33…

pga_aggregate_limit和process关系

之前部署19c时&#xff0c;配置pga_aggregate_limit都是直接配置成0了&#xff0c;配置processes的大小也比较随意&#xff0c;上周维护一个客户安装的环境&#xff0c;重启数据库数据库时告警了&#xff0c;才第一次认真对面了 SYSorcl1> startup ; ORA-00093: pga_aggreg…

鼠标拆卸方法、鼠标按键噪声大解决办法(为按钮加润滑脂)

文章目录 今天我买了一款acer鼠标&#xff0c;发现按键声音有点大&#xff0c;想给它的按钮上一点缓冲脂&#xff0c;这个缓冲脂本来我是买来润滑机械键盘轴体用的&#xff0c;刚好也能用上。 这是我的鼠标外壳结构&#xff0c;我拆卸的时候没注意&#xff0c;给我拆坏了&…

SRP中的shader

SRP中的shader 在Unity中使用SRP时&#xff0c;我们需要使用HLSL来编写shader。HLSL语法与GLSL类似&#xff0c;在使用HLSL的地方我们需要用HLSLPROGRAM和ENDHLSL来包裹&#xff1a; Shader "My Pipeline/Unlit" {Properties {}SubShader {Pass{HLSLPROGRAM#pragma …

MySQL访问方法

访问方法&#xff08;访问类型&#xff09; Const 通过主键或者唯一二级索引来定位到唯一一条满足条件的数据的访问方法称为const。特别指出&#xff0c;如果主键中有两个列l1和l2&#xff0c;查询条件中只有l1的话访问方法就不是const&#xff0c;因为通过l1并不能确定唯一一条…

VisualStudio如何配置PCL点云库?

文章目录 0.引言1.准备PCL点云库2.VisualStudio相关设置3.配置PCL点云库4.测试PCL 0.引言 因笔者课题涉及点云处理&#xff0c;需要通过PCL进行点云数据分析处理&#xff0c;查阅现有网络资料&#xff0c;实现了VisualStudio2015(x86)配置PCL1.8.1点云库&#xff0c;本文记录实…

Python——pyqt-auto-tool自动化小工具(源码)

目录 一、演示视频 二、代码包 三、使用步骤 四、总结 一、演示视频 pyqt-auto-tool自己乱改的自动化小工具_哔哩哔哩_bilibili今天教资考试完&#xff0c;花半天自己乱改的自动化小工具pyqt-auto-tool&#xff0c;二开1、auto-tool原作者&#xff1a;https://www.bilibil…

学习通项目需要用到的

思路&#xff1a; 把长连接弄好课堂详细页面可以看到班级的所有学生&#xff0c;双击学生弹窗&#xff0c;可以确认从该课程中移除学生教师可以在班级详细页面通过输入学生的id&#xff0c;将学生加入该班级做好签到的页面&#xff0c;输入签到标题&#xff0c;选择签到的结束…