鸿蒙内核源码分析(工作模式篇) | CPU的七种工作模式

本篇说清楚CPU的工作模式

工作模式(Working mode) 也叫操作模式(Operating mode)又叫处理器模式(Processor mode),是 CPU 运行的重要参数,决定着处理器的工作方式,比如如何裁决特权级别和报告异常等。
系列篇为方便理解,统一叫工作模式,CPU的工作模式.

正如一个互联网项目的后台管理系统有权限管理一样,CPU工作是否也有权限(模式)? 一个成熟的软硬件架构,肯定会有这些设计,只是大部分人不知道,也不需要知道,老百姓就干好老百姓的活就行了,有工作能吃饱饭就知足了,宫的事你管那么多干嘛,你也管不了.

应用程序就只关注应用功能,业务逻辑相关的部分就行了,底层实现对应用层屏蔽的越干净系统设计的就越优良.

但鸿蒙内核源码分析系列篇的定位就是要把整个底层解剖,全部掰开,看看宫里究竟发生了么事.从本篇开始要接触大量的汇编的代码,将鸿蒙内核的每段汇编代码一一说明白.如此才能知道最开始的开始发生了什么,最后的最后又发生了什么.

七种模式

先看一张图,图来源于 ARM720T.pdf第43页,在ARM体系中,CPU很像有七个老婆的韦小宝,工作在以下七种模式中:

  • 用户模式(usr):该模式是用户程序的工作模式,它运行在操作系统的用户态,它没有权限去操作其它硬件资源,只能执行处理自己的数据,也不能切换到其它模式下,要想访问硬件资源或切换到其它模式只能通过软中断或产生异常。

  • 快速中断模式(fiq):快速中断模式是相对一般中断模式而言的,用来处理高优先级中断的模式,处理对时间要求比较紧急的中断请求,主要用于高速数据传输及通道处理中。

  • 普通中断模式(irq):一般中断模式也叫普通中断模式,用于处理一般的中断请求,通常在硬件产生中断信号之后自动进入该模式,该模式可以自由访问系统硬件资源。

  • 管理模式(svc):操作系统保护模式,CPU上电复位和当应用程序执行 SVC 指令调用系统服务时也会进入此模式,操作系统内核的普通代码通常工作在这个模式下。

  • 终止模式(abt):当数据或指令预取终止时进入该模式,中止模式用于支持虚拟内存或存储器保护,当用户程序访问非法地址,没有权限读取的内存地址时,会进入该模式,

  • 系统模式(sys):供操作系统使用的高特权用户模式,与用户模式类似,但具有可以直接切换到其他模式等特权,用户模式与系统模式两者使用相同的寄存器,都没有SPSR(Saved Program Statement Register,已保存程序状态寄存器),但系统模式比用户模式有更高的权限,可以访问所有系统资源。

  • 未定义模式(und):未定义模式用于支持硬件协处理器的软件仿真,CPU在指令的译码阶段不能识别该指令操作时,会进入未定义模式。

除用户模式外,其余6种工作模式都属于特权模式

  • 特权模式中除了系统模式以外的其余5种模式称为异常模式
  • 大多数程序运行于用户模式
  • 进入特权模式是为了处理中断、异常、或者访问被保护的系统资源
  • 硬件权限级别:系统模式 > 异常模式 > 用户模式
  • 快中断(fiq)与慢中断(irq)区别:快中断处理时禁止中断

每种模式都有自己独立的入口和独立的运行栈空间. 系列篇之CPU篇 已介绍过只要提供了入口函数和运行空间,CPU就可以干活了.入口函数解决了指令来源问题,运行空间解决了指令的运行场地问题.
而且在多核情况下,每个CPU核的每种特权模式都有自己独立的栈空间.注意是特权模式下的栈空间,用户模式的栈空间是由用户(应用)程序提供的.

如何让这七种模式能流畅的跑起来呢? 至少需要以下解决三个基本问题.

  • 栈空间是怎么申请的?申请了多大?
  • 被切换中的模式代码放在哪里?谁来安排它们放在哪里?
  • 模式之间是怎么切换的?状态怎么保存?

这个汇编文件大概 500多行,非常重要,本篇受限于篇幅只列出一小部分,说清楚以上三个问题.系列其余篇中将详细说明每段汇编代码的作用和实现,可前往查阅.

1.异常模式栈空间怎么申请?

鸿蒙是如何给异常模式申请栈空间的

