当前位置: 首页 > news >正文

(Linux操作系统)自定义shell的实现

        讲自定义shell之前我们先看一个东西,那就是进程替换,我们想要父进程fork之后的子进程之后运行一个全新的程序那该怎么办呢?

        这里就要用一个叫做进程替换的一个东西了,程序替换是通过特定的接⼝,加载磁盘上的⼀个全新的程序(代码和数据),加载到调⽤进程的地址空间。

exec进程替换函数

        进程替换就是 替换当前进程的代码和数据段,使进程执行新的程序,PID 保持不变。

        直接把当前程序的上下文数据替换为要执行的程序,一个进程想要执行其他的进程时,可以使用进程替换,而不用创建子进程分配进程id,这样可以提高程序的效率。

        进程替换的原理是通过exec系统调用,用一个新的程序映像替换当前进程的代码和数据段,使进程的执行从新程序的入口点开始。在此过程中,进程的PID、开放的文件描述符、环境变量等资源得以保留,从而实现进程的替换而不改变其标识和部分状态。

我们下面来看看函数接口

他们都包含在unstd.h这个头文件里面;

int execl ( const char *path, const char *arg, ...);
        第一个参数是path(目的是让函数到指定path路径下面寻找要替换的程序)后面这些是可变参数,可以传递给新程序的其他参数。它们会被传递给新程序的主函数main作为argv[1]argv[2]等。
int execlp ( const char *file, const char *arg, ...);

        第一个参数这是要执行的文件名。与execl不同,execlp不会直接使用这个路径去查找文件。而是会在环境变量PATH指定的目录列表中搜索这个文件名。

第二个是这是传递给新程序的第一个参数,通常应该是新程序的名称。在新程序中,这个参数会被作为argv[0],后面以此类推

int execle ( const char *path, const char *arg, ..., char * const envp[]);

        第一个是这是要执行的文件的路径。execle会直接使用这个路径去查找文件,不会像execlp那样在环境变量PATH中搜索。第二个是这是传递给新程序的第一个参数,通常应该是新程序的名称。在新程序中,这个参数会被作为argv[0]。第三个可变参数,第四个是这是一个指向环境变量数组的指针,用于设置新程序的环境变量。数组的每个元素都是一个字符串,形式为name=value。数组必须以空指针NULL结尾,以表示环境变量列表的结束

int execv ( const char *path, char * const argv[]);

        第一个是这是指向要执行的文件路径的指针。execv会使用这个路径去查找并执行文件。第二个是这是指向参数数组的首元素的指针,用于传递给新程序的参数。argv数组的第一个元素(argv[0])通常应该是新程序的名称。数组的最后一个元素必须是空指针NULL,以表示参数列表的结束。

int execvp ( const char *file, char * const argv[]);

        第一个是这是指向要执行的文件名的指针。execvp会使用环境变量PATH来查找这个文件名对应的可执行文件。第二个是这是指向参数数组的首元素的指针,用于传递给新程序的参数。argv数组的第一个元素(argv[0])通常应该是新程序的名称。数组的最后一个元素必须是空指针NULL,以表示参数列表的结束。

int execve ( const char *path, char * const argv[], char * const envp[])

函数是exec家族中最基本的函数,用于执行进程替换。它用新的程序替换当前进程的代码段、数据段和堆栈段。

它会直接使用path参数指定的路径来查找可执行文件,并传递argv参数数组给新程序作为命令行参数。

        同时,它还会传递envp环境变量数组给新程序,允许新程序在定制化的环境中运行。

然而上面提到的函数除了最后一个都是进行库封装之后的函数,最后一个才是系统提供的接口

事实上,只有execve是真正的系统调⽤,其它五个函数最终都调⽤execve,所以execve在man⼿册 第2节, 其它函数在man⼿册第3节。
有了上面的了解之后我们就可以开始准备实现一个自定义shell了

实现自定义shell

首先看看下面的设计思路图片

第一步:打印我们的终端提示符

我们登陆linux的时候一般都会打印一个

这个的提示符界面

        我们首先来实现这个功能,首先分析一下这个界面的组成部分,第一个是登陆的用户名其次是登陆的机器名称,最后是当前所在的目录。

这三个东西可以从环境变量上面获取,我们可以调用get_env()函数来获取对应的环境变量,他会返回一个字符指针,指向一个字符串,我们得到这个三个字符串之后我们可以用snprintf()函数对我们获得的字符串进行格式化一下(这里不是打印)来看看下面的代码

