Преглед на файлове

完成内核: 实现 put_char

simon преди 11 месеца
родител
ревизия
cb87d2fe4a
променени са 8 файла, в които са добавени 179 реда и са изтрити 8 реда
  1. 1 0
      .gitignore
  2. 7 7
      boot/loader.S
  3. 3 1
      build.sh
  4. 11 0
      kernel/main.c
  5. 135 0
      lib/kernel/print.S
  6. 8 0
      lib/kernel/print.h
  7. 14 0
      lib/stdint.h
  8. 0 0
      lib/user/.keep

+ 1 - 0
.gitignore

@@ -71,3 +71,4 @@ Temporary Items
 bochs_log.txt
 a.img
 *.bin
+.vscode/

+ 7 - 7
boot/loader.S

@@ -147,13 +147,13 @@ loader_start:
         mov [total_mem_bytes], edx  ; 将内存换为 byte 单位后存入 total_mem_bytes 处
 
     ; 显示 loadermsg
-    mov sp, LOADER_BASE_ADDR
-    mov bp, loadermsg        ; ES:BP = 字符串地址
-    mov cx, 17               ; CX = 字符串长度
-    mov ax, 0x1301           ; AH = 13h, AL = 01h
-    mov bx ,0x001f           ; 页号 0(BH=0)蓝底粉红色(BL=1fh)
-    mov dx, 0x1800           ; 坐标(行,列)
-    int 0x10                 ; 10h 号中断
+    ; mov sp, LOADER_BASE_ADDR
+    ; mov bp, loadermsg        ; ES:BP = 字符串地址
+    ; mov cx, 17               ; CX = 字符串长度
+    ; mov ax, 0x1301           ; AH = 13h, AL = 01h
+    ; mov bx ,0x001f           ; 页号 0(BH=0)蓝底粉红色(BL=1fh)
+    ; mov dx, 0x1800           ; 坐标(行,列)
+    ; int 0x10                 ; 10h 号中断
 
     ;---------------------- 准备进入保护模式 ------------------------------------------
     ; 1 打开 A20

+ 3 - 1
build.sh

@@ -3,7 +3,9 @@
 echo "Compiling..."
 nasm -I boot/include/ -o boot/mbr.bin boot/mbr.S
 nasm -I boot/include/ -o boot/loader.bin boot/loader.S
-i386-elf-gcc -c -o kernel/main.o kernel/main.c && i386-elf-ld kernel/main.o -Ttext 0xc0001500 -e main -o kernel/kernel.bin
+nasm -f elf -o lib/kernel/print.o lib/kernel/print.S
+i386-elf-gcc -I ./lib/ -I lib/kernel/ -c -o kernel/main.o kernel/main.c
+i386-elf-ld -Ttext 0xc0001500 -e main -o kernel/kernel.bin kernel/main.o lib/kernel/print.o
 
 echo "Creating disk image..."
 bximage -q -func=create -hd=30M hd30M.img

+ 11 - 0
kernel/main.c

