【linux深入剖析】操作系统与用户之间的接口:自定义简易shell制作全过程


🍁你好,我是 RO-BERRY
📗 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识
🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油

在这里插入图片描述


目录

  • 1.shell
  • 2.自定义shell的准备工作
  • 3. 编写myshell.c
    • 3.1获取主机名
    • 3.2获取用户名
    • 3.3获取当前工作目录
    • 3.4 命令行缓冲区
    • 3.5 命令行切割
    • 3.6 执行命令
    • 3.7 进行多命令操作
    • 3.8 内建命令
    • 3.9 导入环境变量
    • 3.10 echo命令


1.shell

Linux的Shell是一种命令行解释器,它是用户与操作系统内核之间的接口。 通过Shell,用户可以输入命令并与操作系统进行交互。Shell可以执行各种任务,如文件管理、进程控制、系统配置等。

Linux中最常用的Shell是Bash(Bourne Again Shell),它是Bourne Shell的增强版本。Bash提供了丰富的功能和命令,使得用户可以更加高效地管理和操作系统。

以下是一些常用的Shell命令和功能:

  1. 文件和目录操作:ls(列出文件和目录)、cd(切换目录)、mkdir(创建目录)、rm(删除文件或目录)等。
  2. 文件查看和编辑:cat(查看文件内容)、grep(在文件中搜索指定内容)、vi(文本编辑器)等。
  3. 进程管理:ps(查看进程信息)、kill(终止进程)等。
  4. 系统信息查看:uname(显示系统信息)、df(查看磁盘空间使用情况)等。
  5. 网络管理:ping(测试网络连接)、ifconfig(配置网络接口)等。

考虑下面这个与shell典型的互动:

在这里插入图片描述

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。

在这里插入图片描述

然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。
所以要写一个shell,需要循环以下过程:

  1. 获取命令行
  2. 解析命令行
  3. 建立一个子进程(fork)
  4. 替换子进程(execvp)
  5. 父进程等待子进程退出(wait)

根据这些思路,和我们前面的学的技术,就可以自己来实现一个shell了。


2.自定义shell的准备工作

  • 首先做一个shell的目录,用来装我们的自定义shel

mkdir shell

  • 然后在目录下创建Makefile文件以及myshell.c文件

ls > Makefile
touch myshell.c

  • 编写Makefile和myshell.c文件

Makefile

myshell:myshell.c                                                                                   
    gcc -o $@ $^
.PHONY:clean
    rm -rf myshell

上面几步很好走,最重要的是我们的myshell.c文件应该如何去编写呢?


3. 编写myshell.c

之前我们总体学了环境变量,环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数。我们想得到当前用户名以及当前目录等等都依靠的是环境变量,因此我们可以通过环境变量来进行编写shell。

getenv("环境变量"):这个函数接口就是我们获取环境变量中存储的数据的方式(之前讲过)

3.1获取主机名

环境变量HOSTNAME

//获取主机名
const char* HostName()
{
  char* hostname=getenv("HOSTNAME");  
  if(hostname) return hostname;
  else return "None";
}

3.2获取用户名

环境变量USER

//获取用户名
char* UserName()
{
  char* hostname=getenv("USER");                                                                  
  if(hostname) return hostname;
  else return "None";
}

3.3获取当前工作目录

环境变量PWD

//获取当前工作目录                                                                                
char* CurrentWorkDir()
{
  char* hostname=getenv("PWD");
  if(hostname) return hostname;
  else return "None";
}

3.4 命令行缓冲区

我们有了上面三个信息后,就可以将上面三个当成我们的命令提示符了,也就是命令行前面显示的我们的用户名以及权限、当前目录。
有了命令行提示符还不够,我们还需要输入我们的指令操作,就必须创建一个命令行缓冲区,帮我们接受命令

在这里我们使用scanf是肯定不行的,scanf只能获取一个指令操作
所以需要用到fgets函数,来帮助我们获取多个指令

