《ORANGE‘s 一个操作系统的实现》--保护模式进阶
保护模式进阶
大内存读写
GDT段
;GDT
[SECTION .gdt]
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_C+DA_32; 非一致代码段, 32
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代码段, 16
LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW ; Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32; Stack, 32 位
LABEL_DESC_TEST: Descriptor 0500000h, 0ffffh, DA_DRW
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址
; GDT 结束GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限dd 0 ; GDT基地址; GDT 选择子
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT
SelectorData equ LABEL_DESC_DATA - LABEL_GDT
SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
SelectorTest equ LABEL_DESC_TEST - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
; END of [SECTION .gdt]
与认识保护模式当中的代码类似,这里也是定义了GDT的描述符、DT表的属性、选择子等内容
其中LABEL_DESC_TEST
的段基址被设定为了0500000h
远远超过实模式的寻址上限
LABEL_DESC_VIDEO
指向了显存的首地址,用于将特定字符显示在屏幕上
数据段
;数据段
[SECTION .data1]
ALIGN 32
[BITS 32]
LABEL_DATA:
SPValueInRealMode dw 0
; 字符串
PMMessage: db "In Protect Mode now. ^-^", 0 ; 在保护模式中显示
OffsetPMMessage equ PMMessage - $$
StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest equ StrTest - $$
DataLen equ $ - LABEL_DATA
; END of [SECTION .data1]
OffsetPMMessage
定义为PMMessage
相对于$$
(即LABEL_DATA
)的偏移地址
OffsetStrTest
定义为StrTest
相对于$$
(即LABEL_DATA
)的偏移地址
DataLen
定义了数据段的长度
堆栈段
; 全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:times 512 db 0TopOfStack equ $ - LABEL_STACK - 1; END of [SECTION .gs]
这段代码定义了一个512个字节的堆栈段LABEL_STACK
和一个栈顶指针TopOfStack
实模式->保护模式代码段
LABEL_BEGIN:mov ax, csmov ds, axmov es, axmov ss, axmov sp, 0100hmov [LABEL_GO_BACK_TO_REAL+3], axmov [SPValueInRealMode], sp; 初始化 16 位代码段描述符mov ax, csmovzx eax, axshl eax, 4add eax, LABEL_SEG_CODE16mov word [LABEL_DESC_CODE16 + 2], axshr eax, 16mov byte [LABEL_DESC_CODE16 + 4], almov byte [LABEL_DESC_CODE16 + 7], ah; 初始化 32 位代码段描述符xor eax, eaxmov ax, csshl eax, 4add eax, LABEL_SEG_CODE32mov word [LABEL_DESC_CODE32 + 2], axshr eax, 16mov byte [LABEL_DESC_CODE32 + 4], almov byte [LABEL_DESC_CODE32 + 7], ah; 初始化数据段描述符xor eax, eaxmov ax, dsshl eax, 4add eax, LABEL_DATAmov word [LABEL_DESC_DATA + 2], axshr eax, 16mov byte [LABEL_DESC_DATA + 4], almov byte [LABEL_DESC_DATA + 7], ah; 初始化堆栈段描述符xor eax, eaxmov ax, dsshl eax, 4add eax, LABEL_STACKmov word [LABEL_DESC_STACK + 2], axshr eax, 16mov byte [LABEL_DESC_STACK + 4], almov byte [LABEL_DESC_STACK + 7], ah
实模式代码段首先将各个段段首的真实地址写入描述符的段基址当中
注意这两行代码:
mov [LABEL_GO_BACK_TO_REAL+3], axmov [SPValueInRealMode], sp
这两行代码将实模式下的cs赋值给LABEL_GO_BACK_TO_REAL标签往后数第三、四个字节,将实模式下的sp赋值给SPValueInRealMode
其中LABEL_GO_BACK_TO_REAL+3
实际上是jmp 0:LABEL_REAL_ENTRY
中的0
,观察下图jmp
指令的内存占用不难发现,byte4
和byte5
指向了jmp
指令的段基址,因此这里是修改jmp
指令的段基址到实模式下的cs
; 为加载 GDTR 作准备xor eax, eaxmov ax, dsshl eax, 4add eax, LABEL_GDT ; eax <- gdt 基地址mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址; 加载 GDTRlgdt [GdtPtr]; 关中断cli; 打开地址线A20in al, 92hor al, 00000010bout 92h, al; 准备切换到保护模式mov eax, cr0or eax, 1mov cr0, eax; 真正进入保护模式jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处
然后加载GDTR,在关中断后打开地址线A20切换到保护模式
保护模式代码段
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]LABEL_SEG_CODE32:mov ax, SelectorDatamov ds, ax ; 数据段选择子mov ax, SelectorTestmov es, ax ; 测试段选择子mov ax, SelectorVideomov gs, ax ; 视频段选择子mov ax, SelectorStackmov ss, ax ; 堆栈段选择子mov esp, TopOfStack
保护模式开始先将各个数据段赋给对应的寄存器
; 下面显示一个字符串,数据段的基址就是LABLE_DATA的物理地址mov ah, 0Ch ; 0000: 黑底 1100: 红字xor esi, esixor edi, edimov esi, OffsetPMMessage ; 源数据偏移(相对于LABEL_DESC_DATA的偏移量)mov edi, (80 * 10 + 0) * 2 ; 目的数据偏移。屏幕每行有 80 个字符,每个字符占用两个字节(一个用于字符,一个用于属性)cld
.1: ;刷新标志寄存器lodsb ; 从 esi 指向的内存地址加载一个字节到 al 寄存器,并递增 esitest al, al ; 测试 al 寄存器的值是否为零jz .2 ; 为0则是终止符,表示显示完毕mov [gs:edi], ax ; add edi, 2jmp .1
.2: ; 显示完毕
然后将ah
置为0ch
,设置屏幕属性为黑底红字,将esi
和edi
清零[,然后将OffsetPMMessage
(即PMMessage
相对于LABEL_DATA
的偏移地址)送到esi
,设定目标数据的偏移地址并刷新标志寄存器
然后进入循环,从esi
指向的内存地址加载一个字节到al
寄存器,并递增esi
,然后判断al
寄存器是否为0('\0'
),如果不是终止字符,则向段基址为gs
(即SelectorVideo
),偏移地址为edi
的显存地址中写入数据ax
(ah
为显示属性,al
为显示字符)
.2: ; 显示完毕call DispReturncall TestReadcall TestWritecall TestRead; 到此停止jmp SelectorCode16:0
显示完毕后依次调用
DispReturn
:模拟回车显示TestRead
:从内存中读内容到显存TestWrite
:向内存中写内容
从先后两次调用TestRead
得到的不同的显示结果可以判断是否成功进行了大内存读写
TestRead
TestRead:xor esi, esimov ecx, 8
.loop:mov al, [es:esi]call DispALinc esiloop .loopcall DispReturnret
函数首先设定ecx
为8,表示读取8个字节的数据
随后进入循环,从es:esi
(即SelectorTest
:esi
)中读取一个字节的内容到al,并调用DispAL
函数将它以16进制的方式打印出来
循环结束后模拟打印一个回车并结束函数
TestWrite
TestWrite:push esipush edixor esi, esixor edi, edimov esi, OffsetStrTest ; 源数据偏移cld
.1:lodsbtest al, aljz .2mov [es:edi], alinc edijmp .1
.2:pop edipop esiret
函数将esi
赋值为OffsetStrTest
(即StrTest
的偏移地址)
然后进入循环,将整个字符串写入es
(即SelectorTest
)中
保护模式->实模式
[SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:; 跳回实模式:mov ax, SelectorNormalmov ds, axmov es, axmov fs, axmov gs, axmov ss, axmov eax, cr0and al, 11111110bmov cr0, eaxLABEL_GO_BACK_TO_REAL:jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值Code16Len equ $ - LABEL_SEG_CODE16
程序首先将实模式的选择子赋给ax
,并初始化其他寄存器,然后置cr0
寄存器PE
标志位为0,设定程序处于实模式
jmp 0:LABEL_REAL_ENTRY
指令已经在前面修改为jmp 实模式下cs对应的地址:LABEL_REAL_ENTRY
,因此程序再次跳转到实模式(即[SECTION .s16]
代码段)下LABEL_REAL_ENTRY
标签处
LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里mov ax, csmov ds, axmov es, axmov ss, axmov sp, [SPValueInRealMode]in al, 92h ; `.and al, 11111101b ; | 关闭 A20 地址线out 92h, al ; /sti ; 开中断mov ax, 4c00h ; `.int 21h ; / 回到 DOS
; END of [SECTION .s16]
最后关闭A20
地址线并开中断,正式回到实模式
LDT(Local Descriptor Table)
本节内容仅仅介绍pmtest3.asm
相对于pmtest2.asm
做的改变,并省略初始化描述符代码
GDT中添加LDT描述符
[SECTION .gdt]
; GDT
;......
; 段基址, 段界限 , 属性
LABEL_DESC_LDT: Descriptor 0, LDTLen - 1, DA_LDT ; LDT; GDT 结束
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限dd 0 ; GDT基地址; GDT 选择子
;......
SelectorLDT equ LABEL_DESC_LDT - LABEL_GDT
;......
; END of [SECTION .gdt]
这部分完成了在GDT中定义LDT描述符的过程,其中LABEL_DESC_LDT
的段基址被填充为标签LABEL_LDT
的真实地址
问:为什么要在GDT中定义LDT?
答:全局描述符表(GDT)是用于存储段描述符的表,而局部描述符表(LDT)是特定于某个进程的描述符表。LDT允许进程拥有自己的段描述符,这有助于实现内存保护和隔离。
LDT段
; 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]
LABEL_LDT_DESC_CODEA
的基址被填充为标签LABEL_CODE_A
的真实地址
注意SelectorLDTCodeA
(即选择子)的定义中多了一项SA_TIL
,其定义为SA_TIL EQU 4
,即将选择子的TIL
标志位设置为1
TIL
标志位用于区分GDT
选择子和LDT
选择子,如果TIL
为1,则系统会从当前LDT
中寻找相应的描述符
LDT代码段
; CodeA (LDT, 32 位代码段)
[SECTION .la]
ALIGN 32
[BITS 32]
LABEL_CODE_A:mov ax, SelectorVideomov gs, ax ; 视频段选择子(目的)mov edi, (80 * 12 + 0) * 2 ; 屏幕第 10 行, 第 0 列。mov ah, 0Ch ; 0000: 黑底 1100: 红字mov al, 'L'mov [gs:edi], ax; 准备经由16位代码段跳回实模式jmp SelectorCode16:0
CodeALen equ $ - LABEL_CODE_A
; END of [SECTION .la]
LDT
代码段的实现与32位代码段大致相同,就是在屏幕上以黑底红字打印字母L并模拟回车显示,并在代码段的末尾调转到SelectorCode16
选择子指向的代码段LABEL_SEG_CODE16
LDT和GDT区别
-
作用范围:
- GDT(Global Descriptor Table):全局描述符表是系统级别的数据结构,它为整个操作系统定义段描述符。所有的进程和线程都共享同一个GDT。
- LDT(Local Descriptor Table):局部描述符表是特定于进程的数据结构,每个进程可以有自己的LDT,用于定义该进程特有的段描述符。
-
权限和隔离:
- GDT:由于GDT是全局的,它通常包含操作系统核心代码和数据的段描述符,这些描述符通常具有较高的权限级别。
- LDT:LDT允许进程拥有自己的段描述符,这有助于实现进程间的内存隔离。每个进程的LDT可以有不同的权限设置,从而提供更细粒度的访问控制。
-
内容:
- GDT:GDT通常包含代码段、数据段、任务状态段(TSS)、门描述符等。
- LDT:LDT通常包含该进程特定的代码段、数据段、资源段等。
-
使用方式:
- GDT:操作系统在启动时初始化GDT,并在进程切换时使用GDT中的描述符来加载新的段寄存器。
- LDT:进程在创建时可以创建自己的LDT,并通过特定的系统调用(如
set_thread_area
或modify_ldt
)来加载和切换LDT。
-
性能影响:
- GDT:由于所有进程共享GDT,频繁的GDT更新可能会影响系统性能。
- LDT:每个进程有自己的LDT,因此LDT的更新不会影响到其他进程,这可以在一定程度上减少系统开销。
-
安全性:
- GDT:由于GDT是全局的,对GDT的不当修改可能会影响整个系统的稳定性和安全性。
- LDT:LDT提供了额外的隔离层,即使一个进程的LDT被破坏,也不会影响到其他进程。
-
操作系统支持:
- GDT:几乎所有的x86操作系统都支持GDT。
- LDT:现代操作系统对LDT的支持有所减少,因为现代操作系统更多地依赖于扁平的内存模型和页式内存管理,而不是传统的段式内存管理。
特权级
在IA32的分段机制下,操作系统总共有4个特权级,从高到低分别是0、1、2、3。数字越小表示的特权级越大。

CPL
CPL
是当前执行的程序或任务的特权级。
- 通常情况下:
CPL
等于代码所在的段的特权级。当程序转移到不同特权级的代码段时,处理器将改变CPL
。 - 遇到一致代码段:一致代码段可以被相同或者更低特权级的代码访问,当处理器访问一个与CPL特权级不同的一致代码段时,
CPL
不会被改变。
DPL
DPL
表示段或者门的特权级
- 数据段:
DPL
规定了可以访问此段的最低特权级 - 非一致代码段(无调用门):
DPL
规定访问此段的特权级 - 调用门:
DPL
规定了当前执行的程序或任务可以访问此调用门的最低特权级 - 一致代码段和通过调用门访问的非一致代码段:
DPL
规定了访问此段的最高特权级 TSS
:DPL
规定了可以访问此TSS
的最低特权级
RPL
对于非一致代码段,处理器通过检查RPL
和CPL
来确认一个访问请求是否合法
RPL
>CPL
:比较目标段的DPL
和当前RPL
RPL
<CPL
:比较目标段的DPL
和当前CPL
特权级转移
jmp
和call
实现转移
-
规则:
-
非一致代码段:
CPL
必须等于目标段的DPL
,同时要求RPL
小于等于DPL
-
一致代码段:则要求
CPL
大于或者等于目标段的DPL
,RPL
不做检查,转移后CPL
不会发生变化
-
-
缺点:
- 对于非一致代码段,只能在相同特权级代码段之间转移
- 遇到一致代码段也最多能从低到高
调用门
调用门结构

调用门作用
笔者认为,调用门实际上充当一个中间人作用,使得低特权级的代码段可以访问高特权级代码
假设现在需要由A代码段
使用call
指令经调用门G
访问B代码段
,设如下几个标记:
CPL
RPL_A
DPL_B
DPL_G
- B代码段为一致代码段:
A代码段
访问调用门G
:- 对于
调用门G
,DPL_G
规定了访问此调用门的最低特权级 CPL
≤ \le ≤DPL_G
且RPL_A
≤ \le ≤DPL_G
- 对于
调用门G
访问B代码段
:- 对于一致代码段,
DPL_B
规定了访问此段的最高特权级 DPL_B
≤ \le ≤CPL
- 对于一致代码段,
- B代码段为非一致代码段:
A代码段
访问调用门G
:- 对于
调用门G
,DPL_G
规定了访问此调用门的最低特权级 CPL
≤ \le ≤DPL_G
且RPL_A
≤ \le ≤DPL_G
- 对于
调用门G
访问B代码段
:- 对于一致代码段使用
call
指令时,DPL_B
规定了访问此段的最高特权级 DPL_B
≤ \le ≤CPL
(如果使用jmp
指令则DPL_B
= = =CPL
)
- 对于一致代码段使用
如图所示

有特权级变化时堆栈的变换
TSS
由于x86架构下,各个特权级之间不共享堆栈,因此程序共需要四个堆栈.这些信息被存储在TSS
数据结构中:

可以看到,TSS
中一共存储了3套ss
和esp
,分别对应0特权级到2特权级(由低特权级到高特权级切换时新堆栈才会从TSS中取得,所以TSS中没有位于最外层的堆栈信息)
call
过程堆栈变化
- 根据目标代码段的
DPL
(新的CPL
)从TSS
中选择应该切换至哪个ss
和esp
- 从TSS中读取新的
ss
和esp
。在这过程中如果发现ss
、esp
或者TSS
界限错误都会导致异常 - 对
ss
描述符进行检验,如果发生错误,同样产生异常 - 暂时性地保存当前
ss
和esp
的值 - 加载新的
ss
和esp
(用新的ss
和esp
去替换旧的) - 将刚刚保存起来的
ss
和esp
的值压入新栈 - 从调用者堆栈(原堆栈)中将参数复制到被调用者堆栈(新堆栈)中,复制参数的数目由调用门中
Param Count
一项来决定。如果ParamCount
是零的话,将不会复制参数 - 将当前的
cs
和eip
压栈 - 加载调用门中指定的新的
cs
和eip
,开始执行被调用者过程。

ret
过程堆栈变化
- 检查保存的
cs
中的RPL
以判断返回时是否要变换特权级 - 弹出并加载被调用者堆栈上的
cs
和eip
(指向call
语句的下一条命令),并会进行代码段描述符和选择子类型和特权级检验 - 如果
ret
指令含有参数,则增加esp
的值以跳过参数,然后esp
将指向被保存过的调用者ss
和esp
。 - 弹出并加载
ss
和esp
,切换到调用者堆栈,被调用者的ss
和esp
被丢弃。在这里将会进行ss
描述符、esp
以及ss
段描述符的检验 - 如果
ret
指令含有参数,增加esp
的值以跳过参数(此时已经在调用者堆栈中) - 检查
ds
、es
、fs
、gs
的值,如果其中哪一个寄存器指向的段的DPL
小于CPL
(此规则不适用于一致代码段),那么一个空描述符会被加载到该寄存器。

相关文章:

《ORANGE‘s 一个操作系统的实现》--保护模式进阶
保护模式进阶 大内存读写 GDT段 ;GDT [SECTION .gdt] ; 段基址, 段界限 , 属性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描…...

【可变参模板】可变参类模板
可变参类模板也和可变参函数模板一样,允许模板定义含有0到多个(任意个)模板参数。可变参类模板参数包的展开方式有多种,以下介绍几种常见的方法。 一、递归继承展开 1.1类型模板参数包的展开 首先先看下面的代码: /…...
Linux 递归删除大量的文件
一般情况下 在 Ubuntu 中,递归删除大量文件和文件夹可以通过以下几种方式快速完成。常用的方法是使用 rm 命令,配合一些适当的选项来提高删除速度和效率。 1. 使用 rm 命令递归删除 最常见的方式是使用 rm 命令的递归选项 -r 来删除目录及其所有内容。…...

设计一个算法,找出由str1和str2所指向两个链表共同后缀的起始位置
假定采用带头结点的单链表保存单词,当两个单词有相同的后缀时,则可共享相同的后缀存储空间,例如,’loading’和’being’的存储映像如下图所示。 设str1和str2分别指向两个单词所在单链表的头结点,链表结点结构为 data…...
Python中如何判断一个变量是否为None
在Python中,判断一个变量是否为None是一个常见的需求,特别是在处理可选值、默认值或者是在函数返回结果可能不存在时。虽然这个操作本身相对简单,但围绕它的讨论可以扩展到Python的哲学、类型系统、以及如何在不同场景下优雅地处理None值。 …...

表观遗传系列1:DNA 甲基化以及组蛋白修饰
1. 表观遗传 表观遗传信息很多为化学修饰,包括 DNA 甲基化以及组蛋白修饰,即DNA或蛋白可以通过化学修饰添加附加信息。 DNA位于染色质(可视为微环境)中,并不是裸露的,因此DNA分子研究需要跟所处环境结合起…...
Android 跳转至各大应用商店应用详情页
测试通过机型品牌: 华为、小米、红米、OPPO、一加、Realme、VIVO、IQOO、荣耀、魅族、三星 import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import …...
Pywinauto鼠标操作指南
Pywinauto是一个强大的Python库,用于自动化Windows桌面应用程序的测试。它提供了一系列工具和API来模拟用户输入,包括键盘、鼠标事件,以及与各种窗口控件交互的能力。本文将详细介绍如何使用Pywinauto来执行鼠标操作,并通过一些示…...

VRAY云渲染动画怎么都是图片?
动画实际上是由一系列连续的静态图像(帧)组成的,当这些帧快速连续播放时,就形成了动画效果。每一帧都是一个单独的图片,需要单独渲染。 云渲染农场的工作方式: 1、用户将3D场景文件和动画设置上传到云渲染…...
共享内存(C语言)
目录 一、引言 二、共享内存概述 1.什么是共享内存 2.共享内存的优势 三、共享内存的实现 1.创建共享内存 2.关联共享内存 3.访问共享内存 4.解除共享内存关联 5.删除共享内存 四、共享内存应用实例 五、总结 本文将深入探讨C语言中的共享内存技术,介绍其原理、…...

《JavaEE进阶》----16.<Mybatis简介、操作步骤、相关配置>
本篇博客讲记录: 1.回顾MySQL的JDBC操作 2..Mybatis简介、Mybatis操作数据库的步骤 3.Mybatis 相关日志的配置(日志的配置、驼峰自动转换的配置) 前言 之前学习应用分层时我们知道Web应用程序一般分为三层,Controller、Service、D…...

HuggingFists算子能力扩展-PythonScript
HuggingFists作为一个低代码平台,很多朋友会关心如何扩展平台算子能力。扩展平台尚不支持的算子功能。本文就介绍一种通过脚本算子扩展算子能力的解决方案。 HuggingFists支持Python和Javascript两种脚语言的算子。两种语言的使用方式相同,使用者可以任选…...
WInform记录的添加和显示
1、程序 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;namespace ComboBoxApp {public part…...

★ C++基础篇 ★ string类的实现
Ciallo~(∠・ω< )⌒☆ ~ 今天,我将继续和大家一起学习C基础篇第五章下篇----string类的模拟实现 ~ 上篇:★ C基础篇 ★ string类-CSDN博客 C基础篇专栏:★ C基础篇 ★_椎名澄嵐的博客-CSDN博客 目录 一 基础结构 二 迭代器 …...
rman compress
级别 初始 备完 耗时 low 1804 3572 0:10 High 1812 3176 2:00 MEDIUM 1820 3288 0:13 BASIC 1828 3444 0:56 ---不如MEDIUM,时间还长 NO COMPRESS 1820 5924 0:5 R…...
创建一个Oracle版本的JDK的Docker镜像
背景说明 OpenJDK 和Oracle JDK 一般情况下我们选择OpenJDK,两者针对大部分场景都可以满足,有些地方例如反射技术获得某些包路径下的类对象等,有时候选择OpenJDK会导致空指针异常。 两者在底层实现方面有部分区别。 创建镜像 这里是Linux…...

Harmony OS DevEco Studio 如何导入第三方库(以lottie为例)?-- HarmonyOS自学2
在做鸿蒙开发时,离不开第三方库的引入 一.有哪些支持的Harmony OS的 第三方库? 第三方库下载地址: 1 tpc_resource: 三方组件资源汇总 2 OpenHarmony三方库中心仓 二. 如何加入到DevEco Studio工程 以 lottie为例 OpenHarmony-TPC/lot…...

JAVA数据导出为Excel
目录 一、导入依赖 二、使用的相关类 1、XSSFWorkbook 构造方法 创建表 操作表 保存表 样式和格式 日期处理 密码保护 其他 2、XSSFSheet 获取属性和信息 行操作 列操作 表的属性 合并单元格 保护表 页眉和页脚 注释 其它 3、XSSFRow 获取属性和信息 单…...

【数据结构与算法 | 灵神题单 | 快慢指针(链表)篇】力扣876, 2095, 234
1. 力扣876:链表的中间节点 1.1 题目: 给你单链表的头结点 head ,请你找出并返回链表的中间结点。 如果有两个中间结点,则返回第二个中间结点。 示例 1: 输入:head [1,2,3,4,5] 输出:[3,4,…...

第十五届蓝桥杯图形化省赛题目及解析
第十五届蓝桥杯图形化省赛题目及解析 一. 单选题 1. 运行以下程序,角色会说( )? A、29 B、31 C、33 D、35 正确答案:C 答案解析: 重复执行直到m>n不成立,即重复执行直到m<n。所有当m小于或者 等于n时&…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
【FTP】ftp文件传输会丢包吗?批量几百个文件传输,有一些文件没有传输完整,如何解决?
FTP(File Transfer Protocol)本身是一个基于 TCP 的协议,理论上不会丢包。但 FTP 文件传输过程中仍可能出现文件不完整、丢失或损坏的情况,主要原因包括: ✅ 一、FTP传输可能“丢包”或文件不完整的原因 原因描述网络…...