@@ -1,6 +1,17 @@
+#include "print.h"
 
 int main(void)
 {
+    put_char('k');
+    put_char('e');
+    put_char('r');
+    put_char('n');
+    put_char('e');
+    put_char('l');
+    put_char('\n');
+    put_char('1');
+    put_char('\b');
+    put_char('3');
     while (1)
         ;
     return 0;

+ 135 - 0
lib/kernel/print.S

@@ -0,0 +1,135 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; 打印函数
+; 1. 备寄存器现场
+; 2. 获取光标坐标值,光标坐标值是下一个可打印字符的位置
+; 3. 获取打印的字符
+; 4. 判断字符是否为控制字符,若是回车符、换行符、退格符三种控制字符之一,则进入相应的处理流程。
+;    否则,其余字符都粗暴地认为是可见字符,进入输出流程处理
+; 5. 判断是否需要滚屏
+; 6. 更新光标坐标值,便其指向下一个打印字符的位置
+; 7. 恢复寄存器现场,退出
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+TI_GDT equ 0 
+RPL0   equ 0
+SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
+CR equ 0xd
+LF equ 0xa
+BACKSPACE equ 0x8
+
+[bits 32]
+section .text 
+global put_char ; 导出为全局符号
+;------------------------ put_char -----------------------------------------------
+; 功能描述: 把栈中的 1 个字符写入光标所在处
+;---------------------------------------------------------------------------------
+put_char:
+    pushad     ; 备份 32 位寄存器环境 8 个 eax->ecx->edx->ebx->esp->ebp->esi->edi
+    mov ax, SELECTOR_VIDEO
+    mov gs, ax  ; gs 为正确的视频段选择子
+
+;;;;;;;;;;;; 获取当前光标位置 ;;;;;;;;;;;;;;;;;;;;;;;;;
+    ; 先获取高 8 位
+    mov dx, 0x3d4     ; 索引寄存器
+    mov al, 0x0e      ; 用于提供光标位置的高 8 位
+    out dx, al
+    mov dx, 0x3d5     ; 通过读写数据端口 0x3d5 来获取或设置光标位置
+    in al, dx         ; 得到了光标位置的高 8 位
+    mov ah, al
+    ; 再获取低 8 位
+    mov dx, 0x3d4
+    mov al, 0x0f
+    out dx, al
+    mov dx, 0x3d5
+    in al, dx
+
+    ; 将光标存入 bx
+    mov bx, ax
+    mov ecx, [esp + 36] ; pushad 压入 4 X 8 = 32 字节 加上主调函数 4 字节
+    
+    cmp cl, CR             ; 0x0d
+    jz .is_carriage_return
+    cmp cl, LF             ; 0x0a
+    jz .is_line_feed
+    cmp cl, BACKSPACE      ; BS(backspace) 的 asc 码是 0x8
+    jz .is_backspace
+    jmp .put_other
+;;;;;;;;;;;;
+.is_backspace:
+;;;;;;;;;;;; backspace 的一点说明 ;;;;;;;;;;;;;;;;;
+; 当为 backspace 时,本质上只要将光标向前一个显存位置即可。后面输入的字符自然会覆盖此处
+; 的字符,但有可能在键入 backspace 后并不再输入新的字符,这时光标已经向前移动到代删除的
+; 字符位置,但字符还在原处。所以此处添加了空格或空字符 0
+    dec bx     ; 光标指向前一个字符
+    shl bx, 1  ; 光标值乘 2 表示光标对应显存中的偏移字节
+    mov byte [gs:bx], 0x20    ; 将待删除的字节补为 0 或空格(0x20)皆可
+    inc bx
+    mov byte [gs:bx], 0x07
+    shr bx, 1
+    jmp .set_cursor
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+.put_other:
+    shl bx, 1  ; 光标位置用 2 字节表示,将光标值乘 2 表示对应显存中的偏移字节
+
+    mov [gs:bx], cl  ; ASCII 字符本身
+    inc bx 
+    mov byte [gs:bx], 0x07   ; 字符属性(黑屏白字)
+    shr bx, 1                ; 恢复老的光标值
+    inc bx                   ; 下一个光标值
+    cmp bx, 2000
+    jl  .set_cursor          ; 若光标值小于 2000,表示未写到显存的最后,则去设置新的光标值
+    ; 若超出屏幕字符数大小(2000),则换行处理
+.is_line_feed:          ; 换行符 LF (\n)
+.is_carriage_return:    ; 回车符 CR (\r)
+    ; 如果是 CR (\r),只要把光标移动到行首就行
+    xor dx, dx     ; dx 是被除数的高 16 位,清 0
+    mov ax, bx     ; ax 是被除数的低 16 位
+    mov si, 80     
+    ; 由于是效仿 Linux,Linux 系统中 \n 表示下一行的行首,所以本系统中也把\n 和 \r 都处理
+    ; 成 Linux 中的 \n 的意思,也就是下一行的行首
+    div si
+
+    sub bx, dx
+.is_carriage_return_end:   ; 回车符 CR 处理结束
+    add bx, 80
+    cmp bx, 2000
+.is_line_feed_end:      ;若是 LF(\n),将光标移+80
+    jl .set_cursor
+
+    ; 屏幕行范围是 0~24,滚屏的原理是将屏幕的第 1~24 行搬运到 0~23 行,再将第 24 行用空格填充
+.roll_screen:       ; 若超出屏幕大小,开始滚屏
+    cld                 ; DF = 0
+    mov ecx, 960        ; 2000-80=1920 个字符要搬运,共 1920*2=3840 字节,一次搬运 4 字节,共 960 次
+    mov edi, 0xc00b8000 ; 第 0 行行首
+    mov esi, 0xc00b80a0 ; 第 1 行行首 0xc00b8000 + (80 * 2)
+    rep movsb
+
+    ; 将最后一行填充为空白
+    mov ebx, 3840    ; 最后一行首字符的第一个字节偏移=1920*2
+    mov ecx, 80      ; 一行 80 字符(160 字节),每次清空 1 字符,需要移动 80 次
+.cls: 
+    mov word [gs:ebx], 0x0720    ; 0x07 黑底白字 0x20 空格符
+    add ebx, 2
+    loop .cls
+    mov bx, 1920      ;!将光标值重置为 1920,最后一行的首字符
+
+.set_cursor:
+    ; 将光标设为 bx 值
+    ;; 1. 先设置高 8 位
+    mov dx, 0x3d4     ; 索引寄存器
+    mov al, 0x0e      ; 用于提供光标位置的高 8 位
+    out dx, al
+    mov dx, 0x3d5     ; 通过读写数据端口 0x3d5 来获取或设置光标位置
+    mov al, bh 
+    out dx, al
+    ;; 2. 再设置低 8 位
+    mov dx, 0x3d4 
+    mov al, 0x0f
+    out dx, al 
+    mov dx, 0x3d5
+    mov al, bl 
+    out dx, al
+.put_char_done:
+    popad 
+    ret

+ 8 - 0
lib/kernel/print.h

@@ -0,0 +1,8 @@
+#ifndef __LIB_KERNEL_PRINT_H
+#define __LIB_KERNEL_PRINT_H
+
+#include "stdint.h"
+
+void put_char(uint8_t char_ascii);
+
+#endif // __LIB_KERNEL_PRINT_H

+ 14 - 0
lib/stdint.h

@@ -0,0 +1,14 @@
+#ifndef __LIB_STDINT_H
+#define __LIB_STDINT_H
+
+typedef signed char int8_t;
+typedef signed short int16_t;
+typedef signed int int32_t;
+typedef signed long long int64_t;
+
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+typedef unsigned long long uint64_t;
+
+#endif // __LIB_STDINT_H

+ 0 - 0
lib/user/.keep