OS_lab——保护模式之GDT、 Descriptor、Selector、GDTR 及其之间关系

1. 保护模式的相关数据结构

保护模式必要的数据结构定义

•   GDT:即为 Global Descriptor Table(全局描述符表),又称段描述符表, 为保护模式下的一个数据结构。其中包含多个 descriptor,定义了段的起始地址,界限属性等。

•   Descriptor:段描述符,包含段基址,段界限,段属性。

•   Selector:选择子,其作用是表示段描述符符相对于 GDT 基址的偏移。

•   GDTRGDT 寄存器。其结构与 GDTPTR 类似,有 6 字节,前两字节为 GDT 界限,后 4 字节为 GDT 基地址。

Descriptor的定义

如下图所示,Descriptor 分别存储了段基址、段界限和相关属性。由于部分原因, 段基址与段界限被分别存储:

2. 从实模式到保护模式

pmtest1.asm代码展示

; ==========================================

; pmtest1.asm

; 编译方法:nasm pmtest1.asm -o pmtest1.bin

; ==========================================



%include    "pm.inc"    ; 常量, 宏, 以及一些说明



org 07c00h

jmp LABEL_BEGIN



[SECTION .gdt]

; GDT

;                                段基址,       段界限     , 属性

LABEL_GDT:         Descriptor       0,                0, 0           ;

空描述符

LABEL_DESC_CODE32: Descriptor       0, SegCode32Len - 1, DA_C + DA_32; 非

一致代码段

LABEL_DESC_VIDEO:  Descriptor 0B8000h,           0ffffh, DA_DRW      ;

显存首地址

; GDT 结束



GdtLen      equ $ - LABEL_GDT   ; GDT 长度

GdtPtr       dw  GdtLen - 1       ; GDT 界限

             dd  0                 ; GDT 基地址



; GDT 选择子

SelectorCode32      equ LABEL_DESC_CODE32   - LABEL_GDT

SelectorVideo       equ LABEL_DESC_VIDEO    - LABEL_GDT

; END of [SECTION .gdt]



[SECTION .s16]

[BITS   16]

LABEL_BEGIN:

mov ax, cs

mov ds, ax

mov es, ax

mov ss, ax

mov sp, 0100h



; 初始化 32 位代码段描述符

xor eax, eax

mov ax, cs

shl eax, 4

add eax, LABEL_SEG_CODE32

mov word [LABEL_DESC_CODE32 + 2], ax

shr eax, 16

mov byte [LABEL_DESC_CODE32 + 4], al

mov byte [LABEL_DESC_CODE32 + 7], ah



; 为加载 GDTR 作准备

xor eax, eax

mov ax, ds

shl eax, 4

add eax, LABEL_GDT           ; eax <- gdt 基地址

mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址



; 加载 GDTR

lgdt    [GdtPtr]



; 关中断

cli

; 打开地址线 A20

in  al, 92h

or  al, 00000010b

out 92h, al

; 准备切换到保护模式

mov eax, cr0

or  eax, 1

mov cr0, eax



; 真正进入保护模式

jmp dword SelectorCode32:0  ; 执行这一句会把 SelectorCode32 装入

cs,

; 并跳转到 Code32Selector:0  处

; END of [SECTION .s16]





[SECTION .s32]; 32 位代码段. 由实模式跳入.

[BITS   32]



LABEL_SEG_CODE32:

mov ax, SelectorVideo

mov g s, ax           ; 视频段选择子(目的)



mov edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。

mov ah, 0Ch                  ; 0000: 黑底    1100: 红字

mov al, 'P'

mov [gs:edi], ax



; 到此停止

jmp $



SegCode32Len    equ $ - LABEL_SEG_CODE32

; END of [SECTION .s32]

执行结果

从实模式到保护模式

1.  准备 GDT。

2.  用 lgdt 加载 gdtr。

3.  打开 A20。

4.  置 cr0  PE 位。

5.  跳转,进入保护模式。

