当前位置: 首页 > news >正文

《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指令的内存占用不难发现,byte4byte5指向了jmp指令的段基址,因此这里是修改jmp指令的段基址到实模式下的cs

image-20240911210235987

	; 为加载 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,设置屏幕属性为黑底红字,将esiedi清零[,然后将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区别

  1. 作用范围

    • GDT(Global Descriptor Table):全局描述符表是系统级别的数据结构,它为整个操作系统定义段描述符。所有的进程和线程都共享同一个GDT。
    • LDT(Local Descriptor Table):局部描述符表是特定于进程的数据结构,每个进程可以有自己的LDT,用于定义该进程特有的段描述符。
  2. 权限和隔离

    • GDT:由于GDT是全局的,它通常包含操作系统核心代码和数据的段描述符,这些描述符通常具有较高的权限级别。
    • LDT:LDT允许进程拥有自己的段描述符,这有助于实现进程间的内存隔离。每个进程的LDT可以有不同的权限设置,从而提供更细粒度的访问控制。
  3. 内容

    • GDT:GDT通常包含代码段、数据段、任务状态段(TSS)、门描述符等。
    • LDT:LDT通常包含该进程特定的代码段、数据段、资源段等。
  4. 使用方式

    • GDT:操作系统在启动时初始化GDT,并在进程切换时使用GDT中的描述符来加载新的段寄存器。
    • LDT:进程在创建时可以创建自己的LDT,并通过特定的系统调用(如set_thread_areamodify_ldt)来加载和切换LDT。
  5. 性能影响

    • GDT:由于所有进程共享GDT,频繁的GDT更新可能会影响系统性能。
    • LDT:每个进程有自己的LDT,因此LDT的更新不会影响到其他进程,这可以在一定程度上减少系统开销。
  6. 安全性

    • GDT:由于GDT是全局的,对GDT的不当修改可能会影响整个系统的稳定性和安全性。
    • LDT:LDT提供了额外的隔离层,即使一个进程的LDT被破坏,也不会影响到其他进程。
  7. 操作系统支持

    • GDT:几乎所有的x86操作系统都支持GDT。
    • LDT:现代操作系统对LDT的支持有所减少,因为现代操作系统更多地依赖于扁平的内存模型和页式内存管理,而不是传统的段式内存管理。

特权级

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

image-20240912185040718
CPL

CPL当前执行的程序或任务的特权级

  • 通常情况下:CPL等于代码所在的段的特权级。当程序转移到不同特权级的代码段时,处理器将改变CPL
  • 遇到一致代码段:一致代码段可以被相同或者更低特权级的代码访问,当处理器访问一个与CPL特权级不同的一致代码段时,CPL不会被改变
DPL

DPL表示段或者门的特权级

  • 数据段:DPL规定了可以访问此段的最低特权级
  • 非一致代码段(无调用门):DPL规定访问此段的特权级
  • 调用门:DPL规定了当前执行的程序或任务可以访问此调用门的最低特权级
  • 一致代码段和通过调用门访问的非一致代码段:DPL规定了访问此段的最高特权级
  • TSSDPL规定了可以访问此TSS最低特权级
RPL

对于非一致代码段,处理器通过检查RPLCPL来确认一个访问请求是否合法

  • RPL>CPL:比较目标段的DPL和当前RPL
  • RPL<CPL:比较目标段的DPL和当前CPL

特权级转移

jmpcall实现转移
  • 规则:

    • 非一致代码段:CPL必须等于目标段的DPL,同时要求RPL小于等于DPL

    • 一致代码段:则要求CPL大于或者等于目标段的DPLRPL不做检查,转移后CPL不会发生变化

  • 缺点:

    • 对于非一致代码段,只能在相同特权级代码段之间转移
    • 遇到一致代码段也最多能从低到高
调用门
调用门结构
image-20240912191132232
调用门作用

笔者认为,调用门实际上充当一个中间人作用,使得低特权级的代码段可以访问高特权级代码

假设现在需要由A代码段使用call指令经调用门G访问B代码段,设如下几个标记:

CPL RPL_A DPL_B DPL_G

  • B代码段为一致代码段:
    • A代码段访问调用门G:
      • 对于调用门G,DPL_G规定了访问此调用门的最低特权级
      • CPL ≤ \le DPL_GRPL_A ≤ \le DPL_G
    • 调用门G访问B代码段:
      • 对于一致代码段,DPL_B规定了访问此段的最高特权级
      • DPL_B ≤ \le CPL
  • B代码段为非一致代码段:
    • A代码段访问调用门G:
      • 对于调用门G,DPL_G规定了访问此调用门的最低特权级
      • CPL ≤ \le DPL_GRPL_A ≤ \le DPL_G
    • 调用门G访问B代码段:
      • 对于一致代码段使用call指令时,DPL_B规定了访问此段的最高特权级
      • DPL_B ≤ \le CPL(如果使用jmp指令则DPL_B = = =CPL)

如图所示

image-20240912194306782

有特权级变化时堆栈的变换

TSS

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

image-20240912200103957

可以看到,TSS中一共存储了3套ssesp,分别对应0特权级2特权级(由低特权级到高特权级切换时新堆栈才会从TSS中取得,所以TSS中没有位于最外层的堆栈信息)

call过程堆栈变化
  1. 根据目标代码段的DPL(新的CPL)从TSS中选择应该切换至哪个ssesp
  2. 从TSS中读取新的ssesp。在这过程中如果发现ssesp或者TSS界限错误都会导致异常
  3. ss描述符进行检验,如果发生错误,同样产生异常
  4. 暂时性地保存当前ssesp的值
  5. 加载新的ssesp(用新的ssesp去替换旧的)
  6. 将刚刚保存起来的ssesp的值压入新栈
  7. 从调用者堆栈(原堆栈)中将参数复制到被调用者堆栈(新堆栈)中,复制参数的数目由调用门中Param Count一项来决定。如果Param Count是零的话,将不会复制参数
  8. 将当前的cseip压栈
  9. 加载调用门中指定的新的cseip,开始执行被调用者过程。
image-20240912203234672
ret过程堆栈变化
  1. 检查保存的cs中的RPL以判断返回时是否要变换特权级
  2. 弹出并加载被调用者堆栈上的cseip(指向call语句的下一条命令),并会进行代码段描述符和选择子类型和特权级检验
  3. 如果ret指令含有参数,则增加esp的值以跳过参数,然后esp将指向被保存过的调用者ssesp
  4. 弹出并加载ssesp,切换到调用者堆栈,被调用者的ssesp被丢弃。在这里将会进行ss描述符、esp以及ss段描述符的检验
  5. 如果ret指令含有参数,增加esp的值以跳过参数(此时已经在调用者堆栈中)
  6. 检查dsesfsgs的值,如果其中哪一个寄存器指向的段的DPL小于CPL(此规则不适用于一致代码段),那么一个空描述符会被加载到该寄存器。
image-20240912203247364

相关文章:

《ORANGE‘s 一个操作系统的实现》--保护模式进阶

保护模式进阶 大内存读写 GDT段 ;GDT [SECTION .gdt] ; 段基址, 段界限 , 属性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描…...

【可变参模板】可变参类模板

可变参类模板也和可变参函数模板一样&#xff0c;允许模板定义含有0到多个&#xff08;任意个&#xff09;模板参数。可变参类模板参数包的展开方式有多种&#xff0c;以下介绍几种常见的方法。 一、递归继承展开 1.1类型模板参数包的展开 首先先看下面的代码&#xff1a; /…...

Linux 递归删除大量的文件

一般情况下 在 Ubuntu 中&#xff0c;递归删除大量文件和文件夹可以通过以下几种方式快速完成。常用的方法是使用 rm 命令&#xff0c;配合一些适当的选项来提高删除速度和效率。 1. 使用 rm 命令递归删除 最常见的方式是使用 rm 命令的递归选项 -r 来删除目录及其所有内容。…...

设计一个算法,找出由str1和str2所指向两个链表共同后缀的起始位置

假定采用带头结点的单链表保存单词&#xff0c;当两个单词有相同的后缀时&#xff0c;则可共享相同的后缀存储空间&#xff0c;例如&#xff0c;’loading’和’being’的存储映像如下图所示。 设str1和str2分别指向两个单词所在单链表的头结点&#xff0c;链表结点结构为 data…...

Python中如何判断一个变量是否为None

在Python中&#xff0c;判断一个变量是否为None是一个常见的需求&#xff0c;特别是在处理可选值、默认值或者是在函数返回结果可能不存在时。虽然这个操作本身相对简单&#xff0c;但围绕它的讨论可以扩展到Python的哲学、类型系统、以及如何在不同场景下优雅地处理None值。 …...

表观遗传系列1:DNA 甲基化以及组蛋白修饰

1. 表观遗传 表观遗传信息很多为化学修饰&#xff0c;包括 DNA 甲基化以及组蛋白修饰&#xff0c;即DNA或蛋白可以通过化学修饰添加附加信息。 DNA位于染色质&#xff08;可视为微环境&#xff09;中&#xff0c;并不是裸露的&#xff0c;因此DNA分子研究需要跟所处环境结合起…...

Android 跳转至各大应用商店应用详情页

测试通过机型品牌&#xff1a; 华为、小米、红米、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库&#xff0c;用于自动化Windows桌面应用程序的测试。它提供了一系列工具和API来模拟用户输入&#xff0c;包括键盘、鼠标事件&#xff0c;以及与各种窗口控件交互的能力。本文将详细介绍如何使用Pywinauto来执行鼠标操作&#xff0c;并通过一些示…...

VRAY云渲染动画怎么都是图片?

动画实际上是由一系列连续的静态图像&#xff08;帧&#xff09;组成的&#xff0c;当这些帧快速连续播放时&#xff0c;就形成了动画效果。每一帧都是一个单独的图片&#xff0c;需要单独渲染。 云渲染农场的工作方式&#xff1a; 1、用户将3D场景文件和动画设置上传到云渲染…...

共享内存(C语言)

目录 一、引言 二、共享内存概述 1.什么是共享内存 2.共享内存的优势 三、共享内存的实现 1.创建共享内存 2.关联共享内存 3.访问共享内存 4.解除共享内存关联 5.删除共享内存 四、共享内存应用实例 五、总结 本文将深入探讨C语言中的共享内存技术&#xff0c;介绍其原理、…...

《JavaEE进阶》----16.<Mybatis简介、操作步骤、相关配置>

本篇博客讲记录&#xff1a; 1.回顾MySQL的JDBC操作 2..Mybatis简介、Mybatis操作数据库的步骤 3.Mybatis 相关日志的配置&#xff08;日志的配置、驼峰自动转换的配置&#xff09; 前言 之前学习应用分层时我们知道Web应用程序一般分为三层&#xff0c;Controller、Service、D…...

HuggingFists算子能力扩展-PythonScript

HuggingFists作为一个低代码平台&#xff0c;很多朋友会关心如何扩展平台算子能力。扩展平台尚不支持的算子功能。本文就介绍一种通过脚本算子扩展算子能力的解决方案。 HuggingFists支持Python和Javascript两种脚语言的算子。两种语言的使用方式相同&#xff0c;使用者可以任选…...

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&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;我将继续和大家一起学习C基础篇第五章下篇----string类的模拟实现 ~ 上篇&#xff1a;★ C基础篇 ★ string类-CSDN博客 C基础篇专栏&#xff1a;★ 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&#xff0c;时间还长 NO COMPRESS 1820 5924 0:5 R…...

创建一个Oracle版本的JDK的Docker镜像

背景说明 OpenJDK 和Oracle JDK 一般情况下我们选择OpenJDK&#xff0c;两者针对大部分场景都可以满足&#xff0c;有些地方例如反射技术获得某些包路径下的类对象等&#xff0c;有时候选择OpenJDK会导致空指针异常。 两者在底层实现方面有部分区别。 创建镜像 这里是Linux…...

Harmony OS DevEco Studio 如何导入第三方库(以lottie为例)?-- HarmonyOS自学2

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

JAVA数据导出为Excel

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

【数据结构与算法 | 灵神题单 | 快慢指针(链表)篇】力扣876, 2095, 234

1. 力扣876&#xff1a;链表的中间节点 1.1 题目&#xff1a; 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[3,4,…...

第十五届蓝桥杯图形化省赛题目及解析

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

linux下NTP服务器实战(chrony软件)

linux下NTP服务器实战(chrony软件) 记录linux下NTP服务器搭建及相关管理操作&#xff0c;使用chrony软件包安装部署。相比ntp服务&#xff0c;Chrony服务适用于更高精度、更高稳定性、自动化等场景。 1. 安装 chrony 在大多数Linux发行版上&#xff0c;chrony可以通过包管理…...

Java设计模式之命令模式介绍和案例示范

一、命令模式简介 命令模式&#xff08;Command Pattern&#xff09;是一种行为型设计模式&#xff0c;它将请求封装为一个对象&#xff0c;从而使你可以用不同的请求对客户端进行参数化、对请求排队或记录日志&#xff0c;以及支持可撤销的操作。命令模式的核心思想是将发出请…...

Leetcode面试经典150题-74.搜索二维矩阵

解法都在代码里&#xff0c;不懂就留言或者私信 二分查找&#xff0c;比较简单 class Solution {/**解题思路&#xff1a;每一行有序、每一列也有序&#xff0c;只是整体不是严格有序的&#xff0c;那我们需要找一个点&#xff0c;只能往两个方向走&#xff0c;往一个方向走是…...

【数字集成电路与系统设计】基本的组合逻辑电路

目录 一、简单例子引入 1.1 端口声明 1.1.2 Verilog实现 1.1.3 Chisel实现 逐行解释 1.2 内部逻辑实现 1.2.1 Verilog实现 1.2.2 Chisel实现 Chisel 关键点解释 1.3 常用的硬件原语 二、Chisel主要数据类型介绍 2.1 数据类型 2.2 数据宽度 2.3 数据转换 2.4 运算…...

11. 建立你的第一个Web3项目

11. 建立你的第一个Web3项目 在这一部分&#xff0c;我们将带你一步步地建立一个简单的Web3项目&#xff0c;从环境搭建到智能合约的创建与部署&#xff0c;再到开发一个去中心化应用&#xff08;dApp&#xff09;并与智能合约交互。这是你迈向Web3开发的第一步。 1. 环境搭建…...

衡石分析平台使用手册-容器部署

容器部署​ 本文介绍如何在容器上部署 HENGSHI SENSE&#xff0c;以及部署后如何进行版本升级和数据备份。 部署前准备工作​ 单机部署前&#xff0c;请完成如下准备工作。 1.检查 docker 的环境。需要满足 Docker 版本 > 17.09安装 docker-compose。 2.获取并导入离线…...

静态库,动态库以及makefile基础

一.静态&#xff08;链接&#xff09;库 libfun.a 静态链接进可执行程序 可执行程序偏大 运行时只需要可执行程序即可 生成静态库步骤 gcc -c fun.c -o fun.o ar rcv libfun.a fun.o //需要用.o文件生成数据库 运行 gcc main.c libfun.a 二.动态库 libfun.so 动…...

Python基础语法(1)上

常量和表达式 我们可以把 Python 当成一个计算器&#xff0c;来进行一些算术运算。 print(1 2 - 3) print(1 2 * 3) print(1 2 / 3) 这里我们可能会有疑问&#xff0c;为什么不是1.6666666666666667呢&#xff1f; 其实在编程中&#xff0c;一般没有“四舍五入”这样的规则…...

使用 Python/java/go做一个微信机器人

E云是一套完整的的第三方服务平台&#xff0c;包含微信API服务、企微API服务、SCRM系统定制、企微系统定制、服务类软件定制等模块&#xff0c;本文档主要讲述个微API服务相关&#xff0c;以下简称API&#xff0c;它能处理用户微信中的各种事件&#xff0c;提供了开发者与个微对…...

【北京迅为】iTOP-i.MX6开发板使用手册第四部分固件编译第十四章非设备树Android4.4系统编译

可根据用户需求更换&#xff0c;百变定制&#xff0c;高端产品无忧&#xff01; 迅为IMX6Q兼容四核商业级 、双核商业级、四核工业级 、更可提供i.MX6Q家族PLUS版本核心板。 核心板采用十层PCB沉金盲埋设计&#xff0c;更能保证电磁兼容与系统稳定。 公众号&#xff1a;迅为电…...