#define CORE_NUM                 LOSCFG_KERNEL_SMP_CORE_NUM //CPU 核数
#ifdef LOSCFG_GDB
#define OS_EXC_UNDEF_STACK_SIZE  512
#define OS_EXC_ABT_STACK_SIZE    512
#else
#define OS_EXC_UNDEF_STACK_SIZE  40
#define OS_EXC_ABT_STACK_SIZE    40
#endif
#define OS_EXC_FIQ_STACK_SIZE    64
#define OS_EXC_IRQ_STACK_SIZE    64
#define OS_EXC_SVC_STACK_SIZE    0x2000 //8K
#define OS_EXC_STACK_SIZE        0x1000 //4K@六种特权模式申请对应的栈运行空间
__undef_stack:.space OS_EXC_UNDEF_STACK_SIZE * CORE_NUM 
__undef_stack_top:__abt_stack:.space OS_EXC_ABT_STACK_SIZE * CORE_NUM
__abt_stack_top:__irq_stack:.space OS_EXC_IRQ_STACK_SIZE * CORE_NUM 
__irq_stack_top:__fiq_stack:.space OS_EXC_FIQ_STACK_SIZE * CORE_NUM
__fiq_stack_top:__svc_stack:.space OS_EXC_SVC_STACK_SIZE * CORE_NUM 
__svc_stack_top:__exc_stack:.space OS_EXC_STACK_SIZE * CORE_NUM
__exc_stack_top:

代码解读

  • 六种异常模式都有自己独立的栈空间
  • 每种模式的OS_EXC_***_STACK_SIZE栈大小都不一样,最大是管理模式(svc)8K,最小的只有40个字节. svc模式为什么要这么大呢?
    因为开机代码和系统调用代码的运行都在管理模式,系统调用的函数实现往往较复杂,最大不能超过8K.
    例如:某个系统调用中定义一个8K的局部变量,内核肯定立马闪蹦.因为栈将溢出,处理异常的程序出现了异常,后面就再也没人兜底了,只能是死局.
  • 鸿蒙是支持多核处理的,CORE_NUM表明,每个CPU核的每种异常模式都有自己的独立栈空间.注意理解这个是理解内核代码的基础.否则会一头雾水.

2.异常模式入口地址在哪?

再看一张图,图来源于 ARM720T.pdf 第56页

这就是一切一切的开始,指定所有异常模式的入口地址表,这就是规定,没得商量的.在低地址情况下.开机代码就是放在 0x00000000的位置, 触发开机键后,硬件将PC寄存器置为0x00000000,开始了万里长征的第一步.在系统运行过程中就这么来回跳.

    b   reset_vector            @开机代码b   _osExceptUndefInstrHdl 	@异常处理之CPU碰到不认识的指令b   _osExceptSwiHdl			@异常处理之:软中断b   _osExceptPrefetchAbortHdl	@异常处理之:取指异常b   _osExceptDataAbortHdl		@异常处理之:数据异常b   _osExceptAddrAbortHdl		@异常处理之:地址异常b   OsIrqHandler				@异常处理之:硬中断b   _osExceptFiqHdl				@异常处理之:快中断

以上是各个异常情况下的入口地址,在reset_vector_mp.S中都能找到,经过编译链接后就会变成

    b   0x00000000      @开机代码b   0x00000004 	    @异常处理之CPU碰到不认识的指令b   0x00000008		@异常处理之:软中断b   0x0000000C	    @异常处理之:取指异常b   0x00000010		@异常处理之:数据异常b   0x00000014		@异常处理之:地址异常b   0x00000018		@异常处理之:硬中断b   0x0000001C		@异常处理之:快中断

不管是主动切换的异常,还是被动切换的异常,都会先跳到对应的入口去处理.每个异常的代码都起始于汇编,处理完了再切回去.举个例子:
某个应用程序调用了系统调用(比如创建定时器),会经过以下大致过程:

  • swi指令将用户模式切换到管理模式(svc)
  • 在管理模式中先保存用户模式的现场信息(R0-R15寄存器值入栈)
  • 获取系统调用号,知道是调用了哪个系统调用
  • 查询系统调用对应的注册函数
  • 执行真正的创建定时器函数
  • 执行完成后,恢复用户模式的现场信息(R0-R15寄存器值出栈)
  • 跳回用户模式继续执行

各异常处理代码很多,不一一列出,本篇只列出开机代码,请尝试读懂鸿蒙内核开机代码,后续讲详细说明每行代码的用处.