jmp跳转:dword前缀

 pmtest1.asm 的第 71 行:jmp dword SelectorCode32:0 删除 dword 标识会导 致编译后偏移地址被截断,可能无法正确地跳转到指定的位置。 原因:此时操作 系统的运行环境已由实模式(16 位)转换为保护模式(32 ),但由于编译器的 限制,此段代码只能放置于 16 位汇编代码部分,因此需要 dword 标识避免 32  长度的偏移地址被截断,而发生未知错误。 测试代码的反编译源码如下所示:

GDT表的构造

Descriptor 的构造

段描述符的构造借助 pm.inc 文件来进行,分别对段基址、段界限与相关属性进行 定义,宏会根据定义将数据重组以满足规范的要求

Selector的构造

Selector 的值为段描述符位置与 GDT 表初始位置之差

GDT表的切换

由于代码在实模式将段的偏移地址存储在的 GDT 表的段基址中。 因此,在保护模 式下,程序使用事先定义好的 Selector 选择子即可实现 GDT 表的切换。

3.2. 由保护模式返回实模式

与从实模式进入保护模式的方法相反,但是由于进入实模式的相关操作需要在 16 位代码段文成,且由于高速缓存的原因,因此,由保护模式返回实模式共一下几 步:

1.  加载相关段寄存器。

2.   cr0 的 PE 位。

3.  跳转,返回 16 位代码段。

4.  设置实模式的段寄存器。

5.  关闭 A20。

6.  开中断

7.  返回实模式

执行效果展示

下图为 pmtest2.asm 代码的执行效果,可以看到,在引导入保护模式后,程序打 印了字符并回到了实模式下的 dos 系统:

4. LDT 切换

LDT (Local Descriptor Table)

LDT  GDT 相同,均为描述符表,但 LDT 表仅有部分作用范围,而 GDT 表则作用 于全局

pmtest3.asm代码片段展示

[SECTION .gdt]

...

LABEL_DESC_LDT:    Descriptor       0,        LDTLen - 1, DA_LDT     ;

LDT

...

SelectorLDT     equ LABEL_DESC_LDT      - LABEL_GDT

; END of [SECTION .gdt]

...

[SECTION .s32]; 32 位代码段. 由实模式跳入.

[BITS   32]

LABEL_SEG_CODE32:

...

; Load LDT

mov ax, SelectorLDT

lldt    ax

jmp SelectorLDTCodeA:0  ; 跳入局部任务

...

; END of [SECTION .s32]

...

; LDT

[SECTION .ldt]

ALIGN   32

LABEL_LDT:

;                             段基址       段界限      属性

LABEL_LDT_DESC_CODEA: Descriptor 0, CodeALen - 1, DA_C + DA_32 ; Code, 32 位

LDTLen      equ $ - LABEL_LDT

; LDT 选择子

SelectorLDTCodeA    equ LABEL_LDT_DESC_CODEA    - LABEL_LDT + SA_TIL

; END of [SECTION .ldt]

LDT表的构造

1.  在 GDT 表中定义 LDT 表所对应的代码段

2.  在 LDT 表中定义对应的 LDT 

–  在 LDT 表的选择子定义上,在代码偏移量的基础上,需要额外增加 一个属性 SA_TIL,该属性将在 pm,inc 宏中存储至选择子的 TL 部分, 用于辨识该选择子是 LDT 表的选择子

LDT表的使用

1.  使用 lldt 指令加载 LDT 表所在的段

2.  使用对应 LDT 表所对应的选择子进行跳转

3.  执行代码

执行效果展示

下图为 pmtest3.asm 代码的执行效果,可以看到,程序在 LDT 表段打印输出了字 母L:

5.权限访问与段间切换

权限访问规则

 IA32 的分段机制中,特权级总共有 4 个特权级别,从高到低分别是 0、1、2、3。数字越小表示的特权级越大。

CPL