在这里插入图片描述

char *fgets(char *s, int size, FILE *stream) 是C语言中的一个函数,用于从指定的文件流中读取一行字符串,并将其存储到字符数组 s 中

它的参数解释如下:

s:指向字符数组的指针,用于存储读取到的字符串。
size:要读取的最大字符数(包括空字符)。
stream:指向要读取的文件流的指针。

该函数会从文件流中读取字符,直到遇到换行符 \n、文件结束符 EOF 或者达到最大字符数 size-1。读取到的字符会存储在字符数组 s 中,并在末尾添加一个空字符 \0。
函数返回值为指向字符数组 s 的指针,如果成功读取到字符串,则返回 s 的地址;如果在读取过程中发生错误或者已经到达文件末尾,则返回 NULL。

注意:使用该函数时需要确保字符数组 s 的大小足够大,以容纳要读取的字符串。

stream文件流指针我们目前并未涉及,我们想输入的话这里就需要写stdin,输出的话这里需要写stdout

#define SIZE 1024
int main()
{
  char commandline[SIZE]; //命令行缓冲区
  printf("%s@%s %s]$ ",UserName(),HostName(),CurrentWorkDir());
  //scanf("%s",commandline); scanf只能获取一个字符串
                                                                       
  fgets(commandline, SIZE, stdin); //获取字符串              
 
  printf("test: %s\n", commandline);
                                 
  return 0;                                                                                       
}          

我们简单进行测试一下:
在这里插入图片描述
我们这里最后的printf里是没有输出换行的,这是因为我们的fgets函数使用了之后会在末尾添加一个空字符 \0
我们也可以将这个\0去掉

因为\0在字符串的末尾,所以我们在字符串长度减一的位置将其改为0即可

int main()
{
  char commandline[SIZE];                                //命令行缓冲区
  printf("%s@%s %s]$ ",UserName(),HostName(),CurrentWorkDir());
  //scanf("%s",commandline); scanf只能获取一个字符串
  
  fgets(commandline, SIZE, stdin);
  commandline[strlen(commandline)-1] = 0;  

  printf("test: %s\n", commandline);

  return 0;
}

最后做成一个交互接口

int Interactive(char out[],int size)
{
  printf("%s@%s %s]$ ",UserName(),HostName(),CurrentWorkDir());
  fgets(out, size, stdin);
  out[strlen(out)-1] = 0;
  return strlen(out);
}

int main()
{
  char commandline[SIZE];                                //命令行缓冲区
  int n = Interactive(commandline,SIZE);
  if(n == 0) continue;   //解决空串问题
  printf("test: %s\n", commandline);

  return 0;
}

3.5 命令行切割

我们对ls -a -l进行了接受储存并打印,但是这里只有ls是指令,而后面的-a-l则是命令行参数,我们在这里就要对指令和命令行参数进行分割处理。那我们如何分割呢?

我们可以使用指针数组来分别存储,将其拆开
在这里插入图片描述
我们这里想将字符串一变多可以使用函数

strtok

strtok是一个C语言中的字符串处理函数,用于将字符串分割成多个子字符串。它的原型定义如下:

char *strtok(char *str, const char *delim);

其中,str是要分割的字符串,delim是分割符。函数会将str按照delim进行分割,并返回第一个子字符串的指针。之后,每次调用strtok时,传入NULL作为第一个参数,函数会继续返回下一个子字符串的指针,直到所有子字符串都被返回。

需要注意的是,strtok会修改原始字符串,将分割符替换为’\0’,因此在使用strtok后,原始字符串会被改变。

#define MAX_ARGC 64
  //对命令行进行切割
  char *argv[MAX_ARGC];
  int i = 0;
  argv[i++]=strtok(commandline, SEP);
  while(argv[i++] = strtok(NULL,SEP));   //故意将 == 写成 = 

