Linux入门:环境变量与进程地址空间
一. 环境变量
1. 概念
1️⃣基本概念
:
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。
2️⃣见一见环境变量
:通过指令env 可以看到当前环境下的环境变量表
3️⃣可以用环境变量回答的诸多疑问
疑问1 :🤔为什么我们平常输入的指令需要直接运行,而我们编译好的可执行文件需要+./指明在当前路径下?
——》💡因为一般我们输入的命令可执行文件,在/usr/bin目录下,并且是PATH这个环境变量告诉OS,要在哪一个目录下查指令的!我们+./ 是为了告诉OS,要在当前目录下寻找!
——》PATH环境变量是一个由 : 为分隔符的路径集,我们直接输入的命令通常会直接在这几个路径下寻找
疑问2 :🤔如果我也想不输入./就能运行我的程序,改怎么做?
——》💡(1) cp /usr/bin ./程序名》将我们的程序拷贝到PATH路径中的其中一个搜索路径!
——》💡(2)在PATH添加一个自己的路径!这样OS就会在自己的路径下寻找!
—》(2)ps: 在指令中直接输入PATH= …就能添加自己的路径,要注意,这样的写法是覆盖式的,也就是要加上原本环境变量的路径,再以:为分隔符+上需要添加的路径。或者PATH=$PATH:…… 这样的意思就是在原有路径的基础上新增路径
—》像这样在命令行环境变量的修改的生命周期随以你的bash进程的结束而重置的,意思就是说,如果你在以上操作中误改了环境变量也没关系,只需重启一下Xshell,所有的环境变量都会重置,我们的修改只影响当次使用。
疑问3 :🤔这些环境变量的内容最开始从哪里来的?
——》💡首先,肯定不是从内存中来的。环境变量的内容最开始都是从系统的配置文件中来的!我们登录Xshell—>启动一个shell进程—>进程会读取跟环境变量有关的配置文件—>然后形成自己的环境变量表—>之后再执行命令时生成子进程去执行命令,就可以把环境变量表传给子进程。
——》这也能解释,为什么在疑问2修改环境变量时,我们重启shell,就能重置环境变量表,因为我们根本就没有真正的修改环境变量!我们的修改操作都是内存级的,没有触碰到配置文件,bash进程在启动时只会去读取配置文件的环境变量表!
疑问4 :🤔我们知道,进程会记录是谁启动了自己——》你在启动进程的时候,系统怎么知道你是谁的?并且把你的uid写到进程的PCB结构体里面去?
——》环境变量表早就告诉了你答案:
当前环境变量下,早就记录了你是谁,你在启动进程的时候,bash进程会传入环境变量表到子进程,根据环境变量表也就知道是谁启动了自己。
2. 查看环境变量的方法
2.1 在命令行中
env ——》查看当前进程的环境变量表
echo $PATH ——》查看一个环境变量,以PATH为例
2.2 在c语言程序中获取
getenv ——》系统级接口函数,获取一个环境变量的字符串
#include<stdio.h>
#include<stdlib.h>int main()
{char * arr = getenv("PATH");printf("%s\n",arr);
}
**extern char environ ——》第三方变量引入
#include<stdio.h>extern char **environ;
int main()
{for(int i = 0 ; environ[i];i++){printf("%s\n",environ[i]);}
}
3. 常见的环境变量
- PATH : 指定命令的搜索路径
- HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录
- SHELL : 当前Shell,它的值通常是/bin/bash。
- PWD:保存当前进程的工作路径
——》PWD:由于这个环境变量的存在,我们创建新文件,删除文件,或者通过./执行命令,系统都可以根据PWD找到当前工作路径执行操作。
4. 命令行参数
4.1 main函数的两个参数
😇其实我们的main函数是可以带参数的,因为他其实也是被别人调用的一个函数,分别是int argc ,char * argv[]:
#include <stdio.h>
int main(int argc,char * argv[])
{printf("argc:%d\n,argc");for(int i = 0 ; i< argc ; i++){printf("argv[%d]:%s\n",i,argv[i]);}
}
- argc : 记录命令行参数的个数
- argv[]:命令行参数表,里面存储就是命令行参数,最后一个成员是null
以上程序运行的例子:
——》命令行参数其实就是我们在执行命令时,包括命令在内的以空格为分割的各个选项,argc记录个数,argv[]以字符串数组的形式记录命令行参数
——》为什么需要命令行参数?同一个程序,就可以根据命令行参数中选项的不同,表现出不同的功能了!我们在shell上的指令都是这样的!
4.2 与环境变量相关的参数
👉 其实main函数除了命令行参数的两个参数外,还有一个参数(第三个参数)——》char * env[] ——》它用于记录传入的环境变量表!
#include <stdio.h>int main(int argc,char * argv[],char * env[])
{for(int i = 0 ; env[i];i++){printf(" env[%d] = %s\n",i,env[i]);}
}
在以上的程序中,我们尝试打印出char * env 的内容,执行命令,他打印出了当前所有的环境变量:
——》综上,我们知道了,在程序中有两个重要的表:命令行参数表,环境变量表。
4.3 理解环境变量的"全局性"
🤔思考:当我们执行程序时,环境变量是怎么一步一步传到我们的程序的?
——》💡我们登录Xshell 👉 启动一个shell进程 👉 进程会读取跟环境变量有关的配置文件 👉 然后形成自己的环境变量表👉 我们执行一个程序,而我们我们执行程序所启动的进程,本质上bash的子进程,子进程会拷贝父进程的相关变量,即使进行了程序替换👉子进程拿到环境变量表。
——》就是因为所有的进程都是bash的亲子进程,而bash已启动时就会获取环境变量——>环境变量可以被所有bash之后的进程全部看到!!所以,环境变量具有“全局属性”:
💭就算其中一个子进程修改了环境变量表,也不会影响全局的环境变量表,子进程修改环境变量表,只会影响他自己的子进程。
💭环境变量的“全局性”是充分运用了进程的继承性质的:子进程在开始时与父进程除了数据是独立的之外完全一样。
4.4 环境变量 VS 本地变量
🤔什么是本地变量?
——》像这样在命令行中直接定义的变量,就是本地变量,它可以通过echo $指令查看
💭本地变量与环境变量相比,只会在bash内部有效,不会被继承
🤔什么情况下需要本地变量呢??
——》就是只希望在bash里面使用但是不希望被子进程继承下去的,比如说我们的命令行提示符,如果是root用户就是# 如果是普通用户就是$
5 总结
💭环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数,它一般具有全局属性。
💭环境变量表记录这每个环境变量,每个程序都会收到一张环境变量表,它是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串,这个指针数组以NULL结尾
- echo: 显示某个环境变量值
- export: 设置一个新的环境变量
- env: 显示所有环境变量
- unset: 清除环境变量
- set: 显示本地定义的shell变量和环境变量
二. 虚拟进程地址空间
先来看一个现象:
#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){ //childprintf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);}else{ //parentprintf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(1);return 0;}
在以上的代码中,我们创建了一个子进程,然后让父进程和子进程都打印出全局变量g_val的地址,然后发现:
——》我们发现,输出出来的变量值和地址是一模一样的,这很好理解呀,因为子进程按照父进程为模版,父子并没有对变量进行进行任何修改。可是将代码稍加改动:
#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{ //parentsleep(3);printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(1);return 0;}
以上代码多了在子进程对g_val对象进行修改,输出现象:
——》父进程与子进程,输出地址是一致的,但是变量内容不一样!?!!😧这好像不对啊!我们知道,进程之间具有独立性,父进程与子进程的数据是相互独立一份的,子进程的g_val进行修改,那他们的g_val数据也应该是不一样的,但是这二者的地址又相等,这是怎么回事🤔?难道是不同的数据存到同一份地址上😯?那就更不可能了😰!
——》所以在此,可以输出结论👉 :(1)变量内容不一样,所以父子进程输出的变量绝对不是同一个变量(2)这个地址绝对不是物理地址!(3)在Linux地址下,这种地址叫做虚拟地址,或称线性地址(4)我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理。
1.1 什么是进程地址空间(是什么)
1.1.1 理解地址空间
——》在之前的学习中,我们知道这张图被称为程序地址空间,但其实这个说法并不准确,实际上应该叫做进程地址空间,上面的编制(0x000000&&0xFFFFFFF)也全都是虚拟的地址。
——例子引入:
——》有一个大富翁,他不仅100亿的资产,而且还有很多的私生子,私生子与私生子之间互相不知道对方的存在。私生子知道自己的老爸是富翁,所以要钱是大把大把的要,只有一个私生子还好说,但是他有数不清的私生子,他这资产怎么遭得住?所以,他给每一个私生子都画了个大饼:自己死后,会把自己的100亿资产继承给你。有了这个承诺,私生子都不急着要钱了,所以向富翁要钱花的时候,需要多少,才取多少,这样,富翁就实现了保持和每一个私生子的友好关系,又能不被私生子们发现其他私生子的存在。
——》回到话题,这里的大富翁就是操作系统,100亿是实际的物理内存,而每一个私生子就是进程,大富翁给他们的大饼就是进程地址空间!OS向每一个进程都许诺了它们有4G(如上图所示结构的)的进程地址空间,每一个进程真的有占这么大的内存吗?并不是!实际上是进程要多少空间,才会向OS申请多少空间。OS让每一个进程都认为自己独占了系统的物理内存大小,进程之间彼此不知道,不关心对方的存在,从而实现一定程度的隔离。
——》饼画多了,OS也要对它们进行管理!先描述,再组织!所谓的进程地址空间,在上图是他的逻辑结构,在内存中,本质是一个内核数据结构对象!OS描述为mm_struct:
1.1.2 理解区域划分
我们在上面了解到,os描述进程地址空间的结构体叫做mm_struct——》🤔这样的成员构成是做到进程地址空间的区域划分的?
💡👇
一个区域只用两个整形进行管理:一个记录着begin的地址,一个记录着end的地址,是这样的实现区域划分!
1.1.3 理解地址空间上的地址
从0x0000 0000到0xFFFF FFFF,一共有多少地址?答案是 4GB = 2^10 X 2^10 X 2^10 X 2^2 X 1字节 =2^32 个地址,这些地址的线性的,连续的——》这样设置地址的方式叫做内存编址。
其实我们可以观察到,地址本质就是一个数字,可以被unsignal long 类型的变量保存着——》这就是区域划分的变量内容,只有一个begin,一个end——》我们不需要记录一个区域中除了begin和end之外其他的地址,就能知道一个地址值是否在这一个区域内了——》所以空间范围内的地址,我们可以随便用,甚至不用记录他,只用知道一个区域的begin和end就行了。
1.1.4 页表
程序在运行时,所用的是虚拟进程地址空间的虚拟地址,这个虚拟地址是怎么和物理地址产生关联的🤔(怎么通过虚拟地址找到物理地址)?
💡进程中维护一个表,用来一个一个记录虚拟地址和物理地址的映射关系,叫做页表。
上图中,管理进程的结构体为task_struct,在task_struct中,有一个mm_struct的结构体指针成员管理者进程地址空间。在其中,g_val变量在进程地址空间中使用的是虚拟地址0x601054,他在实际的物理地址是0x11223344,页表就将g_val的虚拟地址和物理地址记录下来,让他们一一对应。
从理解的角度,页表的结构可以看作一个我们曾经学过的一个数据结构——》map(当然,真实的结构还要复杂很多),由虚拟地址作为key,找到对应的物理地址这个value——》很像一张表,所以叫页表。
父进程创建子进程后,子进程会拷贝父进程PCB中的大部分属性,其中当然包括进程地址空间和页表!页表的内容及映射关系全部都拷贝一份——》在这个时候,父进程的g_val通过页表映射到物理内存的数据和子进程是一样的!父进程的g_val和子进程是同一份!——》类别到其他的数据,比如说,代码区,父子映射到同一份代码区,所以一般来说,父子进程代码共享!
——》有没有一点奇怪?对呀,之前我们说过进程之间具有独立性,他们的代码是共享的,但是数据是独一份的,但是这里的g_val为什么是同一份?原因是,OS具有写实拷贝机制!
——》当不修改变量的时候,父子进程的数据确实的同一份,但是,一旦子进程或者父进程要对数据进行写入(修改),那么这个时候就会出发写实拷贝机制:子进程对父进程的数据空间进行一次拷贝,修改页表中物理地址的部分,重新建立映射关系,再对数据进行写入。——》这样的机制由OS自主完成——》默认不分开,只有在你需要的时候再分开——》这样好处非常大!既节省了内存空间,避免资源的浪费,又保证了进程之间的独立性!
——》最终,我们终于能够解释我们一开始举例的现象了!🤔为什么同一个地址,能看到不同的内容?因为这个地址的虚拟地址!在底层,父子进程使用相同的虚拟地址,但是他们的页表不同,映射到的物理地址也不相同——》所以同一个虚拟地址,在不同的进程,通过同一个页表,就可以看到不同的内容!!!
1.1.5 补充
1️⃣关于变量和地址
:
——》在汇编底层中,变量名其实不存在了,转而变成了地址,地址变成了变量的唯一标识符,对变量进行操作,其实也就是对地址的值进行操作。
2️⃣重新理解进程和进程的独立性
:
——》究其原因,进程为什么具有独立性?我们知道,进程 = 内核数据结构(task_struct/进程地址空间mm_struct/页表/…)+自己的代码和数据,代码和数据是独立的,从前文中我们也可以得知,像进程地址空间mm_struct这样的内核数据结构也是独立的,task_struct是独立的——>右式都具有独立性,左式也就具有相同的性质,所以进程就是独立的。
3️⃣关于页表
上文提到页表的数据结构可以看作一个map,一个虚拟地址对应一个物理地址——>但其实没有谈完,每一个对应关系还存在标记位,用来记录映射关系的相关信息,这里谈两个标记位:
- rwx:有一个标记位记录着物理地址的rwx权限,如果有对应的权限,就可以通过映射关系拿到对应的物理地址。
——》比如代码区的数据,我们知道它是只读的,读取代码时先拿着虚拟地址,通过页表检查权限,有r权限,就成功拿到物理内存的代码区数据;但如果我要修改代码区数据时,流程在页表时检查没有w权限,虚拟地址无法转化为物理地址(这个时候在cpu一般直接报错,os直接把进程杀了),也就改不了代码区的数据了。
——》以前学习C语言的时候我们知道,像char * str = “hello world”,我们通过 * str = “abc”,程序就会直接崩溃——》为什么?原因就是"hello world"这样的数据在内存中是只读的,不可写入,一旦写入,cpu通过页表拿不到对应的物理内存,就会出错(缺页中断),os检查原因认为,你的程序对只读的区域进行写入,是你的代码出了问题,所以就直接杀掉进程,我们的程序也就直接崩溃了。
——》针对上面的问题,C语言就有了const,const是在编译阶段就告诉编译器,我这个变量不可修改,所以一旦对const修饰的变量进行修改,程序就会在编译阶段就给你拦截了。
❓提个小问题
:const char * str = “hello world”与char * str = “hello world”如果都对* str进行修改而发送报错,区别在哪?
——》综上所述,一个是编译器在编译阶段就拦截了,一个是进程运行时cpu报错被OS杀掉了。
- isexists:这个标记位记录着这个映射关系是否有效,说白了就是在页表中填写的这个物理地址是否存在对应的数据。
——》数据是从磁盘加载到内存的,有时候你在程序中定义了一个数据,但是你程序并没有使用这个数据,那么这个数据虽然有在页表中有映射关系,但这份数据不一定加载到内存了,isexists这个标志位就表示它是否加载。如果后面的代码中使用了,才会从磁盘中加载数据进来。
——》由这个标志位支持分配加载,挂起等操作。
4️⃣关于mm_struct
mm_struct他们是进程管理进程地址空间的结构体,在内存中是一个结构体变量,是结构体变量就要初始化啊!进程地址空间相关的信息(比如栈区,代码区,未初始化变量区…各个区域的大小要设置多少)由哪里得到的呢?
——》答案是从可执行程序中来的!编译器在编译程序形成可执行程序后,各个区域的大小信息就已经有了,OS只需要访问特定区域读取可执行文件的相关区域,就能设置好进程地址空间了。——》页表的相关信息包括标记位,映射关系也是一样的!该物理地址是可读可写不是OS规定的,是可执行文件设置的!
——》从这里我们可以窥见一二:操作系统(的进程管理)与(编译器形成的)可执行程序并不是相互独立的关系,二者是息息相关的。
——》堆区申请空间的本质——》改变区域划分,给堆区划分更多的虚拟地址——》再由页表中建立映射关系——》在使用时在申请物理内存。
1.2 进程地址空间存在的原因(为什么)
- 虚拟进程地址空间与页表相互配合,可以有效保护内存。
——》意思就是可以有效解决野指针问题。什么是野指针?我们程序为什么使用了野指针就会崩溃?野指针有两种情况,第一是指向了未被在页表建立映射的虚拟地址,第二是指向了不该被指向区域的地址(比如代码区),当你通过野指针进行写入时,页表会找不到对应的映射条目,或者检查标记位发现你没有该虚拟地址的权限,虚拟地址到物理地址的转化不成功,cpu报错,OS就会把你的进程杀掉——》这样就有效避免会出现野指针问题胡乱修改物理内存,也就保护了物理内存。
- 进程管理 和 内存管理 在系统层面解耦合了
——》OS在内存管理中,不需要知道进程在物理内存申请一片空间是为了做什么。而在进程管理中,也不需要知道物理内存申请有没有成功,如果没有成功,就没有映射关系,程序再怎么折腾也只是导致自己的进程崩溃。
- 让进程以统一的视角看待物理内存
——》可执行程序的代码和数据记载到内存时,不一定是连续的,可能是物理内存的任意位置,或者说连续的虚拟地址在经过页表映射时,物理地址不一定是连续的:
——》进程不需要关心物理内存是否是连续的,在进程中有页表,使用连续的虚拟进程地址空间。页表和虚拟进程地址空间将地址从“无序”(物理地址)变为“有序”(虚拟地址)。
1.3 OS如何管理进程地址空间(怎么做)
进程地址空间在内存中本质是一个结构体:mm_struct,是task_struct结构体(管理进程的结构体)的一个成员,OS只需要把进程(task_struct)管理好,进程地址空间本身就已经管理好了
——》理解全局变量的全局性:今天我们就知道,在代码中具有全局性的变量,不放在栈上,而放在初始化数据区:
——》而该区域的生命周期的跟随进程的,只有进程被销毁,进程地址空间才会被销毁,该区域的数据才会被销毁,全局变量的虚拟地址一直被整个进程看到,所以,全局变量才具有全局性。
本文就到这里,感谢你看到这里❤️❤️! 我知道一些人看文章喜欢静静看,不评论🤔,但是他会点赞😍,这样的人,帅气低调有内涵😎,美丽大方很优雅😊,明人不说暗话,要你手上的一个点赞😘!
希望你能从我的文章学到一点点的东西❤️❤️
相关文章:

Linux入门:环境变量与进程地址空间
一. 环境变量 1. 概念 1️⃣基本概念: 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数 如:我们在编写C/C代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里&#x…...

【Jenkins实战】Windows安装服务启动失败
写此篇短文,望告诫后人。 如果你之前装过Jenkins,出于换域账号/本地帐号的原因想重新安装,你大概率会遇上一次Jenkins服务启动失败提示: Jenkins failed to start - Verify that you have sufficient privileges to start system…...

web实操5——http数据详解,request对象功能
http请求数据 现在我们浏览器f12的那些是浏览器给http格式数据整理之后便于我们阅读的。 原始的http格式信息: 就是按照一定格式和符号的字符串: 请求行:格式如下图 请求头:一个个key,value数据,用,分割…...

C# 如何动态加载程序集
程序集的加载,默认是从当前目录下查找,如果当前目录查找不到,然后再去系统目录中查找,依然查找不到就会从环境变量中查找,如果依然找不到,则会抛出一个异常 FileNotFoundException。 托管代码中࿰…...

前端基础的讲解-JS(10)
作用域链 通过上节作用域我们知道,当我们声明一个函数时,程序会生成一个独立的作用域,如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域;根据 内部函数可以访问外部函数变量 的这种机制,用链…...

JNI整理总结
JNI JNI基础 JNI基础 JNI,java native interface,即java本地接口,是java调用本地化方法的接口,是为java编写本地方法,为jvm嵌入本地应用程序的标准化接口。首要目标是在给定的平台上采用java通过JNI调用本地化方法&am…...

