Linux《进程概念(下)》
在之前我们已经了解了进程基本的概念、知道了如何去创建子进程;还了解了进程状态、进程切换、进程O(1)调度算法等,那么接下来在本篇当中我们就来学习环境变量和程序地址空间的相关知识,相信通过本篇的学习你会有很大的所获,一起加油吧!!!
1. 环境变量
1.1 环境变量的概念
• 环境变量(environment variables)⼀般是指在操作系统中用来指定操作系统运行环境的⼀些参数
• 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪
里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
• 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。
1.2 命令行参数表
以上给了环境变量的概念,但是这些概念现在对我们来说没啥用,因为目前对环境变量完全就没有一定认识,之后通过了解过环境变量具体的实例时候再去理解概念才会又收获。但在此在了解环境变量之前先要来了解命令行参数。
在之前我们在C/C++内编写代码的时候main函数都是没有参数的,那么是否main函数就不能带参呢?
其实和普通的函数一样,main函数也是可以带上参数的,在此第一个参数为就为在执行对应的可执行程序时输入的参数的个数,第二个参数就是一个字符串指针。
例如以下实现的代码:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h> int main(int argc,char* argv[])
{ for(int i=0;i<argc;i++) { printf("argv[%d]:%s\n",i,argv[i]); } return 0;
}
将以上的代码编译成才执行查询之后运行,当我们在运行的时候带上参数就会输出以下的内容
通过以上的示例就可以看出main是可接收用户输入的参数的,之前我们没有使用过只不过是之前实现的程序只需要从头跑到尾即可,不需要处理用户进行选择的情况。
那么用户输入的参数又是怎么样传输给main函数的呢,main函数内又是怎么保存的呢?
其实在我们运行对应的程序时,程序内部都会有一张命令行参数表,在该表当中就存储着命令行内输入的参数。
例如以上的示例,命令行参数表就如下所示:
1.3 环境变量表
以上我们就了解了命令行参数表,那么接下来我们就要来思考一个问题了就是在执行我们自己写的程序时是需要在之前带上./的,而使用系统自带的命令时直接写出名字即可运行对应的指令,这是为什么呢?
要解答以上的问题就需要了解到环境变量,我们知道要执行一个程序就需要先找到对应的程序的路径,在进程内部就会使用PATH来存储系统当中默认搜索指令的路径。
在此可以使用env指令来查看所有的环境变量
在以上就可以看到环境变量内是存在PATH的。
除此之外要查询环境变量还可以使用 echo $环境变量名 来进行查询
那么这时就可以解释为什么我们在使用系统内的指令可以不带路径而直接使用指令的名称就可以调用,这其实就是在输入的指令只要是在PATH内的路径下,就会在用户输入的指令前加上路径。
这时你可能就会好奇那是不是只要将以上我们的mytest的路径也加到PATH内,那么是不是在运行mytest的时也可以不带./就可以执行了呢?
要解答这个问题很简单,来试试看不就知道了
以上直接使用PATH=当前路径这时就可以直接使用mytest了,但是这时又有问题了,那就是现在再查询PATH会发现我们的操作是将之前的PATH给覆盖了,那么这就会造成系统原本的指令无法正常的直接使用了。
注:在此PATH改变时pwd还能正常的使用是因为pwd是内建命令,在之后会进行详细的讲解。
那么这时我们要怎么才能将PATH恢复呢?
这时只需要将Xshell重启即可
在此在PATH内进行路径追加的正确方式是如下所示:
此时就将当前的路径追加到了PATH内,并且还保留了原来的环境变量,这样系统内的指令就还能正常的使用。
其实以上我们能使用env,echo等的指令查询环境变量其实是系统的bash内部存在一张环境变量表,和之前提到的命令行参数表类似,环境变量表也是本质也是一个指针数组。当bash启动的时候就会构建出环境变量表。
当我们使用ls -a等的指令时bash就会先将输入的命令分解到命令行参数表当中,之后再在环境变量表当中查询路径。如果指令存在就创建子进程执行,不存在就报command not found错误提示。
到现在我们就知道了在bash创建时就会在bash内部存在两张表分别是命令行参数表和环境变量表,那么接下来问题又来了,那就是环境变量最开始是从哪里来的呢?
其实环境变量表最开始是存在系统当中的配置文件,使用cd ~指令回到家目录之后就可以看到存在以上的两个隐藏文件
查看.bashrc
查看bash_profile
接下来再查看.bashrc内提到的/etc/bashrc
在此就可以看到很多的环境变量,在此还可以看到之前我们学习过的权限掩码umask
当我们将配置文件当中的环境变量修改时就能让每次登入Xshell的时候查询环境变量都是发生修改的,不过强烈不建议随意的修改配置文件当中的环境变量,这样有时会造成系统的混乱。
那么如果当中Linux当中有10个用户登入,就会存在10个bash,此时这10个bash都会各有一张环境变量表、一张命令行参数表。都会从配置文件当中拷贝对应的环境变量。
通过以上了学习就知道了我们执行一个指令前提是找到对应的指令,在此其实就是bash来进行的,这时因为bash既有命令行参数表;又有环境变量表。
1.4 更多环境变量
以上我们了解了环境变量表,但是我们只了解了PATH这一个环境变量,那么接下来就来了解更多的环境变量。
USER与LOGNAME
在环境变量当中USER表示的用户名,LOGNAME表示的是当前登录的用户名,正常情况下这两个是同是相同的。
SHELL
SHELL表示的是当前bash的路径
HISTCONTROL
HISTCONTROL的作用就是保存用户最近的指令,这也是为什么在我们可以使用CTRL+r和上下键去查询历史的指令,但是一般自会保存最近的指令,要不然会占据太多的内存资源。
HOME
HOME保存的是当前用户的家目录
PWD
PWD存储的是当前的路径
OLDPWD
OLDPWD保存的是最近一次的路径,该环境变量在我们使用cd-的时候就起作用了
1.5 环境变量相关的操作
以上我我们了解了一系列的环境变量,那么接下来就来就来学习一些获取环境变量的方法
其实在以上我们已经了解了两个环境变量的操作,分别是使用env将所有的环境变量打印出来以及使用echo单独的将一个环境变量打印出来。
其实除了以上的之前提到的环境变量的操作方法,接下来再来补充几个相关的操作。
首先是如果我们要创建一个新的环境变量,那么就可以使用export来实现
export
export 变量的名称以及值
例如使用export导入一个新的名为tmp1的环境变量接下来再使用env就可以在环境变量表当中查询到该环境变量
unset
以上使用export就可以创建环境变量,此时如果要将对应的环境变量删除就需要使用unset
unset 环境变量名
例如要将以上创建的tmp1的环境变量从环境变量表当中删除就可以使用unset来实现
1.6 通过代码如何获取环境变量
接下来将学习三种使用代码获取环境变量的方法
1.命令行第三个参数
在之前了解命令行参数表的时候我们已经知道了其实在main函数当中也是可以有参数的,第一个参数为命令行参数的个数,第二个变量为命令行参数的指针数组,但其实除了以上的两个参数以外main函数还可以存在第三个参数;那就是环境变量表的指针数组
例如以下的代码
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h> int main(int argc,char* argv[],char* env[])
{ (void)argc; (void)argv; for(int i=0;env[i];i++) { printf("env[%d]->%s\n",i,env[i]); } return 0;
}
注:以上的代码当中将argc和argv强转成void是因为在gcc/g++当中如果函数的参数在函数体内未使用,那么就会编译报错,因此在此的操作是为了避免编译器的报错。
以上的代码编译运行之后就会输出以下的内容,此时就可以发现在子进程当中已经将bash当中的环境变量表给继承下来了
2.使用系统调用getenv
首先来使用man手册来查询getenv系统调用的的使用方法
通过以上man手册内的描述就可以看出getenv的作用是获取指定的环境变量。
如果现在我们要写一个只有指定的用户才能执行的程序,实现的代码如下所示:
#include<stdio.h>
#include <stdlib.h>
#include<string.h>
int main(int argc,char* argv[],char* env[])
{ (void)argc; (void)argv; (void)env; const char* who=getenv("USER"); if(who==NULL)return 1; if(strcmp(who,"zhz")==0) { printf("程序正常的执行!\n"); } else{ printf("不是指定的zhz用户无法执行!\n"); } return 0;
}
在以上的代码当中就使用了getenv来获取当前用户名的环境变量,接下来使用strcmp来判断当前的用户是不是zhz,是的话就正常的执行否则就输出当前的用户不是zhz。
当使用zhz执行以上的代码生成的可执行程序
使用其他的用户执行生成的可执行程序
通过以上使用getenv的2示例就可以看出为什么子进程可以被子进程继承?
让子进程继承对应的环境变量表就可以在子进程内部实现个性化的需求
3.使用全局指针environ
在此先使用man手册来查询environ的使用方法
通过以上的描述就可以看出erviron其实是一个二级指针也就是环境变量表的指针
接下来来看以下的代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h> extern char** environ;
int main(int argc,char* argv[],char* env[])
{ (void)argc; (void)argv; (void)env; for(int i=0;environ[i];i++) { printf("env[%d]->%s\n",i,environ[i]); } return 0;
}
1.7 环境变量的特性
通过以上的environ全局的指针就可以看出环境变量是具有全局特性的
补充概念:
1.本地变量
其实在相同当中除了环境变量之外还存在本地变量,在此压查询存在的本地变量就需要使用指令set
以上就可以看到使用set指令之后就看到环境变量以及本地变量,当时和环境变量不同的是本地变量是不会被子进程进继承的。
通过以上set输出的结果还可以看到本地变量当中还存在命令行提示符的输出格式
2. 内建命令
以上我们使用的export其实是内建命令,也就是命令的执行不是通过创建子进程来实现的,而是让bash自己亲自执行的,具体的过程是通过bash调用相应的函数或者系统调用。
其实pwd,cd等指令也是内建命令。这也解释了为什么当我们将环境变量当中的PATH改变之后这些命令还能执行,而其他的命令却无法正常的使用。
注:具体的内建命令的讲解将在之后的自定义shell实现章节进行。
2. 程序地址空间
在之前学习C/C++的之后我们就了解到了计算机当中是存在栈区、堆区等不同的区域的。
来看以下的代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_unval;
int g_val = 100;
int main(int argc, char *argv[], char *env[])
{const char *str = "helloworld";printf("code addr: %p\n", main);printf("init global addr: %p\n", &g_val);printf("uninit global addr: %p\n", &g_unval);static int test = 10;char *heap_mem = (char*)malloc(10);char *heap_mem1 = (char*)malloc(10);char *heap_mem2 = (char*)malloc(10);char *heap_mem3 = (char*)malloc(10);printf("heap addr: %p\n", heap_mem); //heap_mem(0), &heap_mem(1)printf("heap addr: %p\n", heap_mem1); //heap_mem(0), &heap_mem(1)printf("heap addr: %p\n", heap_mem2); //heap_mem(0), &heap_mem(1)printf("heap addr: %p\n", heap_mem3); //heap_mem(0), &heap_mem(1)printf("test static addr: %p\n", &test); //heap_mem(0), &heap_mem(1)printf("stack addr: %p\n", &heap_mem); //heap_mem(0), &heap_mem(1)printf("stack addr: %p\n", &heap_mem1); //heap_mem(0), &heap_mem(1)printf("stack addr: %p\n", &heap_mem2); //heap_mem(0), &heap_mem(1)printf("stack addr: %p\n", &heap_mem3); //heap_mem(0), &heap_mem(1)printf("read only string addr: %p\n", str);for(int i = 0 ;i < argc; i++){printf("argv[%d]: %p\n", i, argv[i]);}for(int i = 0; env[i]; i++){printf("env[%d]: %p\n", i, env[i]);}return 0;
}
以上的代码编译形成可执行程序之后输出的结果如下所示:
通过以上的输出结果就可以看出环境变量和命令行参数的地址其实是非常接近的,这其实就是因为这两个就是存储在同一内存空间的。
在学习C/C++的时候我们都看过以下类似的图
以上我们在学习C/C++的时候将以上的图表示的叫做做程序地址空间,但其实之前这种说法是错误的,之前这样讲只不过是为了让我们更好的理解,因为到了内存上的概念就不是语言层面上能理解的了。其实以上空间正确的叫法叫做进程地址空间或者虚拟地址空间。并且该内存实际上不是真正的物理内存。
接下来再来看以下的代码:
#include <stdio.h>
#include <unistd.h>
#include<sys/types.h>
#include <stdlib.h>
int g_unval=0; int main()
{ pid_t pid=fork(); if(pid==0) { while(1) { printf("子:g_unval:%d,pid:%d,ppid:%d,&g_unval:%p\n",g_unval,getpid(),getppid(),&g_unval); g_unval++; sleep(1); } } else{ while(1) { printf("父:g_unval:%d,pid:%d,ppid:%d,&g_unval:%p\n",g_unval,getpid(),getppid(),&g_unval); sleep(1); } } return 0;
}
在以上的代码当中我们使用fork创建子进程,之后在子进程内每秒使得变量g_unval的值加一,而在父进程内不对g_unval的值做任何的修改。观察变量g_unval值的的变化,以及变量的地址。
通过以上的输出结果就会发现一个非常奇怪的问题,那就是在父进程和子进程当中的变量g_unval的地址竟然是一样的,但是在这两个进程当中的g_unval的值却是不一样的,那么这不就出现同一个地址内的变量同时拥有两个值了吗?这就更加说明以上我们看到的地址不是物理地址。
那么要解答以上的问题就需要来学习进程地址空间的相关概念。
2.1 进程地址空间
其实在虚拟地址空间和物理地址空间之后是通过页表进行映射的。
例如以下的示例当一个程序当中创建了一个变量之后之后就会得到该变量的虚拟地址空间的起始地址,那么这时就会通过一个页表将此时的虚拟地址看见内的地址映射出变量实际存储的物理空间的地址。
并且在虚拟地址空间内的每个内存单元的大小和物理空间也是一样的都是一字节,对于32位的机器,那么虚拟地址空间的总大小就是2^32次方字节;对于64位的机器虚拟地址空间的总大小就是2^64次方字节。
在进程的task_struct内存储着指向对应的虚拟地址空间的指针
以上我们就知道了一个进程实际上进程内的数据是任何映射到物理内存上的,那么当我们使用fork创建出子进程的时候又是什么样的呢?
通过之前进程的概念的学习我们知道当创建子进程的时候就会进行写实拷贝,这时子进程会将父进程的task_struck拷贝一份之后对内部的数据进行一定的修改,在此我们要知道的是每个进程都会有自己的虚拟内存空间和页表。子进程创建之后也会将父进程的虚拟地址空间进行和页表拷贝,并且这时和父进程是共用同一份的代码和数据。
当我们在父进程和子进程当中没有对该数据进行修改的时候,父子进程会一直共用物理内存当中的这一份数据,但是当父子进程当中出现了对该数据进行修改的操作的时候,在物理内存当中就会立刻创建一份和原数据一样大的空间并且将原空间内的数据也拷贝过来,之后再改变父子进程当中一个页表的指针映射。
那么有了以上的知识就可以解释为什么之前我们的代码当的g_unval的值在父子进程当中是不一样的,但是地址却是一样的。这其实就是当我们在子进程当中对该变量进行修改的时候在物理内存当中就会创建出一个新的数据块。我们之后在子进程当中访问的实际上是物理内存当中新的变量数据块,而父进程访问的是旧的数据块。而这两个变量的地址相同是因为子进程的虚拟地址空间是拷贝至父进程,变量相同的只是虚拟地址,而物理地址已经不一样了。
在《进程概念(上)》我们在了解fork的时候其实还留了一个问题,那就是为什么fork的返回值即等于0又大于0,现在我们了解了虚拟地址到物理地址的映射之后旧很好理解了;实际上fork函数的返回值在在物内存当中是有两份的,只不过是虚拟父子进程地址是一样的。
在了解了以上的概念之后接下来将通过两个故事来进一步理解虚拟地址空间
故事1:
假设现在有一个美国的大富翁,虽然的他在自己的人生当中积累了很多的财富,但是他的私生活比较乱,他有非常多的私生子。他这个人还有一个特性就是非常喜欢对他的孩子们画大饼,今天和他正在上班的的大儿子说你要好好努力啊,之后你能力够了就让你接班我这个董事长的位置,过来几天又和他正在上高中的二女儿说你要好好学习啊,等你考上了大学我就给你买一份苹果的全家桶,又过了几天又对每天就知道玩游戏的三儿子说你不要每天就知道玩游戏啊,我还等着你继承家产啊。
那么在以上的故事当中其实大富翁就是操作系统,而他的财富就是物理内存,他的一个个私生子就是一个个进程,而他给这些私生子进行画大饼的时候就是操作系统给一个个进程分配对应的虚拟地址空间;这一个个进程都认为自己是独占对应的物理内存。
由于大富翁的私生子非常的多,那么他有时候就会忘了给各个私生子画的饼分别是什么,那么时候就需要将画的饼记录下来,反应到操作系统当中就是要将各个进程的虚拟地址空间记录下来,南萨摩时候就要进行先描述再组织的操作。这时我们就可以知道了其实虚拟地址空间的本质其实也是一个结构体的对象。
那么这个结构体内部的元素又是怎么样的呢?这时候就要我们来接着看以下的故事
故事2:
在小学里三年级的小明是和一个女生坐同桌,但是他的同桌让他觉得很烦,因为他的同桌最近给他们的课桌画了一条“三八线”;只要小明过了线就会被揍一下,并且他的这个同桌还有有点怪癖就是将自己的课桌划分为了几个区域,每个区域内都要放对应的物品,就比如在课桌的最旁边到课桌的内的10厘米要放自己的笔盒,以下的30厘米要放自己的书本。她还不允许别让将她桌子上的物品给搞乱。
那么在以上的故事当中小明的同桌将她的位置进行了划分的依据是各个区域的起始位置和终止的位置。其实在各个进程的虚拟地址空间当中和以上故事当中的类似也是按照起始地址和终止的地址来划分各个区间的。当我们要调整对应区间的大小时只需要将对应区间的起始和终止的地址进行修改。
接下来在Linux的源代码当中就可以看到在进程的task_struct当中存在一个mm_struct的指针也就是指向虚拟地址空间的指针,在观察该指针指向的结构体mm_struct就可以看到在该结构体的内部存在区域的划分
以上我们就了解了虚拟地址空间也是数据结构的对象,那么此时问题就来了,那就是每个进程内部各个区域的划分一开始是从哪里来的呢?
一开始对应的进程是在磁盘当中的,之后其实将进程加载到内存之前就已经构建出对应的数据结构对象,并且在该数据结构对象内部完成了各个区域的划分。
就例如一个进程的代码和数据一共大小是2GB,当该进程被加载到操作系统当中时就会开辟出正文代码和初始化数据为2GB的区域,但是一开始如果物理内存只加载了0.5GB的数据;此时还没有将剩下的数据加载到物理内存当中,那么此时在页表当中就只会将0.5GB的数据进行虚拟和物理之间的映射。
之后当需要使用到0.5GB之后的数据时此时操作系统会发现在该进程的页表内部没有对应的剩下的1.5GB的数据的映射,此时接下来就会发生缺页中断 ,也就是会将磁盘当中剩下的数据也加载到物理内存当中,此时进程将短暂的停止运行,之后等到剩下的物理内存数据和虚拟内存当中在页表也建立映射关系之后继续运行。
因此总的来说磁盘当中的程序加载到操作系统当中会进行以下的操作:
1.在虚拟内存当中申请指定大小的空间,同时还会进行区域的调整
2.加载程序,申请物理空间
3.通过页表建立物理地址和虚拟地址之间的映射关系
通过以上的讲解我们就可以发现虚拟地址空间存在的意义其实是可以让进程管理和内存管理以及IO等操作解耦的,从而实现进程和内存之间的高内聚、低耦合
2.2 虚拟地址空间存在的意义
以上我们了解了虚拟地址空间是如何实现虚拟地址到物理内存之间的映射的,那么此时我们就要来思考了为什么在计算机当中要存在虚拟地址空间,不是直接将进程与物理地址之间建立联系不是更方便吗?
如果是在没有虚拟地址空间的状态下,那么在物理内存的加载数据时就需要对不同的进程之间进行各个数据区的管理,若出现稍微的数据管理不当就会出现一个进程的数据覆盖另外的进程,从而造成数据的丢失,在这种情况下物理内存的管理就会很困难。
而有了虚拟地址空间之后就可以让操作系统只需要对虚拟地址空间进行管理;而无需对应数据实际上映射的物理内存而考虑,这样就可使得每个进程在虚拟地址空间内都是有序的,但是实际上映射的物理空间是乱序的,从而减少了在管理进程的同时还要管理内存。
因此总的来说存在的第一点意义就是:将物理内存当中的“无序”变为“有序”
在之前学习C/C++的时候我们就知道空指针是指指针指向的内存空间已经为空了,此时再对该内存空间内进行数据的修改时就会造成程序的奔溃。那么在了解了虚拟地址空间之后我们要了解其实程序当中空指针解引用出现报错其实是对应的虚拟地址在页表上进行物理地址映射时发现没有对应的物理地址,此时就会映射失败。
还有就是我们之前就了解到如果在程序当中出现以下的代码就会造成程序的奔溃
const char* str="hello world";
*str="YYY";
之前只是了解到常量字符串是存储在常量区的,是不能修改的。其实这里存储的常量区也就是存储在正文代码的。但实际上更本质的原因是该区域的变量在页表内进行虚拟到物理之间的映射时会发现这部分的数据是自读的,没有写的权限,那么此时在查询页表的时候就会进行权限的拦截。
因此总的来说存在的第二点意义就是:在地址转化的过程当中,可以对你的地址和操作进行合法性的判断,进而保护物理内存。
除了以上的存在的第三点意义就是是:让进程管理和内存管理进行一定程度的解耦合。
2.3 虚拟内存空间再理解
以上我们了解了虚拟地址空间实际上是就是一个mm_struct的对象,那么接下来我们就要来思考一个问题就是之前再C/C++当中使用malloc和new等申请内存空间每次申请内存可能不是连续的,那么是不是就是说明在虚拟地址空间内会存在多个堆区呢?
确实是这样的,linux内核使用vm_area_struct 结构来表示⼀个独立的虚拟内存区域(VMA),由于每个不同质的虚拟内存区域功能和内部机制都不同,因此⼀个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。上面提到的两种组织方式使用的就是vm_area_struct结构来连接各个VMA,方便进程快速访问。
以上就是本篇的全部内容了,接下来我们将在下一篇当中开始进程控制的学习,未完待续……
相关文章:
Linux《进程概念(下)》
在之前我们已经了解了进程基本的概念、知道了如何去创建子进程;还了解了进程状态、进程切换、进程O(1)调度算法等,那么接下来在本篇当中我们就来学习环境变量和程序地址空间的相关知识,相信通过本篇的学习你会有很大的所获,一起加…...
MySQL 比较运算符详解
(1)符号类型运算符 运算符名称作用示例等于运算符判断两个值、字符串或表达式是否相等SELECT * FROM users WHERE age 25SELECT name FROM products WHERE category Electronics<>安全等于运算符安全地判断两个值、字符串或表达式是否相等&…...
No qualifying bean of type ‘XXX‘ available
没有某类型的bean可供使用 问题一解决方案错误问题配置类YuApiClientConfig依赖导入测试方法 问题二解决方法问题现场问题解决 问题一 Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘com.transbit.yuapiclientsd…...
手搓一个 MCP Server 实现水质在线数据查询
随着人工智能技术的快速发展,如何将大语言模型(LLM)与实际业务场景结合,提供精准、可控的服务成为一个热门话题。MCP(Model Context Protocol)作为一种开放协议,为应用程序向 LLM 提供标准化的上下文接口,极大地简化了这一过程。本文将以构建一个水质在线查询 MCP 服务…...
neo4j初尝试
neo4j 下载并安装 这里以ubuntu 下载为例 打开neo4j官网,如下图所示,找到下载中心 选择 每个人可以根据自己的系统进行下载。然后解压tar -xf neo4j-community-2025.04.0-unix.tar.gz,如果不出意外的话,这里就可以直接输入命令启动了&#…...
数据分析业务拆解底层思维
业务拆解 分析前要有方法,从用户体验入手,将业务拆解,找到对象以及对象之间的关系。 电商平台卖的不是用户时间,不是流量,而是机会,而作为一个分析师就得分析机会在哪,帮助平台将机会更好的提…...
Linux运维——Vim技巧一
Vim技巧 一、优化重复操作1.1、 . 命令1.2、* 命令1.3、重复修改示例 二、删除单词(daw)三、对数字做算数运算四、操作符与动作五、插入模式5.1、插入模式下删除5.2、返回普通模式5.3、插入-普通模式5.4、不离开插入模式,粘贴寄存器中的文本5…...
第一节:OpenCV 基础入门-简介与环境搭建
一、OpenCV 是什么?为什么值得学习? OpenCV(Open Source Computer Vision Library) 是一个开源的计算机视觉和机器学习库,由英特尔实验室于1999年发起,现已成为全球计算机视觉领域最广泛使用的工具之一。它…...
前端面经-VUE3篇(二)--vue3组件知识(一)组件注册、props 与 emits、透传、插槽(Slot)
组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成一个层层嵌套的树状结构: 一、注册 Vue 组件本质上是一个可以复用的 自定义 HTML 元素,为了在其他组件中使用一…...
Python的简单练习
两数的最大公约数 def gcd(a, b):while b ! 0:a, b b, a % breturn a# 示例 a 36 b 60 print(f"{a} 和 {b} 的最大公约数是: {gcd(a, b)}") while b ! 0: while:是 Python 的 循环语句,意思是“当...的时候一直重复做某事”。 b ! 0&am…...
ipvsadm,是一个什么工具?
1. ipvsadm 是什么? ipvsadm(IP Virtual Server Administration)是 Linux 内核中 IPVS(IP Virtual Server) 模块的管理工具,用于配置和监控内核级的负载均衡规则。它是 Kubernetes 中 kube-proxy 在 IPVS …...
QT6 源(72):阅读与注释单选框这个类型的按钮 QRadioButton,及各种属性验证,
(1)按钮间的互斥: (2)源码来自于头文件 qradiobutton . h : #ifndef QRADIOBUTTON_H #define QRADIOBUTTON_H#include <QtWidgets/qtwidgetsglobal.h> #include <QtWidgets/qabstractbutton.h>…...
Qt 中实现观察者模式(Observer Pattern)
在 Qt 中实现**观察者模式(Observer Pattern)通常利用其内置的信号与槽(Signals & Slots)**机制,这是最符合 Qt 设计哲学的方式。以下是详细实现方法和关键点: —### 1. 观察者模式的核心思想- Subject(被观察者):维护一个观察者列表,在状态变化时通知观察者。- …...
Vue3源码学习5-不使用 `const enum` 的原因
文章目录 前言✅ 什么是 const enum❌ 为什么 Vue 3 不使用 const enum1. 📦 **影响构建工具兼容性**2. 🔁 **难以做模块间 tree-shaking**3. 🧪 **调试困难**4. 📦 **Vue 是库,不掌控用户配置** ✅ 官方推荐做法&…...
自己部署后端,浏览器显示久久未响应
CIDER地址写错了,应该要写成0.0.0.0/0 。。。。...
【RocketMQ NameServer】- NettyEventExecutor 处理 Netty 事件
文章目录 1. 前言2. NettyEventExecutor 线程3. NettyEvent 是怎么来的4. NettyEventExecutor 线程处理不同事件的逻辑4.1 IDLE\CLOSE\EXCEPTION - onChannelIdle4.2 CONNECT - onChannelConnect 5. 小结 本文章基于 RocketMQ 4.9.3 1. 前言 【RocketMQ】- 源码系列目录 上一…...
JAVA刷题记录: 递归,搜索与回溯
专题一 递归 面试题 08.06. 汉诺塔问题 - 力扣(LeetCode) class Solution {public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {dfs(A, B, C, A.size());}public void dfs(List<Integer> a, List<In…...
【进阶】C# 委托(Delegate)知识点总结归纳
1. 委托的基本概念 定义:委托是一种类型安全的函数指针,用于封装方法(静态方法或实例方法)。 核心作用:允许将方法作为参数传递,实现回调机制和事件处理。 类型安全:委托在编译时会检查方法签…...
推理能力:五一模型大放送
--->更多内容,请移步“鲁班秘笈”!!<--- 近日人工智能领域迎来了一波密集的模型发布潮,多家科技巨头和研究机构相继推出了具有突破性特点的AI模型。这些新模型在参数规模、计算效率、多模态能力以及推理能力等方面都展现出…...
数据库=====
创建数据库 1.直接创建数据库 语法:CREATE DATABASE [IF NOT EXISTS] 数据库名 ——[]表示内部内容可省略 2.指定字符集和排序规则方式创建数据库 语法:CREATE DATABASE[IF NOT EXISTS] 数据库名 CHARACTER SET 字符集 COLLATE 排序规则 示例:…...
VITA STANDARDS LIST,VITA 标准清单下载
VITA STANDARDS LIST,VITA 标准清单下载 DesignationTitleAbstractStatusVMEbus Handbook, 4th EditionA users guide to the VME, VME64 and VME64x bus specifications - features over 70 product photos and over 160 circuit diagrams, tables and graphs. The…...
npm pnpm yarn 设置国内镜像
国内镜像 常用的国内镜像: 淘宝镜像 https://registry.npmmirror.com 腾讯云镜像 https://mirrors.cloud.tencent.com/npm/ 华为云镜像 https://repo.huaweicloud.com/repository/npm/ CNPM(阿里系) https://r.cnpmjs.org/ 清华…...
互联网大厂Java面试:从Spring到微服务的技术探讨
场景:互联网大厂Java求职者面试 在一家知名的互联网大厂面试中,面试官王严肃正在面试一位名叫谢飞机的程序员。谢飞机以其独特的幽默感而闻名,但在技术面前,他的能力能否得到认可呢? 第一轮提问:核心技术…...
[machine learning] Transformer - Attention (二)
本文介绍带训练参数的self-attention,即在transformer中使用的self-attention。 首先引入三个可训练的参数矩阵Wq, Wk, Wv,这三个矩阵用来将词向量投射(project)到query, key, value三个向量上。下面我们再定义几个变量: import torch inpu…...
Java多语言DApp质押挖矿盗U源码(前端UniApp纯源码+后端Java)
内容: 这款Java多语言DApp质押挖矿盗U源码提供了完整的前端与后端开发框架,适用于区块链应用开发。系统包括: 前端源码(UniApp):采用UniApp开发,跨平台支持iOS、Android及H5。界面简洁…...
如何解决 403 错误:请求被拒绝,无法连接到服务器
解决 403 错误:请求被拒绝,无法连接到服务器 当您在浏览网站或应用时,遇到 403 错误,通常会显示类似的消息: The request could not be satisfied. Request blocked. We can’t connect to the server for this app o…...
CGI(Common Gateway Interface)协议详解
CGI(通用网关接口)是一种标准化的协议,定义了 Web服务器 与 外部程序(如脚本或可执行文件)之间的数据交互方式。它允许服务器动态生成网页内容,而不仅仅是返回静态文件。 1. CGI 的核心作用 动态内容生成&a…...
HybridCLR 详解:Unity 全平台原生 C# 热更新方案
HybridCLR(原 Huatuo)是 Unity 平台革命性的热更新解决方案,它通过扩展 Unity 的 IL2CPP 运行时,实现了基于原生 C# 的完整热更新能力。下面从原理到实践全面解析这一技术。 一、核心原理剖析 1. 技术架构 原始 IL2CPP 流程&am…...
电脑RGB888P转换为JPEG方案 ,K230的RGB888P转换为JPEG方案
K230开发板本身具备将RGB888P转换为JPEG的能力,但需要正确调用硬件或软件接口。以下是具体分析及解决方案: 一、K230原生支持性分析 1. 硬件支持 K230的NPU(神经网络处理器)和图像处理单元(ISP)理论上支持…...
基于SpringBoot+Vue实现的电影推荐平台功能三
一、前言介绍: 1.1 项目摘要 2023年全球流媒体用户突破15亿,用户面临海量内容选择困难,传统推荐方式存在信息过载、推荐精准度低等问题。传统推荐系统存在响应延迟高(平均>2s)。随着互联网的快速发展,…...
NHANES指标推荐:triglyceride levels
文章题目:Association between triglyceride levels and rheumatoid arthritis prevalence in women: a cross-sectional study of NHANES (1999-2018) DOI:10.1186/s12905-025-03645-y 中文标题:女性甘油三酯水平与类风湿性关节炎患病率之间…...
打印Activity的调用者
有时候我们会发现自己应用中的某个Activity被陌名奇妙的打开了,但是不知道是哪里的代码打开的,此时可以打印Activity的调用堆栈,在Activity的onCreate函数中添加如下代码: Arrays.stream(Thread.currentThread().getStackTrace()…...
深入解析 SqlSugar 与泛型封装:实现通用数据访问层
在现代软件开发中,ORM(对象关系映射)框架的使用已经成为不可或缺的部分,SqlSugar 是一款非常流行且强大的 ORM框架。它不仅提供了简单易用的数据库操作,还具备了高效的性能和灵活的配置方式。为了进一步提升数据库操作…...
普通 html 项目引入 tailwindcss
项目根目录安装依赖 npm install -D tailwindcss3 postcss autoprefixer 初始化生成tailwind.config.js npx tailwindcss init 修改tailwind.config.js /** type {import(tailwindcss).Config} */ module.exports {content: ["./index.html"], //根据自己的项目…...
Go小技巧易错点100例(二十七)
本期分享: 1. Go语言中的Scan函数 2. debug.Stack()打印堆栈信息 3. Go条件编译 正文: Go语言中的Scan函数 在Go语言中,Scan函数是一个强大的工具,它主要用于从输入源(如标准输入、文件或网络连接)读取…...
单细胞测序数据分析流程的最佳实践
单细胞测试数据分析流程是整个论文数据分析过程中相对固定的部分,有一定的标准流程,以下整理了发表论文的相关内容供简要了解,详细内容可以参照2019年发表的综述:Luecken MD, Theis FJ. Current best practices in single-cell RN…...
Elasticsearch:RAG 和 grounding 的价值
作者:来自 Elastic Toms Mura 了解 RAG、grounding,以及如何通过将 LLM 连接到你的文档来减少幻觉。 更多阅读:Elasticsearch:在 Elastic 中玩转 DeepSeek R1 来实现 RAG 应用 想获得 Elastic 认证吗?查看下一期 Elast…...
经典算法 求解台阶问题
求解台阶问题 题目描述 实现一个算法求解台阶问题。介绍如下: 对于高度为 n 的台阶,从下往上走,每一步的阶数为 1、2 或 3 中的一个。问要走到顶部一共有多少种走法。 输入描述 输入一个数字 N: 1 ≤ N ≤ 35表示台阶的高度 …...
伊甸园之东: 农业革命与暴力的复杂性
农业革命的开始 农业革命是人类历史上的第一次重大经济和社会变革,标志着人们从狩猎采集转向农耕。 该变革虽然进展缓慢,却彻底改变了人类的生活方式和社会结构。狩猎采集社会的特征 狩猎采集者生活在小规模、低密度的部落中,依赖于不稳定的自…...
MCP多智能体消息传递机制(Message Passing Between Agents)
目录 🚀 MCP多智能体消息传递机制(Message Passing Between Agents) 🌟 为什么要引入消息传递机制? 🏗️ 核心设计:Agent间消息传递模型 🛠️ 1. 定义标准消息格式 Ὦ…...
Deformable DETR模型解读(附源码+论文)
Deformable DETR 论文链接:Deformable DETR: Deformable Transformers for End-to-End Object Detection 官方链接:Deformable-DETR(这个需要在linux上运行,所以我是用的是mmdetection里面的Deformable DERT,看了一下源码基本是…...
游戏引擎学习第255天:构建配置树
为今天的内容设定背景 今天的任务是构建性能分析(profiling)视图。 目前来看,展示性能分析图形本身并不复杂,大部分相关功能在昨天已经实现。图形显示部分应该相对直接,工作量不大。 真正需要解决的问题,是…...
JavaScript性能优化实战之调试与性能检测工具
在进行 JavaScript 性能优化时,了解和使用正确的调试与性能检测工具至关重要。它们能够帮助我们识别性能瓶颈,精确定位问题,并做出有针对性的优化措施。本文将介绍一些常见的调试和性能检测工具,帮助你更好地分析和优化你的 JavaScript 代码。 1️⃣ Chrome DevTools Chro…...
C#VisionMaster算子二次开发(非方案版)
前言 在网上VisionMaster的教程通常都是按照方案执行的形式,当然海康官方也是推荐使用整体方案的形式进行开发。但是由于我是做标准设备的,为了适配原有的软件框架和数据结构,就需要将特定需要使用的算子进行二次封装。最直接的好处是&#…...
计算机总线系统入门:理解数据传输的核心
一、总线系统简介:计算机内部的交通网络 在计算机系统中,总线是指连接各个组件的一组共享信号线或传输通道,用于在系统内不同的硬件模块之间传递数据、地址、控制信号等信息。它类似于交通系统中的道路,帮助计算机各个部件&#…...
【Linux】Petalinux驱动开发基础
基于Petalinux做Linux驱动开发。 部分图片和经验来源于网络,若有侵权麻烦联系我删除,主要是做笔记的时候忘记写来源了,做完笔记很久才写博客。 专栏目录:记录自己的嵌入式学习之路-CSDN博客 目录 1 一个完整的Linux系统(针对Zynq) 1.1 PS部分 1.2 PL部分(若…...
提升办公效率的PDF转图片实用工具
软件介绍 这款专注于PDF文档处理的工具功能单一但实用,能够将PDF文件内容智能提取并自动拼接成长图,为用户提供便捷的图片化文档处理方案,无需复杂设置即可轻松上手。 简洁直观的用户界面 软件界面设计简洁清爽,没有任何多余…...
动态库与ELF加载
目录 动态库 ELF格式 ELF和后缀的区别 什么是目标文件 ELF文件中的地址--虚拟地址 动静态库和可执行文件 动态库ELF加载 为什么编译时静态库需要指定库?而运行时不需要指定库的,但是动态库需要呢? 总结: 动态库 动态库制作需要的.o文件需要使…...
算法每日一题 | 入门-顺序结构-数字反转
数字反转 题目描述 输入一个不小于 且小于 ,同时包括小数点后一位的一个浮点数,例如 ,要求把这个数字翻转过来,变成 并输出。 输入格式 一行一个浮点数 输出格式 一行一个浮点数 输入输出样例 #1 输入 #1 123.4输出 #1 …...
ROS2学习笔记|实现订阅消息并朗读的详细步骤
本教程将详细介绍如何使用 ROS 2 实现一个节点订阅另一个节点发布的消息,并将接收到的消息通过 espeakng 库进行朗读的完整流程。以下步骤假设你已经安装好了 ROS 2 环境(以 ROS 2 Humble 为例),并熟悉基本的 Linux 操作。 注意&…...