先实现io.asm
vbnet
[bits 32]
; ***********************
; * 硬件端口和常量定义 *
; ***********************
VIDEO_MEMORY equ 0xB8000
LINE_WIDTH equ 80
SCREEN_HEIGHT equ 25
WHITE_ON_BLACK equ 0x0F
KEYBOARD_PORT equ 0x60
KEYBOARD_STATUS_PORT equ 0x64
KEYBOARD_BUF_SIZE equ 256
; 键盘状态标志
CAPS_LOCK equ 0x01
SHIFT_DOWN equ 0x02
CTRL_DOWN equ 0x04
ALT_DOWN equ 0x08
VGA_CRTC_INDEX equ 0x3D4
VGA_CRTC_DATA equ 0x3D5
CURSOR_START equ 0x0A
CURSOR_END equ 0x0B
; ***********************
; * 全局数据定义 *
; ***********************
[section .data]
key_flags db 0 ; 键盘状态标志
cursor_x dd 0 ; 当前光标列
cursor_y dd 0 ; 当前光标行
keyboard_buffer times KEYBOARD_BUF_SIZE db 0
keyboard_head dd 0
keyboard_tail dd 0
; 扫描码转换表 (小写)
scancode_lower:
db 0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 0x08
db 0, 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', 0x0A
db 0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'", '`', 0
db '\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, 0, 0, ' '
; 扫描码转换表 (大写/Shift)
scancode_upper:
db 0, 0, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', 0x08
db 0, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', 0x0A
db 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', 0
db '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0, 0, 0, ' '
; ***********************
; * 显示功能函数 *
; ***********************
[section .text]
; 函数:hide_cursor - 隐藏文本模式光标
global hide_cursor
hide_cursor:
push eax
push edx
; 设置光标起始寄存器 (索引0x0A)
mov al, CURSOR_START
mov dx, VGA_CRTC_INDEX
out dx, al
; 写入值0x20到数据端口(禁用光标)
mov al, 0x20
mov dx, VGA_CRTC_DATA
out dx, al
; 可选:为了兼容性,也设置结束寄存器
mov al, CURSOR_END
mov dx, VGA_CRTC_INDEX
out dx, al
mov al, 0x00 ; 结束扫描线设为0
mov dx, VGA_CRTC_DATA
out dx, al
pop edx
pop eax
ret
; 在指定位置输出字符
; 输入: EBX=行, ECX=列, AL=字符, AH=属性
global put_char
put_char:
push edi
mov edi, ebx
imul edi, LINE_WIDTH
add edi, ecx
shl edi, 1
mov [gs:edi], ax
pop edi
ret
; 输出字符串 (自动换行)
; 输入: EBX=起始行, ECX=起始列, ESI=字符串, AH=属性
global print_str
print_str:
pusha
cld
.print_loop:
lodsb
test al, al
jz .done
push ebx
push ecx
call put_char
pop ecx
pop ebx
inc ecx
cmp ecx, LINE_WIDTH
jb .same_line
mov ecx, 0
inc ebx
cmp ebx, SCREEN_HEIGHT
jb .same_line
dec ebx
dec ecx
call scroll_screen
.same_line:
jmp .print_loop
.done:
popa
ret
; 清屏
global clear_screen
clear_screen:
pusha
mov edi, VIDEO_MEMORY
mov ecx, LINE_WIDTH * SCREEN_HEIGHT
mov ax, (WHITE_ON_BLACK << 8) | ' '
rep stosw
mov dword [cursor_x], 0
mov dword [cursor_y], 0
popa
ret
; 屏幕向上滚动一行
global scroll_screen
scroll_screen:
pusha
; 1. 将第2行到最后行的内容复制到第1行到倒数第2行
mov esi, VIDEO_MEMORY + LINE_WIDTH * 2 ; 源地址 = 第2行开始
mov edi, VIDEO_MEMORY ; 目标地址 = 第1行开始
mov ecx, LINE_WIDTH * (SCREEN_HEIGHT-1) ; 复制 (高度-1) 行
; 使用DWORD移动提高效率
shr ecx, 1 ; 双字计数 = 字数/2
rep movsd
; 如果字符数为奇数,复制最后一个字
;test ecx, 1
;jz .clear_last_line
;movsw
.clear_last_line:
; 2. 清空最后一行
mov edi, VIDEO_MEMORY + LINE_WIDTH * (SCREEN_HEIGHT-1) * 2
mov ecx, LINE_WIDTH
mov ax, (WHITE_ON_BLACK << 8) | ' ' ; 空格字符+属性
rep stosw
; 3. 更新光标位置(保持在最后一行开头)
mov dword [cursor_x], 0
mov dword [cursor_y], SCREEN_HEIGHT
popa
ret
; ***********************
; * 键盘功能函数 *
; ***********************
; 键盘中断处理程序 (IRQ1)
global get_key
get_key:
push edx
push ebx
.wait_key:
; 等待键盘缓冲区有数据
in al, KEYBOARD_STATUS_PORT
test al, 1
jz .wait_key
; 读取键盘扫描码
in al, KEYBOARD_PORT
mov ah, al ; 保存扫描码
; 处理特殊键 (Shift/Ctrl/Alt/CapsLock)
cmp al, 0x2A ; 左Shift按下
je .shift_press
cmp al, 0xAA ; 左Shift释放
je .shift_release
cmp al, 0x36 ; 右Shift按下
je .shift_press
cmp al, 0xB6 ; 右Shift释放
je .shift_release
cmp al, 0x3A ; CapsLock
je .caps_toggle
cmp al, 0x1D ; Ctrl
je .ctrl_press
cmp al, 0x9D ; Ctrl释放
je .ctrl_release
cmp al, 0x38 ; Alt
je .alt_press
cmp al, 0xB8 ; Alt释放
je .alt_release
; 检查是否是释放事件
test al, 0x80
jnz .key_release
; 转换为ASCII
movzx ebx, al
cmp ebx, 58 ; 检查是否在转换表范围内
ja .special_key
; 选择转换表 (根据Shift/Caps状态)
test byte [key_flags], SHIFT_DOWN
jnz .use_upper
test byte [key_flags], CAPS_LOCK
jz .use_lower
.use_upper:
mov al, [scancode_upper + ebx]
jmp .check_valid
.use_lower:
mov al, [scancode_lower + ebx]
.check_valid:
test al, al
jz .special_key
stc ; CF=1表示按下事件
jmp .done
.key_release:
;clc ; CF=0表示释放事件
xor al, al
jmp .done
.special_key:
xor al, al ; AL=0表示特殊键
stc
jmp .done
.shift_press:
or byte [key_flags], SHIFT_DOWN
xor al, al
stc
jmp .done
.shift_release:
and byte [key_flags], ~SHIFT_DOWN
xor al, al
stc
jmp .done
.caps_toggle:
xor byte [key_flags], CAPS_LOCK
; 更新CapsLock LED
call update_leds
xor al, al
stc
jmp .done
.ctrl_press:
or byte [key_flags], CTRL_DOWN
xor al, al
stc
jmp .done
.ctrl_release:
and byte [key_flags], ~CTRL_DOWN
xor al, al
stc
jmp .done
.alt_press:
or byte [key_flags], ALT_DOWN
xor al, al
stc
jmp .done
.alt_release:
and byte [key_flags], ~ALT_DOWN
xor al, al
stc
jmp .done
.done:
pop ebx
pop edx
ret
; 更新键盘LED状态 (CapsLock/NumLock/ScrollLock)
update_leds:
pushad
; 等待键盘可接受命令
mov ecx, 1000
.wait_ready:
in al, KEYBOARD_STATUS_PORT
test al, 0x02
loopnz .wait_ready
; 发送LED更新命令
mov al, 0xED ; LED命令
out KEYBOARD_PORT, al
; 等待应答
mov ecx, 1000
.wait_ack:
in al, KEYBOARD_PORT
cmp al, 0xFA ; ACK
loopne .wait_ack
; 发送LED状态
mov al, [key_flags]
and al, CAPS_LOCK ; 只设置CapsLock
out KEYBOARD_PORT, al
popad
ret
; 获取一个按键 (非阻塞)
; 输出: AL=ASCII字符 (0表示无输入)
global get_char
get_char:
mov eax, [keyboard_head]
cmp eax, [keyboard_tail]
je .no_input
mov al, [keyboard_buffer + eax]
inc eax
cmp eax, KEYBOARD_BUF_SIZE
jne .no_wrap
xor eax, eax
.no_wrap:
mov [keyboard_head], eax
ret
.no_input:
xor al, al
ret
; 读取一行输入 (带回显)
; 输入: EDI=缓冲区, ECX=最大长度
; 输出: EAX=读取字符数
global read_line
read_line:
push ebx
push ecx
push edx
push edi
xor ebx, ebx ; 字符计数
mov edx, [cursor_y]
shl edx, 16
or edx, [cursor_x] ; EDX高16位=行, 低16位=列
.read_loop:
call get_char
test al, al
jz .read_loop
cmp al, 0x0A ; 回车
je .line_end
cmp al, 0x08 ; 退格
je .backspace
; 检查缓冲区是否满
cmp ebx, ecx
jae .read_loop
; 存储并显示字符
mov [edi + ebx], al
inc ebx
push ebx
push ecx
movzx ebx, dx ; 当前列
movzx ecx, dh ; 当前行
mov ah, WHITE_ON_BLACK
call put_char
pop ecx
pop ebx
inc dl ; 列位置+1
cmp dl, LINE_WIDTH
jb .read_loop
mov dl, 0 ; 换行处理
inc dh
cmp dh, SCREEN_HEIGHT
jb .read_loop
dec dh
call scroll_screen
jmp .read_loop
.backspace:
test ebx, ebx
jz .read_loop
dec ebx
push ebx
push ecx
movzx ebx, dx
movzx ecx, dh
mov al, ' '
mov ah, WHITE_ON_BLACK
call put_char
pop ecx
pop ebx
dec dl
jns .read_loop
mov dl, LINE_WIDTH-1
dec dh
jns .read_loop
xor dh, dh
jmp .read_loop
.line_end:
mov byte [edi + ebx], 0
mov eax, ebx
; 更新光标位置
mov [cursor_x], edx
shr edx, 16
mov [cursor_y], edx
pop edi
pop edx
pop ecx
pop ebx
ret
实现一个简易的shell.asm
vbnet
;shell.asm
[bits 32]
; 常量定义
ATA_BASE equ 0x1F0
ATA_DRIVE_SELECT equ 0x1F6
ATA_STATUS equ 0x1F7
ATA_CMD equ 0x1F7
ATA_DATA equ 0x1F0
extern scroll_screen
[section .data]
; Shell界面
msg db "[root@Plain]-(/)# ", 0
cmd_buffer times 80 db 0
; 命令定义
cmd_echo db "echo", 0
cmd_help db "help", 0
cmd_ls db "ls", 0
cmd_cat db "cat", 0
cmd_write db "write", 0
cmd_clear db "clear", 0
cmd_time db "time", 0
time_str db "HH:MM:SS", 0
; 帮助信息
help_msg1 db "Available commands:", 0
help_msg2 db " echo <message> - Display message", 0
help_msg3 db " help - Show this help", 0
help_msg4 db " ls - List files", 0
help_msg5 db " cat <file> - Show file content", 0
help_msg6 db " write <file> > <content> - Write to file", 0
help_msg7 db " clear - Clear screen", 0
; 错误和信息消息
not_msg db "Error: Command not found: ", 0
error_msg db "ERROR: Disk operation failed", 0
dir_entry db " [DIR] ", 0
no_file_msg db "File not found: ", 0
write_success db "Write successful", 0
write_fail db "Write failed", 0
invalid_format_msg db "Invalid write format. Use: write filename > content", 0
; 文件系统参数
files_per_sector equ 16 ; FAT12根目录每扇区16个条目
[section .bss]
; 磁盘缓冲区
bpb_buf resb 512
fat_buf resb 512
; 文件系统参数
part_start resd 1
fat_start resd 1
root_start resd 1
root_sectors resd 1
; ==== 文件系统常量定义 ====
ROOT_DIR_SECTORS equ 14 ; 根目录占用扇区数
ROOT_DIR_START equ 19 ; 根目录起始扇区
FAT1_START equ 1 ; FAT1起始扇区
DATA_START equ 33 ; 数据区起始扇区 = 1 + 9*2 + 14
; ==== 目录条目结构 ====
struc DIR_ENTRY
.name resb 8
.ext resb 3
.attr resb 1
.reserved resb 10
.time resw 1
.date resw 1
.cluster resw 1
.size resd 1
endstruc
[section .text]
extern print_str, put_char, get_key, clear_screen, fs_list_files, fs_files_count, fs_read_file
global shell
shell:
cmp ebx, 25
ja .scroll
mov ecx, 0
mov esi, msg
mov ah, 0x0F
call print_str
; 初始化命令缓冲区
mov edi, cmd_buffer
mov ecx, 18 ; 从第18列开始输入
mov byte [edi], 0 ; 清空缓冲区
.input_loop:
call get_key
test al, al
jz .input_loop
; 处理回车
cmp al, 0x0A
je .execute
; 处理退格
cmp al, 0x08
je .backspace
; 存储并显示字符
mov [edi], al
inc edi
mov ah, 0x0F
call put_char
inc ecx
jmp .input_loop
.backspace:
; 退格处理
cmp edi, cmd_buffer
je .input_loop ; 忽略空退格
dec edi
dec ecx
mov al, ' '
mov ah, 0x0F
call put_char
jmp .input_loop
.scroll:
call scroll_screen
dec ebx
jmp shell
.execute:
; 添加字符串结束符
mov byte [edi], 0
; 检查空命令
mov esi, cmd_buffer
call is_empty
je .empty_cmd
; 跳过前导空格
call skip_spaces
test al, al
jz .empty_cmd
; 检查help命令
mov edi, cmd_help
call cmd_cmp
je .show_help
; 检查echo命令
mov edi, cmd_echo
call cmd_cmp
je .do_echo
mov edi, cmd_time
call cmd_cmp
je do_time
; 检查clear命令
mov edi, cmd_clear
call cmd_cmp
je .do_clear
; 未知命令处理
inc ebx
mov ecx, 0
mov esi, not_msg
mov ah, 0x0C ; 红色错误信息
call print_str
; 只显示命令部分(第一个空格前的内容)
mov esi, cmd_buffer
call print_command_part
inc ebx
jmp shell
.empty_cmd:
inc ebx
mov ecx, 0
jmp shell
.show_help:
; 显示帮助信息
inc ebx
mov ecx, 0
mov esi, help_msg1
mov ah, 0x0A ; 绿色帮助信息
call print_str
inc ebx
mov ecx, 0
mov esi, help_msg2
call print_str
inc ebx
mov ecx, 0
mov esi, help_msg3
call print_str
inc ebx
mov ecx, 0
mov esi, help_msg4
call print_str
inc ebx
mov ecx, 0
mov esi, help_msg5
call print_str
inc ebx
mov ecx, 0
mov esi, help_msg6
call print_str
inc ebx
mov ecx, 0
mov esi, help_msg7
call print_str
inc ebx
jmp shell
.do_echo:
; 跳过"echo"和后续空格
add esi, 4
call skip_spaces
test al, al
jz .no_args1 ; 无参数情况
; 显示echo参数
inc ebx
mov ecx, 0
mov ah, 0x0F
call print_str
.no_args1:
inc ebx ; 换行
jmp shell
; === clear命令实现 ===
.do_clear:
call clear_screen
mov ebx, 0
mov ecx, 0
jmp shell
; === 辅助函数 ===
; 打印命令部分(第一个空格前的内容)
print_command_part:
pusha
mov ecx, 26 ; 错误信息后位置
.loop:
lodsb
test al, al
jz .done
cmp al, ' '
je .done
mov ah, 0x0F
call put_char
inc ecx
jmp .loop
.done:
popa
ret
; 检查字符串是否为空或只有空格
is_empty:
push esi
.loop:
lodsb
cmp al, ' '
je .loop
test al, al
pop esi
ret
; 跳过字符串中的空格
skip_spaces:
lodsb
cmp al, ' '
je skip_spaces
dec esi ; 回退到第一个非空格字符
ret
; 命令比较函数
cmd_cmp:
pusha
.compare:
mov al, [esi]
mov bl, [edi]
; 检查命令是否结束(空格或字符串结束)
cmp al, ' '
je .check_cmd_end
test al, al
jz .check_cmd_end
; 转换为小写比较
cmp al, 'A'
jb .no_change1
cmp al, 'Z'
ja .no_change1
add al, 0x20
.no_change1:
cmp bl, 'A'
jb .no_change2
cmp bl, 'Z'
ja .no_change2
add bl, 0x20
.no_change2:
cmp al, bl
jne .not_equal
inc esi
inc edi
jmp .compare
.check_cmd_end:
; 检查命令字符串是否也结束了
cmp byte [edi], 0
jne .not_equal
.equal:
popa
xor eax, eax ; ZF=1
ret
.not_equal:
popa
or eax, 1 ; ZF=0
ret
; 显示固定数量的字符
print_nchars:
pusha
mov ah, 0x0F
.loop:
lodsb
call put_char
loop .loop
popa
ret
print_hex:
pushad
mov ecx, 8
.loop:
rol eax, 4
mov ebx, eax
and ebx, 0x0f
mov bl, [hex_chars + ebx]
mov ah, 0x0F
call put_char
loop .loop
popad
ret
do_time:
call get_time
inc ebx ; 换行
mov ecx, 0
mov esi, time_str
mov ah, 0x0F ; 白色文字
call print_str
jmp shell
get_time:
pushad
; 禁用NMI并读取小时
mov al, 0x04 ; 小时寄存器
or al, 0x80 ; 禁用NMI
out 0x70, al
in al, 0x71
call bcd_to_ascii
mov [time_str], dh
mov [time_str+1], dl
; 读取分钟
mov al, 0x02
or al, 0x80
out 0x70, al
in al, 0x71
call bcd_to_ascii
mov [time_str+3], dh
mov [time_str+4], dl
; 读取秒
mov al, 0x00
or al, 0x80
out 0x70, al
in al, 0x71
call bcd_to_ascii
mov [time_str+6], dh
mov [time_str+7], dl
popad
ret
bcd_to_ascii:
; 将AL中的BCD码转换为两个ASCII字符,存储在DH和DL中
mov dh, al
shr dh, 4
add dh, '0'
mov dl, al
and dl, 0x0F
add dl, '0'
ret
; === 数据区 ===
[section .data]
no_files_msg db "No files found", 0
cat_usage_msg db "Usage: cat <filename>", 0
; 文件系统参数(在Shell初始化时设置)
data_start dd 33 ; 1+9*2+14=33
root_dir_sectors dd 14 ; 根目录扇区数
fat1_start dd 1 ; FAT1起始扇区
file_cluster dw 0
free_entry dd 0
lba_low db 0
lba_mid db 0
lba_high db 0
sectors_per_track dd 18
num_heads dd 2
boot_drive db 0
hex_chars db '0123456789ABCDEF'
放一下makefile
bash
all:
make bin
make img
make run
bin:
nasm boot.asm -o boot.bin
nasm loader.asm -o loader.bin
#gcc -c -O0 -fno-builtin -m32 -fno-stack-protector -o main.o main.c
nasm -f elf -o kernel.o kernel.asm
nasm -f elf -o io.o io.asm
nasm -f elf -o shell.o shell.asm
i686-elf-ld -s -Ttext 0x100000 -o kernel.bin kernel.o io.o shell.o
img : boot.bin loader.bin kernel.bin
#dd if=boot.bin of=a.img bs=512 count=1
#dd if=loader.bin of=a.img bs=512 seek=1 conv=notrunc
#python img.py
edimg imgin:1.img \
wbinimg src:boot.bin len:512 from:0 to:0 \
copy from:loader.bin to:@: \
copy from:kernel.bin to:@: \
imgout:a.img
run : a.img
#qemu-system-i386 -fda a.img
qemu-system-i386 -fda a.img #-hda disk.img -boot a
最后实现kernel.asm
vbnet
; Plain Kernel
; kernel.asm
[section .text]
%include "io.inc"
global _start
extern shell
extern init_mouse
_start:
call hide_cursor
call clear_screen
mov ebx, 0 ; 行号
mov ecx, 0 ; 列号
mov esi, hello_msg ; 字符串地址
mov ah, 0x0F
call print_str
xor ecx, ecx
mov ebx, 2 ; 行号
call shell
hello_msg db "Welcome to Plain - OS !", 0
input_buffer times 80 db 0
[section .bss]
sector_buf resb 512
bpb_buf resb 512
fat_buf resb 512
part_start resd 1
fat_start resd 1
root_start resd 1
data_start resd 1
root_sectors resd 1
其中io.inc
vbnet
; 常量定义
ATA_BASE equ 0x1F0
ATA_DRIVE_SELECT equ 0x1F6
ATA_STATUS equ 0x1F7
ATA_CMD equ 0x1F7
ATA_DATA equ 0x1F0
; 全局变量
VIDEO_MEMORY equ 0xB8000
LINE_WIDTH equ 80
WHITE_ON_BLACK equ 0x0F
KEYBOARD_PORT equ 0x60
KEYBOARD_STATUS_PORT equ 0x64
KEYBOARD_BUFFER_SIZE equ 32
extern put_char
extern print_str
;extern clear_screen
extern get_char
extern get_key
extern read_line
extern scroll_screen
extern init_fs
;extern write_file
extern ata_read
extern ata_wait
extern hide_cursor
extern clear_screen