极简shell制作

🌎自定义简单shell制作

(ps: 文末有完整代码)

文章目录:

自定义简单shell制作

    简单配置Linux文件

    自定义Shell编写

      命令行解释器
      获取输入的命令
      字符串分割
      子进程进行进程替换

      内建命令处理

        cd命令处理
        路径显示问题
        export命令处理
        echo 命令处理

    自定义Shell源码


前言:

  通过我们之前所学Linux知识以及C语言的知识,到目前为止,我们完全可以独立完成简易shell的制作,那么话不多说,开始今天的话题!

在这里插入图片描述


🚀简单配置Linux文件

  首先,再开始项目之前,需要先简单配置一下Linux文件,选择一个位置,创建本次项目的目录:

mkdir myshell#名字随意,这里方便区分命名myshell

在这里插入图片描述
  如图所示在该目录下,我们还需要创建 makefile文件C的源文件

touch makefile#或者 Makefile
touch myshell.c#其他名字都行,后缀是.c即可

  因为我们构建的是C语言项目,所以makefile内文件配置也很简单,使用vim(vim介绍及其使用)打开makefile文件:

vim makefile

配置makefile文件

cc=-std=c99
mybin:file.cgcc -o $@ $^ -g $(cc)
.PHONY:clean
clean:rm -f mybin 

  保存退出之后,就可以开始编写我们C语言代码啦,配置还是很简单的。


🚀自定义Shell编写

✈️命令行解释器

  首先,我们根据常用的shell行为分析:

在这里插入图片描述

  常用 shell 都有叫做 命令行解释器 的东西(上图红框),而命令行解释器其实就是 由不同的字符串所构成 的,可以拆分成三部分:

第一部分是用户,随后在@之后是主机名字符串,第三部分是 当前所处工作目录。

  我们曾经学过一个获取环境变量的接口 getenv

在这里插入图片描述
  因为上述三个部分皆可以在系统的环境变量中找到,所以我们可以使用 getenv 接口,将环境变量导出,拿到字符串作为我们自定义shell的命令行解释器:

#include<stdio.h>
#include<stdlib.h>char* HostName()//获取主机名
{char* hostname = getenv("HOSTNAME");//获取主机名环境变量if(hostname) return hostname;else return "None";
}char* UserName()//获取用户名
{char* hostname = getenv("USER");//从用户名环境变量的获取用户名if(hostname) return hostname;else return "None";
}char* CurrentWorkDir()//获取当前工作目录
{char* hostname = getenv("PWD");//获取当前路径if(hostname) return hostname;else return "None";
}int main()
{//命令行提示符编写printf("[%s@%s %s]$ ",UserName(), HostName(), CurrentWorkDir());return 0;
}

效果展示:

在这里插入图片描述

  效果还是很不错的,有些细节仍需该进,会在后面慢慢改善。


✈️ 获取输入的命令

  有了命令行解释器,我们在 shell 上还有输入命令这一行为,那么我们自定义shell就需要接收输入的命令行字符串。

  那么我们需要考虑的就是输入命令的情况:1、单个命令输入。2、命令+选项输入。 其实他们的区别很明显,一种 字符串不带空格,一种字符串 带一个或多个空格,比如:

在这里插入图片描述
  使用C语言的scanf显然是行不通的,在这里我推荐使用 fgets 接口,可以接收输入的空格:

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

  返回值表示输入的字符串,前两个参数不用说,但是最后一个参数可以能很多人不太理解,我们在学C语言的时候,可能大家学过三个流:stdin、stdout、stderr 流:

在这里插入图片描述
  当然,不了解也不要紧,我们仅需要知道 我们输入需要从 stdin 流中获取即可,表示从标准输入内获取信息。

  函数第一个参数表示 接收字符串的位置,第二个参数表示 接收大小,我们定义一个数组,用来接收输入的命令行参数:

#define CMD_SIZE 1024//定义数组大小
char commandline[CMD_SIZE];//接收命令行参数的数组

  那么我们就需要把接收的命令行参数放入到 commandline数组里。在 Shell中,一行命令输入完成之后将直接生效。所以在命令输入完成之后,我们有必要给commandline数组结尾,也就是添加 ‘\0’:

int main()
{char commandline[CMD_SIZE];//命令行提示符编写printf("[%s@%s %s]$ ",UserName(), HostName(), CurrentWorkDir());fgets(commandline, CMD_SIZE, stdin);//获取输入命令,将其放在commandline数组内commandline[strlen(commandline) - 1] = '\0';//结束本行命令,记得包含string头文件printf("cmd line: %s\n", commandline);return 0;
}

在这里插入图片描述

  将shell运行起来之后,我们输入的命令就可以被检测并输入到字符数组里面了。

  为了让代码更具可读性,我们可以将输出命令行解释器和输入命令接收操作封装在一个函数内,再在main函数调用:

void Interactive(char out[], int size)//接口封装
{printf("[%s@%s %s]$ ",UserName(), HostName(), CurrentWorkDir());fgets(out, CMD_SIZE, stdin);out[strlen(out) - 1] = '\0';
}int main()
{char commandline[CMD_SIZE];Interactive(commandline, CMD_SIZE); //使用接口调用即可printf("cmd line: %s\n", commandline);return 0;
}

✈️ 字符串分割

  我们平时在shell 中输入的命令选项是不确定的,有时候有多个选项,有时候有一个选项,有时候没有选项,而shell会根据不同的选项来执行不同的动作。

  所以我们有必要将字符串切割,而我们之前在学习命令行参数的时候,提到过main函数参数有一个叫做 argv命令行参数表(const char* argv[]),那么我们就可以创建一个命令行参数表来接收每一个子串。

  那么如何切割字符串呢?这里有一个C语言的接口可供大家使用 strtok

在这里插入图片描述

  第一个参数表示 指向要分割的字符串第一次调用时需要指定这个参数以后的调用要继续分割同一个字符串,就应该把参数 str 设置为 NULL

  第二个参数表示 以什么字符或字符串为结尾进行切割,返回值表示 返回切割后的子串,如果查找不到切割点了,就会返回NULL。

  而我们命令行都是以 空格作为分隔符 的,所以,空格字符就是该接口的第二个参数了,而这个接口会被频繁调用,所以,我们直接使用宏定义空格:

#define MAX_ARGC 64//argv数组的大小
#define SEP " "//表示空格 

   argv是一个指针数组,所以每一个元素都可以指向一段字符串,同时,我们希望argv数组下标能一一对应,所以需要一个键值作为索引:

int i = 0;
argv[i++] =  strtok(commandline, SEP);

  但是,我们输入的命令很可能不止一个空格,所以,我们需要使用循环控制子串的切割,让argv数组的每一个元素都能对应到切割的字符串:

while(argv[i++] = strtok(NULL, SEP));//注意这里用的是=并非==

  并且,这样一个好处就是 在argv数组最后是以 NULL结尾的。同样,为了代码的可读性,我们可以将切割子串的功能封装为一个接口,并且 argv数组放在全局位置,因为根据以往的经验,父子进程可能都会需要argv数组:

void Split(char in[], int size)
{int i = 0;argv[i++] = strtok(in, SEP);//进行子串切割while(argv[i++] = strtok(NULL, SEP));
}int main()
{char commandline[CMD_SIZE];Interactive(commandline, CMD_SIZE); //对命令行字符串切割Split(commandline, CMD_SIZE);for(int j = 0; argv[j]; ++j)//测试命令行参数是否切割成功{printf("argv[%d]:%s\n", j, argv[j]);}return 0;
}

在这里插入图片描述


✈️ 子进程进行进程替换

  前面我们学习过,程序替换成功时,后续程序就不会往下走,又因为进程之间具有独立性,所以需要创建一个子进程来完成进程替换这件事情。

pid_t id = fork();
if(id == 0)
{//子进程,执行程序替换exec*();//进程替换函数,待定exit(0);
}
pid_t rid = waitpid(id, NULL, 0);//阻塞等待子进程
printf("run done, rid: %d\n", rid);

  如果要执行命令,那就需要进行程序替换,但是程序替换我们介绍了七个接口,使用哪一个接口会比较好呢?根据前面所写的代码,我们已经有了 argv 这张命令行参数表,所以使用接口一定是要带 ‘v’ 的。

  带 ‘v’ 的接口也有三个,execvp 接口是最好的选择,为什么大家可以自己思考一下,很简单:

	execvp(argv[0], argv);//根据命令在环境变量里查找,在根据选项做出对应的动作

  同样为了代码可读性,我们将其封装为一个接口:

