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.mzph.cn/news/721791.shtml

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

相关文章

枚举与尺取法(蓝桥杯 c++ 模板 题目 代码 注解)

目录 组合型枚举&#xff08;排列组合模板&#xff08;&#xff09;&#xff09;: 排列型枚举&#xff08;全排列&#xff09;模板&#xff1a; 题目一&#xff08;公平抽签 排列组合&#xff09;&#xff1a; ​编辑 代码&#xff1a; 题目二&#xff08;座次问题 全排…

Java8的Stream执行机制

Java8的Stream执行机制 Stream的概念解说Stream的概念解说-Stream的含义Stream的概念解说-现实类比Stream的概念解说-Stream中的概念Stream的执行机制Stream的执行机制-最直接的流水线实现方式Stream的执行机制-for循环也能干的事Stream的执行机制-基本类图Stream的执行机制-记…

AI大模型的预训练、迁移和中间件编程

大家好&#xff0c;我是爱编程的喵喵。双985硕士毕业&#xff0c;现担任全栈工程师一职&#xff0c;热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。…

RS编码的FPGA实现

RS编码&#xff0c;即Reed-solomon codes&#xff0c;是一类纠错能力很强的特殊的非二进制BCH码&#xff08;BCH码是一种有限域中的线性分组码&#xff0c;具有纠正多个随机错误的能力&#xff09;。对于任选正整数S可构造一个相应的码长为nqS-1的 q进制BCH码&#xff0c;而q作…

2024-03-05 linux 分区老显示满,Use 100%,原因是SquashFS 是一种只读文件系统,它在创建时就已经被填满,所有空间都被使用。

一、这两天一直纠结一个问题&#xff0c;无论怎么修改&#xff0c;linux 分区老显示满&#xff0c;Use 100%&#xff0c;全部沾满。如下图的oem分区。 二、导致出现上面的原因是&#xff1a;SquashFS文件系统里的空间利用率总是显示为100%。 三、SDK里面也说明SquashFS文件系统…

$nextTick底层原理(详细) - vue篇

公众号&#xff1a;需要以下pdf&#xff0c;关注下方 2023已经过完了&#xff0c;让我们来把今年的面试题统计号&#xff0c;来备战明年的金三银四&#xff01;所以&#xff0c;不管你是社招还是校招&#xff0c;下面这份前端面试工程师高频面试题&#xff0c;请收好。 前言 n…

【力扣白嫖日记】1045.买下所有产品的客户

前言 练习sql语句&#xff0c;所有题目来自于力扣&#xff08;https://leetcode.cn/problemset/database/&#xff09;的免费数据库练习题。 今日题目&#xff1a; 1045.买下所有产品的客户 表&#xff1a;Customer 列名类型customer_idintproduct_keyint 该表可能包含重复…

数据结构 - Trie树(字符串统计、最大异或对)

文章目录 前言Part 1&#xff1a;Trie字符串统计1.题目描述输入格式输出格式数据范围输入样例输出样例 2.算法 Part 2&#xff1a;最大异或对1.题目描述输入格式输出格式数据范围输入样例输出样例 2.算法 前言 本篇博客将介绍Trie树的常见应用&#xff0c;包括&#xff1a;Trie…

OPC DA协议网关

在工业自动化领域&#xff0c;数据的采集与传输是至关重要的环节。而OPC DA&#xff08;OLE for Process Control Data Access&#xff09;协议作为这一领域内的标准通信协议&#xff0c;为不同设备和软件之间的数据交换提供了统一的接口。今天&#xff0c;我们就来深入了解一下…

Cookie和session 及Web相关工具

一 Cookie &#xff08;一&#xff09;介绍 Cookie 又称为"小甜饼”。类型为"小型文本文件”&#xff0c;指某些网站为了辨别用户身份而储存在用户本地终端&#xff08;Client Side&#xff09;上的数据&#xff08;通常经过加密&#xff09;。由网景公司的前雇员…

Jmeter性能测试 -3数据驱动实战

什么是数据驱动&#xff1f; 从数据文件中读取测试数据&#xff0c;驱动测试过程的一种测试方法。数据驱动可以理解为更高级的参数化。 特点&#xff1a;测试数据与测试代码分离&#xff1b;数据控制过程 好处&#xff1a;降低开发和维护成本&#xff0c;减少代码量&#xf…

ROS 2基础概念#4:消息(Message)| ROS 2学习笔记

ROS 2消息简介 ROS程序使用三种不同的接口来进行沟通&#xff1a;消息&#xff08;message&#xff09;&#xff0c;服务&#xff08;service&#xff09;和动作&#xff08;action&#xff09;。ROS 2使用一种简化的描述语言&#xff1a;IDL&#xff08;interface definition…

Vue.js 深度解析:模板编译原理与过程

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

代码随想录算法训练营day36|435. 无重叠区间、763.划分字母区间、56. 合并区间

435. 无重叠区间 代码随想录 class Solution:def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:intervals.sort(keylambda x:x[0])result 0for i in range(1,len(intervals)):if intervals[i][0] < intervals[i-1][1]:result 1intervals[i][1] m…

【Spring底层原理高级进阶】Spring Kafka:实时数据流处理,让业务风起云涌!️

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《Spring 狂野之旅&#xff1a;从入门到入魔》 &#x1f680; 本…

【python】堆排序

堆的概念 堆&#xff1a;一种特殊完全二叉树&#xff0c;也就是二叉树必须全部是满的&#xff0c;但是最后一排可以从右向左缺失。 大根堆&#xff1a;每个节点都比他的子节点大 小根堆&#xff1a;每个节点都比子节点小 堆在代码中的形式 堆在代码中实际上就是列表&#…

[Angular 基础] - routing 路由(下)

[Angular 基础] - routing 路由(下) 之前部分 Angular 笔记&#xff1a; [Angular 基础] - 自定义指令&#xff0c;深入学习 directive [Angular 基础] - service 服务 [Angular 基础] - routing 路由(上) 使用 route 书接上回&#xff0c;继续折腾 routing 按照最初的 wi…

Linux--文件(2)-重定向和文件缓冲

命令行中的重定向符号 介绍和使用 在Linux的命令行中&#xff0c;重定向符号用于将命令的输入或输出重定向到文件或设备。 常见的重定向符号&#xff1a; 1.“>“符号&#xff1a;将命令的标准输出重定向到指定文件中&#xff0c;并覆盖原有的内容。 2.”>>“符号&a…

1.初识python

1.初识python 编程语言是用来定义计算机程序的语言&#xff0c;用来向计算机发出指令。 1.python语言是一种面向对象的解释型高级编程语言。 解释型语言&#xff1a;使用专门的解释器对源码程序逐行解释成特定平台的机器并立即执行&#xff0c;是代码在执行时才被解释器一行行…

c++数据结构算法复习基础-- 3 --线性表-单向链表-笔试面试常见问题

1、单链表逆序 思路图 代码实现 //著: 链表结构里记得加 friend void ReverseLink(Clink& link); void ReverseLink(Clink& link) {Node* p link.head_->next_;while( p nullptr){return;}Node* q p->next_;link.head_->next_ nullptr;while(p ! nullpt…