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

8086内存安排

8086实模式的寻址范围只有1MB。其中:

系统硬件使用的存储器地址被安排在高端,地址从0xA0000H(684KB)开始的384KB中,其中有用于显示的视频缓冲区;

内存低端安排了中断向量表和BIOS数据区;

剩下的从0x00500H开始的到0xA0000H总共不到640KB的内存是操作系统和应用程序所能够使用的,应用程序不能够使用这600KB以外的内存,这就是著名的“640KB限制”。

 彩显缓冲区

从上图可以看出,0xB0000H开始到0xB8000H为单色字符模式视频缓冲区;0xB8000H到C0000为彩色字符模式视频缓冲区。

内存地址空间从B8000H~BFFFFH 共32KB空间([B800:0000]到[B800:8FFF]),称为80x25彩色字符模式显示缓冲区,向这个地址写入的数据会立即出现在显示器上。

窗口大小为160x25个字节(4000个字节),每两个字节为一个字符的参数:

偶数地址存放字符的ascii码,奇数地址存放字符属性。

一行可以存放80个字符一共25行,共可以存放80x25个字符2000个字符。每个字符可以有FF=2^8=256种属性。

第7位:代表闪烁效果
第6、5、4位:代表背景色的rgb=red green blue 红绿蓝
第3位:高亮
第2、1、0 位:字符颜色的rgb
对应颜色设置成1就变成对应颜色
0010 0100 绿底红字 十六进制=0x24
0100 0001 红底蓝字 十六进制=0x41
0001 0010 蓝底绿字 十六进制=0x12

对于昨天的第二扇区的代码,直接把数据放到了0xB8000H开始的彩色字符模式视频缓冲区内。

 昨天的运行效果如下。

 根据如下代码,可以看出每行用160字节可以显示80个字符。

单色缓冲区

将把上节中的es由0xB800改为0xB000后,qemu没有进行显示。所以后续还是用es=0xB800H吧。

int13/02H中断

根据int13/02H调用格式:

AH = 02h
AL = number of sectors to read (must be nonzero)
CH = low eight bits of cylinder number
CL = sector number 1-63 (bits 0-5)
high two bits of cylinder (bits 6-7, hard disk only)
DH = head number
DL = drive number (bit 7 set for hard disk)
ES:BX -> data buffer

下述代码将第二扇区复制进了内存es:bx=[new:0]位置

        mov ah, 0x02
        mov al, 1
        mov ch, 0
        mov cl, 2
        mov dh, 0
        mov bx, new
        mov es, bx
        xor bx, bx
        int 0x13

下述代码令cs:ip=[new:0]

        jmp new:0

通过上述两部操作,把第二扇区的代码复制到了[new:0]的位置,同时也让指令指针指向了此位置,从而可以继续运行代码。

去掉冗余代码,最精简的可以使用第二扇区代的代码如下。

[BITS 16]
org 0x7C00
start:
        mov ah, 0x02
        mov al, 1
        mov ch, 0
        mov cl, 2
        mov dh, 0
        mov bx, new
        mov es, bx
        xor bx, bx
        int 0x13
        jmp new:0
data:
        new equ 0x0500
times   510-($-$$) db 0
dw      0xaa55
sect2:
        mov ax, 0xB800
        mov es, ax
	mov byte [es:0x0000], '0'
	mov byte [es:0x0001], 0x48
	mov byte [es:0x00A0], '1'
	mov byte [es:0x00A1], 0x48
	mov byte [es:0x0140], '2'
	mov byte [es:0x0141], 0x48
	mov byte [es:0x00A0], '1'
	mov byte [es:0x00A1], 0x48
	mov byte [es:0x00A0], '1'
	mov byte [es:0x00A1], 0x48
        mov byte [es:420], 'H'
        mov byte [es:421], 0x48
        mov byte [es:422], 'E'
        mov byte [es:423], 0x68
        mov byte [es:424], 'L'
        mov byte [es:425], 0x28
        mov byte [es:426], 'L'
        mov byte [es:427], 0x38
        mov byte [es:428], 'O'
        mov byte [es:429], 0x18
        mov byte [es:430], '!'
        mov byte [es:431], 0x58
        hlt