aws中AcmClient.describeCertificate返回值中没有ResourceRecord
我有一个需求,就是让用户自己把自己的域名绑定我们的提供的AWS服务器。 AWS需要验证证书 上一篇文章中我用php的AcmClient中的requestCertificate方法申请到了证书。 $acmClient new AcmClient([region > us-east-1,version > 2015-12-08,credentials>[/…...

Spring Task详细讲解
✨Spring Task简介 Spring Task 是 Spring 提供的轻量级定时任务工具,也就意味着不需要再添加第三方依赖了,相比其他第三方类库更加方便易用。可以按照约定的时间自动执行某个代码逻辑。 使用场景: 信用卡每月还款提醒银行贷款每月还款提醒…...

C++初阶 --- 类和对象(1)
类和对象(1) 一、类的相关内容1.访问限定符2.class关键字与struct关键字的区别3.类域 二、this指针三、成员函数存放在哪里?1. 函数的存放位置2. 练习题 一、类的相关内容 1.访问限定符 (1)C⼀种实现封装的⽅式&…...

《TCP/IP网络编程》学习笔记 | Chapter 7:优雅地断开套接字连接
《TCP/IP网络编程》学习笔记 | Chapter 7:优雅地断开套接字连接 《TCP/IP网络编程》学习笔记 | Chapter 7:优雅地断开套接字连接基于 TCP 的半关闭单方面断开连接带来的问题套接字和流针对优雅断开的 shutdown 函数为何需要半关闭?基于半关闭…...

Anaconda 和 conda 是什么关系?就像 pip 和 python 一样吗
Anaconda 和 conda是 Anaconda Distribution 还是 Miniconda Anaconda 和 conda Anaconda 和 conda 之间的关系有点类似于 pip 和 Python,但又有所不同。 Anaconda 是一个数据科学和机器学习的发行版,它包含了 Python、conda 以及许多预装的库和工具&am…...

「数据治理」核心专业术语解释!
数据治理就是指在一定的组织范围内,依托制度法规、标准规范、应用实践和支撑技术对数据进行全生命周期的数据确权、质量管理、安全控制、隐私保护、开放共享、交易流通和分析处理。数据治理的目标是为了提高数据的可用性、安全性、流通性,激活数据资源…...

Mac Nginx 前端打包部署
安装homebrew /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 安装Nginx brew install nginx nginx相关命令 nginx启动命令:nginx nginx -s reload #重新加载配置 nginx -s reopen #重启 nginx -s stop #…...

乳腺癌诊断分析——基于聚类分析实现
一、研究背景 乳腺癌属于恶性肿瘤,在早期发现后需要及早将病变组织切除,而且术后还要化疗和放射等辅助治疗,能够抑制癌细胞的扩散和增长。 二、研究目的 研究乳腺癌病人的患病特征通过聚类分析方法对特征进行分类通过上述聚类结果对乳腺诊…...

Ubuntu 22.04 安装配置opencv
OpenCV下载:https://opencv.org/releases/ 编译与安装 安装cmake OpenCV需要使用cmake进行编译 sudo apt-get install cmake安装依赖 sudo apt-get install build-essential pkg-config libgtk2.0-dev libavcodec-dev libavformat-dev libjpeg-dev libswscale-dev lib…...

【软考】系统架构设计师-计算机系统基础(3):嵌入式系统
嵌入式系统:嵌入式处理器、相关支撑硬件、嵌入式OS、支撑软件以及应用软件 嵌入式系统特征:专用性强、技术融合、软硬一体软件为主、通用计算机资源少,... 分层(5层):硬件层 → 抽象层 → 操作系统层 → 中间件层 →…...

实测运行容器化Tomcat服务器
文章目录 前言一、拉取Tomcat 9.0镜像二、运行容器化Tomcat服务器三、访问Tomcat官网首页测试 总结 前言 运行容器化Tomcat服务器,首先确保正确安装docker,并且已启动运行,具体安装docker方法见笔者前面的博文《OpenEuler 下 Docker 安装、配…...

致敬苹果的国产手机,只会失去更多市场,iPhone一骑绝尘!
近期诸多国产手机品牌纷纷发布旗舰手机,随着这些旗舰手机的发布,可以看出他们在进一步致敬苹果--实质就是模仿苹果的设计,如此做的结果恐怕是得不偿失,将导致国产手机损失更多市场。 致敬苹果最为直接的当属手机外壳了,…...

《MYSQL45讲》kill不掉的线程
kill query 线程id :终止这个线程正在执行的语句 kill connection 线程id :关闭这个线程的连接,也会先停止这个线程正在执行的语句。这个connection可以缺省。 本文讨论的情况是:使用了kill命令,却没有断开连接,show processli…...

单体架构 IM 系统之 Server 节点状态化分析
基于 http 短轮询模式的单体架构的 IM 系统见下图,即客户端通过 http 周期性地轮询访问 server 实现消息的即时通讯,也就是我们前面提到的 “信箱模型”。“信箱模型” 虽然实现非常容易,但是消息的实时性不高。 我们在上一篇文章(…...

java xml 文本解析
示例文本 <Message><MessageName>time_request</MessageName><Timestamp>20220217165432906359</Timestamp><Body><EQPID>CMMAB01-DTP01</EQPID></Body> </Message>示例代码 import org.w3c.dom.Document; impo…...

Docker占用空间太大磁盘空间不足清理妙招
docker占用空间太大了,磁盘空间不足,清理3妙招 清除所有已停止的容器(container)、未被任何容器所使用的卷(volume)、未被任何容器所关联的网络(network)、所有悬空镜像(…...

编程之路,从0开始:字符函数和字符串函数
Hello大家好!很高兴我们又见面了! 给生活添点passion,开始今天的编程之路! 目录 1、字符分类函数 2、字符转换函数 3、字符串函数 1、 strcpy 2、 strcat 3、 strcmp 4、strlen(s) 5、strstr(s1, s2) 6、 strtok(s1, s2…...

化工防爆巡检机器人:在挑战中成长,为化工安全保驾护航
随着全球能源需求的不断攀升,化工行业的安全性与高效性愈发受到关注。化工设施规模巨大,而且其中多数存在高风险因素,像是易燃易爆化学物质、高温环境、有毒有害物质以及高压设备等。仅2023年,国内危化品事故就多达652起ÿ…...

音频采样数据格式
音频信号在模拟到数字转换时,会涉及到多个关键参数,如采样率、位深度、通道数等。下面是常见的音频采样数据格式及其相关概念: 1. 采样率 (Sample Rate) 采样率指的是每秒钟对音频信号进行采样的次数,单位为赫兹 (Hz)。常见的值…...

【pytorch】常用强化学习算法实现(持续更新)
持续更新常用的强化学习算法,采用单python文件实现,简单易读 2024.11.09 更新:PPO(GAE); SAC2024.11.12 更新:OptionCritic(PPOC) "PPO" import copy import time import torch import numpy as np import torch.nn as …...

DAY59||并查集理论基础 |寻找存在的路径
并查集理论基础 并查集主要有两个功能: 将两个元素添加到一个集合中。判断两个元素在不在同一个集合 代码模板 int n 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好 vector<int> father vector<int> (n, 0); // C里的一…...

Mybatis执行自定义SQL并使用PageHelper进行分页
Mybatis执行自定义SQL并使用PageHelper进行分页 基于Mybatis,让程序可以执行动态传入的SQL,而不需要在xml或者Select语句中定义。 代码示例 pom.xml 依赖 <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId&g…...

OpenCV DNN
OpenCV DNN 和 PyTorch 都是常用的深度学习框架,但它们的定位、使用场景和功能有所不同。让我们来对比一下这两个工具: 1. 框架和功能 OpenCV DNN:OpenCV DNN 模块主要用于加载和运行已经训练好的深度学习模型,支持多种深度学习…...

什么时候需要复写hashcode()和compartTo方法
在Java编程中,复写(重写)hashCode()和compareTo()方法的需求通常与对象的比较逻辑和哈希集合的使用紧密相关。但请注意,您提到的compartTo可能是一个拼写错误,正确的方法名是compareTo()。以下是关于何时需要复写这两个…...