【日拱一卒行而不辍20220926】自制操作系统

分页原理

在汇编文件chapter3/f/pmtest6.asm中,关于分页设置的代码如下。

此时还未开启多进程,仅有的一个进程中这个表有什么作用吗?页表需要进行切换吗?

每一个进程都会形成这样一个完整的页表。x86处理器中的CR3寄存器便是用来记录当前任务的页表位置的。当程序访问某一线性地址时,CPU会根据CR3寄存器找到当前任务使用的页表,然后根据预先定义的规则查找物理地址。

32位处理程序,一共可以操作的内存为2^32B=4GB。所以1K个表项,每一个表项管理1K个页,每一个页的大小为4K,则总共刚好为1K×1K×4K=4GB,覆盖了所有的完整的可操作的物理内存的地址空间。不管后续如何进行优化,让页表更省存储空间,基本原理还是如上所示。

; 启动分页机制 --------------------------------------------------------------
SetupPaging:
	; 为简化处理, 所有线性地址对应相等的物理地址.

	; 首先初始化页目录
	mov	ax, SelectorPageDir	; 此段首地址为 PageDirBase
	mov	es, ax
	mov	ecx, 1024		; 共 1K 个表项
	xor	edi, edi
	xor	eax, eax
	mov	eax, PageTblBase | PG_P  | PG_USU | PG_RWW
.1:
	stosd
	add	eax, 4096		; 为了简化, 所有页表在内存中是连续的.
	loop	.1

	; 再初始化所有页表 (1K 个, 4M 内存空间)
	mov	ax, SelectorPageTbl	; 此段首地址为 PageTblBase
	mov	es, ax
	mov	ecx, 1024 * 1024	; 共 1M 个页表项, 也即有 1M 个页
	xor	edi, edi
	xor	eax, eax
	mov	eax, PG_P  | PG_USU | PG_RWW
.2:
	stosd
	add	eax, 4096		; 每一页指向 4K 的空间
	loop	.2

	mov	eax, PageDirBase
	mov	cr3, eax
	mov	eax, cr0
	or	eax, 80000000h
	mov	cr0, eax
	jmp	short .3
.3:
	nop

	ret
; 分页机制启动完毕 ----------------------------------------------------------

一共有1M个表项,每一个表项占据4个字节4B空间,如下所示。

 所以在未经优化的情况下,一个进程为了实现页管理,最多需要的存储空间为4MB

第一次的分页机制实现

为了最多程度减少nasm中$与$$等在理解与实际操作中可能产生的错误,以下关于操作系统的实践均在512字节中去实现。如果实在不能在第一扇区去实现,再根据前几日的扇区复制int 13/02来挪动。

最基本的开启分页机制的代码如下。

; ==========================================
; pmtest2.asm
; 编译方法:nasm pmtest2.asm -o boot.bin
; ==========================================

;----------------------------------------------------------------------------
; 描述符类型值说明
; 其中:
;       DA_  : Descriptor Attribute
;       D    : 数据段
;       C    : 代码段
;       S    : 系统段
;       R    : 只读
;       RW   : 读写
;       A    : 已访问
;       其它 : 可按照字面意思理解
;----------------------------------------------------------------------------
DA_32		EQU	4000h	; 32 位段
DA_LIMIT_4K	EQU	8000h	; 段界限粒度为 4K 字节

DA_DPL0		EQU	  00h	; DPL = 0
DA_DPL1		EQU	  20h	; DPL = 1
DA_DPL2		EQU	  40h	; DPL = 2
DA_DPL3		EQU	  60h	; DPL = 3
;----------------------------------------------------------------------------
; 存储段描述符类型值说明
;----------------------------------------------------------------------------
DA_DR		EQU	90h	; 存在的只读数据段类型值
DA_DRW		EQU	92h	; 存在的可读写数据段属性值
DA_DRWA		EQU	93h	; 存在的已访问可读写数据段类型值
DA_C		EQU	98h	; 存在的只执行代码段属性值
DA_CR		EQU	9Ah	; 存在的可执行可读代码段属性值
DA_CCO		EQU	9Ch	; 存在的只执行一致代码段属性值
DA_CCOR		EQU	9Eh	; 存在的可执行可读一致代码段属性值
;----------------------------------------------------------------------------
; 系统段描述符类型值说明
;----------------------------------------------------------------------------
DA_LDT		EQU	  82h	; 局部描述符表段类型值
DA_TaskGate	EQU	  85h	; 任务门类型值
DA_386TSS	EQU	  89h	; 可用 386 任务状态段类型值
DA_386CGate	EQU	  8Ch	; 386 调用门类型值
DA_386IGate	EQU	  8Eh	; 386 中断门类型值
DA_386TGate	EQU	  8Fh	; 386 陷阱门类型值
;----------------------------------------------------------------------------
; 选择子类型值说明
; 其中:
;       SA_  : Selector Attribute