这里我们故意将==写成=是为了让我们的argv以NULL结尾,当我们的argv被赋值为NULL之后,while判断会失败,就能跳出循环,这是因为我们的命令行参数必须以NULL结尾,进行程序替换的接口都必须要求我们的命令行参数以NULL结尾

接下来我们将我们的切割操作定义成一个接口

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define MAX_ARGC 64
#define SIZE 1024
#define SEP " "
char* argv[MAX_ARGC];
//获取主机名
const char* HostName()
{
  char* hostname=getenv("HOSTNAME");
  if(hostname) return hostname;
  else return "None";
}

//获取用户名
const char* UserName()
{ 
  char* hostname=getenv("USER");
  if(hostname) return hostname;
  else return "None";
}

//获取当前工作目录
const char* CurrentWorkDir()
{
  char* hostname=getenv("PWD");
  if(hostname) return hostname;
  else return "None";
}

int Interactive(char out[],int size)
{
  printf("%s@%s %s]$ ",UserName(),HostName(),CurrentWorkDir());
  fgets(out, size, stdin);
  out[strlen(out)-1] = 0;
  return strlen(out);
}
void Split(char in[])
{
  int i = 0;
  argv[i++]=strtok(in, SEP);
  while(argv[i++] = strtok(NULL,SEP));   //故意将 == 写成 =
}
int main()
{
  //接受命令字符串
  char commandline[SIZE];//命令行缓冲区
  int n = Interactive(commandline,SIZE);
  if(n == 0) continue;   //解决空串问题
  //对命令行进行切割
  Split(commandline);

  int i=0;
  for(i = 0;argv[i];i++)
  {
    printf("argv[%d]: %s\n", i, argv[i]);
  }
  return 0;
}

此时的运行结果如下:

在这里插入图片描述

3.6 执行命令

我们接收到了命令并进行了分割,接下来我们就要进行最关键的一步,对其进行执行操作。

此时我们要执行这个命令,我们是命令行解释器bash,我们不能直接进行程序替换执行命令,那么我们整个进程就被替换成这一个命令了,我们在这里需要用到子进程,让子进程执行这个命令

这里要使用execp接口
execp是一个系统调用函数,用于在当前进程中一个新的程序。它是exec函数族中的一员,execp函数的原型如下:

int execp(const char *file, const char *arg, ...);
其中,file参数是要执行的程序的路径,arg参数是传递给新程序的命令行参数。execp函数会将当前进程替换为新程序,并开始执行新程序的代码。

  1. execp函数与其他exec函数的区别在于,它接受一个可变参数列表,可以传递任意数量的参数给新程序。这些参数在新程序中可以通过main函数的参数列表获取到。

  2. execp函数执行成功时,不会返回到原来的程序,而是直接开始执行新程序。如果执行失败,则会返回-1,并设置errno变量来指示错误类型。

  3. 需要注意的是,execp函数只能在子进程中调用,因为它会替换当前进程的代码和数据段。如果在父进程中调用execp函数,那么父进程的代码和数据也会被替换,导致父进程无法继续执行。

  //执行命令
  pid_t id = fork();
  if(id == 0)
  {
    //让子进程执行命令
    execvp(argv[0], argv);
    exit(1);
  }
  //父进程等待子进程并进行回收
  pid_t rid = waitpid(id, NULL, 0);  
  printf("run done, rid: %d\n", rid);

在这里插入图片描述
我们现在已经可以执行我们的命令了

封装成接口:

void Execute()
{
  pid_t id = fork();
  if(id == 0)
  {
    //让子进程执行命令
    execvp(argv[0], argv);
    exit(1);
  }
  //父进程等待子进程并进行回收
  pid_t rid = waitpid(id, NULL, 0);  
}

3.7 进行多命令操作

众所周知,shell不可能只能执行一次命令,我们要进行多次命令操作

