Linux 文件系列:深入理解文件描述符fd,重定向,自定义shell当中重定向的模拟实现

Linux 文件系列:深入理解文件fd,重定向,自定义shell当中重定向的模拟实现

  • 一.预备知识
  • 二.回顾C语言中常见的文件接口跟重定向建立联系
    • 1.fopen函数的介绍
    • 2.fclose函数的介绍
    • 3.代码演示
      • 1.以"w"(写)的方式打开
      • 2.跟输出重定向的联系
      • 3.以 "a"(追加)的方式打开
      • 4.跟追加重定向的联系
  • 三.认识并使用系统接口
    • 1.open
      • 1.open和fopen的联系(引出 FILE和struct file的联系)
    • 2.open的进一步介绍
    • 3.open函数的使用
      • 1.close函数
      • 2.开始使用并且看看这个fd到底是什么?
  • 四.理解文件描述符fd
    • 1.文件描述符fd的本质
    • 2.标准输入,标准输出,标准错误
    • 3.理解Linux下一切皆文件的设计理念
  • 五.理解struct file内核数据结构
  • 六.fd的分配规则
    • 1.先抛出结论
    • 2.代码演示
    • 3.替换标准输出时的现象
  • 七.理解重定向
    • 1.重定向的本质
    • 2.演示一下重定向
      • 1.输出重定向
      • 2.追加重定向
      • 3.输入重定向
        • 1.fread函数
        • 2.演示
  • 八.dup2函数:实现两个fd之间的重定向
    • 1.dup2实现输出重定向
    • 2.dup2实现追加重定向
    • 3.dup2实现输入重定向
  • 九.自定义shell当中重定向的模拟实现
    • 1.原myshell.c代码
    • 2.如何实现重定向
    • 3.定义全局变量
    • 4.检测是否要进行重定向的函数
    • 5.创建子进程进行程序替换的函数修改
    • 6.main函数的修改
    • 7.修改之后myshell.c代码
  • 十.stderr的作用
    • 1.介绍2>&1
    • 2.stderr的作用
    • 3.演示
  • 十一.重定向和程序替换之间是互不影响的

一.预备知识

在这里插入图片描述
经过刚才的分析,我们可以一个很重要的结论:

一个文件要被打开,一定要先在OS中形成被打开的文件对象

下面我们来回顾一下C语言中常见的文件接口
我们会发现重定向跟它们有所联系

二.回顾C语言中常见的文件接口跟重定向建立联系

关于C语言文件操作的详细内容,大家可以看我的这篇博客:
C语言文件操作详解

1.fopen函数的介绍

在这里插入图片描述
在这里插入图片描述

2.fclose函数的介绍

在这里插入图片描述

3.代码演示

1.以"w"(写)的方式打开

在这里插入图片描述

以"w"(写)的方式打开,如果文件不存在,就会在当前进程所在的路径当中创建它

在这里插入图片描述
创建成功
我们用vim写一些内容,再用w打开,看看w是否会清空之前的内容
在这里插入图片描述
在这里插入图片描述
清空成功

2.跟输出重定向的联系

在这里插入图片描述
在这里插入图片描述
我们会发现,fopen的"w"选项跟输出重定向很像啊
下面我们再来看看"a"选项的方式打开跟追加重定向的关系

3.以 “a”(追加)的方式打开

"a"也是写入,不过是从文件结尾处开始写入,是追加式写入,并不会清空文件

在这里插入图片描述
在这里插入图片描述
并没有清空原有内容

4.跟追加重定向的联系

在这里插入图片描述
在这里插入图片描述
我们会发现,fopen的"a"选项跟追加重定向很像啊

三.认识并使用系统接口

下面我们来认识并使用一下系统调用接口
首先我们达成1个共识:

C语言的文件操作接口,它的底层一定封装了系统调用接口

1.open

1.open和fopen的联系(引出 FILE和struct file的联系)

这是C语言提供的库函数:fopen:
在这里插入图片描述
这是系统调用接口:open:
在这里插入图片描述
可见,这个fd跟我们之前常用的FILE*指针很像啊,
其实它们的功能是一样的,C语言的FILE是一个结构体,这个结构体里面封装了fd
而这个fd是被打开的文件的结构体(struct file内核数据结构)中的一个属性,是用来区分不同文件的

2.open的进一步介绍