const char* user()
{char* user = nullptr;user = getenv("USER");return user == nullptr?"NONE!!":user;
}
//获得当前路径
const char* pwd()
{char* pwd = nullptr;// pwd = getenv("PWD");pwd = getcwd(cwdenv,sizeof(cwdenv));return pwd == nullptr?"NONE!!":pwd;
}
//获得家目录
const char* get_home()
{char* home = nullptr;home = getenv("HOME");return home == nullptr?"":home;
}
//获得主机名称
const char* hostname()
{char* hostname = nullptr;hostname = getenv("HOSTNAME");return hostname == nullptr?"NONE!!":hostname;
}

        这里的获取pwd为什么没调用getenv()而是调用getcwd(),这个函数是一个系统调用函数,会返回当前所在的文件路径,而环境变量里面的当前路径的获得是在我们切换目录之后再获得的,有一个滞后性,而这里我们可以立即得到我们的当前路径

得到这三个字符串之后,我们就要按照我的指定格式进行格式化了,使用snprintf()函数进行格式化

第一个参数是一个字符数组的地址,第二个是大小,第三个是一个宏# define FORMAT "[%s@%s %s ]:)#-->"这就是按照我们指定的格式进行格式化,最后是可变参数,我们按顺序传递了三个字符串进去。这个cmd字符数组定义在主函数里面的。

void init_print_cmd_line(char* cmd,int size)
{snprintf(cmd,size,FORMAT,user(),hostname(),pwd());  
}

        当我们格式化好之后,我们就可以打印了,直接传一个字符串的一个首地址过去

void printf_cmd(char* cmd)
{fflush(stdout);printf("%s",cmd);
}

        做完这些之后,在主函数内部合适的位置调用我们就可以打印这个命令提示符了。

第二步 处理输入的命令

看看下面的代码

bool input_cmd(char* out,int size)
{char* ret = fgets(out,size,stdin);if(ret == nullptr){return false ;}int tmp = strlen(out)-1;if(tmp == 0){return false;}out[tmp] = 0;return true;
}

我们这个函数传递了两个参数,第一个是一个字符数组,第二个是大小

首先这里调用fgets函数来从stdin标准流里面读入我们输入的数据,然后放到out数组里面。注意这里的fgets函数会把\n换行符给读入进去,所以我们最后需要特殊处理一下把数组末尾的\n置为0。

最后我们就处理好了,这里假如我们输入 "ls" "-al"在我们的数组里面是这样存放的 "ls -al" 所以还需要特殊处理一下

bool input_cmd(char* out,int size)
{char* ret = fgets(out,size,stdin);if(ret == nullptr){return false ;}int tmp = strlen(out)-1;if(tmp == 0){return false;}out[tmp] = 0;return true;
}

我们来看看处理的代码

bool cmd_to_argv(char* inputcmd)
{my_argc = 0;my_argv[my_argc++] = strtok(inputcmd,FLAG);while((bool)(my_argv[my_argc++] = strtok(NULL,FLAG)));my_argc--;return true;
}

        首先我们知道我们的shell里面会有两张表一个是命令行参数列表,一个是环境变量表,这里设计的函数就是把我们的输入的命令按照空格的方式进行分割,这里调用strok函数进行字符串分割,依次放到我们自己定义的my_argv[]全局数组里面,strok函数进行字符分割的时候第一次调用就要传要分割字符串的数组的首地址,然后需要传入以什么分割的方式进行分割判断,如需再次分割下一次首参数就传入NULL 

然后就一个循环一直分割就完事

最后我们的my_argc要--一下因为我们前面一直都是使用过后再++,所以就多统计了一个所以要--。我们来看看效果

做完这一步之后我们就可以开始进行根据命令进行程序替换了

第三步:创建子进程程序替换

我们来看看下面的代码

void execute()
{pid_t id = fork();if(id == 0){execvp(my_argv[0],my_argv);}else if(id < 0){printf("fall!!\n");}int status = 0;pid_t retid = waitpid(id,&status,0);if(retid > 0){lastcode_exit_status = WEXITSTATUS(status);}}

我们输入完命令之后就需要通过父进程我们自己设计的bash来创建子进程来执行我们的程序,

所以需要fork创建一个然后我们父进程进行等待,等待我们子进程结束,这个代码就很简单,当写完上述逻辑之后我们的bash就能执行除了内建命令的命令了,然后我们下面的这个WEXITSTATUS(status);宏函数就是用来获取子进程退出信息的这个我们等下处理内建命令echo的时候要使用。

第四步:处理内建命令

       cd命令

        我们这里就只处理两个内建命令:echo,cd命令,我们要处理这两个命令之前,我们需要判断输入的命令是不是内建命令,一个很简单的代码就搞定

bool cheakbulitin()
{std::string str = my_argv[0];if(str == "cd"){cd();return true;}else if (str == "echo"){echo();return true;}return false;
}