GDT

全局描述符表Global Descriptor Table,表中每个元素8个字节,每个元素表示一个段(代码段,数据段,栈段)的信息,且GDT在进入保护模式之前必须存在,所以它必须位于1MB以下。

GDTR中存放的是GDT在内存中的基地址和其表长界限。

 LDT和GDT从本质上说是相同的,只是LDT嵌套在GDT之中。

LDTR记录局部描述符表的起始位置,与GDTR不同,LDTR的内容是一个段选择子。

由于LDT本身同样是一段内存,也是一个段,所以它也有个描述符描述它,这个描述符就存储在GDT中,对应这个表述符也会有一个选择子,LDTR装载的就是这样一个选择子。

LDTR可以在程序中随时改变,通过使用lldt指令。如上图,如果装载的是Selector 2则LDTR指向的是表LDT2。

由于每个进程都有自己的一套程序段、数据段、堆栈段,有了局部描述符表LDT则可以将每个进程的程序段CS、数据段DS、堆栈段SS封装在一起,只要改变LDTR就可以实现对不同进程的段进行访问。

如下代码为建立一个GDT的示例,首先定义一段连续内存。本示例构建了3个元素CODE_DESC、DATA_DESC、VIDEO_DESC,并预留了60个元素。在lgdt命令中将GDT_LIMIT和GDT_BASE赋给了寄存器GDTR。

其中:DD 的意思是define double(4-bytes),与之相似的还有db(byte)dw(word,2-bytes)

; gdt
GDT_BASE:
  dd 0x00000000
  dd 0x00000000

CODE_DESC:
  dd DESC_CODE_LOW_32
  dd DESC_CODE_HIGH_32

DATA_DESC:
  dd DESC_DATA_LOW_32
  dd DESC_DATA_HIGH_32

VIDEO_DESC:
  dd DESC_VIDEO_LOW_32
  dd DESC_VIDEO_HIGH_32

; reserve 60 gdt entries space
times 60 dq 0

GDT_SIZE    equ   $ - GDT_BASE
GDT_LIMIT   equ   GDT_SIZE - 1

gdt_ptr:
  dw GDT_LIMIT
  dd GDT_BASE
  ; enable A20
  in al, 0x92
  or al, 0000_0010b
  out 0x92, al

  ; load GDT
  lgdt [gdt_ptr]

  ; open protection mode - set cr0 bit 0
  mov eax, cr0
  or eax, 0x00000001
  mov cr0, eax

  ; refresh pipeline
  jmp dword SELECTOR_CODE:protection_mode_entry

其中,SELECTOR_CODE等的定义如下:

;*************************** segment selector *********************************;
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b

TI_GDT equ 000b
TI_LDT equ 100b

SELECTOR_CODE    equ (0x0001 << 3) + TI_GDT + RPL0
SELECTOR_DATA    equ (0x0002 << 3) + TI_GDT + RPL0
SELECTOR_VIDEO   equ (0x0003 << 3) + TI_GDT + RPL0

jmp dword SELECTOR_CODE:protection_mode_entry

执行这一句会把SELECTOR_CODE装入CS,并跳转到SELECTOR_CODE:protection_mode_entry处。

LDT

LDT示例代码如下所示

; LDT  
[SECTION .ldt]  
ALIGN   32  
LABEL_LDT:  
;                            段基址       段界限      属性  
LABEL_LDT_DESC_CODEA: Descriptor 0, CodeALen - 1, DA_C + DA_32 ; Code, 32 位  
LABEL_LDT_DESC_CODEB: Descriptor 0, CodeBLen - 1, DA_C + DA_32 ; Code, 32 位  
LDTLen      equ $ - LABEL_LDT ;LDT表长度  
  
; LDT 选择子  
SelectorLDTCodeA    equ LABEL_LDT_DESC_CODEA    - LABEL_LDT + SA_TIL  
SelectorLDTCodeB    equ LABEL_LDT_DESC_CODEB    - LABEL_LDT + SA_TIL  
; END of [SECTION .ldt]


; Load LDT  
mov ax, SelectorLDT     
lldt    ax      ;加载LDT在GDT中的描述符
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用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、付费专栏及课程。

余额充值