SA_RPL0		EQU	0	; ┓
SA_RPL1		EQU	1	; ┣ RPL
SA_RPL2		EQU	2	; ┃
SA_RPL3		EQU	3	; ┛

SA_TIG		EQU	0	; ┓TI
SA_TIL		EQU	4	; ┛
;----------------------------------------------------------------------------


;----------------------------------------------------------------------------
; 分页机制使用的常量说明
;----------------------------------------------------------------------------
PG_P		EQU	1	; 页存在属性位
PG_RWR		EQU	0	; R/W 属性位值, 读/执行
PG_RWW		EQU	2	; R/W 属性位值, 读/写/执行
PG_USS		EQU	0	; U/S 属性位值, 系统级
PG_USU		EQU	4	; U/S 属性位值, 用户级
;----------------------------------------------------------------------------

; 宏 ------------------------------------------------------------------------------------------------------
;
; 描述符
; usage: Descriptor Base, Limit, Attr
;        Base:  dd
;        Limit: dd (low 20 bits available)
;        Attr:  dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
	dw	%2 & 0FFFFh				; 段界限 1				(2 字节)
	dw	%1 & 0FFFFh				; 段基址 1				(2 字节)
	db	(%1 >> 16) & 0FFh			; 段基址 2				(1 字节)
	dw	((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)	; 属性 1 + 段界限 2 + 属性 2		(2 字节)
	db	(%1 >> 24) & 0FFh			; 段基址 3				(1 字节)
%endmacro ; 共 8 字节
;
; 门
; usage: Gate Selector, Offset, DCount, Attr
;        Selector:  dw
;        Offset:    dd
;        DCount:    db
;        Attr:      db
%macro Gate 4
	dw	(%2 & 0FFFFh)				; 偏移 1				(2 字节)
	dw	%1					; 选择子				(2 字节)
	dw	(%3 & 1Fh) | ((%4 << 8) & 0FF00h)	; 属性					(2 字节)
	dw	((%2 >> 16) & 0FFFFh)			; 偏移 2				(2 字节)
%endmacro ; 共 8 字节
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

PageDirBase		equ	200000h	; 页目录开始地址: 2M
PageTblBase		equ	201000h	; 页表开始地址: 2M+4K

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	     ;
LABEL_DESC_PAGE_DIR: 	Descriptor	PageDirBase, 4095, DA_DRW;Page Directory
LABEL_DESC_PAGE_TBL: 	Descriptor	PageTblBase, 1023, DA_DRW|DA_LIMIT_4K;Page Tables

; 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
SelectorPageDir	equ	LABEL_DESC_PAGE_DIR	- LABEL_GDT
SelectorPageTbl	equ	LABEL_DESC_PAGE_TBL	- 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 基地址

	; load GDTR
	lgdt	[GdtPtr]

	; close interrupt
	cli

	; open A20
	in	al, 92h
	or	al, 00000010b
	out	92h, al

	; perpare to switch to PM
	mov	eax, cr0
	or	eax, 1
	mov	cr0, eax

	; inter PM
	jmp	dword SelectorCode32:0	; 执行这一句会把 SelectorCode32 装入 cs,
					; 并跳转到 Code32Selector:0  处
; END of [SECTION .s16]


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

LABEL_SEG_CODE32:

	call	SetupPaging

	mov	ax, SelectorVideo
	mov	gs, ax			; 视频段选择子(目的)

	mov	edi, (80 * 11 + 0) * 2	; 屏幕第 11 行, 第 79 列。
	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	mov	al, 'P'
	mov	[gs:edi], ax


; 启动分页机制 --------------------------------------------------------------
SetupPaging:
	; 为简化处理, 所有线性地址对应相等的物理地址.

	; 首先初始化页目录
	mov	ax, SelectorPageDir	; 此段首地址为 PageDirBase
	mov	es, ax
	mov	ecx, 1024		; 共 1K 个表项
	xor	edi, edi
	xor	eax, eax
	mov	eax, PageTblBase | PG_P  | PG_USU | PG_RWW
.1:
	stosd
	add	eax, 4096		; 为了简化, 所有页表在内存中是连续的.
	loop	.1

	; 再初始化所有页表 (1K 个, 4M 内存空间)
	mov	ax, SelectorPageTbl	; 此段首地址为 PageTblBase
	mov	es, ax
	mov	ecx, 1024 * 1024	; 共 1M 个页表项, 也即有 1M 个页
	xor	edi, edi
	xor	eax, eax
	mov	eax, PG_P  | PG_USU | PG_RWW
.2:
	stosd
	add	eax, 4096		; 每一页指向 4K 的空间
	loop	.2

	mov	eax, PageDirBase
	mov	cr3, eax
	mov	eax, cr0
	or	eax, 80000000h
	mov	cr0, eax
	jmp	short .3
.3:
	nop

	ret
; 分页机制启动完毕 ----------------------------------------------------------


	; 到此停止
	jmp	$
	times	261 db 0
	dw 	0xaa55

SegCode32Len	equ	$ - LABEL_SEG_CODE32
; END of [SECTION .s32]

运行效果如下所示

开启了分页之后,单进程涉及的开启32位保护模式、分段机制、分页机制已全部完成。

后续就是进入了多进程操作系统的设计。主要包含了配置任务数据结构、生成新的进程、设置时间切片中断三个主要任务了。

根据512字节内容布局,目前已经使用了240个字节,还有270个字节可以使用。

as@as-virtual-machine:~/osdir/chapter3H$ hexdump -Cv boot
00000000  e9 31 00 00 00 00 00 00  00 00 00 00 6f 01 00 00  |.1..........o...|
00000010  00 98 40 00 ff ff 00 80  0b 92 00 00 ff 0f 00 00  |..@.............|
00000020  20 92 00 00 ff 03 00 10  20 92 80 00 27 00 00 00  | ....... ...'...|
00000030  00 00 00 00 8c c8 8e d8  8e c0 8e d0 bc 00 01 66  |...............f|
00000040  31 c0 8c c8 66 c1 e0 04  66 05 90 7c 00 00 a3 0e  |1...f...f..|....|
00000050  7c 66 c1 e8 10 a2 10 7c  88 26 13 7c 66 31 c0 8c  ||f.....|.&.|f1..|
00000060  d8 66 c1 e0 04 66 05 04  7c 00 00 66 a3 2e 7c 0f  |.f...f..|..f..|.|
00000070  01 16 2c 7c fa e4 92 0c  02 e6 92 0f 20 c0 66 83  |..,|........ .f.|
00000080  c8 01 0f 22 c0 66 ea 00  00 00 00 08 00 00 00 00  |...".f..........|
00000090  e8 13 00 00 00 66 b8 10  00 8e e8 bf e0 06 00 00  |.....f..........|
000000a0  b4 0c b0 50 65 66 89 07  66 b8 18 00 8e c0 b9 00  |...Pef..f.......|
000000b0  04 00 00 31 ff 31 c0 b8  07 10 20 00 ab 05 00 10  |...1.1.... .....|
000000c0  00 00 e2 f8 66 b8 20 00  8e c0 b9 00 00 10 00 31  |....f. ........1|
000000d0  ff 31 c0 b8 07 00 00 00  ab 05 00 10 00 00 e2 f8  |.1..............|
000000e0  b8 00 00 20 00 0f 22 d8  0f 20 c0 0d 00 00 00 80  |... ..".. ......|
000000f0  0f 22 c0 eb 00 90 c3 eb  fe 00 00 00 00 00 00 00  |."..............|
00000100  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000110  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000120  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000130  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000140  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000150  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000160  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000170  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000180  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000190  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
00000200

以下是一个使用Java实现的简单Kafka消费者线程池示例: ```java import java.util.Arrays; import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; public class KafkaConsumerThreadPool { private final KafkaConsumer<String, String> consumer; private final String topic; private ExecutorService executor; public KafkaConsumerThreadPool(String brokers, String groupId, String topic) { Properties prop = createConsumerConfig(brokers, groupId); this.consumer = new KafkaConsumer<>(prop); this.topic = topic; } public void shutdown() { if (consumer != null) { consumer.close(); } if (executor != null) { executor.shutdown(); } } public void run(int numThreads) { executor = Executors.newFixedThreadPool(numThreads); for (int i = 0; i < numThreads; i++) { executor.submit(new KafkaConsumerThread(consumer, topic)); } } private static Properties createConsumerConfig(String brokers, String groupId) { Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, brokers); props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true"); props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000"); props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000"); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); return props; } public static void main(String[] args) { String brokers = "localhost:9092"; String groupId = "test-group"; String topic = "test-topic"; int numThreads = 4; KafkaConsumerThreadPool example = new KafkaConsumerThreadPool(brokers, groupId, topic); example.run(numThreads); } } class KafkaConsumerThread implements Runnable { private final KafkaConsumer<String, String> consumer; private final String topic; public KafkaConsumerThread(KafkaConsumer<String, String> consumer, String topic) { this.consumer = consumer; this.topic = topic; } public void run() { consumer.subscribe(Arrays.asList(this.topic)); while (true) { ConsumerRecords<String, String> records = consumer.poll(100); for (ConsumerRecord<String, String> record : records) { System.out.println(Thread.currentThread().getName() + " : " + record.value()); } } } } ``` 在上面的代码示例中,KafkaConsumerThreadPool类是一个简单的Kafka消费者线程池实现,其中run方法启动了numThreads个线程,每个线程都创建了一个KafkaConsumerThread对象,并调用executor.submit(task)方法提交到线程池中执。KafkaConsumerThread类是一个简单的消费者线程实现,其中run方法中的代码是从Kafka主题中读取消息并处理的逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值