int main()
{
  while(1)
  {
    //接受命令字符串
    char commandline[SIZE];//命令行缓冲区
    Interactive(commandline,SIZE); 
    //对命令行进行切割
    Split(commandline);
    //执行命令
    Execute();
  }
  return 0;
}

我们使用一个死循环
在这里插入图片描述
我们这个时候用的shell就是我们自己的shell了

但是为什么我们的cd命令却跑不了呢?

在这里插入图片描述

这是因为cd命令是由我们的子进程运行的,改变的是子进程的路径,而没有改变我们bash进程的路径

3.8 内建命令

内建命令是指直接内置在操作系统中的命令,可以直接在命令行或终端中使用,而无需额外安装或配置。内建命令通常具有更高的执行效率和更快的响应速度。

我们上面想执行cd,是因为其为内建命令,而不是让子进程进行。需要我们单独进行处理

char* Home()
{
  return getenv("HOME");
}
int BuildinCmd()
{
    int ret = 0;
    if(strcmp("cd", argv[0]) == 0)     //检测是否是cd命令
    {
      //执行内建命令
      ret = 1;
      char* target = argv[1];   //这里是cd命令后面紧跟着的路径或者空路径
      if(!target) target = Home();   //如果target为空,则为家目录,不为空则为目标目录
      
      chdir(target);         //chdir为改变当前路径为target
    }
    return ret;
}

在这里插入图片描述
可以看到我们当前路径是发生了改变的,但是前面的命令提示符上面的路径没有改变,我们的命令提示符提取的是我们的环境变量,也就是说,当我们的环境变量发生改变的时候,我们这里也要进行一个同步的更新才行

我们这里为了方便定义一个pwd的全局变量字符串

 char* pwd[MAX_ARGC];     

我们还要涉及两个函数

  • putenv函数是一个C语言标准库函数,用于设置环境变量的值。它的原型如下:
    int putenv(char *string);

  1. putenv函数接受一个形如"key=value"的字符串参数,将该字符串解析为一个环境变量,并将其添加到当前进程的环境变量列表中。如果该环境变量已存在,则会更新其值。
  2. putenv函数的返回值为0表示成功,非零值表示失败。
  3. 使用putenv函数可以在程序运行时动态地设置环境变量,这对于需要根据不同的条件来改变程序行为的情况非常有用。
    注意:putenv函数在修改环境变量时,只会影响当前进程及其子进程,不会影响其他进程。
  • snprintf是一个C语言中的函数,用于将格式化的数据写入字符串中。它的原型如下:
    int snprintf(char *str, size_t size, const char *format, ...);

  1. 其中,str是目标字符串的指针,size是目标字符串的最大长度,format是格式化字符串,后面的参数是要格式化的数据。
  2. snprintf函数的作用是将格式化的数据按照指定的格式写入到目标字符串中,并返回写入的字符数(不包括终止符’\0’)。如果目标字符串的长度超过了指定的最大长度,snprintf会截断超出部分的数据,以保证不会发生缓冲区溢出。
  3. snprintf函数与sprintf函数类似,但是它多了一个参数size,用于指定目标字符串的最大长度,从而避免了缓冲区溢出的风险。

当我们使用cd改变了当前路径的时候,我们就要用putenv来改变环境变量
然后我们使用snprintf让改变的路径存储到我们的全局变量里

代码实现如下:

int BuildinCmd()
{
    int ret = 0;
    //检测是非是内建命令,是为1.否为0
    if(strcmp("cd", argv[0]) == 0)
    {
      //执行内建命令
      ret = 1;
      char* target = argv[1]; //cd XXX or cd
      if(!target) target = Home();
      
      chdir(target);
      
      snprintf(pwd,SIZE,"PWD=%s",target);
      putenv(pwd);
    }
    
    return ret;
}

在这里插入图片描述
现在确实已经可以和我实现的操作进行同步了,只不过当我们cd两个点的时候,这是特殊代表上级目录的,它跟不上我们的操作

