【Linux内核系列】:深入解析输出以及输入重定向
🔥 本文专栏:Linux
🌸作者主页:努力努力再努力wz



★★★ 本文前置知识:
文件系统以及文件系统调用接口
用c语言简单实现一个shell外壳程序
内容回顾
那么在此前的学习中,我们对于Linux的文件系统已经有了一个基本的概念,那么我们先来来做一个简单的回顾,那么Linux上的一切事物都可以视作文件,那么我们对于Linux上各种文件可以按照状态将其分为两类分别是打开的文件和未打开的文件,那么对于打开的文件,那么它的元数据会被加载到内存中,我们操作系统会为其定义一个file的结构体来记录其各种属性,那么管理这些文件就是管理这些文件对应的file结构体所组织而成的双链表的数据结构,那么我们要打开一个文件就得通过相应的代码来打开,那么意味着我们打开文件只能通过我们的进程来打开,那么打开文件的对象必定就是进程,而单个进程与文件之间的关系是一对多的,那么必然我们进程对应的task_struct结构体要维护一个指针数组,也就是文件描述表,其中该数组的每一个元素都指向一个打开的文件,而其中会默认打开三个标准输入输出文件,也就是stdin和stdout和stderr,那么分别对应指针数组下标0,1,2这三个位置,那么该数组的下标就是我们的文件描述符,那么我们可以用我们open系统调用接口来打开一个文件,那么open系统接口会为奇创建一个file结构体,并且在指针数组中分配一个位置的指针来指向该file结构体,并且返回该位置的数组下标也就是文件描述符来为后续比如write以及read系统接口来使用,那么这就是我们上一篇文章主要讲述的内容,如果对着部分内容还是感到有点陌生的话,那么可以去看我们的上一篇文章
引入
那么在此前我们学习Linux指令的时候,我们知道会有这样一条指令:
echo "hello Linux" > log.txt
echo "hello Linux" >>log.txt
而我们的echo指令本身是获取之后的字符串“hello Linux",然后将该字符串打印在我们的终端也就是显示器上,但是添加了之后的大于符号后,那么它会将“hello Linux”这个字符串给写入到log.txt文件中,而这就是所谓的输出重定向的现象,而如果后面是两个大于符号,那么则是在之前文件末尾继续写入,那么也就是追加重定向。
< log.txt
而小于符号则是本应该从键盘文件中读取内容,替换为读取小于符号后面的目标文件的文件内容,那么这就是输入重定向,那么输入重定向我们相对比较陌生,而输出以及追加重定向我们则比较熟悉,那么在引入了文件系统以及相关的系统调用接口之后,那么输入以及输出重定向的原理本质上就是对文件进行操作,那么我们就可以根据输入以及输出重定向的原理,结合系统调用接口来自己实现输入以及输出重定向,并且完善我们之前的shell外壳程序
输出以及输入重定向的原理以及实现
那么我们知道进程对应的task_struct结构体内部会有一个指针指向一个指针数组,那么该指针数组的每一个元素则指向该进程已经打开的文件的结构体,那么我们知道像我们的echo和pwd以及ls指令,那么这些指令在执行过后,都会在显示器终端打印信息,那么它们既然能够在显示器打印相应的内容,那么必然他们是要将它们要打印的内容给写入到显示器文件中,而我们知道我们进程会默认打开三个标准输入输出文件,那么其中下标为1的位置就是我们的显示器文件,那么他们必然就是往文件描述符下标为1的文件中做写入,而所谓的输出重定向就是原本我们要将显示器文件中写入的内容给替换写入到另一个目标文件中,所以当我们运行ehco “hello Linux” > log.txt时,我们发现我们终端不会打印“hello Linux" 字符串,而是将该字符串写入到了目标文件log.txt当中,那么这就是我们的输出重定向
那么要做到输出重定向的话,那么这里我们就得引入一个关键的系统调用接口,那么就是dup系统调用接口
dup- 头文件:<unistd.h>
- 函数原型:
int dup(int fd)
- 原理:
那么dup接口会接受一个文件描述符fd,那么该文件描述符fd所指向的file结构体就是要复制的对象,那么接下来dup接口会从前往后线性的扫描整个指针数组,直到找到一个空的位置也就是没有指向任何结构体的指针的位置,然后将该位置的指针复制之前文件描述符fd所指向的结构体,并返回该数组下标,而如果分配复制失败则返回-1,而由于这个接口由于要线性扫描整个指针数组,所以一般不推荐使用dup这个接口
dup2- 头文件:<unistd.h>
- 函数原型:
int dup2(int oldfd, int newfd);
- 原理:
那么dup2会接受两个参数分别是oldfd以及newfd,那么我们的oldfd就会是被复制的对象,而我们的newfd则是被替换的对象,那么dup2首先会先close掉我们的指针数组newfd下标所对应的位置,那么其中就会让其指向的结构体的引用计数减一,如果该结构体引用计数为0,那么操作系统则会回收清理该文件对应的结构体以及在内存中的元数据,那么引用计数不为0,那么则是只是将位置的指针给置空,那么close完指针数组中的newfd位置后,那么下一步就是将oldfd指针所指向的结构体给拷贝到newfd,那么此时newfd和oldfd就指向了同一个file结构体,该接口调用成功的返回值就是newfd,失败则返回-1
所以我们输出重定向就是调用该dup2接口,那么我们假设输出重定向的目标文件是log.txt,那么我们首先就是先调用open接口打开我们的log.txt,然后获得了log.txt的文件描述符,那么接着我们在调用dup2,那么将我们原本要向显示器文件也就是文件描述符为1的位置中写入的内容给写入到log.txt中,所以接下来我们就调用dup2接口,那么oldfd就是目标文件log.txt的文件描述符,而newfd则是显示器文件的文件描述符也就是1,那么这样就是实现了所谓的输出重定向,而我们知道我们open的时候可以指定我们打开该文件要进行的行为或者说模式,也就是通过第二个参数来指定,那么如果是输出重定向,那么我们每次输出都是会清空之前的文本内容,从文本开始处写入,所以我们需要或上O_TRUNC的宏定义,而追加重定向则是从之前的文本末尾处接着写入,所以追加重定向打开目标文件的open的第二个参数不能或上O_TRUNC,而是或上O_APPPEND,那么这就是我们输出以及追加重定向的原理实现
而输入重定向的话则是我们打开的模式要设置为只读打开,第二个参数要或上O_RDONLY,那么我们用open打开我们的目标文件假设为log.txt,然后得到其描述符,那么这里我们接着调用dup2替换的文件也就是键盘文件,那么对应的文件描述符就是0,所以第二个参数newfd就是0,那么这样就实现了我们的输入重定向
完善shell外壳程序
那么之前我们的shell外壳程序只是简单实现了获取用户输入的指令,那么判断指令是内置指令还是外部命令,如果是内置指令的话就交给父进程也就是shell外壳程序来执行,而如果是外部指令的话,那么则交给子进程来执行,那么子进程执行流中就会被进程替换为要执行的指令的进程的上下文,而父进程的执行流则是等待子进程的退出,获取子进程的退出码,那么这就是之前我们shell外壳程序所实现的功能
那么在此基础上,我们shell外壳程序无法进行输出以及输入重定向,那么在本文学习了输出以及输入重定向之后,我们就可以完善我们的shell外壳程序这部分功能
1.获取重定向内容
那么我们知道我们的shell外壳程序的第一个环节就是获取用户输入的指令,本质上就是获取用户输入的字符串然后将其保存在临时字符数组temp中,而用户输入指令时会手动用空格分割隔开指令部分和各个参数部分,所以下一个环节就是解析指令部分与参数部分,利用strtok函数利用空格作为分隔符将其分割的各个参数部分的字符串保存在argv字符指针数组的各个位置中,那么在解析的这一步骤中,我们用户可能有输入或者输出重定向的情况出现,那么在解析完字符串的各个参数部分后,那么我们就得从后往前线性扫描每一个元素所指向的字符串中是否出现重定向的符号也就是"<",">",">>",我们可以定义一个int类型的全局变量check_redir来追踪重定向的情况,如果出现了重定向,那么该变量则不为0,那么如果是输出重定向我们则设置为1,追加重定向设置为2,输入重定向则设置为3,然后再定义一个全局属性字符指针,因为如果出现重定向之后,那么该重定向符号的下一个位置就一定还是目标文件的文件名,所以我们得用指针来保存
实现细节:
细节1:.由于这里我们的重定向的符号以及目标文件名并不是有用的指令部分以及参数部分,所以我们接得保存完该文件名之后,就得将其分别设置为NULL,因为在进程替换的时候,会扫描我们该字符指针数组argv,直到遇到NULL结束,而重定向符号如“>”以及文件名如log.txt不是有命令行参数,所以得设置为空
细节2:这里由于我们字符指针数组argv的最后一个元素是NULL,而我们每一个位置都要调用strcmp函数来匹配,所以这里匹配的开始一定是从字符指针数组的倒数第二个位置开始匹配
细节3:这里由于我们的重定向符号以及之后的文件名不是命令行参数,而我们之前解析命令行参数argc是将其计入了的,所以我们就得重新设置我们的返回的命令行参数的个数,这是一个细节
细节4:我们这里定义了两个全局属性的变量分别追踪重定向的情况以及保存文件名,所以在每一次外部的while循环之后,我们都得将其给重新设置
代码实现:
int getString(char temp[],char* argv[])
{int len=strlen(temp);if(len>0&&temp[len-1]=='\n'){temp[len-1]='\0';len--;}int argc=0;char* toke=strtok(temp," ");while(toke!=NULL&&argc<length-1){argv[argc++]=toke;toke=strtok(NULL," ");}argv[argc]=NULL;for(int i=argc-1;i>=0;i--){if(strcmp(argv[i],">")==0){check_redir=1;filename=argv[i+1];argc=i;argv[i]=NULL;argv[i+1]=NULL;break;}if(strcmp(argv[i],">>")==0){check_redir=2;filename=argv[i+1];argc=i;argv[i]=NULL;argv[i+1]=NULL;break;}if(strcmp(argv[i],"<")==0){check_redir=3;filename=argv[i+1];argc=i;argv[i]=NULL;argv[i+1]=NULL;break;}}return argc;
}
2.内置命令的重定向
那么这里我们注意对于内置命令来说,那么由于我们对于一些要往显示器写入的内置命令要做重定向,所以这里我们注意由于内置命令的执行是在父进程中执行的,那么我们关闭比如显示器文件的话就一定会影响父进程,意味着之后我们还得再重新打开,因为下一个命令的执行有可能需要向显示器文件写入,所以这里我个人在实现的时候,我是的思路首先是先open打开目标文件,得到该文件的文件描述符,然后我直接将该指令要向显示器文件中写入的内容用一个数组来保存,然后利用write函数将数组的内容写入该文件,这样就避免了关闭显示器文件,这里由于我自己在实现shell外壳程序的时候,只有cd和pwd两个内置指令,所以这里就只实现了pwd内置命令的输出重定向
代码实现:
void ordercomplete(int argc,char* argv[])
{if(strcmp(argv[0],"cd")==0){if(argc==2){if(chdir(argv[1])==0){char cwd[1024];getcwd(cwd,sizeof(cwd));setenv("pwd",cwd,1);}else{perror("chdir");}}else{printf("error: expected argument for 'cd'\n");}}if(strcmp(argv[0],"pwd")==0){char cwd[length]; // 定义一个足够大的缓冲区来存储路径if (getcwd(cwd, sizeof(cwd)) != NULL) {if(check_redir!=0){int fd;int flag=O_CREAT;if(check_redir==1){flag|=O_WRONLY|O_TRUNC;}if(check_redir==2){flag|=O_APPEND|O_WRONLY;}fd=open(filename,flag,0666);if(fd<0){perror("open");return;}int m=write(fd,cwd,strlen(cwd));if(m<0){perror("write");close(fd);return;}close(fd);}else{printf("Current working directory: %s\n", cwd);}
}else {perror("getcwd failed"); // 输出错误信息}
}
3.外部命令的重定向
而对于外部命令的重定向,那么我们知道外部命令的执行则是创建一个子进程来执行,而我们知道创建一个子进程的本质就是拷贝父进程的task_struct结构体得到子进程的一份task_struct结构体以及父子进程共享同一个物理内存页,而其中拷贝父进程的task_struct结构体意味着就会拷贝一份父进程所对应的文件描述表,所以父子进程各自有一份独立的文件描述表,那么这里我们对于子进程的重定向,那么就无需关心关闭显示器文件所带来的影响,因为子进程对应的文件描述表中的显示器文件也就是下标为1的位置被关闭了,那么它不会影响父进程的文件描述表,所以我们可以在子进程中直接调用我们的dup2系统接口,至于进程替换,由于进程替换只会影响进程的地址空间以及页表的映射,不会影响文件描述符表,所以我们就得在进程替换之前,调用dup2系统接口来完成我们外部命令的重定向,而其中的实现就是我们上文介绍输出以及输入重定向的原理实现
代码实现:
int id=fork();if(id==0){if(check_redir!=0){int fd;int flag=O_CREAT;int exchange;if(check_redir==1){flag|=O_WRONLY|O_TRUNC;exchange=1;}if(check_redir==2){flag|=O_WRONLY|O_APPEND;exchange=1;}if(check_redir==3){flag|=O_RDONLY;exchange=0;}fd=open(filename,flag,0664);if(fd<0){perror("open");exit(1);}int m=dup2(fd,exchange);if(m<0){perror("dup2");close(fd);exit(2);}close(fd);}execvp(argv[0],argv);perror("execvp");exit(EXIT_FAIL);}
4.完整实现
完整代码:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdbool.h>
#define length 1000
#define EXIT_FAIL 40
const char* order[]={"cd","pwd",NULL};
int check_redir=0;
char* filename;
int getString(char temp[],char* argv[])
{int len=strlen(temp);if(len>0&&temp[len-1]=='\n'){temp[len-1]='\0';len--;}int argc=0;char* toke=strtok(temp," ");while(toke!=NULL&&argc<length-1){argv[argc++]=toke;toke=strtok(NULL," ");}argv[argc]=NULL;for(int i=argc-1;i>=0;i--){if(strcmp(argv[i],">")==0){check_redir=1;filename=argv[i+1];argc=i;argv[i]=NULL;argv[i+1]=NULL;break;}if(strcmp(argv[i],">>")==0){check_redir=2;filename=argv[i+1];argc=i;argv[i]=NULL;argv[i+1]=NULL;break;}if(strcmp(argv[i],"<")==0){check_redir=3;filename=argv[i+1];argc=i;argv[i]=NULL;argv[i+1]=NULL;break;}}return argc;
}
bool check(char* argv[])
{for(int i=0;order[i]!=NULL;i++){if(strcmp(argv[0],order[i])==0){return true;}}return false;
}
void ordercomplete(int argc,char* argv[])
{if(strcmp(argv[0],"cd")==0){if(argc==2){if(chdir(argv[1])==0){char cwd[1024];getcwd(cwd,sizeof(cwd));setenv("pwd",cwd,1);}else{perror("chdir");}}else{printf("error: expected argument for 'cd'\n");}}if(strcmp(argv[0],"pwd")==0){char cwd[length]; // 定义一个足够大的缓冲区来存储路径if (getcwd(cwd, sizeof(cwd)) != NULL) {if(check_redir!=0){int fd;int flag=O_CREAT;if(check_redir==1){flag|=O_WRONLY|O_TRUNC;}if(check_redir==2){flag|=O_APPEND|O_WRONLY;}fd=open(filename,flag,0666);if(fd<0){perror("open");return;}int m=write(fd,cwd,strlen(cwd));if(m<0){perror("write");close(fd);return;}close(fd);}else{printf("Current working directory: %s\n", cwd);}
}else {perror("getcwd failed"); // 输出错误信息}
}}
int main()
{int argc;char* argv[length];char temp[length];while(1){printf("[%s@%s %s]$",getenv("USER"),getenv("HOSTNAME"),getenv("PWD"));check_redir=0;filename=NULL;if(fgets(temp,sizeof(temp),stdin)==NULL){perror("fgets");continue;}argc=getString(temp,argv);if(argc==0){continue;}if(check(argv)){ordercomplete(argc,argv);continue;}int id=fork();if(id==0){if(check_redir!=0){int fd;int flag=O_CREAT;int exchange;if(check_redir==1){flag|=O_WRONLY|O_TRUNC;exchange=1;}if(check_redir==2){flag|=O_WRONLY|O_APPEND;exchange=1;}if(check_redir==3){flag|=O_RDONLY;exchange=0;}fd=open(filename,flag,0664);if(fd<0){perror("open");exit(1);}int m=dup2(fd,exchange);if(m<0){perror("dup2");close(fd);exit(2);}close(fd);}execvp(argv[0],argv);perror("execvp");exit(EXIT_FAIL);}else{int status;int m=waitpid(id,&status,0);if(m<0){perror("waitpid");}else{if(WIFEXITED(status)){if(WEXITSTATUS(status)==40){printf("子进程替换失败\n");}if(WEXITSTATUS(status)==1){printf("open调用失败\n");}if(WEXITSTATUS(status)==2){printf("dup2系统调用失败\n");}}}}}return 0;
}
Linux上的运行截图:

结语
那么本篇文章就详细解析了我们Linux下的输入以及输出重定向,那么先介绍了输入以及输出重定向的原理以及实现,然后再完善了我们的shell外壳程序,那么下一篇文章我将会解析缓冲区的内容
我会持续更新,希望你能多多关照,那么如果本文对你有所帮组的话,还请多多三连加关注哦,你的支持就是我创作的最大的动力!

相关文章:
【Linux内核系列】:深入解析输出以及输入重定向
🔥 本文专栏:Linux 🌸作者主页:努力努力再努力wz ★★★ 本文前置知识: 文件系统以及文件系统调用接口 用c语言简单实现一个shell外壳程序 内容回顾 那么在此前的学习中,我们对于Linux的文件系统已经有了…...
【linux网络编程】端口
一、端口(Port)概述 在计算机网络中,端口(Port) 是用来标识不同进程或服务的逻辑通信端点。它类似于一座大楼的房间号,帮助操作系统和网络协议区分不同的应用程序,以便正确地传输数据。 1. 端口…...
PyTorch系列教程:Tensor.view() 方法详解
这篇简明扼要的文章是关于PyTorch中的tensor.view()方法的介绍与应用,与reshape()方法的区别,同时给出示例进行详细解释。 Tensor基础 Tensor(张量)的视图是一个新的Tensor,它与原始Tensor共享相同的底层数据,但具有不同的形状或…...
软件测试的基础入门(二)
文章目录 一、软件(开发)的生命周期什么是生命周期软件(开发)的生命周期需求分析计划设计编码测试运行维护 二、常见的开发模型瀑布模型流程优点缺点适应的场景 螺旋模型流程优点缺点适应的场景 增量模型和迭代模型流程适应的场景…...
Springboot + minio
参考: SpringBoot整合Minio_springboot minio-CSDN博客 <!--minio 依赖--><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.11</version></dependency> applicaio…...
地下变电站如何实现安全智能运营-以110kV站为例看环境监测与设备联控
1、地下变电站简介 在经济发达的地区,由于城市中心土地资源紧张、征地拆迁费用昂贵,因此采用地下变电站来解决这些问题不失为一个好的途径和思路。地下变电站一般采用室内全封闭式组合电气设备,220&#x…...
windows无界面后台定时任务 (重启自启动,ODBS为例)
一、前言 mdb(Microsoft Database)是Microsoft Access中使用的一种数据存储格式,可以通过ODBC驱动程序进行访问和操作,在Python中也可以安装相应模块打开。 这是我在项目中更新bs数据的一个实践记录,结合windows定时一起记录一下,方便以后照搬~ 二、安装 Python安装库…...
FPGA 实验报告:四位全加器与三八译码器仿真实现
目录 安装Quartus软件 四位全加器 全加器、半加器 半加器: 全加器: 四位全加器电路图 创建项目 半加器 全加器 四位全加器 代码实现 半加器 全加器 四位全加器 三八译码器 创建项目 代码展示 modelsim仿真波形图 四位全加器 三八译码…...
win11 Visual Studio 17 2022源码编译 opencv4.11.0 + cuda12.6.3 启用GPU加速
win11 Visual Studio 17 2022 源码编译 opencv4.11.0 cuda12.6.3 启用GPU加速 配置: 生成 opencv 生成 opencv-python 1 下载源码和安装软件 win11 x64 系统 安装Visual Studio 17 2022 下载opencv4.11.0 源码 https://github.com/opencv/opencv/releases/tag/4.11.0 下载…...
Ribbon实现原理
文章目录 概要什么是Ribbon客户端负载均衡 RestTemplate核心方法GET 请求getForEntitygetForObject POST 请求postForEntitypostForObjectpostForLocation PUT请求DELETE请求 源码分析类图关系 与Eureka结合重试机制 概要 什么是Ribbon Spring Cloud Ribbon是一个基于HTTP和T…...
MuMu-LLaMA:通过大型语言模型进行多模态音乐理解和生成(Python代码实现+论文)
MuMu-LLaMA 模型是一种音乐理解和生成模型,能够进行音乐问答以及从文本、图像、视频和音频生成音乐,以及音乐编辑。该模型利用了用于音乐理解的 MERT、用于图像理解的 ViT 和用于视频理解的 ViViT 等编码器,以及作为音乐生成模型(…...
高效Android MQTT封装工具:简化物联网开发,提升性能与稳定性
在Android开发中,封装MQTT工具可以帮助简化与MQTT服务器的通信。MQTT(Message Queuing Telemetry Transport)是一种轻量级的发布/订阅消息传输协议,常用于物联网(IoT)设备之间的通信。 以下是一个简单的MQ…...
数据库原理7
1.“数据库系统运行与维护工具”的研究属于数据库管理系统软件 2.1970年IBM公司的高级研究员E.F.Codd提出了关系数据模型 3.每个属性的属性值是不可分解的,即关系的每个分量必须是一个不可分的数据项。属性值的取值应满足域完整性约束。 4.视图作用:简…...
2025最新比较使用的ai工具都有哪些,分别主要用于哪些方面?
文章目录 一、AI对话与交互工具二、AI写作与内容生成工具三、AI绘画与设计工具四、AI视频生成工具五、办公与效率工具六、其他实用工具选择建议 根据2025年最新行业动态和用户反馈,以下AI工具在多个领域表现突出,覆盖对话、写作、设计、视频生成等场景&a…...
什么是 MyBatis? 它的优点和缺点是什么?
一、 什么是 MyBatis? 定义: MyBatis 是一款优秀的持久层框架,用于简化 Java 应用程序与数据库之间的交互。MyBatis 通过 XML 或注解 的方式,将 SQL 语句与 Java 代码分离,提供了一种灵活的、易于维护的数据访问解决方…...
在ArcMap中通过Python编写自定义工具(Python Toolbox)实现点转线工具
文章目录 一、需求二、实现过程2.1、创建Python工具箱(.pyt)2.2、使用catalog测试代码2.3、在ArcMap中使用工具 三、测试 一、需求 通过插件的形式将点转线功能嵌入ArcMap界面,如何从零开始创建一个插件,包括按钮的添加、工具的实…...
Array and string offset access syntax with curly braces is deprecated
警告信息 “Array and string offset access syntax with curly braces is deprecated” 是 PHP 中的一个弃用警告(Deprecation Notice),表明在 PHP 中使用花括号 {} 来访问数组或字符串的偏移量已经被标记为过时。 背景 在 PHP 的早期版本…...
moodle 开源的在线学习管理系统(LMS)部署
一、Moodle 简介 Moodle(Modular Object-Oriented Dynamic Learning Environment)是一个开源的在线学习管理系统(LMS),广泛应用于教育机构和企业培训。其核心功能包括课程管理、作业提交、在线测试、论坛互动和成绩跟…...
后智能体时代的LLM和Agent
文章目录 1. 关于AI重塑的哲学体系2. 关于AI大模型体系的认知3. 关于AI大模型体系的畅想4. 关于人和AI大模型体系的共处5. 写在最后 随着OpenAI、Deepseek、Manus等等智能体的爆火,人们茶前饭后、插科打诨的话题都离不开这些智能体,现状也正如《人民日报…...
Day6 DFS
一、跳台阶 一个楼梯共有 nn 级台阶,每次可以走一级或者两级,问从第 00 级台阶走到第 nn 级台阶一共有多少种方案。 输入格式 共一行,包含一个整数 nn。 输出格式 共一行,包含一个整数,表示方案数。 数据范围 1…...
Releases(发布) 和 版本管理 是两个紧密相关的概念
在软件开发和维护中,Releases(发布) 和 版本管理 是两个紧密相关的概念,特别是在开源项目或企业软件开发中。 1. Releases(发布) Release 是指软件的一个正式发布版本,通常经过开发、测试、修复 Bug,并被认为是足够稳定和可用于生产环境的版本。 主要特点 里程碑:通…...
模型微调——模型性能提升方法及注意事项(自用)
名词补充 人为为训练数据标注的标签称为黄金标准或真实值,这个过程一定程度上保证训练的准确性,但是其人工标注的成本和时间很高,并且标注的标签受人的主观因素影响。 导致模型性能不佳的因素和解决办法 ①不同类别的数据不平衡:统…...
景联文科技:以精准数据标注赋能AI进化,构筑智能时代数据基石
在人工智能技术席卷全球的浪潮中,高质量数据已成为驱动AI模型进化的核心燃料。作为全球领先的AI数据服务解决方案提供商,景联文科技深耕数据标注领域多年,以技术为基、以专业为本,致力于为全球客户提供全场景、高精度、多模态的数…...
嵌入式L6计算机网络
Telnet不加密 socket是应用层和下面的内核...
华为鸿蒙系统全景解读:从内核设计到生态落地的技术革命
华为鸿蒙系统全景解读:从内核设计到生态落地的技术革命 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,可以分享一下给大家。点击跳转到网站。 https://www.captainbed.cn/ccc 文章目录 华为鸿蒙系统全景解读&#x…...
2025最新软件测试面试八股文(含答案+文档)
1、请试着比较一下黑盒测试、白盒测试、单元测试、集成测试、系统测试、验收测试的区别与联系。 参考答案: 黑盒测试:已知产品的功能设计规格,可以进行测试证明每个实现了的功能是否符合要求。 白盒测试:已知产品的内部工作过程…...
微前端框架 Qiankun 的应用及问题分析
一、Qiankun 的核心应用场景与优势 多技术栈共存与灵活集成 Qiankun 支持主应用与子应用使用不同技术栈(如 Vue、React、Angular 等),通过 HTML Entry 方式接入子应用,无需深度改造子应用即可实现集成,降低了技术迁移成…...
八卡5090服务器首发亮相!
AI 人工智能领域热度居高不下。OpenAI 的 GPT - 4 凭强悍语言处理能力,在内容创作、智能客服等领域广泛应用。清华大学团队的 DeepSeek 大模型在深度学习训练优势突出,正促使各行业应用端算力需求向推理主导转变,呈爆发式增长 。 随着 DeepS…...
C#类型转换基本概念
一、基本定义 C# 类型转换是将数据从一种类型转换为另一种类型的过程,分为 隐式转换 和 显式转换 两类。 强类型语言特性:C# 要求变量类型在编译时确定,类型转换需满足兼容性或显式规则。目的:处理不同数据类…...
基于SSM+Vue+uniapp的驾校预约管理小程序+LW示例
系列文章目录 1.基于SSM的洗衣房管理系统原生微信小程序LW参考示例 2.基于SpringBoot的宠物摄影网站管理系统LW参考示例 3.基于SpringBootVue的企业人事管理系统LW参考示例 4.基于SSM的高校实验室管理系统LW参考示例 5.基于SpringBoot的二手数码回收系统原生微信小程序LW参考示…...
