Hello world!

Posted 千里之行,始于足下。

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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)是\'\\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

上述代码做了一些事情:

  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...\\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;
}

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

边角料

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备份。

以上是关于Hello world!的主要内容,如果未能解决你的问题,请参考以下文章

Hello World代码

Hello World代码

怎么用maven写出hello world

第一行代码Hello,World!

hello.equals(hello)的代码值是多少?

Hello,World!