为了解决这个麻烦,我们需要使用getcwd函数接口

getcwd是一个C语言函数,用于获取当前工作目录的路径名。它的原型如下:
char *getcwd(char *buf, size_t size);

  1. 该函数接受两个参数,第一个参数是一个字符数组指针,用于存储获取到的当前工作目录路径名;第二个参数是buf的大小,用于指定buf的长度。
  2. getcwd函数会将当前工作目录的路径名复制到buf中,并返回buf的指针。如果获取成功,则返回的指针与buf相同;如果获取失败,则返回NULL。
  3. getcwd函数在实际应用中常用于获取当前程序所在的目录路径,以便进行文件操作或其他相关操作。

代码实现如下:

int BuildinCmd()
{
    int ret = 0;
    //检测是非是内建命令,是为1.否为0
    if(strcmp("cd", argv[0]) == 0)
    {
      //执行内建命令
      ret = 1;
      char* target = argv[1]; //cd XXX or cd
      if(!target) target = Home();
      chdir(target);
      
      char temp[1024];
      getcwd(temp, 1024);

      snprintf(pwd,SIZE,"PWD=%s",temp);
      putenv(pwd);
    }
    
    return ret;
}

在这里插入图片描述

3.9 导入环境变量

我们现在进行导入环境变量,其实际上是子进程在运行,我们的bash进程是得不到我们的环境变量的,所以我们对于导入环境变量也需要特殊处理

也就是说export命令也是一个内建命令,需要我们自己导入到自己的环境变量里
设置一个全局变量存储我们的环境变量

env[SIZE]

int BuildinCmd()
{
    int ret = 0;
    //检测是非是内建命令,是为1.否为0
    if(strcmp("cd", argv[0]) == 0)
    {
      //执行内建命令
      ret = 1;
      char* target = argv[1]; 
      if(!target) target = Home();
      chdir(target);
      
      char temp[1024];
      getcwd(temp, 1024);

      snprintf(pwd,SIZE,"PWD=%s",temp);
      putenv(pwd);
    }
    else if(strcmp("export",argv[0]) == 0)
    {
      ret = 1;
      if(argv[1])
      {
        strcpy(env,argv[1])          //防止argv每次接收到的命令行数据覆盖掉,导致环境变量消失
        putenv(env);                //将环境变量导入
      }
    }
    return ret;
}

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

3.10 echo命令

echo命令也是一个内建命令

echo $?:可以打印出上次进程退出的退出码
echo $PATH:打印当前用户的主目录路径

char lastcode = 0 ;    //全局变量退出码