开机代码
    reset_vector:   //开机代码/* clear register TPIDRPRW */mov     r0, #0					@r0 = 0mcr     p15, 0, r0, c13, c0, 4 	@c0,c13 = 0, C13为进程标识符 含义见 ARM720T.PDF 第64页/* do some early cpu setup: i/d cache disable, mmu disabled */ @禁用MMU, i/d缓存mrc     p15, 0, r0, c1, c0, 0  	@r0 = c1 ,c1寄存器详细解释见第64页bic     r0, #(1<<12) 			@位清除指令,清除r0的第11位bic     r0, #(1<<2 | 1<<0)		@清除第0和2位 ,禁止 MMU和缓存 0位:MMU enable/disable 2位:Cache enable/disablemcr     p15, 0, r0, c1, c0, 0 	@c1=r0 /* r11: delta of physical address and virtual address */@物理地址和虚拟地址的增量adr     r11, pa_va_offset @将基于PC相对偏移的地址pa_va_offset值读取到寄存器R11中ldr     r0, [r11]		  @将R11的值给r0sub     r11, r11, r0	  @r11 = r11 - r0	mrc     p15, 0, r12, c0, c0, 5              /* r12: get cpuid */ @获取CPUIDand     r12, r12, #MPIDR_CPUID_MASK @r12经过掩码过滤cmp     r12, #0	@当前是否为0号CPUbne     secondary_cpu_init @不是0号主CPU则调用secondary_cpu_init/* if we need to relocate to proper location or not */adr     r4, __exception_handlers            /* r4: base of load address */ @r4获得加载基地址ldr     r5, =SYS_MEM_BASE                   /* r5: base of physical address */@r5获得物理基地址subs    r12, r4, r5                         /* r12: delta of load address and physical address */ @r12=r4-r5 加载地址和物理地址的增量beq     reloc_img_to_bottom_done            /* if we load image at the bottom of physical address *//* we need to relocate image at the bottom of physical address */ldr     r7, =__exception_handlers           /* r7: base of linked address (or vm address) */ldr     r6, =__bss_start                    /* r6: end of linked address (or vm address) */sub     r6, r7                              /* r6: delta of linked address (or vm address) */add     r6, r4                              /* r6: end of load address */
异常的优先级

当同时出现多个异常时,该响应哪一个呢?这涉及到了异常的优先级,顺序如下

  • 1.Reset (highest priority).
  • 2.Data Abort.
  • 3.FIQ.
  • 4.IRQ.
  • 5.Prefetch Abort.
  • 6.Undefined Instruction, SWI (lowest priority).

可以看出swi的优先级最低,swi就是软中断,系统调用就是通过它来实现的.

3.异常模式怎么切换?

写应用程序经常会用到状态,来记录各种分支逻辑,传递参数.这么多异常模式,相互切换,中间肯定会有很多的状态需要保存.比如:如何能知道当前运行在哪种模式下?怎么查?去哪里查呢?
答案是: CPSR(一个) 和 SPSR(5个)
这些寄存器:

  • 保存有关最近执行的ALU操作的信息
  • 控制中断的启用和禁用
  • 设置处理器操作模式

CPSR 寄存器

CPSR(current program status register)当前程序的状态寄存器
CPSR有4个8位区域:标志域(F)、状态域(S)、扩展域(X)、控制域(C)
32 位的程序状态寄存器可分为4 个域:

    1. 位[31:24]为条件标志位域,用f 表示;
    1. 位[23:16]为状态位域,用s 表示;
    1. 位[15:8]为扩展位域,用x 表示;
    1. 位[7:0]为控制位域,用c 表示;

CPSR和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义.
而CPSR寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息.

CPSR的低8位(包括I、F、T和M[4:0])称为控制位,程序无法修改,
除非CPU运行于特权模式下,程序才能修改控制位

N、Z、C、V均为条件码标志位。它们的内容可被算术或逻辑运算的结果所改变,
并且可以决定某条指令是否被执行!意义重大!

  • CPSR的第31位是 N,符号标志位。它记录相关指令执行后,其结果是否为负.
    如果为负 N = 1,如果是非负数 N = 0.
  • CPSR的第30位是Z,0标志位。它记录相关指令执行后,其结果是否为0.
    如果结果为0.那么Z = 1.如果结果不为0,那么Z = 0.
  • CPSR的第29位是C,进位标志位(Carry)。一般情况下,进行无符号数的运算。
    加法运算:当运算结果产生了进位时(无符号数溢出),C=1,否则C=0。
    减法运算(包括CMP):当运算时产生了借位时(无符号数溢出),C=0,否则C=1。
  • CPSR的第28位是V,溢出标志位(Overflow)。在进行有符号数运算的时候,
    如果超过了机器所能标识的范围,称为溢出。