刚才我们还没有介绍第2个参数呢,下面我们来看一下
在这里插入图片描述
至此我们也理解了fopen是如何对open进行封装的
下面我们来使用一下open函数并且看看这个fd到底是啥啊?

3.open函数的使用

1.close函数

在这里插入图片描述

2.开始使用并且看看这个fd到底是什么?

在这里插入图片描述
在这里插入图片描述
现在我们有了两个问题:

  1. 0 1 2去哪了?
  2. 为什么会是 3 4 5 6?

下面就让我们借助这两个问题来深入理解一下文件描述符fd

四.理解文件描述符fd

1.文件描述符fd的本质

在这里插入图片描述

2.标准输入,标准输出,标准错误

在C语言的学习中我们都听说过

C语言程序(也就是进程),只要运行起来,默认就打开3个流

在这里插入图片描述
今天我们要说明的是:
在这里插入图片描述

3.理解Linux下一切皆文件的设计理念

在这里插入图片描述

五.理解struct file内核数据结构

在这里插入图片描述

六.fd的分配规则

1.先抛出结论

在这里插入图片描述

2.代码演示

分配规则1就不言而喻了,我们来验证分配规则2
在这里插入图片描述
我们先关闭stdin,然后在打开log.txt
如果该进程中log.txt被分配的fd是0,那么验证成功
在这里插入图片描述
验证成功

3.替换标准输出时的现象

下面我们先关闭stdout,然后再打开log.txt
在这里插入图片描述
在这里插入图片描述
为什么最后的

printf("log.txt的fd是: %d\n",fd);

没有成功打印呢?

因为stdout是标准输出流,是显示器对应的流,
我们平常printf是将字符串打印到stdout当中,但是我们在printf之前已经把stdout关掉了
所以不会打印到显示器

可是当我加了一行代码
在这里插入图片描述
cat log.txt之后
在这里插入图片描述
发现刚才printf中本来要往显示器上打印的数据现在写到了log.txt里面
这说明:

1.printf只认识stdout,也就是fd为1的文件2.上层的fd并没有改变,但是底层fd指向的内容发生改变了
本来fd值为1的这个fd应该要指向显示器这个设备文件的
但是在这个进程当中  现在指向log.txt了3.也就是说这个过程其实就是进行了一种类似于狸猫换太子式的指向的改变

七.理解重定向

1.重定向的本质

经由刚才的print的例子之后,我们可以发现:
由此可以得出重定向的本质:
在这里插入图片描述

重定向的本质,其实就是修改特定文件fd的指向

2.演示一下重定向

1.输出重定向

这是log.txt之前的数据
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
输出重定向成功

2.追加重定向

在这里插入图片描述
在这里插入图片描述
追加重定向成功

3.输入重定向

要进行输入重定向,我们要使用fread函数

1.fread函数

在这里插入图片描述

2.演示

在这里插入图片描述
在这里插入图片描述
输入重定向成功

八.dup2函数:实现两个fd之间的重定向

其实库里面给我们提供了一个函数dup2
可以实现两个fd之间的重定向
下面我们使用dup2函数再来演示一下重定向
在这里插入图片描述

1.dup2实现输出重定向

在这里插入图片描述
在这里插入图片描述
此时log.txt的fd是3

2.dup2实现追加重定向

在这里插入图片描述
在这里插入图片描述
实现成功

3.dup2实现输入重定向

在这里插入图片描述
在这里插入图片描述
实现成功

九.自定义shell当中重定向的模拟实现

经过上面的练习之后,下面我们修改一下我们的myshell.c代码,模拟实现一下重定向
关于myshell.c代码的实现,大家可以看我的博客当中的
Linux自定义shell的编写,里面实现了自定义shell