void Execute()
{pid_t id = fork();if(id == 0){//child process execvp(argv[0], argv);exit(1);}pid_t rid = waitpid(id, NULL, 0);printf("run done, rid: %d\n", rid);
}

在这里插入图片描述

  但是这里我们自定义shell只能运行一次,为了让命令一直能运行下去,我们就得循环执行:

int main()
{while(1){char commandline[CMD_SIZE];Interactive(commandline, CMD_SIZE); //对命令行字符串切割Split(commandline, CMD_SIZE);//执行命令Execute();}return 0;
}

在这里插入图片描述
  这样,我们的shell就初具雏形了!


✈️内建命令处理
🚩 cd命令处理

  我们来看这样一个现象:

在这里插入图片描述
  命名我已经切换目录很多次了,但是为什么目录没有改变呢?其实这是因为我们一直是在使用子进程执行命令的,所以仅仅是子进程一直在切换目录,父进程的目录却一直不变。

  所以向cd 这种命令,我们就不能交给子进程操作,而这样的命令我们称为 内建命令

  为了解决内建命令,我们可以 把cd 命令来单独处理,用一个接口封装。在执行命令之前,检测输入的命令是否是内建命令,如果是,则处理内建命令,如果不是则直接跳过,执行其他命令。

  我们根据封装接口的返回值判断是否为cd 命令,在选择跳过还是处理命令,那么在接口内部的实现。

int BuildinCmd()
{int ret = 0;if(strcmp("cd", argv[0]) == 0)//通过字符串匹配检测是不是cd 命令{ret = 1;//处理cd 命令}return ret;
}

  处理cd 命令之前我们得先了解cd 命令有哪些特殊表示,cd 命令无外乎:cd -,cd ~,cd /工作目录或文件/,cd。其中只有cd 是不带空格的,其行为是:

在这里插入图片描述
  如果cd 不带任何选项,那么其行为就是 切换到家目录。知道了这种特殊情况之后就好办了,除了这个不带选型的命令以外,其他的命令全部要根据选项处理,那么就要根据选项切换目录了,我们可以使用 chdir 接口切换目录:

在这里插入图片描述

const char* Home()
{return getenv("HOME");//从HOME环境变量获取当前系统的家目录
}int BuildinCmd()
{int ret = 0;if(strcmp("cd", argv[0]) == 0)//通过字符串匹配检测是不是cd 命令{ret = 1;//处理cd 命令char* target = argv[1];if(!target) target = Home();chdir(target);}return ret;
}int main()
{while(1){char commandline[CMD_SIZE];Interactive(commandline, CMD_SIZE); //对命令行字符串切割Split(commandline, CMD_SIZE);//处理内建命令int n = BuildinCmd();if(n) continue;//执行命令Execute();}return 0;
}

在这里插入图片描述
  这样cd 命令自由的使用了,切换目录也丝毫不费力了。


🚩 路径显示问题

  这里还有一个很明显的错误行为,我的命令行解释器的路径从开始就没有变过,其实是因为我们没有更新PWD环境变量,我们可以手动给当前进程更新环境变量,使用一个数组存储当前目录,再使用 putenv 将环境变量导出:

在这里插入图片描述

char pwd[CMD_SIZE];//定义全局数组int BuildinCmd()
{int ret = 0;if(strcmp("cd", argv[0]) == 0)//通过字符串匹配检测是不是cd 命令{ret = 1;//处理cd 命令char* target = argv[1];if(!target) target = Home();chdir(target);snprintf(pwd, CMD_SIZE, "PWD=%s", target);//将改变后的路径以 PWD=...的形式写入进pwd数组putenv(pwd);//此时数组内容为PWD=...此时putenv就可以更改环境变量了}return ret;
}

在这里插入图片描述
  刚才的问题解决了…吗??并没有,我们使用cd …或者cd -这种命令的时候路径就显示不出来了,虽然说我们这么写的代码不对,但是我们思路是对的,更新PWD环境变量,那么我们只好使用 Linux 提供的 getcwd 接口了:

在这里插入图片描述

  这个接口可以 获取当前工作目录的绝对路径。

int BuildinCmd()
{int ret = 0;if(strcmp("cd", argv[0]) == 0){ret = 1;char* target = argv[1];if(!target) target = Home();chdir(target);char tmp[1024];getcwd(tmp, 1024);//获取当前工作目录snprintf(pwd, CMD_SIZE, "PWD=%s", tmp);putenv(pwd);}return ret;
}

在这里插入图片描述

  这样就可以正常切换目录了。


🚩 export命令处理

  当我们在 自定义 Shell 中导入一个新的环境变量时,也是由子进程进行程序替换完成这件事的,所以,当我们使用hell进行env时,是看不到导入的环境变量的:

在这里插入图片描述

  所以,export也是一个内建命令,那么我们就需要在对应的接口里处理export命令:

char env[CMD_SIZE];//全局数组,接收环境变量int BuildinCmd()
{int ret = 0;if(strcmp("cd", argv[0]) == 0){//...}else if(strcmp("export", argv[0]) == 0)//处理export内建命令{ret = 1;if(argv[1]){strcpy(env, argv[1]);//将需要导入的环境变量放到数组当中putenv(env);//使用接口导入环境变量}}return ret;
}

  此处理方法与cd命令类似,仔细看注释也是很好理解的:

在这里插入图片描述


🚩 echo 命令处理

  我们曾经在shell中演示过 echo的各种用法,其中有 echo $? 表示上一个进程的退出码,除此之外,还有:echoecho $env_nameecho ...,这些特殊情况我们依旧需要处理。

  首先,比较特殊的就是 echo $?这个命令,这个命令需要显示上一个进程的退出码,而获取进程的退出码,这个时候我们就需要先在全局范围内设置退出码变量:

int lastcode = 0;//退出码

  退出码是在执行完进程之后返回的结果,所以必定要在 execute 接口内接收执行命令的退出码(进程退出码相关知识):

int lastcode = 0;void Execute()
{pid_t id = fork();if(id == 0){//child process execvp(argv[0], argv);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);//使用阻塞等待if(rid == id) lastcode = WEXITSTATUS(status);//保存退出码
}

  而echo 命令也是一个内建命令。它是在 shell 程序中提供的命令,用于在终端输出文本或环境变量的值。所以我们也需要在内建命令中处理echo命令:

int BuildinCmd()
{int ret = 0;if(strcmp("cd", argv[0]) == 0){ret = 1;//...}else if(strcmp("export", argv[0]) == 0){ret = 1;//...}else if(strcmp("echo", argv[0]) == 0)//处理echo命令{ret = 1;if(argv[1] == NULL)//单单echo命令{printf("\n");//仅仅换行}else{if(argv[1][0] == '$')//argv是一个指针数组,相当于 *(argv[1]),这样获取对你理解有帮助{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;
}

在这里插入图片描述


🚀自定义Shell源码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>#define CMD_SIZE 1024
#define MAX_ARGC 64
#define SEP " "int lastcode = 0;char* argv[MAX_ARGC];
char pwd[CMD_SIZE];
char env[CMD_SIZE];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";
}char* Home()
{return getenv("HOME");
}void Interactive(char out[], int size)
{printf("[%s@%s %s]$ ",UserName(), HostName(), CurrentWorkDir());fgets(out, CMD_SIZE, stdin);out[strlen(out) - 1] = '\0';
}void Split(char in[], int size)
{int i = 0;argv[i++] = strtok(in, SEP);//进行子串切割while(argv[i++] = strtok(NULL, SEP));if(strcmp(argv[0], "ls") == 0){argv[i - 1] = "--color";argv[i] = NULL;}
}void Execute()
{pid_t id = fork();if(id == 0){//child process execvp(argv[0], argv);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) lastcode = WEXITSTATUS(status);//保存退出码
}int BuildinCmd()
{int ret = 0;if(strcmp("cd", argv[0]) == 0){ret = 1;char* target = argv[1];if(!target) target = Home();chdir(target);char tmp[CMD_SIZE];getcwd(tmp, CMD_SIZE);snprintf(pwd, CMD_SIZE, "PWD=%s", tmp);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)//单单echo命令{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;
}int main()
{while(1){char commandline[CMD_SIZE];Interactive(commandline, CMD_SIZE); //对命令行字符串切割Split(commandline, CMD_SIZE);//处理内建命令int n = BuildinCmd();if(n) continue;//执行命令Execute();}return 0;
}

  自定义Shell目前就到此为止,当然你可以根据你的喜好去在此基础上拓展更多内容,更加完善这个Shell。


在这里插入图片描述
  如果这篇文章对您有用的话,还望三连支持博主~~

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

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

相关文章

.NET 检测地址/主机/域名是否正常

&#x1f331;PING 地址/主机名/域名 /// <summary>/// PING/// </summary>/// <param name"ip">ip</param>/// <returns></returns>public static bool PingIp(string ip){System.Net.NetworkInformation.Ping p new System.N…

OpenAI 新推出 AI 问答搜索引擎——SearchGPT 震撼登场

您的浏览器不支持 video 标签。 OpenAI-SearchGPT 近日&#xff0c;OpenAI 曝光了自己的一款令人瞩目的 AI 问答搜索引擎——SearchGPT。这款搜索引擎带来了全新的搜索体验&#xff0c;给整个行业带来了巨大的压力。 SearchGPT 支持多种强大的功能。首先&#xff0c;它能够通过…

蓝桥杯练习系统(算法训练)ALGO-949 勇士和地雷阵

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 勇士们不小心进入了敌人的地雷阵&#xff08;用n行n列的矩阵表示&#xff0c;*表示某个位置埋有地雷&#xff0c;-表示某个…

ARP防火墙能够为网络安全贡献什么样的力量

ARP防火墙&#xff08;Address Resolution Protocol Firewall&#xff09;作为网络安全的一环&#xff0c;起到保护网络免受ARP欺骗攻击的关键作用。今天德迅云安全给您介绍ARP防火墙的相关方面&#xff0c;帮助您深入了解和认识这一关键的安全措施。 网络安全对于现代社会的信…

金三银四面试题(二十四):享元模式知多少?

什么是享元模式 享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构型设计模式&#xff0c;旨在通过共享对象来减少内存使用&#xff0c;从而提高性能。它主要用于处理大量细粒度对象的情况&#xff0c;通过将这些对象的可共享部分&#xff08;内部状态&#xff09…

毫米波雷达原理(含代码)(含ARS548 4D毫米波雷达数据demo和可视化视频)

毫米波雷达原理 1. 传统毫米波雷达1.1 雷达工作原理1.2 单目标距离估计1.3 单目标速度估计1.4 单目标角度估计1.5 多目标距离估计1.6 多目标速度估计1.7多目标角度估计1.7 总结 3. FMCW雷达数据处理算法4. 毫米波雷达的目标解析(含python代码)5. ARS548 4D毫米波雷达数据demo(含…

MYSQL从入门到精通(二)

1、MYSQL高级概述 【1】架构概述 【2】索引优化 【3】查询截取 【4】mysql锁机制 【5】主从复制 2、MYSQL概述 【1】mysql内核 【2】sql优化工程师 【3】mysql服务器的优化 【4】各种参数常量设定 【5】查询语句优化 【6】主从复制 【7】软硬件升级 【8】容灾百分 【9】sql编…

Flutter笔记:Widgets Easier组件库(1)使用各式边框

Flutter笔记 Widgets Easier组件库&#xff08;1&#xff09;&#xff1a;使用边框 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress o…

Leetcode—377. 组合总和 Ⅳ【中等】

2024每日刷题&#xff08;124&#xff09; Leetcode—377. 组合总和 Ⅳ 算法思想 实现代码 class Solution { public:int combinationSum4(vector<int>& nums, int target) {vector<unsigned long long>dp(target 1);dp[0] 1;for(int i 1; i < target;…

React、React Router 和 Redux 常用Hooks 总结,提升您的开发效率!

Hooks 是 React 16.8 中引入的一种新特性&#xff0c;它使得函数组件可以使用 state 和其他 React 特性&#xff0c;从而大大提高了函数组件的灵活性和功能性。下面分别总结React、React Router 、Redux中常用的Hooks。 常用Hooks速记 React Hooks useState&#xff1a;用于…

社交媒体数据恢复:WorldTalk

WorldTalk数据恢复方法 在本文中&#xff0c;我们将探讨如何恢复在WorldTalk中删除的信息。请注意&#xff0c;这些步骤并不是专门针对WorldTalk软件设计的&#xff0c;而是基于一般的手机数据恢复流程。由于WorldTalk是一款全球5亿人使用的交友APP&#xff0c;用户分别来自中…

EDA(一)Verilog

EDA&#xff08;一&#xff09;Verilog Verilog是一种用于电子系统设计自动化&#xff08;EDA&#xff09;的硬件描述语言&#xff08;HDL&#xff09;&#xff0c;主要用于设计和模拟电子系统&#xff0c;特别是在集成电路&#xff08;IC&#xff09;和印刷电路板&#xff08;…

TCP 协议

TCP协议段格式 源/目的端口号&#xff1a;表示数据是从哪个进程来&#xff0c;到哪个进程去。 序号&#xff1a;发送数据的序号。 确认序号&#xff1a;应答报文的序号&#xff0c;用来回复发送方的。 4 位首部长度&#xff1a;一个 TCP 报头&#xff0c;长度是可变的&#xff…

zotero better notes报错:Error: ReferenceError: topItem is not defined

我的自定义笔记模板名称是&#xff1a;简约风格 然后就遇到了以下报错&#xff1a; Error: ReferenceError: topItem is not defined 解决办法&#xff1a; 将模板名称前面加上[Item] 之后就可以正常导入笔记模板了~

Node.js -- 包管理工具

文章目录 1. 概念介绍2. npm2.1 npm 下载2.2 npm 初始化包2.3 npm 包(1) npm 搜索包(2) npm 下载安装包(3) require 导入npm 包的基本流程 2.4 开发依赖和生产依赖2.5 npm 全局安装(1) 修改windows 执行策略(2) 环境变量Path 2.6 安装包依赖2.7 安装指定版本的包2.8 删除依赖2.…

FIFO Generate IP核使用——AXI接口FIFO简介

AXI接口FIFO是从Native接口FIFO派生而来的。AXI内存映射接口提供了三种样式&#xff1a;AXI4、AXI3和AXI4-Lite。除了Native接口FIFO支持的应用外&#xff0c;AXI FIFO还可以用于AXI系统总线和点对点高速应用。 AXI接口FIFO不支持Builtin FIFO和 Shift Register FIFO配置。 当…

分布式与一致性协议之Paxos算法(三)

Paxos算法 兰伯特关于Multi-Paxos的思考 领导者 我们可以通过引入领导者(Leader)节点来解决第一个问题。也就是说将领导者节点作为唯一提议者&#xff0c;如图所示。这样就不存在多个提议者同时提交提案的情况&#xff0c;也就不存在提案冲突的情况了。这里补充一点:在论文中…

CVE-2022-2602:unix_gc 错误释放 io_uring 注册的文件从而导致的 file UAF

前言 复现该漏洞只是为了学习相关知识&#xff0c;在这里仅仅做简单记录下 exp&#xff0c;关于漏洞的详细内容请参考其他文章&#xff0c;最后在 v5.18.19 内核版本上复现成功&#xff0c;v6.0.2 复现失败 漏洞利用 diff --git a/include/linux/skbuff.h b/include/linux/s…

保存钉钉群直播回放下载:直播回放下载步骤详解

今天&#xff0c;我们就来拨开云雾&#xff0c;揭开保存钉钉群直播回放的神秘面纱。教会你们如何下载钉钉群直播回放 首先用到的工具我全部打包好了&#xff0c;有需要的自己下载一下 钉钉群直播回放工具下载&#xff1a;https://pan.baidu.com/s/1WVMNGoKcTwR_NDpvFP2O2A?p…

从零开始学AI绘画,万字Stable Diffusion终极教程(一)

【第1期】SD入门 2022年8月&#xff0c;一款叫Stable Diffusion的AI绘画软件开源发布&#xff0c;从此开启了AIGC在图像上的爆火发展时期 率先学会SD的人&#xff0c;已经挖掘出了越来越多AI绘画有趣的玩法 从开始的AI美女、线稿上色、真人漫改、头像壁纸 到后来的AI创意字、AI…