【Linux】进程替换与自定义 Shell:原理与实战
目录
一、进程程序替换
1、替换原理
2、替换函数
(1)函数解释
① filename / pathname
② 参数表传递
③ 环境变量表传递
(2)命名理解
二、自定义shell命令行解释器
1、实现原理
2、实现代码
(1)获取环境变量
(2)输出命令行提示符
(3)获取用户输入的命令
(4)命令行分析
(5)检测并执行内键命令
(6)执行命令
(7)完整代码
三、函数和进程之间的相似性
一、进程程序替换
fork()函数创建新的子进程后,子进程如果想执行一个全新的程序呢?进程的程序替换来完成这个功能!程序替换是通过特定的接口,加载磁盘上的一个全新程序(代码和程序),加载到调用进程的地址空间中!
1、替换原理
用 fork 创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种 exec 函数以执行另一个程序。当进程调用一种 exec 函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用 exec 并不创建新进程,所以调用 exec 前后该进程的ID并未改变。exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。
示例:
#include<stdio.h>
#include<unistd.h>int main()
{printf("程序运行!\n");execl("/usr/bin/ls", "ls", "-l", "-a", NULL);printf("程序运行完毕!\n");return 0;
}$ ./proc # 原始代码中的第二个printf函数被替换,不执行!
程序运行!
total 32
drwxrwxr-x 2 zyt zyt 4096 Apr 18 17:13 .
drwxrwxr-x 16 zyt zyt 4096 Apr 18 17:07 ..
-rw-rw-r-- 1 zyt zyt 58 Apr 18 17:12 Makefile
-rwxrwxr-x 1 zyt zyt 16000 Apr 18 17:13 proc
-rw-rw-r-- 1 zyt zyt 190 Apr 18 17:13 test.c
一旦程序替换成功,就去执行新代码了,后续的原始代码也就不存在了(被覆盖了)。
exec* 函数只有失败返回值,没有成功返回值。所以对于exec函数不用对返回值做判断。
2、替换函数
● 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
● 如果调用出错则返回-1,若成功,不返回。
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
(1)函数解释
① filename / pathname
pathname就是取【路径名+程序】作为参数;filename就是文件名作为参数,当指定filename作为参数时:
● 如果filename中包含“/”,则将其视为路径名。
● 否则就按照PATH环境变量,在它所指定的各目录中搜索可执行文件。【不知道PATH环境变量的话看前文:深入浅出:环境变量与进程地址空间的核心机制-CSDN博客】
② 参数表传递
函数execl、execlp 和 execle 要求新程序的每一个命令行参数都说明一个单独的参数。这种参数表以空指针结尾。对execl、execlp 和 execle三个函数表示命令行参数的一般方法:
char *arg(), char *argl, ..., char *argn, (char *)0
这种语法显示的说明了最后一个命令行参数之后跟了一个空指针。如果用常量0来表示一个空指针,则必须将它强制转化成为一个指针:否则它将被解释成整形参数。如果一个整形数的长度与char*的长度不同,那么exec函数的实际参数将出错。
对于其余的4个函数,则应先构造一个指向各参数的指针数组,然后将该数组指针地址作为这4个函数的参数(建立argv)。
③ 环境变量表传递
以e结尾的三个函数(execle、execve、fexecve)可以传递一个指向环境字符串指针数组的指针。在ISO C原型(国际标准化组织制定的C语言标准)之前,execle的参数是:
char *pathname, char *arg(), ..., char *argn, (char *)0, char *envp[]
从中可见,最后一个参数是指向环境字符串的各字符指针构成的数组的指针。而在ISO C原型中,所有命令行参数、空指针和envp指针都是用省略号(...)表示。
其他四个函数,则使用调用进程中的environ变量为新程序复制现有的环境。通常,一个进程允许将其环境传播给其子进程,但有时也有这种情况,进程想为子进程指定某一个确定的环境,可以直接用putenv()函数【添加或修改环境变量,它接受一个形式为 "name=value" 的字符串,并将其添加到当前进程的环境变量列表中】;或者通过第三方变量environ。
(2)命名理解
这7个函数的参数很难记忆。函数名的字符会给我们一些帮助。
● 字母p表示该函数取filename作为参数,并且用PATH环境变量寻找可执行文件。
● 字母l表示该函数取一个参数表,参数以可变参数列表的形式传递。
● 字母v与字母l互斥,v表示该函数取了一个argv[]矢量,参数以字符指针数组的形式传递。
● 字母e表示该函数取envp[]数组,自己维护环境变量,而不使用当前环境。
调用示例如下:
#include <unistd.h>int main() {char *const argv[] = {"ps", "-ef", NULL};char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};// 使用 execl 执行 /bin/ps 命令,需要完整路径execl("/bin/ps", "ps", "-ef", NULL);// 使用 execlp 执行 ps 命令,带 p 的,可以使用环境变量 PATH,无需写全路径execlp("ps", "ps", "-ef", NULL);// 使用 execle 执行 ps 命令,带 e 的,需要自己组装环境变量execle("/bin/ps", "ps", "-ef", NULL, envp);// 使用 execv 执行 /bin/ps 命令,需要完整路径execv("/bin/ps", argv);// 使用 execvp 执行 ps 命令,带 p 的,可以使用环境变量 PATH,无需写全路径execvp("ps", argv);// 使用 execve 执行 /bin/ps 命令,带 e 的,需要自己组装环境变量execve("/bin/ps", argv, envp);exit(0);
}
在很多Unix的实现中,这7个函数中只有execve是内核的系统调用。另外6个只是库函数,它们最终都要调用该系统调用。这7个函数之间的关系如图:
二、自定义shell命令行解释器
1、实现原理
考虑下面这个与shell的互动:
[root@localhost epoll]# ls
client.cpp readme.md server.cpp utility.h[root@localhost epoll]# psPID TTY TIME CMD3451 pts/0 00:00:00 bash3514 pts/0 00:00:00 ps
用下图的时间轴来表示事件的发生次序。其中时间从左向右,shell由标识为sh的方块表示,它随着时间的流逝从左向右移动。shell从用户读入字符串“ls”。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。 然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序并等待这个进程结束。
所以,实现一个自定义shell需要循环以下过程:
(1)获取命令行
(2)解析命令行
(3)建立一个子进程(fork)
(4)替换子进程(execvp)
(5)父进程等待子进程退出(wait)
2、实现代码
(1)获取环境变量
要实现一个自定义 Shell,环境变量的处理是关键部分。正常情况shell启动要从系统中获取环境变量,而我们自定义shell的环境变量信息要从父shell中获取。
手动获取:在 C 中可以通过 extern char **environ 访问继承的环境变量。将父Shell的环境变量逐条复制到g_env中,然后将g_env中的变量添加到当前进程的环境变量表。【注意:(这里使用了putenv() 函数)此后g_env[i]的内存由系统管理,不能再手动free!Unix/Linux系统提供的全局变量,指向当前进程的环境变量表,】
// 环境变量表
#define MAX_ENVS 1024
char *g_env[MAX_ENVS];
int g_envs = 0;// 从父shell1获取环境变量表,本来是要从配置文件获取的
void InitEnv()
{extern char **environ;memset(g_env, 0, sizeof(g_env)); // 初始化g_envs = 0;// 1.获取环境变量for(int i = 0; environ[i]; i++){// (1)申请空间g_env[i] = (char*)malloc(strlen(environ[i])+1);// (2)拷贝到我们的环境变量表strcpy(g_env[i], environ[i]);g_envs++;}// 测试:添加了一个新的环境变量g_env[g_envs++] = (char*)"HAHA=for_test";g_env[g_envs] = NULL; // 表的最后一个成员必须是NULL// 2.获取到的环境变量导入shell的表中for(int i = 0; g_env[i]; i++){putenv(g_env[i]);}
}
(2)输出命令行提示符
提示符格式:[用户名@主机名:当前目录的基名]#
① MakeCommandline() 中使用 snprintf() 格式化提示符:【format:格式化字符串,用于指定如何格式化后续的参数】【...:可变参数列表,根据 format 的要求提供相应的值】
int snprintf(char *str, size_t size, const char *format, ...);
② PrintCommandline() 打印命令行提示符:fflush用来确保提示符立即显示(尤其在无换行符时)。
#define COMMAND_SIZE 1024
#define HOSTNAME_MAX 1024
#define FORMAT "[%s@%s:%s]# "// shell定义的全局变量char cwd[1019];
char cwdenv[1024]; const char *GetUserName()
{const char * name = getenv("USER");return name == NULL ? "None" : name;
}const char *GetHostName()
{char hostname[HOSTNAME_MAX];return (gethostname(hostname, HOSTNAME_MAX) == -1) ? "None" : hostname;
}const char *GetPwd()
{//const char * pwd = getenv("PWD");const char *pwd = getcwd(cwd, sizeof(cwd)); // 通过系统调用获取if(pwd != NULL){ // 把自己的环境变量导给进程snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
}// 对路径做包装,只显示当前位置
std::string DirName(const char *pwd)
{
#define SLASH "/"std::string dir = pwd;if(dir == SLASH) return SLASH;auto pos = dir.rfind(SLASH);if(pos == std::string::npos) return "BUG";return dir.substr(pos+1);
}// 制作命令行提示符
void MakeCommandline(char cmd_prompt[], int size)
{// 设置式化输入snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());//snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());
}// 打印命令行提示符
void PrintCommandline()
{char prompt[COMMAND_SIZE];MakeCommandline(prompt, COMMAND_SIZE);printf("%s", prompt);fflush(stdout);
}
(3)获取用户输入的命令
GetCommandline() 从标准输入读取一行用户输入的命令。
fgets() 是一个在 C 语言中用于从文件流中读取字符串的函数,属于标准库 <stdio.h> 中的函数。它能够从指定的文件流中读取一行数据,并将其存储到目标缓冲区中,同时可以限制读取的最大字符数,从而避免缓冲区溢出。
char* fgets(char* str, int size, FILE* stream);
但 fgets() 会保留输入中的换行符,所以调用后要清理换行符。
// 获取用户输入命令
bool GetCommandline(char *out, int size)
{// 从标准输入流获取命令,其实就是字符串char* c = fgets(out, size, stdin);if(c == NULL) return 1;out[strlen(out)-1] = '\0'; // 清理\n, 至少会按一次回车,所以不会出错if(strlen(out) == 0) return false;// 什么都没输入return true;
}
(4)命令行分析
函数实现:命令行字符串的解析,将用户输入的命令行(如 "ls -a -l")拆分为 参数数组 g_argv,并记录参数个数 g_argc。
strtok() 是一个在 C 语言中用于字符串分割的函数,属于标准库 <string.h> 中的函数。它可以根据指定的分隔符将字符串分割成多个子字符串,并且在多次调用中逐个返回这些子字符串。
char* strtok(char* str, const char* delim);
在第一次调用时,str 是指向要分割的原始字符串的指针。 在后续调用中,str 应该传入 NULL,表示继续分割上一次调用后剩余的部分。
// 全局的命令行参数表
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc = 0;// 3. 命令行分析
bool CommandParse(char *commandline)
{// "ls -a -l" -> "ls" "-a" "-l"// 字符串切割 strtok()函数
#define SEP " "g_argc = 0;// 第一次调用:传入要分割的字符串和分隔符g_argv[g_argc++] = strtok(commandline, SEP);// 后续调用:传入 NULL 和分隔符,继续分割同一字符串while(g_argv[g_argc++] = strtok(NULL, SEP));g_argc--; // 修正参数计数,因为while 循环中 g_argc++ 在赋值后执行的return g_argc > 0 ? true : false;
}
(5)检测并执行内键命令
CheckAndExecBuiltin() 检查是否为内建命令。CD() 实现目录切换(cd 命令),Echo() 实现 echo 命令。
chdir() 是一个在 C 语言中用于更改当前工作目录的函数,属于标准库 <unistd.h> 中的函数(在 POSIX 系统中)。它允许程序动态地改变当前进程的工作目录。
// 最后一个程序退出码
int lastcode = 0;// 全局的命令行参数表
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc = 0;// 内键命令
bool CD()
{if(g_argc == 1) // 只有一个cd时{std::string home = GetHome();if(home.empty()) return true;chdir(home.c_str()); // 切换路径}else{std::string where = g_argv[1]; // 目标路径if(where == "-"){}else if(where == "~"){}else{chdir(where.c_str());}}return true;
}bool Echo()
{if(g_argc == 2){//std::string opt = g_argv[1];if(opt == "$?") // eg1: echo $? 打印退出码{std::cout << lastcode << std::endl;lastcode = 0;return true;}if(opt[0] == '$') // eg2: echo $PATH 打印环境变量{std::string env_name = opt.substr(1);const char * env_value = getenv(env_name.c_str()); // 获取环境变量的值if(env_value) std::cout << env_value << std::endl;}}return false;
}// 4. 检测并执行内键命令
bool CheckAndExecBuiltin()
{std::string cmd = g_argv[0];if(cmd == "cd"){return CD();}else if(cmd == "echo"){return Echo();}return false;
}
(6)执行命令
Execute() 实现了 Shell 中外部命令的执行,核心是通过 fork() 创建子进程,并在子进程中调用 execvp 执行目标命令(自定义shell),父进程则等待子进程结束并记录其退出状态。
// 5.执行命令
int Execute()
{pid_t id = fork();if(id == 0){// child, 程序替换execvp(g_argv[0], g_argv);exit(1);}// fatherint status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);}return 1;
}
(7)完整代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>#define COMMAND_SIZE 1024
#define HOSTNAME_MAX 1024
#define FORMAT "[%s@%s:%s]# "// shell定义的全局变量// 全局的命令行参数表
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc = 0;char cwd[1019];
char cwdenv[1024]; // 环境变量表
#define MAX_ENVS 1024
char *g_env[MAX_ENVS];
int g_envs = 0;// 最后一个程序退出码
int lastcode = 0;const char *GetUserName()
{const char * name = getenv("USER");return name == NULL ? "None" : name;
}const char *GetHostName()
{char hostname[HOSTNAME_MAX];return (gethostname(hostname, HOSTNAME_MAX) == -1) ? "None" : hostname;
}const char *GetPwd()
{//const char * pwd = getenv("PWD");const char *pwd = getcwd(cwd, sizeof(cwd)); // 通过系统调用获取if(pwd != NULL){ // 把自己的环境变量导给进程snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
}const char *GetHome()
{const char *home = getenv("HOME");return home == "" ? NULL : home;
}// 对路径做包装
std::string DirName(const char *pwd)
{
#define SLASH "/"std::string dir = pwd;if(dir == SLASH) return SLASH;auto pos = dir.rfind(SLASH);if(pos == std::string::npos) return "BUG";return dir.substr(pos+1);
}// 制作命令行提示符
void MakeCommandline(char cmd_prompt[], int size)
{// 设置式化输入snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());//snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());
}// 打印命令行提示符
void PrintCommandline()
{char prompt[COMMAND_SIZE];MakeCommandline(prompt, COMMAND_SIZE);printf("%s", prompt);fflush(stdout);
}// 获取用户输入命令
bool GetCommandline(char *out, int size)
{// 从标准输入流获取命令,其实就是字符串char* c = fgets(out, size, stdin);if(c == NULL) return 1;out[strlen(out)-1] = '\0'; // 清理\n, 至少会按一次回车,所以不会出错if(strlen(out) == 0) return false;// 什么都没输入return true;
}// 3. 命令行分析
bool CommandParse(char *commandline)
{// "ls -a -l" -> "ls" "-a" "-l"// 字符串切割 strtok()函数
#define SEP " "g_argc = 0;// 第一次调用:传入要分割的字符串和分隔符g_argv[g_argc++] = strtok(commandline, SEP);// 后续调用:传入 NULL 和分隔符,继续分割同一字符串while(g_argv[g_argc++] = strtok(NULL, SEP));g_argc--;return g_argc > 0 ? true : false;
}void PrintArray()
{for(int i = 0; g_argv[i]; i++){printf("argv[%d]->%s\n", i, g_argv[i]);}printf("%d\n",g_argc);
}// 从父shell1获取环境变量表,本来是要从配置文件获取的
void InitEnv()
{extern char **environ;memset(g_env, 0, sizeof(g_env)); // 初始化g_envs = 0;// 1.获取环境变量for(int i = 0; environ[i]; i++){// (1)申请空间g_env[i] = (char*)malloc(strlen(environ[i])+1);// (2)拷贝到我们的环境变量表strcpy(g_env[i], environ[i]);g_envs++;}// 测试:添加了一个新的环境变量g_env[g_envs++] = (char*)"HAHA=for_test";g_env[g_envs] = NULL; // 表的最后一个成员必须是NULL// 2.获取到的环境变量导入shell的表中for(int i = 0; g_env[i]; i++){putenv(g_env[i]);}
}// 内键命令
bool CD()
{if(g_argc == 1) // 只有一个cd时{std::string home = GetHome();if(home.empty()) return true;chdir(home.c_str());}else{std::string where = g_argv[1];if(where == "-"){}else if(where == "~"){}else{chdir(where.c_str());}}return true;
}bool Echo()
{if(g_argc == 2){//std::string opt = g_argv[1];if(opt == "$?") // eg1: echo $?{std::cout << lastcode << std::endl;lastcode = 0;return true;}if(opt[0] == '$') // eg2: echo $PATH{std::string env_name = opt.substr(1);const char * env_value = getenv(env_name.c_str()); // 获取环境变量的值if(env_value) std::cout << env_value << std::endl;}}return false;
}// 4. 检测并执行内键命令
bool CheckAndExecBuiltin()
{std::string cmd = g_argv[0];if(cmd == "cd"){return CD();}else if(cmd == "echo"){return Echo();}return false;
}// 5.执行命令
int Execute()
{pid_t id = fork();if(id == 0){// child, 程序替换execvp(g_argv[0], g_argv);exit(1);}// fatherint status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);}return 1;
}
// 释放通过malloc分配的环境变量内存
void FreeEnvMemory1()
{for(int i = 0; i < g_envs; i++) {if(g_env[i] != NULL) {free(g_env[i]); // 释放每个环境变量字符串g_env[i] = NULL; // 置空指针}}g_envs = 0; // 重置计数器
}void FreeEnvMemory() {if (!g_used_putenv) {// 未调用 putenv,可以安全释放for (int i = 0; i < g_envs; i++) {free(g_env[i]);g_env[i] = NULL;}} else {// 调用过 putenv,只能释放数组指针(不释放字符串内存)for (int i = 0; i < g_envs; i++) {g_env[i] = NULL; // 仅置空指针}}g_envs = 0;
}int main()
{// shell启动要从系统中获取环境变量,而我们的环境变量信息要从父shell中获取InitEnv();// 当然是循环啦!while(true){// 1、输出命令行提示符PrintCommandline();// 2. 获取输入的命令char commandline[COMMAND_SIZE];if(!GetCommandline(commandline, COMMAND_SIZE))continue;//printf("%s\n", commandline);// 3. 命令行分析, 字符串拆分成多个元素if(!CommandParse(commandline)) continue;//PrintArray();// 4.检测并执行内建命令if(CheckAndExecBuiltin()) continue;// 5. 执行命令Execute();}// 释放空间FreeEnvMemory();return 0;
}
三、函数和进程之间的相似性
一个C程序有很多函数组成。一个函数可以调用另一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过 call/return 系统进行通信。这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux 鼓励将这种应用于程序之内的模式扩展到程序之间。如下图:
一个C程序可以 fork/exec 另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过 exit(n) 来返回值。调用它的进程可以通过 wait(&ret) 来获取 exit 的返回值。
相关文章:
【Linux】进程替换与自定义 Shell:原理与实战
目录 一、进程程序替换 1、替换原理 2、替换函数 (1)函数解释 ① filename / pathname ② 参数表传递 ③ 环境变量表传递 (2)命名理解 二、自定义shell命令行解释器 1、实现原理 2、实现代码 (1)获…...
【AI提示词】数据分析专家
提示说明 数据分析师专家致力于通过深入分析和解读数据,帮助用户发现数据背后的模式和趋势。他们通常在商业智能、市场研究、社会科学等领域发挥重要作用,为决策提供数据支持。 提示词 # 角色 数据分析师专家## 注意 1. 数据分析师专家需要具备高度的…...
Lucky配置反向代理+Https安全访问AxureCloud服务(解决证书续签问题)
前言 之前用AxureCloud配置了SSL证书,发现ssl证书3个月就过期了,还需要手动续证书,更改配置文件,重启服务才能正常使用,太过于麻烦。也暴露了过多不安全的端口在公网,操作过于麻烦。另外暴露了过多不安全的…...
vscode使用remote ssh插件连接服务器的问题
本人今天发现自己的vscode使用remote ssh连接不上服务器了,表现是:始终在初始化 解决方法: 参考链接:vscode remote-ssh 连接失败的基本原理和优雅的解决方案 原因 vscode 的 SSH 之所以能够拥有比传统 SSH 更加强大的功能&a…...
WWW和WWWForm类
WWW类 WWW类是什么 //WWW是Unity提供的简单的访问网页的类 //我们可以通过该类上传和下载一些资源 //在使用http是,默认的请求类型是get,如果想要用post上传需要配合WWWFrom类使用 //它主要支持的协议: //…...
利用课程编辑器创新教学,提升竞争力
(一)快速创建优质教学内容 对于教育机构来说,教学内容的质量是吸引学员的关键因素之一。而课程编辑器就像是一位得力的助手,帮助教师快速创建出优质的教学内容。课程编辑器通常具有简洁易用的界面,教师即使没有专业的…...
spark与hadoop的区别
一.概述 二.处理速度 三.编程模型 四:实时性处理 五.spark内置模块 六.spark的运行模式...
【项目日记(三)】
目录 SERVER服务器模块实现: 1、Buffer模块:缓冲区模块 2、套接字Socket类实现: 3、事件管理Channel类实现: 4、 描述符事件监控Poller类实现: 5、定时任务管理TimerWheel类实现: eventfd 6、Reac…...
【图片转PDF工具】如何批量将文件夹里的图片以文件夹为单位批量合并PDF文档,基于WPF实现步骤及总结
应用场景 在实际工作和生活中,我们可能会遇到需要将一个文件夹内的多张图片合并成一个 PDF 文档的情况。例如,设计师可能会将一个项目的所有设计稿图片整理在一个文件夹中,然后合并成一个 PDF 方便交付给客户;摄影师可能会将一次拍摄的所有照片按拍摄主题存放在不同文件夹…...
深度解析算法之位运算
33.常见位运算 1.基础位运算 << 左移操作符 > >右移操作符号 ~取反 &按位与:有0就是0 |按位或:有1就是1 ^按位异或:相同为0,不用的话就是1 /无进位相加 0 1 0 0 1 1 0 1 0 按位与结果 0 1 1 按位或结果 0 0 1 …...
深入探索Qt异步编程--从信号槽到Future
概述 在现代软件开发中,应用程序的响应速度和用户体验是至关重要的。尤其是在图形用户界面(GUI)应用中,长时间运行的任务如果直接在主线程执行会导致界面冻结,严重影响用户体验。 Qt提供了一系列工具和技术来帮助开发者实现异步编程,从而避免这些问题。本文将深入探讨Qt…...
【KWDB 创作者计划】_本地化部署与使用KWDB 深度实践
引言 KWDB 是一款面向 AIoT 场景的分布式多模数据库,由开放原子开源基金会孵化及运营。它能在同一实例同时建立时序库和关系库,融合处理多模数据,具备强大的数据处理能力,可实现千万级设备接入、百万级数据秒级写入、亿级数据秒级…...
基于XC7V690T的在轨抗单粒子翻转系统设计
本文介绍一种基于XC7V690T 的在轨抗单粒子翻转系统架构;其硬件架构主要由XC7V690TSRAM 型FPGA芯片、AX500反熔丝型FPGA 芯片以及多片FLASH 组成;软件架构主要包括AX500反熔丝型FPGA对XC7V690T进行配置管理及监控管理,对XC7V690T进行在轨重构管理,XC7V690T通过调用内部SEMIP核实…...
机器学习 Day13 Boosting集成学习方法: Adaboosting和GBDT
大多数优化算法可以分解为三个主要部分: 模型函数:如何组合特征进行预测(如线性加法) 损失函数:衡量预测与真实值的差距(如交叉熵、平方损失) 优化方法:如何最小化损失函数&#x…...
Floyd算法求解最短路径问题——从零开始的图论讲解(3)
目录 前言 Djikstra算法的缺陷 为什么无法解决负权图 模拟流程 什么是Floyd算法 Floyd算法的核心思想 状态表示 状态转移方程 边界设置 代码实现 逻辑解释 举例说明 Floyd算法的特点 结尾 前言 这是笔者图论系列的第三篇博客 第一篇: 图的概念,图的存储,图的…...
spark和hadoop的区别与联系
区别 1. 数据处理模型 Hadoop:主要依赖 MapReduce 模型,计算分 Map(映射)和 Reduce(归约)两个阶段,中间结果常需写入磁盘,磁盘 I/O 操作频繁,数据处理速度相对受限&#…...
XMLXXE 安全无回显方案OOB 盲注DTD 外部实体黑白盒挖掘
# 详细点: XML 被设计为传输和存储数据, XML 文档结构包括 XML 声明、 DTD 文档类型定义(可 选)、文档元素,其焦点是数据的内容,其把数据从 HTML 分离,是独立于软件和硬件的 信息传输…...
C# .NET如何自动实现依赖注入(DI)
为解决重复性的工作,自动实现依赖注入(DI) 示例代码如下 namespace DialysisSOPSystem.Infrastructure {public static class ServiceCollectionExtensions{/// <summary>/// 批量注入服务/// </summary>/// <param name&qu…...
FastGPT Docker Compose本地部署与硅基流动免费AI接口集成指南
本文参考:https://doc.tryfastgpt.ai/docs/development/ 一、背景与技术优势 FastGPT是基于LLM的知识库问答系统,支持自定义数据训练与多模型接入。硅基流动(SiliconFlow)作为AI基础设施平台,提供高性能大模型推理引…...
AI对话高效输入指令攻略(三):使用大忌——“AI味”
免责声明: 1.本文所提供的所有 AI 使用示例及提示词,仅用于学术写作技巧交流与 AI 功能探索测试,无任何唆使或鼓励利用 AI 抄袭作业、学术造假的意图。 2.文章中提及的内容旨在帮助读者提升与 AI 交互的能力,合理运用 AI 辅助学…...
算法 | 成长优化算法(Growth Optimizer,GO)原理,公式,应用,算法改进研究综述,matlab代码
===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 成长优化算法 一、算法原理二、核心公式三、应用领域四、算法改进研究五…...
生产环境问题排查:日志分析与性能瓶颈定位(一)
引言 在当今数字化时代,各类应用系统如潮水般涌现,支撑着我们生活和工作的方方面面。从日常使用的电商平台、社交网络,到企业内部复杂的业务系统,它们的稳定运行和高效性能至关重要。而在生产环境中,日志分析与性能瓶…...
go语言的八股文
1.go语言触发异常的场景有哪些 运行时错误 1.空指针解引用:尝试访问一个未初始化的指针指向的内存,会导致程序崩溃并触发异常。 2.数组越界访问:试图访问数组中不存在的索引,比如数组长度为5,却尝试访问索引为10的元素…...
Office文件内容提取 | 获取Word文件内容 |Javascript提取PDF文字内容 |PPT文档文字内容提取
关于Office系列文件文字内容的提取 本文主要通过接口的方式获取Office文件和PDF、OFD文件的文字内容。适用于需要获取Word、OFD、PDF、PPT等文件内容的提取实现。例如在线文字统计以及论文文字内容的提取。 一、提取Word及WPS文档的文字内容。 支持以下文件格式: …...
组态软件工业化自动领域的可视化配置利器
组态软件是工业自动化领域的可视化配置利器,在工业生产中发挥着至关重要的作用,以下从定义、特点、功能、应用场景、市场现状及发展趋势等方面进行详细介绍: 定义 组态软件,又称组态监控系统软件,是用于数据采集和过程…...
Ansys electronics安装多版本simulink打开s-function冲突解决方法
安装了Ansys Electronics 2022 R1和2024 R1,想通过simplorer和simulink中的S-function进行联合仿真,结果注册表一直是2024 R1,修改方法如下: 1. WINR打开cmd,注意要用管理员权限打开 2. 输入 "D:\ANSYS\AnsysE…...
ubuntu--安装双系统
教程 BIOS设置 启动盘生成和ubuntu安装 boot option #1设置USB为第一启动项 rufus下载 官网: 链接 点击“链接”下面的按钮,即可下载。(注意查看自己的电脑是x64还是x84) 网盘下载: 链接...
快速搭建 Cpolar 内网穿透(Mac 系统)
1、Cpolar快速入门教程(官方) 链接地址:Cpolar 快速入门 2、官方教程详解 本地安装homebrew /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"这个是从 git 上拉取的&#x…...
【pytorch】torch.nn.Unfold操作
说明 一个代码里涉及到了unfold的操作,看了半天官网都没整明白维度怎么变化的,参考这个链接搞明白了: https://blog.csdn.net/ViatorSun/article/details/119940759 https://zhuanlan.zhihu.com/p/361140988 维度计算 输入( N,…...
使用PyTorch实现图像增广与模型训练实战
本文通过完整代码示例演示如何利用PyTorch和torchvision实现常用图像增广方法,并在CIFAR-10数据集上训练ResNet-18模型。我们将从基础图像变换到复杂数据增强策略逐步讲解,最终实现一个完整的训练流程。 一、图像增广基础操作 1.1 准备工作 #matplotli…...
PyTorch实现糖尿病预测的CNN模型:从数据加载到模型部署全解析【N折交叉验证、文末免费下载】
本文将详细介绍如何使用PyTorch框架构建一个卷积神经网络(CNN)来预测糖尿病,包含完整的代码实现、技术细节和可视化分析。 1. 项目概述 本项目使用经典的Pima Indians Diabetes数据集,通过5折交叉验证训练一个1D CNN模型,最终实现糖尿病预测…...
红队专题-漏洞挖掘-代码审计-反序列化
漏洞挖掘-代码审计-反序列化 加固/防御命令执行相关日志Tools-JNDIExploitJNDI Java Naming and Directory Interface Java命名目录接口注入原理payload参数渗透测试-php命令执行-RCE+Struts2拿webshell普通权限 命令执行 拿 webshellCMD echo 写入一句话 php文件菜刀连接Strut…...
【2025软考高级架构师】——计算机系统基础(7)
摘要 本文主要介绍了计算机系统的组成,包括硬件和软件两大部分。硬件由处理器、存储器、总线、接口和外部设备等组成,软件则涵盖系统软件和应用软件。文章还详细阐述了冯诺依曼计算机的组成结构,包括 CPU、主存储器、外存等,并解…...
【网络原理】TCP协议如何实现可靠传输(确认应答和超时重传机制)
目录 一. TCP协议 二. 确定应答 三. 超时重传 一. TCP协议 1)端口号 源端口号:发送方端口号目的端口号:接收方端口号 16位(2字节)端口号,可以表示的范围(0~65535) 源端口和目的…...
Java synchroinzed和ReentrantLock
synchronized —— JVM亲儿子的暗黑兵法 核心思想:“锁即对象,对象即锁!” 底层三板斧 对象头里的锁密码 每个Java对象头里藏了两个骚东西: Mark Word:32/64位的比特修罗场,存哈希码、GC年龄࿰…...
【Linux】vim配置----超详细
目录 一、插件管理器准备 二、目录准备 三、安装插件 一、插件管理器准备 Vim-plug 是一个Vim插件管理器,利用异步并行可以快速地安装、更新和卸载插件。它的安装和配置都非常简单,而且在操作过程中会给出很多易读的反馈信息,是一个自由、…...
驱动开发硬核特训 · Day 15:电源管理核心知识与实战解析
在嵌入式系统中,电源管理(Power Management)并不是“可选项”,而是实际部署中影响系统稳定性、功耗、安全性的重要一环。今天我们将以 Linux 电源管理框架 为基础,从理论结构、内核架构,再到典型驱动实战&a…...
如何使用人工智能大模型,免费快速写工作计划?
如何使用人工智能大模型,免费快速写工作计划? 具体视频教程https://edu.csdn.net/learn/40406/666579...
延长(暂停)Windows更新
延长(暂停)Windows更新 因为不关闭更新有时候就会出现驱动或者软硬件不兼容,导致蓝屏出现。 注:为什么选择延长更新而不是用软件暂停更新,因为使用软件暂停更新会出现一下问题,比如微软商店打不开等等 键…...
QT实现串口透传的功能
在一些产品的开发的时候,需要将一个串口的数据发送给另外一个串口进行转发。 具体的代码如下: #include "mainwindow.h" #include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::Ma…...
分布类相关的可视化图像
目录 一、直方图(Histogram) 1.定义 2.特点 3.局限性 4.类型 5.应用场景 6.使用Python实现 二、密度图(Density Plot) 1.定义 2.特点 3.局限性 4.类型 5.应用场景 6.使用Python实现 三、箱线图(Box Plo…...
【android bluetooth 框架分析 02】【Module详解 12】【 BidiQueue、BidiQueueEnd、Queue介绍】
1. BidiQueue 和 BidiQueueEnd 蓝牙协议栈里面有很多 BidiQueue ,本节就专门来梳理这块内容。 2. BidiQueue 介绍 BidiQueue,是 Host 与 Controller 层通信的中枢之一, acl_queue_、sco_queue_、iso_queue_ 都是 BidiQueue 类型。让我们一起看一下这个…...
c++通讯录管理系统
通讯录是一个可以记录亲人,好友的信息工具。 功能包括: 1,添加联系人:向通讯录添加新人,包括(姓名,性别年龄,联系电话,家庭住址) 2,显示联系人…...
React 打包
路由懒加载 原本的加载方式 #使用lazy()函数声明的路由页面 使用Suspense组件进行加载 使用CDN优化...
day1 python训练营
变量与输出 print(1,2,3,sep\n,endsep用来区分两个变量,end会紧跟最后一个变量) print(1,2,3,sepaaa,endsep用来区分两个变量,3后面不会再输出aaa) 格式化字符串 变量名值 print(f"变量名{变量名}") 变量的基础运算 ,-*,/ 注意*不要忘写。比如2j就不…...
C语言状态字与库函数详解:概念辨析与应用实践
C语言状态字与库函数详解:概念辨析与应用实践 一、状态字与库函数的核心概念区分 在C语言系统编程中,"状态字"和"库函数"是两个经常被混淆但本质完全不同的概念,理解它们的区别是掌握系统编程的基础。 1. 状态字&…...
软件测试笔记(测试的概念、测试和开发模型介绍、BUG介绍)
软件测试笔记 认识测试 软件测试是啥? 说白了,就是检查软件的功能和效果是不是用户真正想要的东西。比如用户说“我要一个能自动算账的软件”,测试就是看这个软件到底能不能准确算账、有没有漏掉功能。 软件测试定义:软件测试就…...
Python多进程同步全解析:从竞争条件到锁、信号量的实战应用
1. 进程同步的必要性 在多进程编程中,当多个进程需要访问共享资源时,会出现竞争条件问题。例如火车票售卖系统中,如果多个售票窗口同时读取和修改剩余票数,可能导致数据不一致。 1.1 竞争条件示例 from multiprocessing import…...
Vue3 + TypeScript,关于item[key]的报错处理方法
处理方法1:// ts-ignore 注释忽略报错 处理方法2:item 设置为 any 类型...
Spring源码中关于抽象方法且是个空实现这样设计的思考
Spring源码抽象方法且空实现设计思想 在Spring源码中onRefresh()就是一个抽象方法且空实现,而refreshBeanFactory()方法就是一个抽象方法。 那么Spring源码中onRefresh方法定义了一个抽象方法且是个空实现,为什么这样设置,好处是什么。为…...