CPL 是当前执行的程序或任务的特权级。它被存储在 cs  ss 的第 0 位和第 1  上。在通常情况下,CPL 等于代码所在的段的特权级。当程序转移到不同特权级 的代码段时,处理器将改变 CPL。一致代码段可以被相同或者更低特权级的代码 访问。当处理器访问一个与 CPL 特权级不同的一致代码段时,CPL 不会被改变。

DPL

DPL 表示段或者门的特权级。它被存储在段描述符或者门描述符的 DPL 字段中。 当当前代码段试图访问一个段或者门时,DPL 将会和 CPL 以及段或门选择子的 RPL 相比较,根据段或者门类型的不同,DPL 将会被区别对待:

•   数据段:DPL 规定了可以访问此段的最低特权级

•   非一致代码段(不使用调用门的情况下):DPL 规定访问此段的特权

•   一致代码段:DPL 规定了访问此段的最高特权级

RPL

RPL 是通过段选择子的第 0 位和第 1 位表现出来。处理器通过检查 RPL  CPL  确认一个访问请求是否合法。也就是说,如果 RPL 的数字比 CPL 大(数字越大特 权级越低),那么 RPL 将会起决定性作用,反之亦然。

特权级检验测试

使用 pmtest2.asm,将数据段描述符的 DPL 与其段选择子的 RPL 进行修改: 

[SECTION .gdt]

; GDT

;

LABEL_DESC_DATA:   Descriptor 0, DataLen-1, DA_DRW+DA_DPL1 ; Data

; GDT 结束



; GDT 选择子

SelectorData   equ LABEL_DESC_DATA - LABEL_GDT + SA_RPL3

; END of [SECTION .gdt]

调试结果如下所示:

调试程序报错:load_seg_reg (DS, 0x0023): RPL & CPL must be <= DPL 证明此 处违反了特权级管理的规则

段间切换

一下几种方法可以实现不同代码段之间的转移:

   使用 jmp  call 指令

  目标操作数包含目标代码段的段选择子

–  目标操作数指向一个包含目标代码段选择子的调用门描述符。

•   使用门描述符进行转移

其中,通过 jmp  call 所能进行的代码段间转移是非常有限的,对于非一致代码 段,只能在相同特权级代码段之间转移。遇到一 致代码段也最多能从低到高,而 CPL 不会改变。 若需要进行不同特权级之间的转移,则需要即运用门描述符 进行转移。

门描述符

门描述符的结构

调用门直接定义了目标代码对应段的选择子、入口偏移地址等一系列属性,可直 接进行代码段的跳转。 门描述符有以下 4 种:

   调用门

   中断门

   陷阱门

   任务门

调用门的使用

pmtest4.asm 代码片段展示

[SECTION .gdt]

; GDT

;                            段基址,       段界限     , 属性

LABEL_DESC_CODE_DEST: Descriptor 0,SegCodeDestLen-1, DA_C+DA_32; 非一致 代码段,32

...

; 门                               目标选择子,偏移,DCount, 属性

LABEL_CALL_GATE_TEST:  Gate  SelectorCodeDest,       0,            0,

DA_386CGate+DA_DPL0

...

; GDT 选择子

SelectorCodeDest    equ LABEL_DESC_CODE_DEST    - LABEL_GDT



SelectorCallGateTest    equ LABEL_CALL_GATE_TEST    - LABEL_GDT

; END of [SECTION .gdt]

...

[SECTION .s32]; 32 位代码段. 由实模式跳入.

[BITS   32]

...

; 测试调用门(无特权级变换),将打印字母 'C'

call    SelectorCallGateTest:0

...

jmp SelectorLDTCodeA:0  ; 跳入局部任务,将打印字母 'L'。

...

; END of [SECTION .s32]

使用 pm.inc 中的 Gate 宏进行门的调用,在代码片段中,此门所指向的位置为 SelectorCodeDest:0,即 LABEL_DESC_CODE_DEST 处的代码。

执行效果展示

程序进入调用门所指向的代码段,输出了字母 C

