Hello world!

v2-b6052cd808447efc06febd4d74a0de33_1200x500

write in front

Project address: https://github.com/bajdcc/MiniOS

I remember that I modified (actually copied) a piece of SilverRainZ/OS67 code last year. It is no problem to run it with bochs, but it always reports an error with qemu. The key is that qemu is easy to debug. Therefore, I deleted the code in anger, kept the most concise part, and then added functions slowly. During the coding process, I also found some comment problems in OS67. I directly pasted the data URL in the code comments for easy reading.

So what does this minimal kernel look like?

Introduction to the kernel

The directory is divided into:

  • boot boot file
  • include header files
  • script script
  • src/kernel kernel code
  • usr/logo.txt system logo
  • * makefile

The parts in bold are more important.

All codes are stored in a floppy disk. The format of the floppy disk after compilation is: [0-200H] boot item, [200H-] kernel, and the rest is filled with zeros.

Boot section

The system kernel we compiled comes with a boot file, which is ultimately stored in a floppy disk (Floppy) in binary form. When qemu starts, it reads the floppy disk and executes the code.

Boot files are written in assembly.

; 引导扇区 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)是'\r \n'
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

The above code does a few things:

  1. The code above at times 18 db 0: mainly the parameters to fill the floppy disk
  2. _print16: used to output
  3. Then enter the entry
  4. 10H interrupt: set VGA display mode to 16-color 80x25 matrix
  5. Then 13H interrupt: read the floppy disk data ( read only the 2-20th sector, that is, the kernel part, and the boot area is located in the 1st sector ) to the memory 0x8000. Floppy disk size: 80 (track) x 18 (sector) x 512 bytes (sector size) x 2 (double-sided) = 1440 x 1024 bytes = 1440 KB = 1.44MB
  6. Then set the global descriptor
  7. Enable A20 address line
  8. switch to protected mode
  9. Jump to 0x8000, which is the beginning of the kernel code, the kernel is the compiled code of the c file in the src directory

kernel part

There are several files under src/kernel, all very simple.

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

It first copies the kernel code at 0x8000 to 0x100000, and then runs to the kernel entry 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...\n");
}

int os_main(void)
{
    init();

    vga_setcolor(VGA_COLOR_LIGHTBLUE, VGA_COLOR_BLACK);

    puts("\n");
    puts("Hello world!  --- OS by bajdcc \n");
    puts("\n");

LOOP:
    hlt();
    goto LOOP;

    return 0;
}

What the code does is print a bunch of characters. .

Leftovers

VGA character display

Printing characters can be achieved through interrupts, but there is another way here, that is, outb uses IO operations plus direct operation of video memory.

#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 );
}

The beating pointer on the screen should be set through outb, and the output character only needs to activate the corresponding video memory.

Write Makefiles

# 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 [email protected] $(OBJS) -M>lst/map.map
	$(OBJCPY) -O binary [email protected] $@

# 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 This line means to put the compiled code of the latter into the floppy disk
make fs This function has not been used for the time being, and will be used later, just hang it Hard disk, which is the compiled program
make run need to be familiar with qemu command line
CGDB

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

This is the configuration file for GDB debugging, b is the abbreviation for break, and c is the abbreviation for continue.

Backed up by https://zhuanlan.zhihu.com/p/25819125 .

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325153898&siteId=291194637