1.原myshell.c代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
//#define DEBUG 1
#define SEP " "char cwd[1024]={'\0'};
int lastcode=0;//上一次进程退出时的退出码char env[1024][1024]={'\0'};
int my_index=0;const char* getUsername()
{const char* username=getenv("USER");if(username==NULL) return "none";return username;
}const char* getHostname()
{const char* hostname=getenv("HOSTNAME");if(hostname==NULL) return "none";return hostname;
}const char* getPwd()
{const char* pwd=getenv("PWD");if(pwd==NULL) return "none";return pwd;
}//分割字符串填入usercommand数组当中
//例如: "ls -a -l" 分割为"ls" "-a" "-l"
void CommandSplit(char* usercommand[],char* command)
{int i=0;usercommand[i++]=strtok(command,SEP);while(usercommand[i++]=strtok(NULL,SEP));
}//解析命令行
void GetCommand(char* command,char* usercommand[])
{command[strlen(command)-1]='\0';//清理掉最后的'\0'CommandSplit(usercommand,command);
#ifdef DEBUGint i=0;while(usercommand[i]!=NULL){printf("%d : %s\n",i,usercommand[i]);i++;}
#endif
}//创建子进程,完成任务
void Execute(char* usercommand[])
{pid_t id=fork();if(id==0){//子进程执行部分execvp(usercommand[0],usercommand);//如果子进程程序替换失败,已退出码为1的状态返回exit(1);}else{//父进程执行部分int status=0;//阻塞等待pid_t rid=waitpid(id,&status,0);if(rid>0){lastcode=WEXITSTATUS(status);}}
}void cd(char* usercommand[])
{chdir(usercommand[1]);char tmp[1024]={'\0'};getcwd(tmp,sizeof(tmp));sprintf(cwd,"PWD=%s",tmp);putenv(cwd);lastcode=0;
}   int echo(char* usercommand[])
{//1.echo后面什么都没有,相当于'\n'if(usercommand[1]==NULL){printf("\n");lastcode=0;return 1;}//2.echo $?  echo $PWD echo $char* cmd=usercommand[1];int len=strlen(cmd);if(cmd[0]=='$' && len>1){//echo $?if(cmd[1]=='?'){printf("%d\n",lastcode);lastcode=0;}//echo $PWDelse{char* tmp=cmd+1;const char* env=getenv(tmp);//找不到该环境变量,打印'\n',退出码依旧为0if(env==NULL){printf("\n");}else{printf("%s\n",env);}lastcode=0;}}else{printf("%s\n",cmd);}return 1;
}void export(char* usercommand[])
{//exportif(usercommand[1]==NULL){lastcode=0;return;}strcpy(env[my_index],usercommand[1]);putenv(env[my_index]);my_index++;
}int doBuildIn(char* usercommand[])
{//cdif(strcmp(usercommand[0],"cd")==0){if(usercommand[1]==NULL) return -1;cd(usercommand);return 1;}//echoelse if(strcmp(usercommand[0],"echo")==0){return echo(usercommand);}//exportelse if(strcmp(usercommand[0],"export")==0){export(usercommand);}return 0;
}int main()
{while(1){//1.打印提示符信息并获取用户的指令printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());char command[1024]={'\0'};fgets(command,sizeof(command),stdin);char* usercommand[1024]={NULL};//2.解析command字符串,放入usercommand指针数组当中GetCommand(command,usercommand);//3.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0int flag=doBuildIn(usercommand);//返回值!=0说明是内建命令,无需执行第4步if(flag!=0) continue;//4.创建子进程,交由子进程完成任务Execute(usercommand);}return 0;
}

2.如何实现重定向

以输出重定向为例:

指令 > log.txt

输出重定向的作用其实就是把本来应该往显示器上打印的内容打印到了log.txt上
也就是说进行输出重定向的话,我们的log.txt就替代了显示器的位置
也就是说执行指令之前我们只需要执行一个
dup2(fd,1)即可
fd是log.txt的文件描述符,1是显示器的文件描述符

也就是说对于用户输入的一个完整的指令
例如:

ls -a -l > log.txt

在这里插入图片描述
我们要做的是:
1.检测是否需要进行重定向(检测指令当中是否有> 或者 >> 或者<)
2.如果需要,把这个指令拆分为两部分
“ls -a -l"和"log.txt”
后半部分是重定向到哪个文件当中
前半部分是真正的指令
如何拆分呢?把>改为’\0’,>>改为’\0’>,<改为’\0’即可

注意:

ls -a -l >                        log.txt

这样写也是可以的,因此我们要取出log.txt的时候要跳过空格

3.定义全局变量

第一步:
我们定义全局变量redir和四个宏常量,文件名和跳过空格的宏
在这里插入图片描述
在这里插入图片描述
注意:
在解析命令行之前就要检测是否要进行重定向
因为如果要进行重定向,就会对命令行进行拆分,拆分之后的指令才是真正要执行的指令

在后续执行指令时只需要根据全局变量redir是否是NoneRedir来判断是否要进行重定向
如果要进行重定向,根据redir具体的值来判断要进行输出/追加/输入重定向
进而判断filename的打开方式和dup2要覆盖显示器还是键盘

然后分类打开和覆盖即可

4.检测是否要进行重定向的函数

//跳过空格的宏
#define SKIP_SPACE(pos) do{ while(isspace(*pos)) pos++; }while(0)//检测是否要进行重定向
void CheckRedir(char* command)
{int len=strlen(command);char* start=command,*end=command+len-1;while(end>=start){//输入重定向//cat < log.txtif(*end=='<'){*end='\0';filename=end+1;SKIP_SPACE(filename);redir=InputRedir;break;}else if(*end=='>'){//追加重定向//ls -a -l >> log.txtif(end>start && *(end-1)=='>'){*(end-1)='\0';filename=end+1;SKIP_SPACE(filename);redir=AppendRedir;break;}//输出重定向else{*end='\0';filename=end+1;SKIP_SPACE(filename);redir=OutPutRedir;break;}}else{end--;}}
}

在这里插入图片描述
在这里我们就只演示非内建命令的重定向操作了
因为只演示非内建命令就能够做到让大家很好地去理解重定向了

5.创建子进程进行程序替换的函数修改

在这里插入图片描述

//创建子进程,完成任务
void Execute(char* usercommand[])
{pid_t id=fork();if(id==0){//检测是否要进行重定向int fd=0;//输出重定向if(redir==OutPutRedir){fd=open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);dup2(fd,1);}//追加重定向if(redir==AppendRedir){fd=open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);dup2(fd,1);}//输入重定向if(redir==InputRedir){fd=open(filename,O_RDONLY);dup2(fd,0);}//子进程执行部分execvp(usercommand[0],usercommand);//如果子进程程序替换失败,已退出码为1的状态返回exit(1);}else{//父进程执行部分int status=0;//阻塞等待pid_t rid=waitpid(id,&status,0);if(rid>0){lastcode=WEXITSTATUS(status);}}
}

6.main函数的修改

在这里插入图片描述

int main()
{while(1){redir=NoneRedir;filename=NULL;//1.打印提示符信息并获取用户的指令printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());char command[1024]={'\0'};fgets(command,sizeof(command),stdin);command[strlen(command)-1]='\0';//清理掉最后的'\n'//2.检测重定向CheckRedir(command);char* usercommand[1024]={NULL};//3.解析command字符串,放入usercommand指针数组当中GetCommand(command,usercommand);//4.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0int flag=doBuildIn(usercommand);//返回值!=0说明是内建命令,无需执行第4步if(flag!=0) continue;//5.创建子进程,交由子进程完成任务Execute(usercommand);}return 0;
}

7.修改之后myshell.c代码

模拟实现重定向的目的是为了让我们更好地去理解重定向
因此本次实现重定向只是简单的模拟实现,跟系统的重定向并不完全相同

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
//#define DEBUG 1#define SEP " "#define NoneRedir 0
#define OutPutRedir 1
#define AppendRedir 2
#define InputRedir 3
int redir=NoneRedir;char* filename=NULL;char cwd[1024]={'\0'};
int lastcode=0;//上一次进程退出时的退出码char env[1024][1024]={'\0'};
int my_index=0;const char* getUsername()
{const char* username=getenv("USER");if(username==NULL) return "none";return username;
}const char* getHostname()
{const char* hostname=getenv("HOSTNAME");if(hostname==NULL) return "none";return hostname;
}const char* getPwd()
{const char* pwd=getenv("PWD");if(pwd==NULL) return "none";return pwd;
}//分割字符串填入usercommand数组当中
//例如: "ls -a -l" 分割为"ls" "-a" "-l"
void CommandSplit(char* usercommand[],char* command)
{int i=0;usercommand[i++]=strtok(command,SEP);while(usercommand[i++]=strtok(NULL,SEP));
}//解析命令行
void GetCommand(char* command,char* usercommand[])
{if(strlen(command)==0) return;CommandSplit(usercommand,command);
#ifdef DEBUGint i=0;while(usercommand[i]!=NULL){printf("%d : %s\n",i,usercommand[i]);i++;}
#endif
}//创建子进程,完成任务
void Execute(char* usercommand[])
{pid_t id=fork();if(id==0){//检测是否要进行重定向int fd=0;//输出重定向if(redir==OutPutRedir){fd=open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);dup2(fd,1);}//追加重定向if(redir==AppendRedir){fd=open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);dup2(fd,1);}//输入重定向if(redir==InputRedir){fd=open(filename,O_RDONLY);dup2(fd,0);}//子进程执行部分execvp(usercommand[0],usercommand);//如果子进程程序替换失败,已退出码为1的状态返回exit(1);}else{//父进程执行部分int status=0;//阻塞等待pid_t rid=waitpid(id,&status,0);if(rid>0){lastcode=WEXITSTATUS(status);}}
}void cd(char* usercommand[])
{chdir(usercommand[1]);char tmp[1024]={'\0'};getcwd(tmp,sizeof(tmp));sprintf(cwd,"PWD=%s",tmp);putenv(cwd);lastcode=0;
}   int echo(char* usercommand[])
{//1.echo后面什么都没有,相当于'\n'if(usercommand[1]==NULL){printf("\n");lastcode=0;return 1;}//2.echo $?  echo $PWD echo $char* cmd=usercommand[1];int len=strlen(cmd);if(cmd[0]=='$' && len>1){//echo $?if(cmd[1]=='?'){printf("%d\n",lastcode);lastcode=0;}//echo $PWDelse{char* tmp=cmd+1;const char* env=getenv(tmp);//找不到该环境变量,打印'\n',退出码依旧为0if(env==NULL){printf("\n");}else{printf("%s\n",env);}lastcode=0;}}else{printf("%s\n",cmd);}return 1;
}int doBuildIn(char* usercommand[])
{if(usercommand[0]==NULL) return 0;//cdif(strcmp(usercommand[0],"cd")==0){if(usercommand[1]==NULL) return -1;cd(usercommand);return 1;}//echoelse if(strcmp(usercommand[0],"echo")==0){return echo(usercommand);}//exportelse if(strcmp(usercommand[0],"export")==0){//exportif(usercommand[1]==NULL){lastcode=0;return 1;}strcpy(env[my_index],usercommand[1]);putenv(env[my_index]);my_index++;}return 0;
}//跳过空格的宏
#define SKIP_SPACE(pos) do{ while(isspace(*pos)) pos++; }while(0)//检测是否发生了重定向
void CheckRedir(char* command)
{int len=strlen(command);char* start=command,*end=command+len-1;while(end>=start){//输入重定向//cat < log.txtif(*end=='<'){*end='\0';filename=end+1;SKIP_SPACE(filename);redir=InputRedir;break;}else if(*end=='>'){//追加重定向//ls -a -l >> log.txtif(end>start && *(end-1)=='>'){*(end-1)='\0';filename=end+1;SKIP_SPACE(filename);redir=AppendRedir;break;}//输出重定向else{*end='\0';filename=end+1;SKIP_SPACE(filename);redir=OutPutRedir;break;}}else{end--;}}
}int main()
{while(1){redir=NoneRedir;filename=NULL;//1.打印提示符信息并获取用户的指令printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());char command[1024]={'\0'};fgets(command,sizeof(command),stdin);command[strlen(command)-1]='\0';//清理掉最后的'\n'//2.检测重定向CheckRedir(command);char* usercommand[1024]={NULL};//3.解析command字符串,放入usercommand指针数组当中GetCommand(command,usercommand);//4.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0int flag=doBuildIn(usercommand);//返回值!=0说明是内建命令,无需执行第4步if(flag!=0) continue;//5.创建子进程,交由子进程完成任务Execute(usercommand);}return 0;
}

十.stderr的作用

首先先介绍一下2>&1这一语法

1.介绍2>&1

下面我们用fprintf来演示一下
在这里插入图片描述
在这里插入图片描述
如果我们现在就是想要把标准错误和标准输出都往显示器上打印呢?

./mycmd > log.txt 2>&1

在这里插入图片描述
又因为我们先把1重定向到log.txt中,再把2重定向到1中
因此就做到把2和1中的内容全都往log.txt中打印了

2.stderr的作用

我们平常学习编程的时候,程序写的并不大
程序运行时的错误信息和正常信息我们都统一往显示器上打印了
可是一旦程序特别大,要打印的信息特别多,此时区分显示器上的正常信息和错误信息就很麻烦了

而区分正常信息和错误信息之后就能够方便我们对错误信息进行统一排查,提高效率

因此标准输出的作用是:接收打印的正常信息
标准错误的作用是接收打印的错误信息

3.演示

还是刚才那份代码
现在我们想把正常信息重定向到log.txt中
错误信息重定向到log.txt.error中

./mycmd 1>log.txt 2>log.txt.error
把1重定向给log.txt
把2重定向给log.txt.error

在这里插入图片描述
注意:这样重定向时不能带空格
也就是说不能这样写:

./mycmd 1 > log.txt 2 > log.txt.error

在这里插入图片描述

十一.重定向和程序替换之间是互不影响的

为什么它们之间是互不影响的呢?
因为程序替换时改变的是进程结构体当中的页表中虚拟地址空间和物理地址空间的映射和进程地址空间中的相关属性

而重定向改变的是文件描述符表中fd的指向
两者互不影响

以上就是Linux 文件系列:深入理解文件fd,重定向,自定义shell当中重定向的模拟实现的全部内容,希望能对大家有所帮助!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/271700.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nginx正向代理域名的配置

目录 前言 1.打开文件 2. 启用代理 3. 指定代理服务器 4. 保存配置文件并重新加载Nginx。 5. 添加域名解析。 6. 配置客户端。 总结 前言 Nginx是一个高性能、开源的Web服务器软件&#xff0c;不仅可以作为反向代理服务器使用&#xff0c;还可以作为正向代理服务器使用…

Filter过滤器+JWT令牌实现登陆验证

一、背景 我们需要在客户端访问服务器的时候给定用户一定的操作权限&#xff0c;比如没有登陆时就不能进行其他操作。如果他需要进行其他操作&#xff0c;而在这之前他没有登陆过&#xff0c;服务端则需要将该请求拦截下来&#xff0c;这就需要用到过滤器&#xff0c;过滤器可以…

HNU-算法设计与分析-甘晴void学习感悟

前言 算法设计与分析&#xff0c;仅就课程而言&#xff0c;似乎是数据结构与算法分析的延续 教材使用&#xff1a; 课程 关于课程&#xff0c;橙学长讲的非常清晰&#xff0c;我深以为然。 HNUCS-大三课程概览-CSDN博客文章浏览阅读1.3k次&#xff0c;点赞5次&#xff0c;收…

开发Chrome扩展插件

1.首先开发谷歌chrome扩展插件&#xff0c;没有严格的项目结构目录&#xff0c;但是需要保证里面有一个mainfest.json文件 (必不可少的文件)。在这个文件里有三个属性必不可少&#xff1a;name、version、mainfest_version&#xff1b; // 清单文件的版本&#xff0c;这个必须写…

Linux学习之线程

目录 线程概念 1.什么是线程&#xff1f; 2.线程的优缺点 3.线程异常 4.线程用途 线程操作 1.如何给线程传参 2.线程终止 3.获取返回值 4.分离状态 5.退出线程 线程的用户级地址空间&#xff1a; 线程的局部存储 线程的同步与互斥 互斥量mutex 数据不一致的主要过…

Cluade3干货:超越GPT,模型特点分析+使用教程|2024年3月更新

就在刚刚&#xff0c;Claude 发布了最新的大模型 Claude3&#xff0c;并且一次性发布了三个模型&#xff0c;分别是 Claude 3 Haiku&#xff1a;&#xff08;日本俳句 &#xff09;Claude 3 Sonnet&#xff08;英文十四行诗&#xff09;Claude 3 Opus&#xff08;古典乐作品集…

静态时序分析:SDC约束命令set_case_analysis详解

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 目录 指定值 指定端口/引脚列表 简单使用 set_case_analysis命令用于对电路进行特定模式的设定&#xff0c;例如对于一个工作在正常模式下的芯片&#xff0c;…

基于机器学习的曲面拟合方法

随着科技的不断发展&#xff0c;机器学习成为了最近最热门的技术之一&#xff0c;也被广泛应用于各个领域。其中&#xff0c;基于机器学习的曲面拟合方法也备受研究者们的关注。曲面拟合是三维模型处理中的重要技术&#xff0c;其目的是用一组数据点拟合出平滑的曲面&#xff0…

Python数据处理实战(4)-上万行log数据提取并作图进阶版

系列文章&#xff1a; 0、基本常用功能及其操作 1&#xff0c;20G文件&#xff0c;分类&#xff0c;放入不同文件&#xff0c;每个单独处理 2&#xff0c;数据的归类并处理 3&#xff0c;txt文件指定的数据处理并可视化作图 4&#xff0c;上万行log数据提取并作图进阶版&a…

STM32(16)使用串口向电脑发送数据

发送字节 发送数组 发送字符和字符串 字符&#xff1a; 字符串&#xff1a; 字符串在电脑中以字符数组的形式存储

访问修饰符、Object(方法,使用、equals)、查看equals底层、final--学习JavaEE的day15

day15 一、访问修饰符 含义&#xff1a; 修饰类、方法、属性&#xff0c;定义使用的范围 理解&#xff1a;给类、方法、属性定义访问权限的关键字 注意&#xff1a; ​ 1.修饰类只能使用public和默认的访问权限 ​ 2.修饰方法和属性可以使用所有的访问权限 访问修饰符本类本包…

干货 | MSC细胞培养 “秘籍”

MSC培养细节&#xff0c;这里有您想知道的~ MSC&#xff1a;间充质干细胞&#xff0c;是一群贴壁生长、形态类似于成纤维细胞的多能成体干细胞&#xff0c;存在于脐带、骨髓和脂肪组织等多种组织中&#xff0c;并且可以分化成多种不同的组 实验数据分享 1、样本&#xff1a;冻…

【比较mybatis、lazy、sqltoy、mybatis-flex、easy-query操作数据】操作批量新增、分页查询(三)

orm框架使用性能比较 比较mybatis、lazy、sqltoy、mybatis-flex、easy-query操作数据 环境&#xff1a; idea jdk17 spring boot 3.0.7 mysql 8.0测试条件常规对象 orm 框架是否支持xml是否支持 Lambda对比版本mybatis☑️☑️3.5.4sqltoy☑️☑️5.2.98lazy✖️☑️1.2.4…

JavaScript极速入门(2)

JQuery W3C标准给我们提供了一系列函数,让我们可以操作: 网页内容 网页结构 网页样式 但是原生的JavaScript提供的API操作DOM元素时,代码比较繁琐,冗长.我们学习使用JQuery来操作页面对象. JQuery是一个快速,简洁且功能丰富的JavaScript框架,于2006年发布.它封装JavaScript常…

乐优商城(八)商品详情

1. 搭建商品详情微服务 当用户搜索到商品后&#xff0c;如果想要了解商品的更多信息&#xff0c;就需要进入商品详情页。 由于商品详情浏览量比较大&#xff0c;所以我们会创建一个微服务&#xff0c;用来展示商品详情。我们的商品详情页会采用 Thymeleaf 模板引擎渲染后&…

【三维重建】相移法+格雷码

本篇文章介绍一种稠密点云的获取方式——条纹结构光三维重建算法。 在学习此算法前&#xff0c;我们需要对基于视觉的三维重建算法有一定了解。 需要了解什么是相机模型、相机标定以及三角化的相关知识。 【三维重建】摄像机几何-CSDN博客 【三维重建】摄像机标定&#xff…

【C++干货基地】六大默认成员函数: This指针 | 构造函数 | 析构函数

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…

听 GPT 讲 client-go 源代码 (24)

分享更多精彩内容&#xff0c;欢迎关注&#xff01; File: client-go/applyconfigurations/batch/v1/jobstatus.go 在client-go的applyconfigurations/batch/v1/jobstatus.go文件中&#xff0c;定义了与Job的状态相关的配置和操作。 文件中定义了以下几个结构体&#xff1a; Jo…

Kube-Prometheus 监控Istio

推荐 Istio 多集群监控使用 Prometheus&#xff0c;其主要原因是基于 Prometheus 的分层联邦&#xff08;Hierarchical Federation&#xff09;。 通过 Istio 部署到每个集群中的 Prometheus 实例作为初始收集器&#xff0c;然后将数据聚合到网格层次的 Prometheus 实例上。 网…

Kruscal建树+倍增LCA,蓝桥2023省赛,网络稳定性

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 2.网络稳定性 - 蓝桥云课 (lanqiao.cn) 二、解题报告 1、思路分析 考虑到…