int BuildinCmd()
{
    int ret = 0;
    //检测是非是内建命令,是为1.否为0
    if(strcmp("cd", argv[0]) == 0)
    {
      //执行内建命令
      ret = 1;
      char* target = argv[1]; 
      if(!target) target = Home();
      chdir(target);
      
      char temp[1024];
      getcwd(temp, 1024);

      snprintf(pwd,SIZE,"PWD=%s",temp);
      putenv(pwd);
    }
    else if(strcmp("export",argv[0]) == 0)
    {
      ret = 1;
      if(argv[1])
      {
        strcpy(env,argv[1]);    
        putenv(env);
      }
    }
    else if(strcmp("echo", argv[0]) == 0)
    {
      ret = 1 ;
      if(argv[1] == NULL)
      {
        printf("\n");

      }
      else
      {
        if(argv[1][0] == '$')
        {
          if(argv[1][1] == '?')
          {
            printf("%d\n",lastcode);
            lastcode = 0;
          }
          else{
            char *e = getenv(argv[1]+1);
            if(e) printf("%s\n",e);
          }
        }
        else
          printf("%s\n", argv[1]);
      }
    }
    return ret;

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何遍历map

小王学习录 前言遍历map集合1. 使用for-each循环遍历 entrySet()2. 使用迭代器遍历 entrySet()3. 通过 keySet() 遍历4. 使用迭代器遍历 keySet()5. 仅遍历 values() 如果只关心map中的值而不关心键&#xff0c;可以遍历 values()&#xff1a;6. 使用流(Streams)进行遍历 总结 …

typeorm导致nestjs通过@Query接收的参数为undefined

依赖版本如下,发现引入typeorm后导致接收不到Query参数,解决办法是将 TypeOrmModule导入语句放到前面就可以了

MT3004·找四边形

题目&#xff1a; 样例输入 4 12 1 2 1 3 1 4 2 1 2 3 2 4 3 1 3 2 3 4 4 1 4 2 4 3 样例输出 12 数据范围 算法设计 涉及的算法 枚举和图论基础 采用邻接矩阵g[N]来存储图&#xff0c;其中vector<ll> g[N]是建立了一个二维的vector 来用sum记录每个点 i 到达点 j…

java集合框架——Map集合概述

前言&#xff1a; 之前接触了单列合集&#xff0c;现在又接触了双列合集。整理下心得&#xff0c;打好基础&#xff0c;daydayup&#xff01;&#xff01; Map集合 Map集合称为双列集合&#xff0c;也被称为“键值对集合”。格式&#xff1a;{key1value1,key2value2...}&#…

网络学习:邻居发现协议NDP

目录 前言&#xff1a; 一、报文内容 二、地址解析----NS/NA 目标的被请求组播IP地址 邻居不可达性检测&#xff1a; 重复地址检测 路由器发现 地址自动配置 默认路由器优先级和路由信息发现 重定向 前言&#xff1a; 邻居发现协议NDP&#xff08;Neighbor Discovery…

MySQL数据库实现增删改查基础操作

准备工作 安装mysql8.0 (安装时一定要记住用户名和密码)安装数据库可视化视图工具Navicat 请注意⚠️⚠️⚠️⚠️ a. 编程类所有软件不要安装在中文目录下 b. Navicat破解版下载安装教程&#xff1a;&#xff08;由于文章审核提示版权问题&#xff0c;链接不方便给出&#xff…

虚拟内存相关知识汇总(程序重定位)

前置知识&#xff1a; Windows的内存可以被分为两个层面&#xff1a;物理内存和虚拟内存。其中&#xff0c;物理内存非常复杂&#xff0c;需要进入到Windows内核级别ring0才能看到。通常在用户模式下&#xff0c;用调试器看到的内存地址都是虚拟地址。 1.虚拟内存的定义 虚拟…

PCIE问题定位000:PCIe需要的定位手段

1、PCIe debug环境说明 本文将以PCIe EP用户逻辑举例&#xff0c;描述PCIe可以添加哪些定位手段。 如图所示&#xff0c;PCIe IP作为endpoint与RC对接&#xff0c;用户实现了应用逻辑&#xff0c;与PCIe IP进行交互&#xff0c;交互信号中data格式为TLP报文格式&#xff0c;且…

Linux_基础指令(一)

目录 1、ls指令 1.1 ls -l 1.2 ls -a 1.3 ls -i 2、pwd指令 3、cd指令 3.1 路径的概念 3.1.1 绝对路径 3.1.2 相对路径 3.2 cd ~ 3.3 cd - 4、touch指令 5、mkdir指令 6、删除系列的指指令 6.1 rmdir 6.2 rm 7、man指令 8、cp指令 9、move指令 结…

【智能算法】斑鬣狗优化算法(SHO)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过。 3.代码实现4.参考文献 1.背景 2017年&#xff0c;Dhiman等人受到斑鬣狗自然狩猎行为启发&#xff0c;提出了斑鬣狗优化算法(Spotted Hyena Optimizer, SHO)。 2.算法原理 2.1算法思想 SHO将斑鬣狗狩猎行为分为围捕-狩猎-进攻三…

多线程JUC 第2季 wait和notify唤醒机制

一 wait和notify的区别与相同 1.1 wait和notify的作用 1) 使用wait()、notify()和notifyAII()时需要先对调用对象加锁。否则直接调用的话会抛出 IllegalMonitorStateExceptiona。 2) 调用wait()方法后&#xff0c;线程状态。由RUNNING变为WAITING&#xff0c;并将当前线程放置…