MSR{条件} 程序状态寄存器(CPSR 或SPSR)_<域>,操作数
MSR 指令用于将操作数的内容传送到程序状态寄存器的特定域中
示例如下:

	MSR CPSR,R0   @传送R0 的内容到CPSRMSR SPSR,R0   @传送R0 的内容到SPSRMSR CPSR_c,R0 @传送R0 的内容到CPSR,但仅仅修改CPSR中的控制位域

MRS{条件} 通用寄存器,程序状态寄存器(CPSR 或SPSR)
MRS 指令用于将程序状态寄存器的内容传送到通用寄存器中。该指令一般用在以下两种情况:

  1. 当需要改变程序状态寄存器的内容时,可用MRS 将程序状态寄存器的内容读入通用寄存器,修改后再写回程序状态寄存器。
  2. 当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后保存。
    示例如下:
MRS R0,CPSR   @传送CPSR 的内容到R0
MRS R0,SPSR   @传送SPSR 的内容到R0@MRS指令是唯一可以直接读取CPSR和SPSR寄存器的指令

SPSR 寄存器

SPSR(saved program status register)程序状态保存寄存器.五种异常模式下一个状态寄存器SPSR,用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。

  • 1、SPSR 为 CPSR 中断时刻的副本,退出中断后,将SPSR中数据恢复到CPSR中。
  • 2、用户模式和系统模式下SPSR不可用,所以SPSR寄存器只有5个

鸿蒙全栈开发全新学习指南

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大厂APP实战项目开发】

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:https://gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

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

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

相关文章

实时追踪维修进度,报修管理小程序让你省心又省力!

随着生活、工作节奏的日益加快&#xff0c;日常的售后报修、故障报修处理流程给我们带来种种困扰。我们都知道大多数企业、个人用户还在使用传统报修方式&#xff0c;如电话报修、纸质报修单等方式&#xff0c;不仅效率低下&#xff0c;而且难以追踪维修进度&#xff0c;给我们…

AI游戏外挂为何违法?

尊敬的读者们&#xff0c;大家好&#xff01;今天我想和大家探讨一个备受争议的话题——AI游戏外挂的合法性。近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;AI外挂逐渐成为游戏领域的一大毒瘤。那么&#xff0c;为什么AI游戏外挂会被视为违法行为呢&#xff1f;本…

【中级软件设计师】上午题16-算法(应试考试简略版)

上午题16-算法 1 回溯法1.1 n皇后问题 2 分治法3 动态规划3.1 0-1背包问题3.2 最长公共子序列3.3 矩阵连乘 4 贪心算法5 分支限界法总结 1 回溯法 深度优先方法搜索 1.1 n皇后问题 2 分治法 一般来说&#xff0c;分治算法在每一层递归上都有3个步骤 &#xff08;1&#xff…

C++——缺省参数与重载函数

目录 ​前言 一.缺省参数 1.1缺省参数概念 1.2缺省参数分类 注意事项&#xff1a; 二.函数重载 2.1函数重载概念 2.2c支持函数重载原理——命名修饰 前言 本篇文章主要讲述c中有关于缺少参数与函数重载的相关概念与实例&#xff0c;以下是本人拙见&#xff0c;如有错误…

【Linux学习笔记】一篇文章彻底搞定“Linux生产者与消费者“!

本章重点 1.生产者消费者模型2.posix信号量&#xff0c;以及读写锁。3. 理解基于读写锁的读者写者问题。 一. 生产者消费者模型 为何要使用生产者消费者模型 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯&#xff0…

专业音频修复软件:iZotope RX 11 for Mac 激活版

iZotope RX 专为满足后期制作专业人士的苛刻需求而设计的一款专业音频修复软件。iZotope RX 10添加了新的特性和功能&#xff0c;以解决当今后期项目中存在的一些最常见的修复问题&#xff0c;使其成为音频后期制作的最终选择。虽然包含许多其他新功能&#xff0c;但这里是新的…

微信小程序的设计与实现

微信小程序的设计与实现 目录 1.系统简述&#xff1a; 2.开发工具及相关技术&#xff1a; 2.1 HTML、WXSS、JAVASCRIPT技术 2.2 Vanilla框架 2.3 uni-app框架 2.4 MYSQL数据库 3.工程结构及其说明&#xff1a; 4.主要功能展示 4.1登录 4.2 注册 4.3 首页…

【C++11】C++11类与模板语法的完善

目录 一&#xff0c;新的类功能 1-1&#xff0c;默认成员函数 1-2&#xff0c;强制生成关键字 二&#xff0c;可变参数模板 2-1&#xff0c;模板参数包 2-3&#xff0c;模板参数包的实际运用 2-2&#xff0c;STL容器empalce的相关接口 三&#xff0c;模板参数包和empla…