然后我们先说一下cd命令,看看下面的代码

这里我们就处理两个最常用的一个是cd不带任何路径,第二个是cd到哪去

bool cd()
{if(my_argc == 1){std::string home = get_home();if(home.empty()){return true;}chdir(home.c_str());}else {std::string where = my_argv[1];if(where == "~"){//.......return true;}else if(where == "-"){//.......return true;}else {chdir(where.c_str());return true;}}return false;
}

        首先说第一个cd不带任何路径,就直接切换回到自己的家目录上,首先使用get_home函数获取当前用户的家目录,然后这里我们使用chdir函数来帮助我们切换当前所在的路径,很简单的一个逻辑就完成了 然后我们处理切第二个,我们的命令输入的格式一般是 cd /usr/bin 这种类似的,要切换的路径是存在后面my_argv[1]这个里面,我们只需要把这个字符串给传入chdir函数就行了,就实现了我们两个cd的最核心的功能

echo命令

来先看看代码

bool echo()
{if(my_argc == 2){std::string tmp = my_argv[1];if(tmp == "$?"){printf("%d\n",lastcode_exit_status);return true;}else if(tmp[0] == '$'){//输出指定的环境变量std::string envname = tmp.substr(1);char* ret = getenv(envname.c_str()); if(ret == nullptr){std::cout<<"找不到指定的环境变量!"<<std::endl;return true;}std::cout<<ret<<std::endl;}else {printf("-->%s\n",tmp.c_str());}}return false;
}

        首先我们需要判断my_argc是不是等于2,如果是就代表我们要让echo做事情,首先就需要判断echo 后面跟的是不是特殊的转意字符,如果是$?就代表我们要获取上一个子进程退出的状态对这个进行特殊处理,如果是$就代表我们要输出指定的环境变量,这里调用c++string里面的substr函数进行字符串截取,如果什么都不带就代表我们就是纯在屏幕上打印内容

完成这些之后我们的shell就已经能进行基本的正常使用了

最后我们来模拟一下shell的启动过程

第五步模拟shell启动过程

我们知道shell启动的时候会到配置文件里面读取环境变量表,这个配置文件是用shell脚本语言编写的,我们这里就用C语言进行实现,我们来看看下面的代码

void init_env()
{genv_s = 0;extern char** environ;memset(g_env,0,sizeof(g_env));for(int i = 0;environ[i];i++){char* tmp = (char*)malloc(strlen(environ[i])+1);if(tmp == nullptr){perror("malloc_fail");exit(1);}g_env[i] = tmp;strcpy(g_env[i],environ[i]);genv_s++;}g_env[genv_s++] = (char*)"qqqqqqqtest=HAHAHAHAHAHAHAHHAHAHA11111111111111111111111111111111111111";g_env[genv_s] = NULL;for(int i = 0;g_env[i];i++){putenv(g_env[i]);}environ = g_env;
}

        首先我们要声明这个environ这个全局二级指针变量,这个是指向一个char*的环境变量表的,这个是由系统提供的,假设我们这个指针没指向任何地方,然后我们进行模拟从系统配置文件里面读取环境变量,然后我们自己需要首先定义一个全局g_env[]环境变量表,然后对这个表里面进行初始化,然后把环境变量表里面的ket=value结构的字符串依次开空间放入到我们提供的环境变量表里面,最后一个元素设置为NULL,这里我们自己手动输入了一个环境变量进行检测 ,最后调用putenv进行导成环境变量,把environ这个指针指向我们的环境变量表,这样我们的环境变量都会被子进程给继承,我们来检测一下,看看我们自己输入的环境变量存不存在

这样我们一个简单的shell就完成了

下面是源码

源码

# include<cstdio>
# include<cstdlib>
# include<cstring>
# include<unistd.h>
# include<sys/types.h>
# include<wait.h>
# include<string>
# include<iostream># define SIZE 1024
# define FORMAT "[%s@%s %s ]:)#-->"
# define ARGVSIZE 128
# define FLAG " "
# define ENVSIZE 1024
# define GENVSIZE 128
int lastcode_exit_status = 0;
int my_argc  = 0;
char* my_argv[ARGVSIZE];
char* g_env[GENVSIZE];
char cwdenv[1024];int genv_s = 0;const char* user()
{char* user = nullptr;user = getenv("USER");return user == nullptr?"NONE!!":user;
}const char* pwd()
{char* pwd = nullptr;// pwd = getenv("PWD");pwd = getcwd(cwdenv,sizeof(cwdenv));return pwd == nullptr?"NONE!!":pwd;
}const char* get_home()
{char* home = nullptr;home = getenv("HOME");return home == nullptr?"":home;
}const char* hostname()
{char* hostname = nullptr;hostname = getenv("HOSTNAME");return hostname == nullptr?"NONE!!":hostname;
}void init_print_cmd_line(char* cmd,int size)
{snprintf(cmd,size,FORMAT,user(),hostname(),pwd());  
}void printf_cmd(char* cmd)
{fflush(stdout);printf("%s",cmd);
}bool input_cmd(char* out,int size)
{char* ret = fgets(out,size,stdin);if(ret == nullptr){return false ;}int tmp = strlen(out)-1;if(tmp == 0){return false;}out[tmp] = 0;return true;
}bool cmd_to_argv(char* inputcmd)
{my_argc = 0;my_argv[my_argc++] = strtok(inputcmd,FLAG);while((bool)(my_argv[my_argc++] = strtok(NULL,FLAG)));my_argc--;return true;
}bool cd()
{if(my_argc == 1){std::string home = get_home();if(home.empty()){return true;}chdir(home.c_str());}else {std::string where = my_argv[1];if(where == "~"){//.......return true;}else if(where == "-"){//.......return true;}else {chdir(where.c_str());return true;}}return false;
}bool echo()
{if(my_argc == 2){std::string tmp = my_argv[1];if(tmp == "$?"){printf("%d\n",lastcode_exit_status);return true;}else if(tmp[0] == '$'){//输出指定的环境变量std::string envname = tmp.substr(1);char* ret = getenv(envname.c_str()); if(ret == nullptr){std::cout<<"找不到指定的环境变量!"<<std::endl;return true;}std::cout<<ret<<std::endl;}else {printf("-->%s\n",tmp.c_str());}}return false;
}bool cheakbulitin()
{std::string str = my_argv[0];if(str == "cd"){cd();return true;}else if (str == "echo"){echo();return true;}return false;
}void execute()
{pid_t id = fork();if(id == 0){execvp(my_argv[0],my_argv);}else if(id < 0){printf("fall!!\n");}int status = 0;pid_t retid = waitpid(id,&status,0);if(retid > 0){lastcode_exit_status = WEXITSTATUS(status);}}
void init_env()
{genv_s = 0;extern char** environ;memset(g_env,0,sizeof(g_env));for(int i = 0;environ[i];i++){char* tmp = (char*)malloc(strlen(environ[i])+1);if(tmp == nullptr){perror("malloc_fail");exit(1);}g_env[i] = tmp;strcpy(g_env[i],environ[i]);genv_s++;}g_env[genv_s++] = (char*)"qqqqqqqtest=HAHAHAHAHAHAHAHHAHAHA11111111111111111111111111111111111111";g_env[genv_s] = NULL;for(int i = 0;g_env[i];i++){putenv(g_env[i]);}environ = g_env;
}int main()
{char cmd[SIZE];char input_to_cmd[SIZE]; init_env();while(1){init_print_cmd_line(cmd,sizeof((cmd)));printf_cmd(cmd);if(!(input_cmd(input_to_cmd,sizeof(input_to_cmd)))){continue;}cmd_to_argv(input_to_cmd);if(cheakbulitin()){continue;}//printf_argv();execute();//创建进程}return 0;
}

相关文章:

(Linux操作系统)自定义shell的实现

讲自定义shell之前我们先看一个东西&#xff0c;那就是进程替换&#xff0c;我们想要父进程fork之后的子进程之后运行一个全新的程序那该怎么办呢&#xff1f; 这里就要用一个叫做进程替换的一个东西了&#xff0c;程序替换是通过特定的接⼝&#xff0c;加载磁盘上的⼀个全新的…...

安卓jks提取pem和pk8文件

你需要安装&#xff1a; Java Keytool OpenSSL 系统要求&#xff1a;Mac/Linux/Windows 都可以。 keytool -importkeystore -srckeystore holder-keystore.jks -destkeystore holder-keystore.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass yzhafzKPj4 -dest…...

人脸检测-人脸关键点-人脸识别-人脸打卡-haar-hog-cnn-ssd-mtcnn-lbph-eigenface-resnet

链接&#xff1a;https://pan.baidu.com/s/1VhGdyIW5GWuTNkfbCEc5eA?pwdz0eo 提取码&#xff1a;z0eo --来自百度网盘超级会员V2的分享 创建环境 conda create -n 环境名称python3.8 conda activate 环境名称 然后配置环境 pip install requirements.txt 运行程序&…...

Gobuster :dir、dns、vhost

Gobuster 及其相关技术知识​​必须​​用于法律明确允许的场景&#xff01;&#xff01;&#xff01; 1. dir 模式&#xff1a;目录/文件枚举 用途&#xff1a;扫描目标网站的目录和文件&#xff0c;常用于发现隐藏资源或敏感文件。 ​​关键参数​​&#xff1a; -u URL&am…...

Vue+Threejs项目性能优化

使用Vue和Three.js开发的项目&#xff0c;但运行一段时间后电脑内存就满了&#xff0c;导致性能下降甚至崩溃&#xff0c;分析内存泄漏的原因优化如下&#xff1a; 资源释放管理 手动释放Three.js资源&#xff1a; 在Vue组件的beforeDestroy或destroyed生命周期中&#xff0…...

Leetcode - 双周赛135

目录 一、3512. 使数组和能被 K 整除的最少操作次数二、3513. 不同 XOR 三元组的数目 I三、3514. 不同 XOR 三元组的数目 II四、3515. 带权树中的最短路径 一、3512. 使数组和能被 K 整除的最少操作次数 题目链接 本题实际上求的就是数组 nums 和的余数&#xff0c;代码如下&…...

[特殊字符] PostgreSQL MCP 开发指南

简介 &#x1f680; PostgreSQL MCP 是一个基于 FastMCP 框架的 PostgreSQL 数据库交互服务。它提供了一套简单易用的工具函数&#xff0c;让你能够通过 API 方式与 PostgreSQL 数据库进行交互。 功能特点 ✨ &#x1f504; 数据库连接管理与重试机制&#x1f50d; 执行 SQL…...

等离子体浸没离子注入(PIII)

一、PIII 是什么&#xff1f;基本原理和工艺 想象一下&#xff0c;你有一块金属或者硅片&#xff08;就是做芯片的那种材料&#xff09;&#xff0c;你想给它的表面“升级”&#xff0c;让它变得更硬、更耐磨&#xff0c;或者有其他特殊功能。怎么做呢&#xff1f;PIII 就像是用…...

TinyEngine 2.4版本正式发布:文档全面开源,实现主题自定义,体验焕新升级!

本文由体验技术团队李璇原创。 前言 TinyEngine低代码引擎使开发者能够定制低代码平台。它是低代码平台的底座&#xff0c;提供可视化搭建页面等基础能力&#xff0c;既可以通过线上搭配组合&#xff0c;也可以通过cli创建个人工程进行二次开发&#xff0c;实时定制出自己的低…...

gemini讲USRP

您好&#xff01;USRP (Universal Software Radio Peripheral) 是一种软件无线电 (SDR) 设备系列&#xff0c;由 Ettus Research (现为 National Instruments 旗下公司) 开发和销售。USRP 提供了一个灵活且可配置的平台&#xff0c;用于设计、原型开发和部署各种无线通信系统。…...

智能超表面通信控制板--通道电压并行控制版

可重构智能超表面&#xff08;Reconfigurable Intelligent Surface, RIS&#xff09;技术是一种新兴的人工电磁表面技术&#xff0c;它通过可编程的方式对电磁波进行智能调控&#xff0c;从而在多个领域展现出巨大的应用潜力。超表面具有低成本、低能耗、可编程、易部署等特点&…...

Spring Task(笔记)

介绍&#xff1a; 应用场景&#xff1a; cron表达式&#xff1a; cron表达式在线生成器&#xff1a; 入门案例&#xff1a;...

YOLOv3的改进思路与方法:解析技术难点与创新突破

YOLOv3作为目标检测领域的经典算法&#xff0c;凭借其出色的速度和性能平衡获得了广泛应用。然而&#xff0c;随着计算机视觉技术的不断发展&#xff0c;YOLOv3在某些场景下的局限性也逐渐显现。本文将深入分析YOLOv3的不足之处&#xff0c;并系统介绍常见的改进策略和方法&…...

【解锁元生代】ComfyUI工作流与云原生后端的深度融合:下一代AIGC开发范式革命

## 从单机到云原生的认知跃迁 当2023年Stable Diffusion WebUI还在争夺本地显卡性能时&#xff0c;ComfyUI已悄然开启工作流模块化革命&#xff1b;当2024年AI绘画工具陷入"参数调优内卷"&#xff0c;云原生技术正重塑AI开发的基础设施层。二者的深度融合&#xff0…...

shell 编程之正则表达式与文本处理器

目录 一、正则表达式 1. 概念 2. 作用 3. 分类 二、基础正则表达式&#xff08;BRE&#xff09; grep 命令选项 三、扩展正则表达式&#xff08;ERE&#xff09; 与 BRE 的区别 四、文本处理器 1. sed 工具 2. awk 工具 五、总结 总结对比 元字符总结 工具对比与…...

Shell编程之正则表达式与文本处理器

目录 一、引言 二、正则表达式 2.1 定义与用途 2.2 基础正则表达式 2.2.1 查找特定字符 2.2.2 利用中括号 “[]” 查找集合字符 2.2.3 查找行首 “^” 与行尾字符 “$” 2.2.4 查找任意一个字符 “.” 与重复字符 “*” 2.2.5 查找连续字符范围 “{}” 2.3 元字符总结…...

TMDOG——语言大模型进行意图分析驱动后端实践

语言大模型进行意图分析驱动后端实践 项目概述 项目地址&#xff1a;https://github.com/TMDOG666/AI_Backend_Demo 该项目通过语言大模型&#xff0c;通过分析用户意图、拆分任务、构建API调用链来驱动后端实践。 以一个简单的教务系统后端为例&#xff0c;将教务系统后端…...

未启用CUDA支持的PyTorch环境** 中使用GPU加速解决方案

1. 错误原因分析 根本问题&#xff1a;当前安装的PyTorch是CPU版本&#xff0c;无法调用GPU硬件加速。当运行以下代码时会报错&#xff1a;model YOLO("yolov8n.pt").to("cuda") # 或 .cuda()2. 解决方案步骤 步骤1&#xff1a;验证CUDA可用性 在Pyth…...

【mysql】Mac 通过 brew 安装 mysql 、启动以及密码设置

Mac 通过 brew 安装 mysql 、启动以及密码设置 使用 brew 安装 mysqlmysql 启动mysql密码设置参考文章&#xff1a; 使用 brew 安装 mysql brew install mysqlmysql 启动 下载完毕&#xff0c;终端告诉我们mysql数据库没有设置密码的&#xff0c;我们可以直接执行 mysql -u r…...

Vue2 nextTick

核心源码位置 Vue 2 的 nextTick 实现主要在 src/core/util/next-tick.js 文件中。 完整源码结构 import { noop } from shared/util import { handleError } from ./error import { isIE, isIOS, isNative } from ./envexport let isUsingMicroTask falseconst callbacks …...

Ubuntu 安装 NVIDIA显卡驱动、CUDA 以及 CuDNN工具

文章目录 一、简介二、查看显卡设备三、安装显卡驱动四、安装CUDA工具箱五、安装CuDNN小结 一、简介 NVIDIA 驱动&#xff1a;操作系统与 NVIDIA 显卡硬件之间的桥梁&#xff0c;负责驱动显卡硬件的运行&#xff0c;显卡的“底层操作系统”&#xff0c;一切的基础。CUDA&#…...

LeetCode算法题(Go语言实现)_50

题目 现有一个包含所有正整数的集合 [1, 2, 3, 4, 5, …] 。 实现 SmallestInfiniteSet 类&#xff1a; SmallestInfiniteSet() 初始化 SmallestInfiniteSet 对象以包含 所有 正整数。 int popSmallest() 移除 并返回该无限集中的最小整数。 void addBack(int num) 如果正整数 …...

idea报错java: 非法字符: ‘\ufeff‘解决方案

解决方案步骤以及说明 BOM是什么&#xff1f;1. BOM的作用2. 为什么会出现 \ufeff 错误&#xff1f;3. 如何解决 \ufeff 问题&#xff1f; 最后重新编译&#xff0c;即可运行&#xff01;&#xff01;&#xff01; BOM是什么&#xff1f; \ufeff 是 Unicode 中的 BOM&#xff0…...

WPF依赖注入IHostApplicationLifetime关闭程序

WPF依赖注入IHostApplicationLifetime关闭程序 使用Application.Current.Shutdown();退出会报异常 应该使用 app.Dispatcher.InvokeShutdown(); Application.Current.Shutdown();app.Dispatcher.InvokeShutdown();static App app new();[STAThread]public static void Main(…...

如何在 IntelliJ IDEA 中安装通义灵码 - AI编程助手提升开发效率

随着人工智能技术的飞速发展&#xff0c;AI 编程助手已成为提升开发效率和代码质量的强大工具。在众多 AI 编程助手之中&#xff0c;阿里云推出的通义灵码凭借其智能代码补全、代码解释、生成单元测试等丰富功能&#xff0c;脱颖而出&#xff0c;为开发者带来了全新的编程体验。…...

【力扣】两两交换链表中的节点

两两交换链表中的节点 代码&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, ListNode *n…...

数据共享交换平台之文件交换

数据共享交换平台的文件交换管理功能提供部门与部门之间的文件交换通道&#xff0c;满足跨部门之间文件交换需求。文件交换需要能够按照交换业务场景对交换通道进行分类管理。文件交换管理需满足如下要求&#xff1a; 1.文件交换统计&#xff1a;支持查看本部门与其他部门之间…...

什么是全球代理?如何选择全球代理服务?

在全球化不断深化的今天&#xff0c;互联网已经成为人类沟通、工作和学习的重要纽带。而全球代理则是这一纽带上的关键技术之一&#xff0c;它赋予了我们探索不同地区网络资源的能力。今天&#xff0c;我们来聊聊什么是全球代理、它能做什么&#xff0c;以及如何选择合适的全球…...

Spring Boot整合Kafka的详细步骤

1. 安装Kafka 下载Kafka&#xff1a;从Kafka官网下载最新版本的Kafka。 解压并启动&#xff1a; 解压Kafka文件后&#xff0c;进入bin目录。 启动ZooKeeper&#xff1a;./zookeeper-server-start.sh ../config/zookeeper.properties。 启动Kafka&#xff1a;./kafka-server-…...

【正点原子STM32MP257连载】第四章 ATK-DLMP257B功能测试——USB WIFI测试 #WIFI蓝牙二合一 #RTL8733BU

1&#xff09;实验平台&#xff1a;正点原子ATK-DLMP257B开发板 2&#xff09;浏览产品&#xff1a;https://www.alientek.com/Product_Details/135.html 3&#xff09;全套实验源码手册视频下载&#xff1a;正点原子资料下载中心 文章目录 第四章 ATK-DLMP257B功能测试——USB…...

Doip功能寻址走UDP协议

目前使用 connect()函数的UDP客户端 ,这里接收数据 解析的地方 查看一下。 如果使用 bind()、sendto()、recvfrom() 组合 那么返回值 和发送要在做调整&#xff0c;&#xff0c;根据业务需要后续在调整 其余的 和原来的 逻辑都是一样的&#xff0c;只是协议变了而已。 if serv…...

硬件电路设计之51单片机(2)

声明&#xff1a;绘制原理图和PCB的软件为嘉立创EDA。根据B站尚硅谷嵌入式之原理图&PCB设计教程学习所作个人用笔记。 目录 一、原理图详解 1、TypeC接口 &#xff08;1&#xff09;TypeC接口介绍 &#xff08;2&#xff09;TypeC原理图 2、5V转3.3V 3、单片机电源开…...

Deeplizard 深度学习课程(一)—— Pytorch 和 Tensor 简介

前言 该pytorch笔记参考deeplizard官方网站课程&#xff0c;有相应视频和博客&#xff0c;链接如下&#xff1a; deeplizardhttps://deeplizard.com/learn/video/v5cngxo4mIg 1.Pytorch 简介 PyTorch 是一个深度学习框架和一个科学计算包。PyTorch 的科学计算方面主要是 PyTo…...

Delphi HMAC算法

1. 前言 今天做一个三方接口&#xff0c;接口文档描述签名采用MD5&#xff0c;但是实际测试过程中&#xff0c;始终校验不通过&#xff0c;经过和三方沟通&#xff0c;才知道采用的是HMAC-MD5。由于Delphi7没有对HMAC的支持&#xff0c;则采用XE版本来支持。本次使用Delphi XE …...

Ubuntu服务器性能调优指南:从基础工具到系统稳定性提升

一、性能监控工具的三维应用 1.1 监控矩阵构建 通过组合工具搭建立体监控体系&#xff1a; # 实时进程监控 htop --sort-keyPERCENT_CPU# 存储性能采集 iostat -dx 2# 内存分析组合拳 vmstat -SM 1 | awk NR>2 {print "Active:"$5"MB Swpd:"$3"…...

深度解析C++开源OCR引擎:架构、编译优化与工业级部署指南

1. 引言:OCR技术演进与现状分析 光学字符识别(OCR)技术经历了从传统模式识别到深度学习的三代发展: 第一代:基于模板匹配(1970s-1990s) 第二代:特征提取+分类器(1990s-2010s) 第三代:端到端深度学习(2010s-至今) 当前工业界主流方案呈现"双轨制"发展态势…...

关于Newtonsoft.Json

历史 Newtonsoft.Json&#xff08;也称为 Json.NET&#xff09;是由 James Newton - King 开发的一个开源的 JSON 处理库&#xff0c;它于 2007 年首次发布。在早期&#xff0c;.NET 平台缺乏一个强大且灵活的 JSON 处理工具&#xff0c;Newtonsoft.Json 应运而生&#xff0c;…...

Spark-Sql编程(三)

一、数据加载与保存 通用方式&#xff1a;使用spark.read.load和df.write.save&#xff0c;通过format指定数据格式&#xff08;如csv、jdbc、json等&#xff09;&#xff0c;option设置特定参数&#xff08;jdbc格式下的url、user等&#xff09;&#xff0c;load和save指定路…...

CTF--好像需要管理员

一、原网页&#xff1a; 二、步骤&#xff1a; 1.扫描&#xff1a; 发现&#xff1a;robots.txt 2.打开robots.txt&#xff1a; 3.打开resul.php&#xff1a; 4.代码解析&#xff1a; if ($_GET[x]$password) //检查通过 URL 参数 x 传递的值是否等于变量 $password 的值 详…...

耀圣控制设备有限公司总经理李雨蔓的创业之路

破浪者李雨蔓&#xff1a;从零到行业标杆的铿锵之路 在浙江永嘉这片被誉为“中国泵阀之乡”的热土上&#xff0c;一位86年出生的女性企业家&#xff0c;用十年光阴书写了一段白手起家的传奇。她&#xff0c;是一曲关于勇气、智慧与匠心的赞歌。从技术员到行业标杆的缔造者&…...

Spring Boot JPA 开发之Not an entity血案

项目状况介绍 项目环境 JDK 21Spring Boot 3.4.3Hibernate: 6.6.13.Final项目描述 因为是微服务架构,项目层级如下 project-parent project-com project-A … project-X 其中: project-parent定义依赖库的版本project-com 定义了一些公用的方法和配置,包括持久层的配置。…...

什么是车规级MCU?STM32也能上车规级场景?

一、车规级MCU的定义 车规级MCU&#xff08;Microcontroller Unit&#xff09;是专为汽车电子系统设计的微控制器芯片&#xff0c;集成CPU、存储器、外设接口等功能模块&#xff0c;用于实现车辆控制、数据处理和实时响应。其核心特点包括&#xff1a; 高可靠性&#xff1a;需在…...

vue3.2 + element-plus 实现跟随input输入框的弹框,弹框里可以分组或tab形式显示选项

效果 基础用法&#xff08;分组选项&#xff09; 高级用法&#xff08;带Tab栏&#xff09; <!-- 弹窗跟随通用组件 SmartSelector.vue --> <template><div class"smart-selector-container"><el-popover :visible"visible" :w…...

go 指针接收者和值接收者的区别

go 指针接收者和值接收者的区别 指针接收者和值接收者的区别主要有两点&#xff1a; Go 中函数传参是传值&#xff0c;因此指针接收者传递的是接收者的指针拷贝&#xff0c;值接收者传递的是接收者的拷贝---在方法中指针接收者的变量会被修改&#xff0c;而值接收者的成员变量…...

部署qwen2.5-VL-7B

简单串行执行 from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor from qwen_vl_utils import process_vision_info import torch, time, threadingdef llm(model_path,promptNone,imageNone,videoNone,imagesNone,videosNone,max_new_tokens2048,t…...

Go:测试

go test 工具 go test是 Go 语言包的测试驱动程序 &#xff0c;包依据特定约定组织 。包目录中以_test.go结尾的文件是go test编译对象&#xff0c;而非go build的编译目标 。 特殊测试函数 在*_test.go文件中有三种特殊函数 &#xff1a; 功能测试函数&#xff1a;以Test为…...

用微信小程序制作一个性行为同意协议系统

用微信小程序制作一个性行为同意协议系统 用微信小程序制作一个性行为同意协议系统&#xff0c;具备查询、修改、增加和演示的Web功能。首先&#xff0c;我需要明确这个系统的核心功能和法律合规性。性同意是一个敏感且法律相关的话题&#xff0c;必须确保系统的设计符合法律法…...

leetcode 122. Best Time to Buy and Sell Stock II

题目描述 这道题可以用贪心思想解决。 本文介绍用动态规划解决。本题分析方法与第121题一样&#xff0c;详见leetcode 121. Best Time to Buy and Sell Stock 只有一点区别。第121题全程只能买入1次&#xff0c;因此如果第i天买入股票&#xff0c;买之前的金额肯定是初始金额…...

FairyGUI图标文字合批失败的原因

1&#xff09;FairyGUI图标文字合批失败的原因 2&#xff09;为什么Cubemap的内存占用超高 3&#xff09;如何找到网格某个切面的中心点 4&#xff09;为什么SafeZone在倒屏后方向相反 这是第428篇UWA技术知识分享的推送&#xff0c;精选了UWA社区的热门话题&#xff0c;涵盖了…...

C/C++ 通用代码模板

✅ C 语言代码模板&#xff08;main.c&#xff09; 适用于基础项目、算法竞赛或刷题&#xff1a; #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <math.h>// 宏定义区 #define MAX_N 1000 #defi…...