6. 使用调用门进行特权级的变换

调用门使用的特权检验

堆栈的特权级转换——TSS

由于在进行 call-ret 跳转时,堆栈会存储跳转前的 cs:eip 地址,并在子程序结 束时返回。但是在有特权级的代码跳转时,所调用的堆栈段也会发生改变。因此, 需要使用 TSS(Tasj-State Stack)来避免这类问题。

TSS 的结构

 TSS 辅助的转移过程

1.  根据目标代码段的DPL(新的 CPL) 从 TSS 中选择应该切换至哪个 ss  esp

2.  从 TSS 中读取新的 ss  esp。在这过程中如果发现 ss、esp 或者 TSS 界限 错误都会导致无效 TSS 异常(#TS)。

3.  对 ss 描述符进行检验,如果发生错误,同样产生#TS 异常。 同样产生#TS  m。mt 接出媒设

4.  暂时性地保存当前 ss  esp 的值。

5.  加载新的 ss  esp。

6.  将刚刚保存起来的 ss  esp 的值压入新栈。

7.  从调用者堆栈中将参数复制到被调用者堆栈(新堆栈)中,复制参数的数目 由调用门中 Param Count 一项来决定。如果 Param Count  0 的话,将不 会复制参数。

8.  将当前的 cs  eip 压栈。

9.  加载调用门中指定的新的 cs  eip,开始执行被调用者过程。 

  TSS 辅助的返回过程

1.  检查保存的 cs 中的 RPL 以判断返回时是否要变换特权级。

2.  加载被调用者堆栈上的 cs  eip (此时会进行代码段描述符和选择子类型 和特权级检验)。

3.  如果 ret 指令含有参数,则增加 esp 的值以跳过参数,然后 esp 将指向被 保存过 的调用者 ss  esp 。注意 ret 的参数必须对应调用    ParamCount 的值。

4.  加载 ss  esp,切换到调用者堆栈,被调用者的 ss  esp 被丢弃。在这 里将会进行 ss 描述符、esp 以及 ss 段描述符的检验。

5.  如果 ret 指令含有参数,增加 esp 的值以跳过参数(此时已经在调用者堆栈 中)。

6.  检查 dsesfsgs 的值,如果其中哪一个寄存器指向的段的 DPL 小于 CPL(此规则不适用于一致代码段),那么一个空描述符会被加载到该寄存 器。

使用调用门进行特权级转换

pmtest5.asm 实现了一个 ring3 特权级的代码(将在屏幕中打印数字 3);并初始  TSS,通过调用门引用 ring0 特权级的代码片段在屏幕上打印字母 C。 实验结 果如下图所示:

测试成功

1. 解决问题与动手改

1.1 GDTDescriptorSelectorGDTR 结构,及其含义是什么?他们的关联关 系如何?pm.inc 所定义的宏怎么使用?

       GDT(Global Descriptor Table 全局描述符表)又叫段描述符表,为保护模 式下的一个数据结构。其中包含多个 descriptor,定义了段的起始地址,界限属 性等,其作用是提供段式存储机制;

Descriptor 为段描述符,包含段基址、段界限、段属性;

       Selector 为选择子,可以对应一个描述符。在 pmtest1.asm 程序中,其对应 的就是描述符相对于 GDT 基址的偏移。

       GDT 是一个结构数组,包含多个 Descriptor,每个 Descriptor 都是 GDT 数组 的一个表项,存储各个段的段基址、段界限和属性。Selector 记录对应 Descriptor 相对于 GDT 基址(LABEL_GDT)的偏移、表类型(GDT/LDT)和段描述符特权。GDTR 则是记录了 GDT 表的基地址和界限。综合以上,就能够得到某个 Descriptor 的地 址。而保护模式下寻址就是先靠 GDTR 找到 GDT,而后根据 Descriptor 找到对应 段的地址,而后再加上段内偏移 offset,就获得某个线性地址。

程序中 Descriptor 由 pm.inc 中的宏 Descriptor 生成。宏的具体使用如下: 

a. 宏名 Descriptor,3 表明有三个参数,分别为段基址、界限、属性;

b. 第一行 dw 为两字节,决定了段界限;

c. 第二三行 dw  dd 确定了段基址 1、2;

d. 第四行 dw 两字节构成段属性和段界限 2;

e. 最后一行的 dw 两字节构成段基址 3。

1.2  从实模式到保护模式,关键步骤有哪些?为什么要关中断?为什么要打开 A20 地址线?从保护模式切换回实模式,又需要哪些步骤?

从实模式到保护模式步骤:

a. 准备 GDT,初始化 GDT 属性,将需要的段的描述符和选择子定义好; 

b. 初始化段;

c. 将 GDT 的物理地址填充到 GdtPtr 中,然后将填充的地址加载到寄存器 gdtr;

d. 关中断;

e. 打开 A20 地址线;

f. 置 cr0  PE 位;

g. 跳转到描述符对应段首地址,进入保护模式。

       需要关中断的原因是:保护模式下的中断处理机制和实模式下不同,如果开 启中断而对应的中断处理机制尚未完善将会出现错误。

       需要打开 A20 地址线的原因是:在 8086 CPU 中只 20 位地址总线,它的最大寻 址能力只能达到 1 MB。8086 设计当程序在访问 1 MB 以上的内存地址时,将从 0 地址开始“ 回卷 ”,也就是说当访问 1 MB  1 位时,实际访问的空间是 1 地址。 而在之后的 CPU 中,访问空间早已超过 1 MB,这就导致了不兼容。IBM 设计如过 A20 不被打开,则继续使用回滚机制,第二十个地址为是 0。如果打开 A20,则可 以正常访问 1 MB 以上的内存地址。

从实模式到保护模式步骤:

  1. 加载一个合适的描述符选择子到有关段寄存器;
  2. 跳转至 16 位代码段;
  3.  cr0  PE 位;
  4. 跳转回到实模式;
  5. 关闭 A20;
  6. 打开中断。

1.3  解释不同权限代码的切换原理,call, jmp,retf 使用场景如何,能够互换吗?

       对于 jmp 而言,长跳转和短跳转仅仅存在结果上的不同,短跳转对应段内跳 转,长跳转对应段间跳转。

       对 call 而言,由于 call 指令会影响堆栈,所以长调用和短调用会产生不同 影响,在短调用当中,call 指令会将下一条指令 eip 压栈,当遇到 ret 指令执行 时,该 eip 会被从堆栈中弹出。

       在长调用时,call 指令也会将 eip  cs 都压入栈中,遇到 retf 时会弹出 eip  cs,大致来说 call 相当于 push+jmp  ret 相当于 pop+jmp。

1.4  动手改:

 自定义添加 1  GDT 代码段、1  LDT 代码段,GDT 段内要对一个内存数据结 构写入一段字符串,然后 LDT 段内代码段功能为读取并打印该 GDT 的内容;

该功能参考 pmtest3.asm 实现,实验结果如下:

第一步:在.Data 段中修改 StrTest 内容

第二步:修改 32 味代码段,跳入 LDT 局部任务中

第三步:修改 LDT,CodeA 中的逻辑,将偏移设为 TestStr 的偏移量,最后输出到 屏幕上

 自定义 2  GDT 代码段 A、B,分属于不同特权级,功能自定义,要求实现 A-->B 的跳转,以及 B-->A 的跳转。

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

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

相关文章

pyinstaller生成的exe文件启动时间漫长的原因

加-F慢的原因是&#xff0c;pyinstaller把所有资源文件包括python解释器的依赖文件和库都打包到exe一个文件中&#xff0c;用户打开时&#xff0c;pyinstaller需要先执行一边解压操作&#xff0c;把依赖文件全部解压出来。慢就慢在这里。 如果不加-F&#xff0c;你会发现那些文…

STL标准库与泛型编程(侯捷)笔记3

STL标准库与泛型编程&#xff08;侯捷&#xff09; 本文是学习笔记&#xff0c;仅供个人学习使用。如有侵权&#xff0c;请联系删除。 参考链接 Youbute: 侯捷-STL标准库与泛型编程 B站: 侯捷 - STL Github:STL源码剖析中源码 https://github.com/SilverMaple/STLSourceCo…

常用服务器管理面板整理汇总

服务器管理面板是用于管理和控制服务器的软件&#xff0c;可以帮助管理员更轻松地进行服务器管理和维护。以下是几种常用的服务器管理面板&#xff1a; 1、宝塔面板【官网直达】 宝塔面板是一款服务器运维管理软件&#xff0c;支持Windows和Linux等操作系统&#xff0c;提供了…

《动手学深度学习》学习笔记 第6章 卷积神经网络

本系列为《动手学深度学习》学习笔记 书籍链接&#xff1a;动手学深度学习 笔记是从第四章开始&#xff0c;前面三章为基础知道&#xff0c;有需要的可以自己去看看 关于本系列笔记&#xff1a; 书里为了让读者更好的理解&#xff0c;有大篇幅的描述性的文字&#xff0c;内容很…

TypeScript 从入门到进阶之基础篇(六) 类型(断言 、推论、别名)| 联合类型 | 交叉类型

系列文章目录 TypeScript 从入门到进阶系列 TypeScript 从入门到进阶之基础篇(一) ts基础类型篇TypeScript 从入门到进阶之基础篇(二) ts进阶类型篇TypeScript 从入门到进阶之基础篇(三) 元组类型篇TypeScript 从入门到进阶之基础篇(四) symbol类型篇TypeScript 从入门到进阶…

鱼类识别Python+深度学习人工智能+TensorFlow+卷积神经网络算法

一、介绍 鱼类识别系统。使用Python作为主要编程语言开发&#xff0c;通过收集常见的30种鱼类&#xff08;‘墨鱼’, ‘多宝鱼’, ‘带鱼’, ‘石斑鱼’, ‘秋刀鱼’, ‘章鱼’, ‘红鱼’, ‘罗非鱼’, ‘胖头鱼’, ‘草鱼’, ‘银鱼’, ‘青鱼’, ‘马头鱼’, ‘鱿鱼’, ‘鲇…

如何批量自定义视频画面尺寸

在视频制作和编辑过程中&#xff0c;对于视频画面尺寸的调整是一项常见的需求。有时候&#xff0c;为了适应不同的播放平台或满足特定的展示需求&#xff0c;我们需要对视频尺寸进行批量调整。那么&#xff0c;如何实现批量自定义视频画面尺寸呢&#xff1f;本文将为您揭示这一…

github action

https://www.bilibili.com/video/BV1PM411B7um/?spm_id_frompageDriver&vd_sourcefd0f4be6d0a5aaa0a79d89604df3154a workflow pipeline

PyTorch 进阶指南,这个宝典太棒了

最新写了很多关于 Pytorch 的文章&#xff0c;主要针对刚刚接触 Pytorch 的同学&#xff0c;文章我给大家列出来了&#xff0c;喜欢可以从0开始学习&#xff1a; 小白学 PyTorch 系列&#xff1a;这一次&#xff0c;我准备了 20节 PyTorch 中文课程小白学 PyTorch 系列&#x…

【深度deepin】深度安装,jdk,tomcat,Nginx安装

目录 一 深度 1.1 介绍 1.2 与别的操作系统的优点 二 下载镜像文件及VM安装deepin 三 jdk&#xff0c;tomcat&#xff0c;Nginx安装 3.1 JDK安装 3.2 安装tomcat 3.3 安装nginx 一 深度 1.1 介绍 由深度科技社区开发的开源操作系统&#xff0c;基于Linux内核&#xf…

学完 Pinia 再也不想用 vuex 真香啊!!!!

&#x1f495;Pinia 注册 ✔ vue3 与 Pinia 注册 import { createApp } from vue import { createPinia } from pinia import App from ./App.vueconst app createApp()app.use(createPinia()) app.mount(#app)✔ vue2 与 Pinia 注册 import Vue from vue import App from …

java推荐系统:好友推荐思路

1.表的设计 表里面就两个字段&#xff0c;一个字段是用户id&#xff0c;另外一个字段是好友id&#xff0c;假如A跟B互为好友&#xff0c;那在数据库里面就会有两条数据 2.推荐好友思路 上面的图的意思是&#xff1a;h跟a的互为好友&#xff0c;a跟b&#xff0c;c&am…

Python笔记04-数据容器列表、元组、字符串、集合、字典

文章目录 listtuple 元组str序列&#xff08;切片&#xff09;setdict集合通用功能 Python中的数据容器&#xff1a; 一种可以容纳多份数据的数据类型&#xff0c;容纳的每一份数据称之为1个元素 每一个元素&#xff0c;可以是任意类型的数据&#xff0c;如字符串、数字、布尔等…

JavaWeb——新闻管理系统(Jsp+Servlet)之jsp新闻删除

java-ee项目结构设计 1.dao:对数据库的访问&#xff0c;实现了增删改查 2.entity:定义了新闻、评论、用户三个实体&#xff0c;并设置对应实体的属性 3.filter&#xff1a;过滤器&#xff0c;设置字符编码都为utf8&#xff0c;防止乱码出现 4.service:业务逻辑处理 5.servlet:处…

Qt/QML编程学习之心得:Linux下USB接口使用(25)

很多linux嵌入式系统都有USB接口,那么如何使用USB接口呢? 首先,linux的底层驱动要支持,在linux kernal目录下可以找到对应的dts文件,(device tree) usb0: usb@ee520000{compatible = "myusb,musb";status = "disabled";reg = <0xEE520000 0x100…

Unity中Shader序列帧动画(总结篇)

文章目录 前言一、半透明混合自定义调整1、属性面板2、SubShader中3、在片元着色器(可选)3、根据纹理情况自己调节 二、适配Build In Render Pipeline三、最终代码 前言 在前几篇文章中&#xff0c;我们依次解决了实现Shader序列帧动画所遇到的问题。 Unity中Shader序列图动画…

2.C++的编译:命令行、makefile和CMake

1. 命令行编译 命令行编译是指直接在命令行中输入以下指令&#xff1a; 预处理&#xff1a;gcc -E main.c -o main.i 编译&#xff1a;gcc -S main.i -o main.s 汇编&#xff1a;gcc -c main.s -o main.o 链接&#xff1a;gcc main.o -o main 命令汇总&#xff1a;gcc main.c …

LabVIEW开发自动读取指针式仪表测试系统

LabVIEW开发自动读取指针式仪表测试系统 在工业领域&#xff0c;尤其是煤矿、变电站和集气站等环境中&#xff0c;指针式仪表因其简单的结构、抗干扰能力强以及能适应高温高压等恶劣环境条件而被广泛应用于设备运行状态监视。然而&#xff0c;传统的人工读表方式不仅成本高昂&…

【Leetcode】移除后集合的最多元素数

目录 &#x1f4a1;题目描述 &#x1f4a1;思路 &#x1f4a1;总结 100150. 移除后集合的最多元素数 &#x1f4a1;题目描述 给你两个下标从 0 开始的整数数组 nums1 和 nums2 &#xff0c;它们的长度都是偶数 n 。 你必须从 nums1 中移除 n / 2 个元素&#xff0c;同时从 …

SpringMVC源码解析——HTTP请求处理(持续更新中)

在SpringMVC源码解析——DispatcherServlet的逻辑处理中&#xff0c;最后介绍到了org.springframework.web.servlet.DispatcherServlet的doDispatch方法中关于处理Web HTTP请求的核心代码是调用AbstractHandlerMethodAdapter类的handle方法&#xff0c;源码如下&#xff1a; /*…