002.反应式编程的必要性

在实际应用程序中&#xff0c;您可以在许多情况下发现可能的时变变量—例如&#xff0c;GPS位置、温度、鼠标坐标&#xff0c;甚至文本框的内容。所有这些都有一个随时间变化的值应用程序会发生反应&#xff0c;因此是时变的。还有一点值得一提时间本身就是一个时变;它的值一直…

Unicode字符集和UTF编码

文章目录 前言一、字符集和编码方式二、unicode字符集utf32编码utf8编码utf8编码函数示例utf8解码函数示例 utf16编码utf16编码解码函数示例 总结 前言 本文详细介绍 u n i c o d e unicode unicode 字符集和其相关的三种编码方式&#xff1a; u t f 8 utf8 utf8&#xff0c;…

华为认证存储HCIE有用吗?

首先&#xff0c;对于个人来说&#xff0c;获得华为存储认证可以证明其具备信息存储技术的专业能力 1.专业认可&#xff1a;获得华为存储认证&#xff0c;尤其是HCIE-Storage级别的证书&#xff0c;意味着持有者对信息存储技术有着全面深入的理解&#xff0c;能够设计、部署、…

JPA@Entry报错Could not determine recommended JdbcType for Java type

问题很明显&#xff0c;无法自动决定类型&#xff0c;那就手动告诉该字段。 一、直接上解决方案 如果是一对一的关系用 OneToOne 如果是一对多的关系用 OneToMany 如果是多对一的关系用 ManyToOne 二、另一个无空构造函数的问题 使用注解后&#xff0c;注解报错找不到空的…

实训八:使用jQuery技术实现企业信息展示系统的相关功能

实训八:使用jQuery技术实现企业信息展示系统的相关功能 1.题目 使用jQuery技术实现企业信息展示系统的相关功能。 2.目的 (1)掌握jQuery的基本知识。 (2)掌握jQuery的应用方法。 (3)进一步理解Ajax程序的设计方法。 (4)会利用所学知识设计简单的应用程序。 3.内容 用jQuery技术…

【SpringBoot记录】从基本使用案例入手了解SpringBoot-数据访问-更改DataSource(2)

前言 通过上一个数据访问基本案例成功可以发现&#xff0c;SpringBoot在数据访问案例中也做了许多自动配置&#xff0c;上节只分析了其中的Properties。 而在自动配置包的jdbc下 还有其他配置文件。 根据名称可以大致了解他们的作用&#xff1a; DataSourceAutoConfiguration…

如何8步完成hadoop单机安装

前言 Hadoop是一个开源框架&#xff0c;用于存储和处理大规模数据集。 系统要求 Ubuntu 20.044GB&#xff08;建议8GB&#xff09;hadoop-3.3.6 步骤1&#xff1a;更新系统 打开终端并输入以下命令来更新您的系统&#xff1a; apt update 步骤2&#xff1a;安装Java Had…

uniapp 使用renderjs的一些详细介绍

一、简介 官方链接&#xff1a;uniapp官网中的renderjs方法的详细介绍 二、renderjs 定义 renderjs是一个运行在视图层的js。它比WXS更加强大。它只支持app-vue和web。 作用&#xff1a; 大幅降低逻辑层和视图层的通讯损耗&#xff0c;提供高性能视图交互能力。在视图层操作d…

.Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 发布到 Win7+

.Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 实测可以完整运行在 win7sp1/win10/win11. 如果用其他工具打包,还可以运行在mac/linux下, 传送门BlazorHybrid 发布为无依赖包方式 安装 WebView2Runtime 1.57 MB或136 MB 测试DEMO 发布为依赖包方式 安装 WebView2Runtime 1.…

python Pandas 操作

Pandas 介绍 Pandas 是一个功能强大的 Python 数据分析工具库&#xff0c;常用于数据处理与分析工作。它为 Python 提供了快速、灵活以及表达能力强的数据结构&#xff0c;旨在简化“实际工作中”的数据操作&#xff0c;使得 Python 成为一种强大而高效的数据分析环境。 核心特…

抱怨无用,行动破局

故事的开始 这个专栏&#xff0c;以及本文的目的&#xff0c;是为了记录我从创立盘多啦这个平台开始&#xff0c;到后续的发展历程的专栏。同时也是给自己一个坚持的动力和警醒的作用。 首先&#xff0c;我是一名程序员&#xff0c;并且对于自身感兴趣的东西&#xff0c;都有…