Linux——进程控制模拟shell
1.进程创建
我们在之前的文章中介绍过进程创建的方法,可以通过系统调用接口fork来创建新的进程。
fork在创建完新的子进程之后,返回值是一个pid,对于父进程返回子进程的pid,对于子进程返回0。fork函数后父子进程共享代码,即二者执行的是同一份代码。不同的是对于数据二者相互独立各有一份,于是对于父进程的数据修改不会影响到子进程(在修改时进行写时拷贝将父子数据独立起来)。在fork函数内,进程已经被创建,进程之间相互独立,各自返回各自的返回值(父进程返回子进程PID,子进程返回0)并向下继续执行代码。因而两个进程进入到了不同的分支语句中。
因为有了进程和地址空间的基础知识的铺垫,我们来系统的梳理一下进程创建还有写时拷贝问题:
①在fork调用后,创建出一个新的进程,即现在存在两个进程,他们的关系是父子进程,通过fork的返回值来区分。
②一个进程在内存中的基本内容我们可以暂时简单简化地理解为:进程的PCB,PCB中圈定的一个进程地址空间,以及一个用于映射真实物理地址的页表。对于创建的子进程而言,它为了和父进程独立,所以将这三部分都拷贝了一份给自己。因此此时父进程和子进程的进程地址空间和页表是完全一致的,共用同一份物理内存中的代码和数据。
③在子进程拷贝页表时,会将所有内容修改为只读属性,这是为了写时拷贝做准备。
④当父子进程的一方想要对数据进行修改,则会通过页表映射修改物理内存内容,但是此时由于只读属性,触发了系统错误。于是发生了缺页中断,经过系统检测判定为要发生写时拷贝。于是就会申请内存、拷贝内容、修改页表,然后再恢复执行。
2.进程终止和等待
2.1 进程正常终止
进程的正常终止有着多种方式:
①在main函数中使用return语句,这样可以结束main函数从而结束进程,并返回状态码。
②void exit(int status)
exit是一个C标准库函数,它可以在代码的任何地方结束进程,并且会完成诸如刷新缓冲区、关闭文件描述符等清理操作,然后返回状态码。
③void _exit(int status)
_exit是一个系统调用接口,它用于直接终止进程,返回状态码,但不会完成清理操作。
辨析 :
对于以上三种退出方式,return用于函数返回,当作用于main函数时则会结束主进程,并返回一个值。在执行main函数中的return时,C语言标准库会隐式调用exit()函数来处理程序的退出。
exit则是可以用在代码的任何位置(和return相比,如果不在main函数中,return只能返回结束当前函数)来直接结束整个进程,与此同时会完成清理操作。
而_exit()和exit一样可以直接结束进程,但是不会完成如刷新缓冲区的清理操作。这是因为_ exit是一个比exit更底层的接口,而缓冲区作为语言缓冲区在其层次之上。如果使用_exit结束一个进程,会造成资源泄露,但是进程结束操作系统会自动完成进程的资源回收工作,所以实际不会出现资源泄露的问题。
2.2 进程等待
我们在之前介绍过,如果父进程不对结束的子进程进行处理,那么子进程将会成为僵尸进程,其PCB始终占据着内存,导致内存泄漏。这种情况直到父进程主动回收子进程,或者父进程结束后子进程变成孤儿进程,被1号进程领养后处理才结束。所以使用父进程妥善处理已经结束的子进程是很有必要的。
2.2.1 进程等待的方法
①pid_t wait(int *status);
wait函数帮助父进程获取子进程的退出信息,它会等待任意一个子进程结束,结束的子进程pid作为wait函数的返回值交给父进程,而退出码则会通过输出型参数status带回父进程。如果在调用wait函数时子进程并未退出,那么就会将父进程阻塞在其内部,直到子进程结束。
#include<cstdio>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>int main()
{pid_t id = fork();if(id==0){//子进程int cnn = 5;while(cnn--){printf("我是子进程,我的pid是%d\n",getpid());sleep(1);}exit(87);}else if(id>0){//父进程int status;printf("我是父进程,我的pid是%d\n",getpid());pid_t ret = wait(&status);if(ret > 0){printf("%d 成功退出,退出码为:%d\n",ret,WEXITSTATUS(status));}}return 0;
}
②pid_t waitpid(pid_t pid, int *status, int options);
另外一个函数接口waitpid相比于wait具有更加丰富的功能。
参数
pid:可以传递指定要等待进程的pid,或者也可传参为-1来等待任意一个子进程(和wait功能相同)。
status:同样为一个输出型参数,带出进程的退出信息。
options:选择等待的可选功能项。如传参为0,表示阻塞等待;传参为WNOHANG时表示非阻塞等待,此时需要自己调用非阻塞接口完成轮询检测。
返回值
>0 表示等待成功,返回值即为对应子进程的pid;
==0 表示等待成功,但是子进程暂未退出;
<0 则说明等待失败。
#include<cstdio>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>int main()
{pid_t id = fork();if(id==0){//子进程int cnn = 5;while(cnn--){printf("我是子进程,我的pid是%d\n",getpid());sleep(1);}exit(87);}else if(id>0){//父进程int status;printf("我是父进程,我的pid是%d\n",getpid());while(true){pid_t ret = waitpid(id,&status,WNOHANG);if(ret > 0){printf("%d 成功退出,退出码为:%d\n",ret,WEXITSTATUS(status));break;}else if(ret==0){printf("进程未退出\n");//其他工作sleep(2);}else {printf("等待失败");break;}}}return 0;
}

2.2.2 理解退出码
辨析错误码和退出码:
错误码我们过去经常见到,错误码通常是指errno变量中的值,它表示特定操作(如系统调用或库函数)发生错误的原因。errno是一个全局变量,当出现错误时会自动将错误码存储在errno中,不同的值代表着不同的错误信息。我们可以通过perror和strerror来查看错误信息。
perror:void perror(const char *s); 打印输入的参数字符串+此时errno对应的错误信息
strerror:char *strerror(int errnum); 打印指定错误码(传入参数)的错误信息
对于退出码,它是在进程结束后返回的一个退出状态信息,表示程序的执行结果,一般约定0为成功,而非0为出现错误,至于退出码和原因的对应关系没有固定的要求。
错误码是全局变量,是在进程执行出现错误后自动为errno赋值,其本质上还是进程自己内部的事情。而退出码则是子进程向父进程“汇报”的方式,是两个进程间的交互。退出码在子进程结束时通过main函数的return或者exit交付给父进程,父进程用wait的status接收。
status:
status并非一个简单的int类型的数字,对于不同的退出方式status的内容是不一样的,此处简单讨论status的低16位。
当程序是执行完成并退出时,视为正常终止,此时16位的高8位是真正的退出码,因此要拿到错误码需要对status右移8位,可以采取WEXITSTATUS(status)宏来优雅完成。因此可以看出退出码的范围是0~255的。
当程序异常,如被信号终止,那么此时仍然会记录退出信息,放在status中(如下图),具体细节将在信号部分系统解释。
3.进程程序替换
我们创建新的进程使用的都是fork,但是我们会发现fork创建的子进程和父进程执行的是同样的代码,区别仅仅是不同分支。为了使得子进程可以去执行新的程序,我们可以通过exec函数,将当前进程的代码和数据由新的程序进行替换,从而启动新的程序。
3.1 exec函数
exec函数分为如上几种,根据其名字我们就可以推断出其含义和使用方法。
l:表示列表list,即表示该接口的参数是以列表的形式(可变参数)传入的
v:与list相对,表示数组vector,即表示该接口的参数是以数组argv的方式传入的
p:表示可以不使用路径,具体要替换的可执行程序通过PATH环境变量寻找
e:表示可以使用新的环境变量,环境变量在参数最后由数组传入
对于exec的使用,还有一些要点需要强调:
(1)execl的基本使用方法
#include<cstdio>
#include<unistd.h>int main()
{execl("/usr/bin/ls","ls","-a","-l","--color");
}
exec函数本质是从磁盘中找到可执行程序,然后加载到内存中,覆盖调用exec函数的代码和数据,从而执行这个可执行程序。如上所示,path参数应该指定为具体替换的可执行程序,之后的可变参数即为执行这个可执行程序的参数,表示如何执行这个可执行程序。
如上这段代码就完成了ls程序的替换,后续的可变参数实际上也就是命令行参数了。
(2)execv的基本使用方法
#include<cstdio>
#include<unistd.h>int main()
{char* const argv[] = {(char*)"ls",(char*)"-a",(char*)"-l",(char*)"--color",nullptr};execv("/usr/bin/ls",argv);
}
对于vector的传参方式,需要使用一个数组,其中是命令行参数。和之前介绍过的命令行参数列表argv一样,最后一个元素一定是nullptr。
(3)execvpe的基本使用方法
#include<cstdio>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>int main()
{pid_t id = fork();if(id>0){//子进程char* const argv[] = {(char*)"Show",nullptr};char* const env[] = {(char*)"ONE=1",(char*)"TWO=2",(char*)"THREE=3"};execvpe("./show",argv,env);exit(1);}pid_t rid = waitpid(id,nullptr,0);if(rid>0){printf("等待成功\n");}return 0;
}
我们在上述的代码中使用了execvep,所以需要手动将环境变量表传参,在正常情况下环境变量表是继承自父进程的,但是在这种手动传递的情况下,进程替换后的show程序(打印环境变量表内容)就拿到了我们提供的环境变量表,于是对于替换后的程序而言,环境变量表就是这个env了。
于是可以总结出,对于一个进程环境变量的来源:
①父进程创建子进程,子进程会拷贝父进程的环境变量表。
a.可以通过extern char** environ;声明环境变量表后访问环境变量表获取。
b.通过main函数的参数env也可以拿到环境变量的字符串数组,访问方式和参数列表相同。
c.可以通过系统调用接口getenv(环境变量名)的方法,获得指定环境变量的内容。
②在进程替换时,使用env参数传递新的环境变量表,这样替换后的进程拿到的环境变量表就是这张env了。
③通过putenv()函数也可以完成对环境变量的新增操作。
(4) 在(3)的代码中,我们还可以发现,实际上这段代码是一个多进程的方式进行的进程替换。fork出子进程后,exec进行写时拷贝,将子进程的页表指向的物理内存加载入show程序的代码数据,然后从新程序的main函数开始执行。
(5) 函数名中包括p的函数,表示可以不使用路径,具体要替换的可执行程序通过PATH环境变量寻找,于是有execl("ls","ls","-a","-l","--color"); 其中第一个ls是执行的程序,而第二个ls是命令行参数列表。
(6)我们发现exec函数都有返回值int,实际上当进程成功替换后并没有返回值,因为进程替换成功了代码数据全部被覆盖了。所以虽说返回-1是失败,实际上一旦返回了值就肯定失败了。
(7)对于如上所示的execl、execlp、execle、execv、execvp、execvpe都是被封装后的C标准库接口,都是真正的系统调用——execve封装过来的。根据传参方式和需求灵活选择即可。
4.shell模拟实现
shell其实就是一个命令解释器,是用户和计算机与计算机系统交互的一个途径。用户输入的指令被shell获取,然后进行处理解析,调用对应的程序。
我们一直在强调一件事,就是所有的指令实际都是可执行程序,可以通过which找到指令程序所在的路径。所以shell并不生产程序,它只是个程序的搬运工,是一个调用者。
考虑shell的工作模式,我们大致可以将其分为四步进行。
在此之前,需要先对环境变量进行初始化。我们在自己的shell中模拟了一个环境变量列表,在shell进程被父进程创建后,shell实际上有一张真正的环境变量列表。而我们创建的这个环境变量列表则是方便我们理解与传参的一份模拟。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>using namespace std;const int basesize = 1024; //字符串长度上限
const int argvnum = 64; //命令行列表个数上限
const int envnum = 64; //环境变量个数上限char* gargv[argvnum]; //命令行参数列表
int gargc = 0; //命令行参数个数
char* genv[envnum]; //环境变量列表,以此自己定义的表来模拟,是为系统真正的环境变量表int lastcode = 0; //上个进程的退出码void InitEnv()
{extern char** environ;int i = 0;while(environ[i]){genv[i] = (char*)malloc(strlen(environ[i])+1);strncpy(genv[i], environ[i], strlen(environ[i])+1);i++;}genv[i] = nullptr;
}int main()
{InitEnv(); //初始化环境变量:从父shell中拷贝获取char buffer[basesize];while(true){// 1. 打印命令行提示符PrintCommandLine(); // 2. 获取用户命令if(!GetCommandLine(buffer)){continue;}// 3. 分析命令ParseCommandLine(buffer); if (BuildCommand()){continue;}// 4. 执行命令ExecuteCommand();}return 0;
}
4.1 打印命令行提示符
命令行提示字符串我们常见,实际上由用户名、主机名、工作路径组合而成。
注意点:
①对于用户名和主机名,我们可以直接从环境变量中获取,调用getenv()函数即可。
②对于工作路径,如果直接从环境变量获取,我们会发现在cd命令之后,取到的路径不发生任何改变。这是因为环境变量PWD并不会主动根据我们的工作路径的变化而变化,而是需要靠shell去维护的。当使用了cd命令(调用了chdir()函数),此时修改了工作路径,但这个工作路径的修改是发生在进程(也就是shell进程)的PCB中的。于是我们在获取工作路径时,需要通过getcwd()函数来获得此时真正的工作路径。
当获得了工作路径后,我们还需要对环境变量中PWD的更新负起责任,通过putenv接口即可修改环境变量表中的内容。但是我们自己实现的shell中,也模拟了一份环境变量表,这个表就需要自己手动修改环境变量了。
string GetUserName()
{string USER = getenv("USER");return USER.empty() ? "None" : USER;
}string GetHostName()
{string HOST = getenv("HOSTNAME");return HOST.empty() ? "None" : HOST;
}string GetPwd()
{//通过getcwd取得工作路径,然后以此为pwd//同时需要对环境变量表更新//putenv更新的是进程真正的环境变量表//自己创建的一个模拟的genv需要手动更新char pwd[basesize];char pwdenv[basesize];if(nullptr == getcwd(pwd, sizeof(pwd))) return "None";snprintf(pwdenv, sizeof(pwdenv),"PWD=%s", pwd);putenv(pwdenv); //int putenv(const char *string); 修改或新建环境变量int i = 0;while(strncmp("PWD=",genv[i],4)!=0) i++;strncpy(genv[i],pwdenv,strlen(pwdenv)+1);return pwd;//在这种情况下,是从环境变量中读取PWD,而cd命令使用chdir改变的是进程的工作路径,只修改了PCB中的cwd,而不修改环境变量表//string pwd = getenv("PWD");//return pwd.empty() ? "None" : pwd;
}string GetSimpleDir()
{string pwd = GetPwd();size_t pos = pwd.rfind("/");if(pos == string::npos)return pwd;if(pos == 0) //"/"return pwd;else return pwd.substr(pos+1);
}string MakeCommandLine()
{//[xlz44847@localhost home]$ //[用户名@主机名 工作目录]提示符char command_line[basesize];snprintf(command_line, basesize, "[%s@%s %s]# ",GetUserName().c_str(), GetHostName().c_str(), GetSimpleDir().c_str());return command_line;
}void PrintCommandLine()
{printf("%s", MakeCommandLine().c_str());fflush(stdout); //刷新缓冲区
}
4.2 接收用户输入命令
对于用户输入命令,使用fgets这个对空格符不敏感的接收方式来接收。
bool GetCommandLine(char* buffer)
{char* ret = fgets(buffer, basesize, stdin);//fgets在遇到换行符和文件结尾时停止,会读入换行符if(!ret)return false;buffer[strlen(buffer)-1] = '\0';if(strlen(buffer) == 0) return false;return true;
}
4.3 解析命令
对于用户输入命令,实际就是执行程序的命令行参数列表,所以我们要做的就是将这个命令字符串进行分割,组成一个命令行参数列表。
void ParseCommandLine(char* buffer)
{memset(gargv, 0, sizeof(gargv));gargc = 0;const char* sep = " ";gargv[gargc++] = strtok(buffer, sep);while((bool)(gargv[gargc++] = strtok(nullptr, sep)));gargc--;
}
4.4 执行命令
4.4.1 外部命令
执行一个命令,我们一般会通过创建子进程的方式来完成。使用execvpe进行进程替换,然后进行差错处理。
//外部命令由子进程执行
bool ExecuteCommand()
{pid_t id = fork();if(id < 0) return false;if(id == 0){//子进程execvpe(gargv[0], gargv, genv);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){if(WIFEXITED(status)) //WIFEXITED:判断子进程是否正常退出//正常退出指通过exit或到达主程序结尾而结束,与之相对的是由信号进行终止{lastcode = WEXITSTATUS(status); //WEXITSTATUS:获得子进程的退出码}else {lastcode = 178;}return true;}return false;
}
4.4.2 内建命令
有一些命令交给子进程是无法完成任务的。比如cd指令修改shell进程的工作路径,这种指令实际上是对shell这个进程自身的变化操作。当创建子进程后自然无法对父进程进行任何操作了,所以这种命令需要shell自己调用函数来完成。
其中对于echo而言,将其作为内建命令的原因是因为echo $?打印上一次退出码,这种指令就无法通过子进程完成,因此也将其作为内建命令。
//內建命令shell来执行
bool BuildCommand()
{if(strcmp(gargv[0], "cd") == 0){if(gargc == 2){chdir(gargv[1]);lastcode = 0;}else {lastcode = 1;}return true;}else if(strcmp(gargv[0], "export") == 0){if(gargc == 2){int i = 0;while(genv[i++]);genv[i] = (char*)malloc(strlen(gargv[1])+1);strncpy(genv[i],gargv[1],strlen(gargv[1])+1);genv[++i] = nullptr;lastcode = 0;}else {lastcode = 2;}return true;}else if(strcmp(gargv[0], "env") == 0){for(int i = 0; genv[i]; i++){printf("%s\n", genv[i]);}lastcode = 0;return true;}else if(strcmp(gargv[0], "echo") == 0){if(gargc == 2){if(gargv[1][0]=='$'){//echo $?if(gargv[1][1]=='?'){printf("%d\n",lastcode);lastcode = 0;}//echo $PATHelse {int i = 0;string cmp = gargv[1];cmp = cmp.substr(1);cmp += '=';while(strncmp(cmp.c_str(),genv[i],cmp.length())!=0) i++;printf("%s\n",&genv[i][cmp.length()]);lastcode = 0;}}//echo "xxx"else {printf("%s\n",gargv[1]);lastcode = 0;}}else {lastcode = 3;}return true;}return false;
}
相关文章:
Linux——进程控制模拟shell
1.进程创建 我们在之前的文章中介绍过进程创建的方法,可以通过系统调用接口fork来创建新的进程。 fork在创建完新的子进程之后,返回值是一个pid,对于父进程返回子进程的pid,对于子进程返回0。fork函数后父子进程共享代码ÿ…...
OpenCV的图像矫正
一、原理 图像矫正的原理是透视变换,下面来介绍一下透视变换的概念。 透视变换(Perspective Transform)基于一个4对点的映射关系(4个源点到4个目标点),通过这些点之间的映射,可以计算一个变换…...
基于php求职招聘系统设计
摘要 随着社会信息化时代的到来,如今人们社会的生活节奏普遍加快,人们对于工作效率的要求也越来越高,企业 举办招聘会耗时耗财,个人参加招聘会漫无目的寻找不到“方向”,网络搜索工作量目的性不强,信息量繁…...
并行口的基本概念
单片机的并行口结构包括多个并行I/O端口,用于与外部设备进行并行数据传输。这些端口能够直接读写外部存储器、显示设备、打印机等外设的数据,是单片机与外界交互的重要通道。在深入探讨之前,我们先简要了解下单片机的基本概念。 单片机&…...
(六)机器学习 - 正态数据分布
正态数据分布,也称为高斯分布(Gaussian distribution),是一种在统计学中非常重要的概率分布。它描述了自然和社会科学中许多现象的分布情况,如人的身高、体重、智商等。正态分布的图形特征是中间高、两边低,…...
电脑系统报错找不到d3dcompiler_47.dll怎么修复?怎么预防“d3dcompiler_47.dll”文件缺失?
“d3dcompiler_47.dll”文件缺失的修复与预防策略 在日常使用电脑软件,尤其是运行大型游戏或图形密集型应用时,我们可能会遇到一些令人困惑的系统报错。其中之一便是“找不到d3dcompiler_47.dll”的错误提示。这个错误不仅影响软件的正常运行࿰…...
Github 2024-12-12 Go开源项目日报Top10
根据Github Trendings的统计,今日(2024-12-12统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Go项目10Go Ethereum: 以太坊Go语言官方实现 创建周期:3717 天开发语言:Go协议类型:GNU Lesser General Public License v3.0Star数量:4504…...
RIP协议
介绍 路由信息协议RIP(Routing Information Protocol)是基于距离矢量(Distance-Vector)算法的路由协议,它是一种较为简单的内部网关协议IGP(Interior Gateway Protocol)。它的主要功能是帮助路…...
vue-router查漏补缺
一、动态路由匹配 1.带参数的动态路由匹配 import User from ./User.vue// 这些都会传递给 createRouter const routes [// 动态字段以冒号开始{ path: /users/:efg, component: User }, ]这种方式的路由会匹配到/users/abc或者/users/123,路径参数用冒号:表示,并…...
使用Flink CDC实现 Oracle数据库数据同步的oracle配置操作
使用Flink CDC实现 Oracle数据库数据同步的oracle配置操作,包括开启日志归档和用户授权。 flink官方参考资料: https://nightlies.apache.org/flink/flink-cdc-docs-master/zh/docs/connectors/flink-sources/oracle-cdc/ 操作步骤: 1.启用…...
mqtt.js 客户端封装
mqtt.js 客户端封装 没封装前使用 const client mqtt.connect(ws://10.10.20.9:9001) onMounted(() > {const topicList []const channelCount 12for(let i 0; i < channelCount; i) {topicList.push(/test/sendImage${i 1})}client.on(connect, () > {console…...
www.aws.training网站的账户密码如何修改
www.aws.training网站的账户密码如何修改 参加aws考试后, 账号密码如何修改呢? 是不是找了好久都找不到. 右上角, 我的账户, 点击注销. 然后会跳到页面: 点击那个网址链接, 进入新的页面,点安全: 右上角: 更改密码. 按提示来就好....
使用Python实现高性能数据存储
在数据驱动的时代,处理和存储海量数据已成为关键需求。高性能数据存储不仅能够确保数据的快速读写,还能提升系统的整体性能。Python作为一种灵活且功能强大的编程语言,提供了多种高效的数据存储解决方案。本文将详细介绍如何使用Python实现高…...
[已解决]nvm安装node.js 报错 拒绝访问此应用无法在你电脑上运行
报错如下: 出错背景: 心血来潮把node删掉重新安装,想用nvm来进行管理node 出错原因: npm下载失败、下载不完整 不完整的npm展示: 出错根本原因: 可能因为镜像源或者网络波动,导致node下载…...
图文检索(36):Decomposing Semantic Shifts for Composed Image Retrieval
Decomposing Semantic Shifts for Composed Image Retrieval 摘要方法3.1 前期准备3.2 视觉语言表示3.3 降级过程3.4 升级过程3.5 训练和推理 结论 发布时间(2024 AAAI) 标题:分解语义转换以实现组合图像检索 摘要 语义转换网络 (SSN)&…...
批量计算(Batch Processing)
批量计算(Batch Processing)是一种数据处理方式,指的是将大量任务或数据分批次进行处理,而不是实时处理每一个任务。这种处理方式通常在任务之间没有依赖关系时使用,可以大大提高计算效率和资源利用率。批量计算广泛应…...
Linux yum-config-manager命令异常
错误信息 使用 yum-config-manager命令时错误信息如下 sudo yum-config-manager \ > --add-repo \ > https://download.docker.com/linux/centos/docker-ce.repo sudo: yum-config-manager: command not found 解决办法 第一步: sudo yum -y install yum-u…...
【C++】关联存储结构容器-set(集合)详解
目录 一、基本概念 二、内部实现 三、常用操作 3.1 构造函数 3.2 插入操作 3.3 删除操作 3.4 查找操作 3.5 访问元素 3.6 容量操作 3.7 交换操作 四、特性 五、应用场景 结语 一、基本概念 set是C标准模板库(STL)中的一种关联容器…...
如何利用SPSS软件进行多组间显著性检验以abcd显示
1.SPSS软件中进行多组间的显著性检验 要在SPSS软件中进行多组间的显著性检验,并以abcd显示结果,你可以按照以下步骤操作: 数据准备: 确保你的数据已经正确输入SPSS,其中每行代表一个观测值,包含至少两列&a…...
C++ 之计时函数总结
C 之计时函数总结 总结Windows下C开发时的计时实现 1. clock() #include <time.h> //引入头文件void main() {clock_t start, end;start clock();fun(); //需计时的函数end clock(); float t_cfloat(end-start)/CLOCKS_PER_SEC;cout << "func cos…...
仿dota2渲染--shader常见贴图含义(直观展示)
Properties {[Header(Texture)]_MainTex ("RGB:颜色 A:透贴", 2D) "white"{}_NormTex ("RGB:法线贴图", 2D) "bump" {}_MaskTex ("R:高光强度 G:边缘光强度 B:高光染色 A:高光次幂…...
NoSQL大数据存储技术测试(5)MongoDB的原理和使用
单项选择题 第1题 关于 MongoDB 集群部署下面说法不正确的是() 已经不使用主从复制的模式 在实际应用场景中, Mongodb 集群结合复制集和分片机制 MongoDB 支持自动分片, 不支持手动切分 (我的答案) 每…...
视图、转发与重定向、静态资源处理
目录 视图 默认视图 视图机制原理 自定义视图 请求转发与重定向 静态资源处理 视图 每个视图解析器都实现了 Ordered 接口并开放出一个 order 属性 可以通过 order 属性指定解析器的优先顺序,order 越小优先级越高 默认是最低优先级,Integer.MAX_…...
ThinkPHP 5.1 的模板布局功能
ThinkPHP 5.1 的模板布局功能,包括全局配置、模板标签以及动态方法布局三种方式。以下是对这三种方式的要点概括和补充,以便于更好地理解和使用: 方式一:全局配置方式 适用于全站使用相同布局的情况,配置简单且统一。…...
入门网络安全工程师要学习哪些内容【2025年寒假最新学习计划】
🤟 基于入门网络安全/黑客打造的:👉黑客&网络安全入门&进阶学习资源包 大家都知道网络安全行业很火,这个行业因为国家政策趋势正在大力发展,大有可为!但很多人对网络安全工程师还是不了解,不知道网…...
Redis Cluster 分片机制
Redis 集群是 Redis 提供的一种分布式实现,用于水平扩展数据存储能力。通过 Redis 集群,可以将数据分片存储在多个 Redis 节点上,同时提供高可用性和故障转移功能。 分片(Sharding): Redis 集群将数据划分…...
LightRAG测试BUG
错误一: LightRAG无法回答错误: INFO:lightrag:kw_prompt result:{{"high_level_keywords": ["xxx", "xxx"],"low_level_keywords": ["xxx", "xxx", "xxx"] }} JSON parsing e…...
关于手柄摇杆的连线
由于时间实在是太久远了,我也忘记具体的连线了,只能提供当时的连线图片。...
运算符优先级和,|| 的介绍
运算符优先级 关系运算的优先级高于逻辑运算,所以需要加上小括号来改变 更详细的运算符优先级可以去MATLAB官网看 &运算的优先级高于|优先级: &&,|| 的介绍 我们知道,&运算时,若第一个为 0 0 0&…...
Excel的文件导入遇到大文件时
Excel的文件导入向导如何把已导入数据排除 入起始行,选择从哪一行开始导入。 比如,前两行已经导入了,第二次导入的时候排除前两行,从第三行开始,就将导入起始行设置为3即可,且不勾选含标题行。 但遇到大文…...
SQL Having用法
拿个业务场景说这个案例,比如我们有个表里面可能有批改过的数据,批改过得数据不会随着新批改的数据覆盖,而是逐条插入表中,如果想找出包含最早批改的数据和最新批改数据的话,那么我们就需要用到了havinng 用法,假设最开…...
【mysql优化 | 新增分区】
在同步其他系统的数据,大概每天有16w多,目前已经600多万条数据了,导致查询的时候特别慢。 因为是报表,而且是每天统计,所以我们可以按照日期进行分区。 ALTER TABLE table PARTITION BY RANGE (TO_DAYS(rsdate)) (PA…...
vue 组件之间的传值方式
一、父组件向子组件传值 父组件可以使用 props 将数据传递给子组件。 <!-- 父组件 --> <template><ChildComponent :message"parentMessage" /> </template><script> import ChildComponent from ./ChildComponent.vue;export defau…...
VScode执行任务
背景 在vscode 中 如果执行命令需要传递进来参数,那么直接通过命令行终端的方式不太方便。通过task 任务的方式来进行启动执行,降低反复输入参数等繁琐工作。 首先可以查看vscode 官方文档 task 启动 crtl shift p .vscode/task.json 示例 执行cp…...
以太网链路详情
文章目录 1、交换机1、常见的概念1、冲突域2、广播域3、以太网卡1、以太网卡帧 4、mac地址1、mac地址表示2、mac地址分类3、mac地址转换为二进制 2、交换机的工作原理1、mac地址表2、交换机三种数据帧处理行为3、为什么会泛洪4、转发5、丢弃 3、mac表怎么获得4、同网段数据通信…...
将PDF流使用 canvas 绘制然后转为图片展示在页面上(二)
将PDF流转为图片展示在页面上 使用 pdfjs-dist 库来渲染 PDF 页面到 canvas 上,然后将 canvas 转为图片 安装 pdfjs-dist 依赖 npm install pdfjs-dist 或者 yarn add pdfjs-dist创建一个组件来处理 PDF 流的加载和渲染 该组件中是一个包含 PDF 文件的 ArrayBuffer…...
若依集成Uflo2工作流引擎
文章目录 1. 创建子模块并添加依赖1.1 新建子模块 ruoyi-uflo1.2 引入 Uflo2 相关依赖 2. 配置相关 config2.1 配置 ServletConfig2.2 配置 UfloConfig2.3 配置 TestEnvironmentProvider 3. 引入Uflo配置文件4. 启动并访问 Uflo2 是由 BSTEK 自主研发的一款基于 Java 的轻量级工…...
CV(4)--边缘提取和相机模型
前言 仅记录学习过程,有问题欢迎讨论 边缘提取(涉及语义分割): 图象的边缘是指图象局部区域亮度变化显著的部分,也有正负之分,暗到亮为正 求边缘的幅度:sobel,Canny算子 图像分高频分量和低…...
使用html 和javascript 实现微信界面功能2
1.功能说明: 对上一篇的基础上进行了稍稍改造 主要修改点: 搜索功能: 在搜索框后面增加了搜索按钮。 搜索按钮调用performSearch函数来执行搜索操作。 表单形式的功能: 上传文件: 修改为表单形式,允许用户通过文件输入控件选择文件并上传。 …...
音视频入门基础:MPEG2-TS专题(12)—— FFmpeg源码中,把各个transport packet组合成一个Section的实现
一、引言 从《音视频入门基础:MPEG2-TS专题(9)——FFmpeg源码中,解码TS Header的实现》可以知道:FFmpeg源码中使用handle_packet函数来处理一个transport packet(TS包),该函数的前半…...
数据结构Day一
1.思维导图 2.顺序表的创建 seq.h #ifndef __SEQ_H__ #define __SEQ_H__#define max 30 typedef int DataType; typedef struct {DataType data[max];int len; }seqList,*seqListPtr;seqListPtr seq_create();#endifseq.c #include <stdio.h> #include <stdlib.h&g…...
cpptoml介绍
cpptoml 是一个用于 C 的开源库,旨在提供对 TOML(Toms Obvious, Minimal Language)格式的支持。它允许开发者轻松地在 C 项目中读取、解析和生成 TOML 格式的配置文件。cpptoml 是一个轻量级、易于使用的库,适用于那些希望将 TOML…...
迭代器(转
package Scala4 //迭代器 object hd { def main(args: Array[String]): Unit { var li1 List(1, 2, 3, 4, 5, 6) //依次输出List的元素 //1.循环 //li1.foreach(println) //2.迭代器 //2.1创建一个迭代器(iterator) // val it1li1.iterator.take(3)/…...
鸿蒙元服务上架
鸿蒙元服务上架 一、将代码打包成 .app 文件1. 基本需求2. 生成密钥和证书请求文件3. 申请发布证书4. 申请发布Profile5. 配置签名信息6. 更新公钥指纹7. 打包项目成 .app 文件 二、发布元服务1. 进入应用信息页面2. 上传软件包3. 配置隐私协议4. 配置版本信息5. 提交审核&…...
查询三网话费余额接口,移动话费余额接口、电信话费余额接口、联通话费余额的接口+html前端查询UI界面
PHP是直接请求的接口,HTML代码也是直接请求的接口。如果HTML想上线运行,还是需要做下安全的。 下边是PHP代码 <?php // 定义API接口地址和参数 $apiUrl "https://api.taolale.com/api/Inquiry_Phone_Charges/get"; //API文档地址&…...
C#开发-集合使用和技巧(十)Union用法-并集
在 C# 中,IEnumerable 的 Union 方法用于返回两个序列的并集。Union 方法会去除重复的元素,确保结果集中每个元素都是唯一的。以下是 Union 方法的基本用法: 基本语法 public static IEnumerable<TSource> Union<TSource>(this…...
PyTorch 切片运算 (Slice Operator)
PyTorch 切片运算 {Slice Operator} 1. [:, -1, :]2. [:, [-1], :]References 1. [:, -1, :] https://github.com/karpathy/llama2.c/blob/master/model.py import torchlogits torch.arange(1, 16) print("logits.shape:", logits.shape) print("logits:\n&…...
leaflet 双屏对比
本章主要讲的是leaflet的双屏对比,本文参考了插件:Leaflet.Sync,我这里对原有的文件进行了重写,去掉了一部分不需要的内容,增加了flyTo和panTo方法,新的方法,如果需要可以自行下载资源。 目录 …...
渗透测试学习笔记(二)kali相关
一.kali 基础工具 NetCat -网络工具中的瑞士军刀:允许用户通过 TCP 或 UDP 协议发送和接收数据。WireShark-开源抓包软件TCPdump-命令行抓包分析工具 二. 被动信息收集 2.1 被动信息收集指从公开渠道获取信息,主要是已经公开的信息。 要点࿱…...
Python中的正交配对测试库:allpairspy
Python中的正交配对测试库:allpairspy 简介Python 中的 All-Pairs Testing 库安装 allpairspy使用 allpairspy 库的代码示例运行结果总结 简介 All-Pairs Testing (正交配对测试) 是一种广泛应用于软件测试中的组合测试方法,其核心思想是通过生成所有可…...