我使用了 micro_os_loader 并制作了自己的内核,它工作得很好。内核必须在第 2 扇区,这没关系,但我想在第 4 扇区编写一个程序,并希望在内核中作为子程序运行它。但我该怎么做呢?在内核中,我应该怎么做才能让我的程序在第 4 扇区运行。
1 回答
这在很大程度上取决于您的内核是如何工作的。
您需要将程序代码读入内存,为其创建一个进程,然后……嗯……下一步已经非常依赖于设计了。
基本上你需要让 CS:IP 指向程序的入口点。
例如,如果您正在处理多任务,您可能会使用一个处理程序设置定时器中断,该处理程序保存 PCB 中的所有寄存器并从进程队列中选择另一个进程。然后它从属于该进程的 PCB 加载寄存器,篡改堆栈上的返回地址以指向从该进程执行的下一条指令,并发出iret以赋予进程控制权。
同时,内核在一个循环中空闲 - 例如:
idle:
hlt
jmp idle
从这里开始,内核没有任何线性工作要做。相反,它处于空闲状态,等待来自进程的系统调用和来自硬件的中断来处理。系统调用通常以中断处理程序的形式实现,因此系统中发生的各种异步事件都可以统一处理。
[编辑]
对于另一个示例,我们将使用抢占式多任务处理,愚蠢地将调度程序硬编码到计时器 IRQ 处理程序中,而没有深入了解所有细节。例如,实现进程队列取决于您(它只是内存中某处的 PCB 列表)。我们不会重新编程计时器,因为它不是必需的(它已经在运行并生成 IRQ 0)。但是,您可以根据需要对其进行重新编程,以便以不同于默认 8253/8254 设置提供的速率切换任务。另外,请注意 IRL 您还需要保存 FPU 状态和其他一些东西......
; cdecl calling convention
; ugly pieces of code appear! DO NOT COPY-PASTE OR OTHERWISE USE THIS! this is just for illustration
proc timerInterruptHandler
cli ; disabling interrupts so they don't mess up anything. no need to worry about re-enabling them as FLAGS is implicitly saved on the stack
push ax ; let's first save our current CPU state on the stack
push cx
push dx
push bx
push bp
push sp
push si
push di
push es
push ss
push ds
push bp
mov bp,sp
call getCurrentProcess ; get the current process instance. let's now assume that the context is simply saved at relative address 0
mov di,ax
lea si,[bp + 2]
mov cx,14
rep movsw ; save the contents of each register to the buffer
call selectNextProcess ; we'll select the next process from the queue (or the first when the current is the last one).
mov si,ax
lea di,[bp + 2]
mov cx,14
rep movsw ; overwrite the saved registers on the stack with the saved state of our new process
; upon returning from the handler (the next steps), the state of the new process will be loaded
mov sp,bp
pop bp
pop ds
pop ss
pop es
pop di
pop si
pop sp
pop bp
pop bx
pop dx
pop cx
call acknowledgeMasterPICInterrupt ; every IRQ must be acknowledged to the corresponding PIC (both if the IRQ is triggered by the salve PIC, i.e. IRQ8+)
pop ax
iret
endp timerInterruptHandler
; void setUpTaskScheduler(void)
proc setUpTaskScheduler ; here we assume that no IRQ remapping is done, thus IRQ 0 (timer) is at INT 8.
pushf
cli
push bx
push ds
mov cx,2 ; multiplying the interrupt number by 4 gives you the address of the IVT record to modify
mov bx,8
shl bx,cx
xor ax,ax
mov ds,ax
lea ax,[cs:timerInterruptHandler] ; write the offset first
mov [word ptr ds:bx + 0],ax
mov ax,cs
mov [word ptr ds:bx + 2],ax ; then the segment
pop ds
pop bx
popf
ret
endp setUpTaskScheduler
; void acknowledgeMasterPICInterrupt(void)
proc acknowledgeMasterPICInterrupt
mov al,20h
out 20h,al
ret
endp acknowledgeMasterPICInterrupt
; void acknowledgeSlavePICInterrupt(void)
proc acknowledgeSlavePICInterrupt
mov al,20h
out 0A0h,al
call acknowledgeMasterPICInterrupt
ret
endp acknowledgeSlavePICInterrupt
_main:
; ...
call setUpTaskScheduler
; Once interrupts are enabled the scheduler will start doing its work
; ...
[/编辑]
在这里您可以找到重新编程 PIT 的代码示例。