• Hello world!


    v2-b6052cd808447efc06febd4d74a0de33_1200x500

    写在前面

    项目地址:https://github.com/bajdcc/MiniOS

    想起去年魔改(其实就是copy)了一份SilverRainZ/OS67的代码,用bochs运行没问题,但用qemu老是报错,关键是qemu调试起来顺手。故怒删代码,保留最精简部分,然后慢慢加功能,在coding过程中,也发现了OS67中的一些注释问题,我将资料网址直接贴在代码注释里,方便阅读 。

    那么这个最精简的内核是怎样的呢?

    内核初谈

    目录分为:

    • boot 引导文件
    • include 头文件
    • script 脚本
    • src/kernel 内核代码
    • usr/logo.txt 系统LOGO
    • * makefile

    加粗的部分是比较重要的。

    所有代码保存在软盘中,最终编译后软盘的格局:[0-200H] 引导项,[200H-] 内核,剩余部分填零。

    引导部分

    我们编译的系统内核附带上引导文件,最终是以二进制形式保存在软盘(Floppy)中的,qemu启动的时候,读取软盘并执行代码。

    引导文件用汇编写成。

    ; 引导扇区 FAT12
    
    %INCLUDE "gdt.asm" ; 段描述表定义
    
    [BITS 16]
    org 0x7c00 ; 加载地址偏移 参考http://blog.csdn.net/u011542994/article/details/46707815
    
    BS_jmpBoot      jmp   entry
                    db    0x90
    BS_OEMName      db    "CCOSV587"        ; OEM name / 8 B
    BPB_BytsPerSec  dw    512               ; 一个扇区512字节 
    BPB_SecPerClus  db    1                 ; 每个簇一个扇区
    BPB_RsvdSecCnt  dw    1                 ; 保留扇区数, 必须为1
    BPB_NumFATs     db    2                 ; FAT表份数
    BPB_RootEntCnt  dw    224               ; 根目录项数
    BPB_TotSec16    dw    2880              ; RolSec16, 总扇区数
    BPB_Media       db    0xf0              ; 介质种类: 移动介质
    BPB_FATSz16     dw    9                 ; FATSz16 分区表占用扇区数
    BPB_SecPerTrk   dw    18                ; SecPerTrk, 磁盘 
    BPB_NumHeads    dw    2                 ; 磁头数    
    BPB_HiddSec     dd    0                 ; HiddSec
    BPB_TotSec32    dd    2880              ; 卡容量
    BS_DrvNum       db    0                 ; DvcNum
    BS_Reserved1    db    0                 ; NT保留    
    BS_BootSig      db    0x29              ; BootSig扩展引导标记
    BS_VolD         dd    0xffffffff        ; VolID 
    BS_VolLab       db    "FLOPPYCDDS "     ; 卷标
    BS_FileSysType  db    "FAT12   "        ; FilesysType
    
    times 18 db 0
    
    _print16:
    loop:
        lodsb   ; ds:si -> al
        or al,al
        jz done
        mov ah,0x0e
        mov bx,15        
        int 0x10        ; 打印字符
        jmp loop
    done:
        ret
    
    ;============================================================
    ; 入口
    entry:
        mov ax,0        
        mov ss,ax
        mov sp,0x7c00
        mov ds,ax
        mov es,ax   ; bios interrupt expects ds
    
        ; shift to text mode, 16 color 80*25
        ; 参考自http://blog.csdn.net/hua19880705/article/details/8125706
        ;      http://www.cnblogs.com/magic-cube/archive/2011/10/19/2217676.html
        mov ah,0x0
        mov al,0x03  ; 设置模式 16色 80x25矩阵
        int 0x10     ; 设置颜色
    
        mov si, msg_boot
        call _print16
    
    
    ;============================================================
    ; 从软盘中读取内核代码
    ; 参考http://blog.chinaunix.net/uid-20496675-id-1664077.html
    ;     http://chuanwang66.iteye.com/blog/1678952
    
    ; 读磁盘时,将读到的扇区放到[es:bx]开始的内存中
    ; 写磁盘时,将[es:bx]开始的一个扇区写到磁盘上
    ; 这两处,[es:bx]都称为 数据缓冲区
    
    ; read 20 sector (360 KB) form floppy
    loadloader:      
        mov bx,0    
        mov ax,0x0800 
        mov es,ax   ; [es:bx] buffer address point -> 0x8000 将读取数据存放至0x8000
        mov cl,2    ; 扇区 Sector
        mov ch,0    ; 磁道 Track
        mov dh,0    ; 盘面 Cylinder
        mov dl,0    ; 驱动器号 driver a:
        ; kernel locates after bootloader, which is the second sector
    
    readloop:
        mov si,0    ; 错误计数 err counter
    
    retry:
        mov ah,0x02 ; int 0x13 ah = 0x02 read sector form dirve
        mov al,1    ; read 1 sector 
        int 0x13    ; 读取磁道1
        jnc next    ; 没有错误则继续读取
        add si,1
        cmp si,5    ; 累计错误出现5次就报错
        jae error
        mov ah,0
        mov dl,0    ; driver a
        int 0x13    ; 复位 reset
        jmp next 
    
    next: 
        mov ax,es
        add ax,0x20 ; 一个扇区是512B=0x200,es是段,故再除以16,得到0x20
        mov es,ax
    
        add cl,1    ; 读下一个扇区 sector + 1
        cmp cl,18   ; 18 sector 如果读满了所有18个扇区,就
        jbe readloop
    
        mov cl,1
        add dh,1    ; 盘面 + 1
        cmp dh,1
        jbe readloop
    
        mov dh,0
        add ch,1    ; 磁道 + 1
        cmp ch,20   ; 只读取20个磁道共360KB
        jbe readloop
        jmp succ
    
    error:        
        mov  si,msg_err ; 报错
        call _print16
        jmp $           ; halt
    
    succ:    
        mov si,msg_succ ; 读取成功
        call _print16
    
        ; fill and load GDTR 读取全局描述符表寄存器
        ; 参考http://x86.renejeschke.de/html/file_module_x86_id_156.html
        xor eax,eax
        mov ax,ds
        shl eax,4
        add eax,GDT                 ; eax <- gdt base 
        mov dword [GdtPtr+2],eax    ; [GdtPtr + 2] <- gdt base 
    
        lgdt [GdtPtr]
        cli
    
        ; turn on A20 line
        ; 参考 http://blog.csdn.net/yunsongice/article/details/6110648
        in al,0x92
        or al,00000010b
        out 0x92,al
    
        ; 切换到保护模式 shift to protect mode  
        mov eax,cr0
        or eax,1
        mov cr0,eax
    
        ; special, clear pipe-line and jump
        ; 前面读取软盘数据到0x8000处,现在跳转至0x8000 
        jmp dword Selec_Code32_R0:0x8000
    
    msg_boot:
        db "[Bootsector] loading...",13,10,0 ; 13 10(0x0D 0x0A)是'
     
    '
    msg_err:
        db "[Bootsector] error",13,10,0
    msg_succ:
        db "[Bootsector] ok",13,10,0
    msg_temp:
        db 0,0,0
    msg_get_mem_map_err:
        db "[Bootsector] failed",0
    
    GDT: ; 全局描述符表
    DESC_NULL:        Descriptor 0,       0,            0                         ; null
    DESC_CODE32_R0:   Descriptor 0,       0xfffff - 1,  DA_C+DA_32+DA_LIMIT_4K    ; uncomfirm 
    DESC_DATA_R0:     Descriptor 0,       0xfffff - 1,  DA_DRW+DA_32+DA_LIMIT_4K  ; uncomfirm  ; 4G seg 
    DESC_VIDEO_R0:    Descriptor 0xb8000, 0xffff,       DA_DRW+DA_32              ; vram 
    
    GdtLen  equ $ - GDT     ; GDT len
    GdtPtr  dw  GdtLen - 1  ; GDT limit
            dd  0           ; GDT Base
    
    ; GDT Selector 
    Selec_Code32_R0 equ     DESC_CODE32_R0 - DESC_NULL
    Selec_Data_R0   equ     DESC_DATA_R0   - DESC_NULL 
    Selec_Video_R0  equ     DESC_VIDEO_R0  - DESC_NULL
    
    times 510 - ($-$$) db 0 ; 填充零
    db 0x55, 0xaa
    

    上述代码做了一些事情:

    1. 在 times 18 db 0 上面的代码:主要是填充软盘的参数
    2. _print16:用来输出
    3. 接着进入entry
    4. 10H中断:设置VGA显示模式为16色 80x25矩阵
    5. 接着13H中断:读软盘数据(只读第2-20个扇区,即内核部分,而引导区位于第1个扇区内)到内存0x8000处。软盘大小:80(磁道)x 18(扇区)x 512 bytes(扇区的大小) x 2(双面)= 1440 x 1024 bytes = 1440 KB = 1.44MB
    6. 然后设置全局描述符
    7. 启用A20地址线
    8. 切换到保护模式
    9. 跳转到0x8000处,也就是内核代码开始处,内核就是src目录下c文件编译后的代码

    内核部分

    src/kernel下面有几个文件,都非常简单。

    loader.asm

    ; loader.asm
    ; jmp to C kernel, achieve some function in asm 
    ; 
    
    ; kernel code segment selector
    SEL_KERN_CODE   EQU 0x8
    ; kernel data segment selector
    SEL_KERN_DATA   EQU 0x10
    ; vedio memory
    SEL_KERN_VEDIO  EQU 0x18
    ; 用户地址起始
    USER_BASE       EQU 0xc0000000
    
    align 4
    
    [bits 32]
    [section .text]
    
    [extern os_main]
    [global start]
    start:
        xor eax, eax
        mov ax, SEL_KERN_DATA
        mov ds, ax
        mov ax, SEL_KERN_DATA
        mov es, ax
        mov ax, SEL_KERN_VEDIO
        mov gs, ax
        mov ax, SEL_KERN_DATA
        mov ss, ax
        mov esp, 0x7c00 ; 联想到bootsect中的org 0x7c00
    
    ; mov the kernel to 0x100000
    [extern kernstart]
    [extern kernend]
        mov eax, kernend
        mov ecx, kernstart
        sub eax, ecx
        mov ecx, eax
        mov esi, 0x8000
        mov edi, 0x100000
        cld
        rep movsb
        jmp dword SEL_KERN_CODE:go
    
    go:
        mov	edi, (160*3)+0   ; 160*50 line 3 column 1
        mov	ah, 00001100b    ; red color
    
        mov esi, msg 
        call print
    
        push 0
        jmp os_main          ; os entry
    
        jmp $                ; halt
    
    print:
        add edi, 160
        push edi
        cld
    
    loop:
        lodsb
        cmp al, 0
        je outloop
        mov	[gs:edi], ax
        add edi, 2
        jmp loop
    
    outloop:
        pop edi
        ret
    
    msg:
        db "=== [ OS ENTRY ] ===", 0
    

    它首先将0x8000处的内核代码复制到0x100000处,然后运行到内核入口os_main。

    main.c

    #include <type.h>
    #include <asm.h>
    #include <vga.h>
    #include <print.h>
    #include <debug.h>
    
    void print_ok(void)
    {
        putchar('[');
        vga_setcolor(VGA_COLOR_GREEN, VGA_COLOR_BLACK);
        puts("OK");
        vga_setcolor(VGA_COLOR_LIGHTGREY, VGA_COLOR_BLACK);
        putchar(']');
    }
    
    void init(void)
    {
        vga_init();
    
        print_ok();
        puts(" init vga...
    ");
    }
    
    int os_main(void)
    {
        init();
    
        vga_setcolor(VGA_COLOR_LIGHTBLUE, VGA_COLOR_BLACK);
    
        puts("
    ");
        puts("Hello world!  --- OS by bajdcc 
    ");
        puts("
    ");
    
    LOOP:
        hlt();
        goto LOOP;
    
        return 0;
    }
    

    代码的作用就是打印一堆字符。。

    边角料

    VGA字符显示

    打印字符可以通过中断实现,不过,这里还有一种方式,就是outb利用IO操作加上直接操作显存。

    #define VGA_CRT_IC  0x3d4   // vga index register port
    #define VGA_CRT_DC  0x3d5   // vga data register port
    struct vga_char *vga_mem;       /* vga[25][80] at 0xb8000 */
    struct vga_char color;          /* use vag_char structure to store color */
    struct point cur;
    static void move_cur()
    {
    	uint16_t tmp;
    
    	tmp = cur.y * 80 + cur.x;
    
    	/* cursor high port to vga index register */
    	outb( VGA_CRT_IC, 0xe );
    	outb( VGA_CRT_DC, tmp >> 8 );
    	/* cursor low port to vga index register */
    	outb( VGA_CRT_IC, 0xf );
    	outb( VGA_CRT_DC, tmp );
    }
    

    屏幕上跳动的指针要通过outb设置位置,输出字符只要发动相应显存即可。

    编写Makefile

    # makefile
    
    .PHONY: init run fs fsck clean
    .IGNORE: init
    
    MAKE = make -r
    AS = nasm
    CC = gcc
    DEL = rm -f
    QEMU = qemu
    LD = ld
    OBJCPY = objcopy
    GDB = cgdb
    IMG = qemu-img
    MKFS = mkfs.minix
    FSCK = fsck.minix
    CFLAGS = -c -O0 -Wall -Werror -nostdinc -fno-builtin -fno-stack-protector -funsigned-char 
    		 -finline-functions -finline-small-functions -findirect-inlining 
    		 -finline-functions-called-once -Iinclude -m32 -ggdb -gstabs+ -fdump-rtl-expand
    ROOTFS = bin/rootfs
    OBJS = bin/loader.o bin/main.o bin/asm.o bin/vga.o bin/string.o bin/print.o bin/debug.o
    
    # default task
    default: Makefile
    	$(MAKE) bin/floppy.img
    
    # create a 1.44MB floppy include kernel and bootsector
    bin/floppy.img: boot/floppy.asm bin/bootsect.bin bin/kernel 
    	$(AS) -I ./bin/ -f bin -l lst/floppy.s $< -o $@ 
    
    # bootsector
    bin/bootsect.bin: boot/bootsect.asm 
    	$(AS) -I ./boot/ -f bin -l lst/bootsect.s $< -o $@ 
    
    bin/loader.o : src/kernel/loader.asm
    	$(AS) -I ./boot/ -f elf32 -g -F stabs -l lst/loader.s $< -o $@ 
    
    # link loader.o and c objfile 
    # generate a symbol file(kernel.elf) and a flat binary kernel file(kernel)
    bin/kernel: script/link.ld $(OBJS) 
    	$(LD) -T$< -melf_i386 -static -o $@.elf $(OBJS) -M>lst/map.map
    	$(OBJCPY) -O binary $@.elf $@
    
    # compile c file in all directory
    bin/%.o: src/*/%.c
    	$(CC) $(CFLAGS) -c $^ -o $@
    
    #----------------------------------------
    
    # init
    init:
    	mkdir lst
    	mkdir bin
    	mkdir $(ROOTFS)
    
    # make a disk with minix v1 file system
    fs: 
    	$(DEL) bin/rootfs.img
    	$(IMG) create -f raw bin/rootfs.img 10M
    	$(MKFS) bin/rootfs.img -1 -n14
    	sudo mount -o loop -t minix bin/rootfs.img $(ROOTFS)
    	mkdir $(ROOTFS)/bin
    	mkdir $(ROOTFS)/share
    	sleep 1
    	sudo umount $(ROOTFS)
    
    # check root file system
    fsck:
    	$(FSCK) -fsl bin/rootfs.img
    
    # run with qemu
    run:
    	$(QEMU) -S -s 
    		-drive file=bin/floppy.img,if=floppy,format=raw 
    		-drive file=bin/rootfs.img,if=ide,format=raw,cyls=18,heads=2,secs=80 
    	    -boot a -m 64 &
    	sleep 1
    	$(GDB) -x script/gdbinit
    
    # clean the binary file
    clean: 
    	$(DEL) bin/*.lst 
    	$(DEL) bin/*.o 
    	$(DEL) bin/*.bin 
    	$(DEL) bin/*.tmp 
    	$(DEL) bin/kernel 
    	$(DEL) bin/kernel.elf
    	$(DEL) bin/floppy.img
    	$(DEL) lst/*
    

    bin/floppy.img: boot/floppy.asm bin/bootsect.bin bin/kernel 这行意思是将后者编译后的代码塞进软盘中
    make fs 这功能暂时没用过,后面会用,就是挂个硬盘,里面是编译后的程序
    make run 需要熟悉qemu命令行
    CGDB

    target remote localhost:1234
    symbol-file bin/kernel.elf
    b os_main
    c
    

    这是GDB调试的配置文件,b是break中断的缩写,c是continue缩写。

    https://zhuanlan.zhihu.com/p/25819125备份。

  • 相关阅读:
    es6-compact-table 名词备忘
    JS 防抖和节流函数
    为什么 JS 对象内部属性遍历的顺序乱了
    JS 发送 HTTP 请求方法封装
    JS 一些位操作的妙用
    JS 格式化时间
    linux ssh连接
    c# checked 和 unchecked
    c# mvc action 跳转方式
    IIS 动态与静态压缩
  • 原文地址:https://www.cnblogs.com/bajdcc/p/8972941.html
Copyright © 2020-2023  润新知