wordpress子比主题7.6美化插件及新手零基础搭建教程源码下载

版权申请&#xff1a;本文A5资源网原创&#xff0c;经原创作者允许转载许可声明。下载地址http://a5.org.cn/a5_ziyuan/39172.html 本源码由网友在某宝二十几元购买&#xff0c;现分享给大家。下图为源码文件及演示图&#xff0c;安装教程比较详细新手零基础就可搭建 子比主…

NeRF——基于神经辐射场的三维场景重建和理解

概述 三维重建是一种将物理世界中的实体转换为数字模型的计算机技术。其基本概念是通过对物理世界中的物体或场景进行扫描或拍摄&#xff0c;并使用计算机算法将其转换为三维数字模型。抽象意义上的三维模型指的是&#xff1a;形状和外观的组合&#xff0c;并且可以渲染成不同…

【Redis知识点总结】(四)——如何保证缓存与数据库中的数据一致性

Redis知识点总结&#xff08;四&#xff09;——如何保证缓存与数据库中的数据一致性 更新缓存删除缓存先删除缓存后更新数据库先更新数据库后删除缓存 使用canal总结 面试会经常遇到这种问题&#xff1a;你们如何保证缓存与数据库中的数据一致性&#xff1f;或者是&#xff1a…

小白必看的Python基础之函数篇

函数最重要的目的是方便我们重复使用相同的一段程序。 将一些操作隶属于一个函数&#xff0c;以后你想实现相同的操作的时候&#xff0c;只用调用函数名就可以&#xff0c;而不需要重复敲所有的语句。 函数的定义 首先&#xff0c;我们要定义一个函数, 以说明这个函数的功能…

把软件加入开机自启动

注意这个方法最佳效果是适用于打开软件后,关闭窗口不会停止服务 例如 nginx 1.把nginx的快捷方式放到如图所示的文件夹下 C:\Users\KIA_27\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup 注意KIA_27应改为你自己的用户名

从政府工作报告探计算机行业发展——探索计算机行业发展蓝图

目录 前言 一、政策导向与行业发展 &#xff08;一&#xff09;政策导向的影响 &#xff08;二&#xff09;企业如何把握政策机遇推动创新发展 二、技术创新与产业升级 三、数字经济与数字化转型 四、国际合作与竞争态势 五、行业人才培养与科技创新 &#xff08;一&a…

KubeSphere集群安装-nfs分布式文件共享-对接Harbor-对接阿里云镜像仓库-遇到踩坑记录

KubeSphere安装和使用集群版 官网:https://www.kubesphere.io/zh/ 使用 KubeKey 内置 HAproxy 创建高可用集群:https://www.kubesphere.io/zh/docs/v3.3/installing-on-linux/high-availability-configurations/internal-ha-configuration/ 特别注意 安装前注意必须把当前使…

【十】【算法分析与设计】滑动窗口(1)

209. 长度最小的子数组 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续 子数组 [nums(l), nums(l1), ..., nums(r-1), nums(r)] &#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0 。 …

[嵌入式系统-40]:龙芯1B 开发学习套件 -10-PMON启动过程start.S详解

目录 一、龙芯向量表与启动程序的入口&#xff08;复位向量&#xff09; 1.1 复位向量&#xff1a; 1.2 代码执行流程 1.3 计算机的南桥 VS 北桥 二、PMON代码执行流程 三、Start.S详解 3.1 CPU初始化时所需要的宏定义 &#xff08;1&#xff09;与CPU相关的一些宏定义…