本项目已在Github开源:Plain-OS
shell.asm
;shell.asm
[bits 32]
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_run db "run", 0
cmd_ping db "ping", 0
cmd_sleep db "sleep", 0
cmd_task db "task", 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
help_msg8 db " run <file> - Execute ELF program", 0
help_msg9 db " sleep <ms> - Delay execution", 0
help_msg10 db " task <cmd> - Run command in background", 0
; 错误和信息消息
not_msg db "Error: Command not found: ", 0
error_msg db "ERROR: Disk operation failed", 0
dir_entry db " [DIR] ", 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
align 8
idt:
times 256 dq 0 ; 256个门描述符,每个8字节
idt_ptr:
dw 256*8 - 1 ; IDT界限 = 大小 - 1
dd idt ; IDT线性基地址
[section .text]
extern print_str, put_char, get_key, clear_screen, fs_list_files, fs_files_count, fs_read_file
extern mem_alloc, mem_free, fs_get_file_size
;extern elf_load, elf_get_entry
extern scroll_screen, do_ping_impl
global shell
shell:
call init_timer
call init_task_system
;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 ; 清空缓冲区
mov al, ' '
mov ah, 0xFF
call put_char
.input_loop:
call get_key
test al, al
jz .input_loop
mov [current_line], ebx
mov [current_column], ecx
; 处理回车
cmp al, 0x0A
je .execute
; 处理退格
cmp al, 0x08
je .backspace
; 存储并显示字符
mov [edi], al
inc edi
mov ah, 0x0F
call put_char
inc ecx
mov al, ' '
mov ah, 0xFF
call put_char
jmp .input_loop
.backspace:
; 退格处理
cmp edi, cmd_buffer
je .input_loop ; 忽略空退格
mov al, ' '
mov ah, 0x0F
call put_char
dec edi
dec ecx
mov al, ' '
mov ah, 0xFF
call put_char
jmp .input_loop
.scroll:
call scroll_screen
mov ebx, 24
mov ecx, 0
jmp shell
.execute:
mov al, ' '
mov ah, 0x0F
call put_char
; 添加字符串结束符
mov byte [edi], 0
; 检查空命令
mov esi, cmd_buffer
call is_empty
je .empty_cmd
; 跳过前导空格
call skip_spaces
test al, al
jz .empty_cmd
mov edi, cmd_task
call cmd_cmp
je do_task
; 检查help命令
mov edi, cmd_help
call cmd_cmp
je .show_help
; 检查echo命令
mov edi, cmd_echo
call cmd_cmp
je .do_echo
; 检查echo命令
mov edi, cmd_ls
call cmd_cmp
je do_ls
mov edi, cmd_time
call cmd_cmp
je do_time
mov edi, cmd_cat
call cmd_cmp
je do_cat
mov edi, cmd_run
call cmd_cmp
je do_run
mov edi, cmd_sleep
call cmd_cmp
je do_sleep
;mov edi, cmd_write
;call cmd_cmp
;je do_write
; 检查clear命令
mov edi, cmd_clear
call cmd_cmp
je .do_clear
mov edi, cmd_ping
call cmd_cmp
je do_ping
; 未知命令处理
jmp .do_run1
.cmd_error:
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
.do_run1:
call skip_spaces
test al, al
jz .no_filename1
; 读取文件到内存
call fs_read_file
jc .cmd_error
inc ebx
mov ecx, 0
; 设置新栈
;mov ebp, 0x90000
;mov esp, ebp
; 跳转到二进制文件
call esi
inc ebx
jmp shell
.file_not_found1:
inc ebx
mov ecx, 0
mov esi, no_file_msg
mov ah, 0x0C
call print_str
; 显示尝试的文件名
mov ecx, 16
mov esi, cmd_buffer
mov ah, 0x0F
call print_str
inc ebx
jmp shell
.no_filename1:
inc ebx
mov ecx, 0
mov esi, run_usage_msg
mov ah, 0x0C
call print_str
jmp shell
.empty_cmd:
cmp ebx, 25
ja .scroll
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
; === ls命令实现 ===
do_ls:
call fs_list_files
; 显示文件名
inc ebx
mov ecx, 0
mov ah, 0x0F
call print_str
; 换行
inc ebx
mov ecx, 0
jmp shell
; === cat命令实现 ===
do_cat:
; 跳过"cat"和空格
add esi, 3
call skip_spaces
test al, al
jz .no_filename
; 直接调用文件系统
call fs_read_file
jc .file_not_found
; 显示内容 (esi已指向内容字符串)
inc ebx
mov ecx, 0
mov ah, 0x0F
call print_str ; 直接打印整个内容
inc ebx
jmp shell
.file_not_found:
inc ebx
mov ecx, 0
mov esi, no_file_msg
mov ah, 0x0C
call print_str
; 显示尝试的文件名
mov ecx, 16
mov esi, cmd_buffer+3
mov ah, 0x0F
call print_str
inc ebx
jmp shell
.no_filename:
inc ebx
mov ecx, 0
mov esi, cat_usage_msg
mov ah, 0x0C
call print_str
jmp shell
; === run命令实现 ===
; 常量定义
PROG_BASE equ 0x100000 ; 程序加载地址 (1MB)
PROG_STACK equ 0x9F000 ; 程序专用栈空间 (64KB)
MAX_SIZE equ 32768 ; 最大程序大小 (32KB)
do_run:
; 跳过"run"和空格
add esi, 3
call skip_spaces
test al, al
jz .no_filename
; 读取文件到ESI
call fs_read_file
jc .file_not_found
; 保存所有寄存器状态
pusha
; 复制程序到固定地址
mov edi, PROG_BASE
mov ecx, MAX_SIZE
cld ; 清除方向标志
rep movsb ; 复制程序代码
; 设置程序专用栈
mov ebp, PROG_STACK
mov esp, ebp
; 准备调用环境
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
xor esi, esi
xor edi, edi
; 跳转到程序
push .return_point ; 返回地址
push PROG_BASE ; 调用地址
ret
.return_point:
; 恢复寄存器状态
popa
jmp shell
.file_not_found:
mov esi, no_file_msg
call print_str
jmp shell
.no_filename:
mov esi, run_usage_msg
call print_str
jmp shell
; === ping命令实现 ===
do_ping:
; 跳过"ping"和空格
add esi, 4
call skip_spaces
test al, al
jz .no_ip
; 调用网络ping功能
push esi ; 压入IP字符串指针
call do_ping_impl
add esp, 4 ; 清理栈
jmp shell
.no_ip:
; 显示用法错误
inc ebx
mov ecx, 0
mov esi, ping_usage_msg
mov ah, 0x0C
call print_str
jmp shell
; === sleep命令实现 ===
do_sleep:
add esi, 5 ; 跳过"sleep"
call skip_spaces
test al, al
jz .invalid
call atoi ; 将参数转换为毫秒数
push eax
call sleep_ms ; 调用睡眠函数
add esp, 4
jmp shell
.invalid:
mov esi, sleep_usage_msg
call print_str
jmp shell
sleep_usage_msg db "Usage: sleep <milliseconds>", 0
; === task命令实现 ===
do_task:
add esi, 4 ; 跳过"task"
call skip_spaces
test al, al
jz .invalid
; 创建新任务
call create_task
; 显示任务启动信息
mov esi, task_start_msg
call print_str
mov eax, [current_pid]
dec eax
call print_dec
call newline
jmp shell
.invalid:
mov esi, task_usage_msg
call print_str
jmp shell
task_usage_msg db "Usage: task <command>", 0
; === 数字转换函数 ===
; 输入:ESI=字符串指针
; 输出:EAX=数值
atoi:
push ebx
push ecx
push edx
xor eax, eax ; 清零结果
xor ebx, ebx ; 临时存储字符
.convert:
lodsb ; 加载下一个字符
test al, al ; 检查字符串结束
jz .done
cmp al, '0'
jb .invalid
cmp al, '9'
ja .invalid
sub al, '0' ; 转换为数字
imul ebx, 10 ; 结果 *= 10
add ebx, eax ; 结果 += 当前数字
jmp .convert
.invalid:
xor ebx, ebx ; 无效输入返回0
.done:
mov eax, ebx ; 结果存入EAX
pop edx
pop ecx
pop ebx
ret
; === 命令解析和执行 ===
; 输入:ESI=命令字符串
parse_and_execute:
pushad
; 保存原始命令指针
mov edi, esi
; 跳过前导空格
call skip_spaces
test al, al
jz .empty
mov edi, cmd_task
call cmd_cmp
je do_task
; 检查echo命令
mov edi, cmd_echo
call cmd_cmp
je .do_echo
; 检查echo命令
mov edi, cmd_ls
call cmd_cmp
je do_ls
mov edi, cmd_time
call cmd_cmp
je do_time
mov edi, cmd_cat
call cmd_cmp
je do_cat
mov edi, cmd_run
call cmd_cmp
je do_run
mov edi, cmd_sleep
call cmd_cmp
je do_sleep
mov edi, cmd_ping
call cmd_cmp
je do_ping
; 如果不是内置命令,尝试作为外部程序执行
jmp do_run
.empty:
popad
ret
.do_echo:
add esi, 5 ; 跳过"echo "
call print_str
popad
ret
; === 十进制打印函数 ===
; 输入:EAX=要打印的数字
print_dec:
pushad
mov ebx, 10 ; 除数
xor ecx, ecx ; 数字位数计数器
.divide_loop:
xor edx, edx
div ebx ; EDX:EAX / EBX
push edx ; 保存余数
inc ecx
test eax, eax
jnz .divide_loop
.print_loop:
pop eax ; 取出数字
add al, '0' ; 转换为ASCII
mov ah, 0x0F ; 显示属性
call put_char
loop .print_loop
popad
ret
; === 换行函数 ===
newline:
xor ecx, ecx
inc ebx
ret
; 定义任务结构体
struc task
.pid: resd 1 ; 进程ID
.status: resd 1 ; 状态 (0=空闲, 1=运行, 2=阻塞)
.esp: resd 1 ; 栈指针
.eip: resd 1 ; 指令指针
.cr3: resd 1 ; 页目录地址
.cmd: resb 64 ; 命令字符串
.regs: resd 8 ; 保存的寄存器 (EAX, EBX, ECX, EDX, ESI, EDI, EBP, EFLAGS)
endstruc
; 全局变量
[section .data]
current_task dd 0 ; 当前任务指针
task_count dd 0 ; 活动任务数
ticks dd 0 ; 系统时钟滴答数
task_list times 16*task_size db 0 ; 任务列表(最多16个任务)
current_pid dd 1 ; 下一个PID
; 初始化任务系统
init_task_system:
pushad
; 初始化第一个任务(Shell)
mov edi, task_list
mov dword [edi + task.pid], 1
mov dword [edi + task.status], 1
mov dword [current_task], edi
inc dword [task_count]
inc dword [current_pid]
; 分配栈空间 (16KB)
push 16384
call mem_alloc
add esp, 4
add eax, 16384 - 32 ; 栈顶
mov [edi + task.esp], eax
; 设置初始上下文
mov dword [eax + 0], 0x202 ; EFLAGS (IF=1)
mov dword [eax + 4], shell ; EIP
mov dword [eax + 8], 0 ; EAX
mov dword [eax + 12], 0 ; EBX
mov dword [eax + 16], 0 ; ECX
mov dword [eax + 20], 0 ; EDX
mov dword [eax + 24], 0 ; ESI
mov dword [eax + 28], 0 ; EDI
mov dword [eax + 32], 0 ; EBP
popad
ret
; 初始化定时器 (PIT 8254)
init_timer:
push eax
; 设置PIT通道0为100Hz
mov al, 0x36 ; 通道0,模式3,二进制计数
out 0x43, al
mov ax, 11932 ; 1193182Hz / 100Hz = 11932
out 0x40, al ; 低字节
mov al, ah
out 0x40, al ; 高字节
; 设置IRQ0处理程序
mov eax, timer_interrupt
mov [idt + 8*0x20], word ax ; 低16位偏移
mov [idt + 8*0x20 + 2], word 0x08 ; 代码段选择子
mov [idt + 8*0x20 + 4], byte 0x00 ; 保留
mov [idt + 8*0x20 + 5], byte 0x8E ; 类型=中断门, DPL=0
shr eax, 16
mov [idt + 8*0x20 + 6], word ax ; 高16位偏移
pop eax
ret
; 定时器中断处理 (IRQ0)
timer_interrupt:
pushad
; 发送EOI
mov al, 0x20
out 0x20, al
; 更新系统时钟
inc dword [ticks]
; 检查是否需要调度
cmp dword [task_count], 1
jbe .no_schedule
; 保存当前任务上下文
mov edi, [current_task]
mov [edi + task.esp], esp
; 保存寄存器状态
mov eax, [esp + 28] ; 从pushad中获取EFLAGS
mov [edi + task.regs + 28], eax
mov [edi + task.regs + 0], eax
mov [edi + task.regs + 4], ebx
mov [edi + task.regs + 8], ecx
mov [edi + task.regs + 12], edx
mov [edi + task.regs + 16], esi
mov [edi + task.regs + 20], edi
mov [edi + task.regs + 24], ebp
; 调用调度器
call schedule
.no_schedule:
popad
iret
; 任务调度器
schedule:
push ebp
mov ebp, esp
push ebx
push esi
push edi
; 查找下一个就绪任务
mov edi, [current_task]
mov ecx, 16 ; 最大任务数
.next_task:
add edi, task_size
cmp edi, task_list + (16 * task_size)
jb .check_task
mov edi, task_list
.check_task:
cmp dword [edi + task.status], 1 ; 检查是否运行中
je .found_task
loop .next_task
; 没有找到其他任务,继续运行当前任务
mov edi, [current_task]
jmp .switch_done
.found_task:
; 更新当前任务指针
mov [current_task], edi
; 加载新任务的页目录
mov eax, [edi + task.cr3]
test eax, eax
jz .no_paging
mov cr3, eax
.no_paging:
; 恢复栈指针
mov esp, [edi + task.esp]
; 恢复寄存器状态
mov eax, [edi + task.regs + 0]
mov ebx, [edi + task.regs + 4]
mov ecx, [edi + task.regs + 8]
mov edx, [edi + task.regs + 12]
mov esi, [edi + task.regs + 16]
mov edi, [edi + task.regs + 20]
mov ebp, [edi + task.regs + 24]
push dword [edi + task.regs + 28] ; EFLAGS
popfd
.switch_done:
pop edi
pop esi
pop ebx
pop ebp
ret
; 创建新任务
; 输入: ESI=命令字符串
create_task:
pushad
; 查找空闲任务槽
mov edi, task_list
mov ecx, 16
.find_slot:
cmp dword [edi + task.status], 0
je .found_slot
add edi, task_size
loop .find_slot
; 无可用槽位
mov esi, task_full_msg
call print_str
jmp .exit
.found_slot:
; 设置任务信息
mov eax, [current_pid]
mov [edi + task.pid], eax
inc dword [current_pid]
mov dword [edi + task.status], 1 ; 运行中
; 复制命令
push esi
push edi
add edi, task.cmd
mov ecx, 64
.copy_cmd:
lodsb
test al, al
jz .copy_done
stosb
loop .copy_cmd
.copy_done:
pop edi
pop esi
; 分配栈空间 (16KB)
push 16384
call mem_alloc
add esp, 4
mov [edi + task.esp], eax
add eax, 16384 - 32 ; 栈顶
; 设置初始上下文
mov dword [eax + 0], 0x202 ; EFLAGS (IF=1)
mov dword [eax + 4], task_entry ; EIP
mov dword [eax + 8], 0 ; EAX
mov dword [eax + 12], 0 ; EBX
mov dword [eax + 16], 0 ; ECX
mov dword [eax + 20], 0 ; EDX
mov dword [eax + 24], 0 ; ESI
mov dword [eax + 28], 0 ; EDI
mov dword [eax + 32], 0 ; EBP
; 设置页目录 (如果启用分页)
mov dword [edi + task.cr3], 0 ; 暂时不使用分页
inc dword [task_count]
.exit:
popad
ret
; 任务入口点
task_entry:
; 解析并执行命令
mov esi, [current_task]
add esi, task.cmd
call parse_and_execute
; 任务退出
call task_exit
; 任务退出处理
task_exit:
pushad
; 标记任务为结束
mov edi, [current_task]
mov dword [edi + task.status], 0
; 释放栈空间
push dword [edi + task.esp]
call mem_free
add esp, 4
dec dword [task_count]
; 切换到下一个任务
call schedule
; 这里不会执行,因为已经切换到其他任务
popad
ret
; 睡眠函数 (毫秒)
; 输入: 毫秒数 (栈上)
sleep_ms:
push ebx
mov ebx, [esp+8] ; 获取毫秒数
; 简单延时实现(实际OS中应使用定时器中断)
mov eax, 10000 ; 根据CPU速度调整
mul ebx
mov ecx, eax
.delay_loop:
pause
loop .delay_loop
pop ebx
ret 4
; 任务列表显示
do_tasks:
pushad
mov esi, task_list
mov ecx, 16
.task_loop:
cmp dword [esi + task.status], 0
je .next_task
; 显示PID
mov eax, [esi + task.pid]
call print_dec
mov al, ' '
call put_char
; 显示状态
mov eax, [esi + task.status]
cmp eax, 1
je .running
cmp eax, 2
je .blocked
mov al, '?'
jmp .print_status
.running:
mov al, 'R'
jmp .print_status
.blocked:
mov al, 'B'
.print_status:
call put_char
mov al, ' '
call put_char
; 显示命令
push esi
add esi, task.cmd
call print_str
pop esi
call newline
.next_task:
add esi, task_size
loop .task_loop
popad
jmp shell
; 任务管理相关消息
task_full_msg db "Error: No available task slots", 0
task_start_msg db "Started task PID: ", 0
; === 光标闪烁任务 ===
; 在系统初始化时添加这个任务
init_cursor_task:
push esi
push edi
; 查找空闲任务槽
mov edi, task_list
mov ecx, 16
.find_slot:
cmp dword [edi + task.status], 0
je .found_slot
add edi, task_size
loop .find_slot
jmp .exit ; 没有可用槽位
.found_slot:
; 设置任务信息
mov eax, [current_pid]
mov [edi + task.pid], eax
inc dword [current_pid]
mov dword [edi + task.status], 1 ; 运行中
; 设置任务命令
mov byte [edi + task.cmd], 0 ; 空命令
; 分配栈空间 (4KB)
push 4096
call mem_alloc
add esp, 4
mov [edi + task.esp], eax
add eax, 4096 - 32 ; 栈顶
; 设置初始上下文
mov dword [eax + 0], 0x202 ; EFLAGS (IF=1)
mov dword [eax + 4], cursor_task_entry ; EIP
mov dword [eax + 8], 0 ; EAX
mov dword [eax + 12], 0 ; EBX
mov dword [eax + 16], 0 ; ECX
mov dword [eax + 20], 0 ; EDX
mov dword [eax + 24], 0 ; ESI
mov dword [eax + 28], 0 ; EDI
mov dword [eax + 32], 0 ; EBP
inc dword [task_count]
.exit:
pop edi
pop esi
ret
; 光标任务入口点
cursor_task_entry:
; 获取当前光标位置 (需要根据你的系统实现)
; 这里假设ebx=行, ecx=列
mov ebx, [current_line]
mov ecx, [current_column]
; 无限循环实现闪烁
.cursor_loop:
; 显示白色光标(0xFF空格)
mov al, ' '
mov ah, 0xFF
call put_char_at
; 延时约300ms
push 300
call sleep_ms
add esp, 4
; 显示黑色光标(0x00空格)
mov al, ' '
mov ah, 0x00
call put_char_at
; 延时约300ms
push 300
call sleep_ms
add esp, 4
jmp .cursor_loop
; 在指定位置显示字符
; 输入: ebx=行, ecx=列, al=字符, ah=属性
put_char_at:
push edi
push eax
; 计算显存位置 (假设80x25文本模式)
mov eax, ebx
mov edi, 80
mul edi
add eax, ecx
shl eax, 1 ; 每个字符占2字节
; 写入显存
add eax, 0xB8000 ; 文本模式显存地址
mov edi, eax
pop eax
mov [edi], ax
pop edi
ret
; 全局变量
[section .data]
current_line dd 0
current_column dd 0
cursor_state db 0 ; 0=关闭, 1=打开
[section .bss]
filename_buffer resb 32 ; 存储临时文件名
; === 数据区 ===
[section .data]
ping_usage_msg db "Usage: ping <ip>", 0
no_file_msg db "File not found: ", 0
cat_usage_msg db "Usage: cat <filename>", 0
hex_chars db '0123456789ABCDEF'
invalid_type_msg db "Not an executable ELF", 0
invalid_arch_msg db "Unsupported architecture", 0
no_segments_msg db "No loadable segments", 0
alloc_failed_msg db "Memory allocation failed", 0
run_error_msg db "Error: Cannot execute file: ", 0
invalid_elf_msg db "Error: Not a valid ELF file", 0
run_usage_msg db "Usage: run <filename>", 0
exec_success_msg db "Program exited with code: ", 0
还要实现系统中断
kernel.asm
; ============ kernel.asm ============
[bits 32]
; 段选择子定义
KERNEL_CS equ 0x08 ; 内核代码段选择子
KERNEL_DS equ 0x10 ; 内核数据段选择子
; 系统调用中断号
SYSCALL_INT equ 0x80
; 系统调用号定义
SYS_PRINT equ 0
SYS_GETKEY equ 1
SYS_CLEAR equ 2
SYS_RUN equ 3
[section .text]
%include "io.inc"
; 全局函数声明
global _start
extern shell
extern init_mouse, init_network
extern print_str, put_char, get_key, clear_screen
[section .bss]
align 32
kernel_stack:
resb 4096 ; 4KB内核栈
stack_top:
; 数据段定义
[section .data]
; 中断描述符表 (IDT)
align 8
idt:
times 256 dq 0 ; 256个门描述符,每个8字节
idt_ptr:
dw 256*8 - 1 ; IDT界限 = 大小 - 1
dd idt ; IDT线性基地址
; 欢迎消息
hello_msg db "Welcome to Plain - OS !", 0
net_init_failed_msg db "Network Error!", 0
; 系统调用表
sys_call_table:
dd sys_print_str ; 0
dd sys_get_key ; 1
dd sys_clear_screen ; 2
dd sys_run_program ; 3
SYS_CALL_MAX equ ($ - sys_call_table)/4
[section .text]
; 内核入口点
_start:
; 设置内核段寄存器
mov ax, KERNEL_DS
mov ds, ax
mov es, ax
mov fs, ax
; 设置内核栈
mov esp, stack_top
; 初始化IDT
call init_idt
; 初始化硬件
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
inc ebx
call init_network
test eax, eax
jz .init_failed ; 如果返回0表示初始化失败
; 启动shell
xor ecx, ecx
mov ebx, 5
call shell
jmp $
.init_failed:
; 处理初始化失败
mov esi, net_init_failed_msg
call print_str
jmp $
; ============ IDT初始化 ============
init_idt:
; 1. 先清零IDT
mov edi, idt
mov ecx, 256
xor eax, eax
rep stosd
; 2. 设置系统调用中断门 (DPL=3允许用户程序调用)
mov eax, syscall_handler
mov word [idt + 8*SYSCALL_INT], ax ; 偏移低16位
mov word [idt + 8*SYSCALL_INT + 2], KERNEL_CS ; 选择子
mov byte [idt + 8*SYSCALL_INT + 4], 0 ; 保留
mov byte [idt + 8*SYSCALL_INT + 5], 0xEE ; P=1, DPL=3, 32位中断门
shr eax, 16
mov word [idt + 8*SYSCALL_INT + 6], ax ; 偏移高16位
; 3. 加载IDT
lidt [idt_ptr]
ret
; ============ 系统调用处理程序 ============
syscall_handler:
pushad
; 验证系统调用号范围
cmp eax, SYS_CALL_MAX
jae .invalid_call
; 调用相应处理函数
call [sys_call_table + eax*4]
jmp .done
.invalid_call:
mov eax, -1 ; 无效调用号返回-1
.done:
mov [esp+28], eax ; 将返回值存入栈中的EAX位置
popad
iret
; ============ 系统调用实现 ============
sys_print_str:
call sys_print_char
ret
sys_print_char:
push edi
push eax
; 计算显存位置: (行*80 + 列)*2
mov edi, ebx
imul edi, LINE_WIDTH
add edi, ecx
shl edi, 1
; 写入显存
mov ax, dx ; 组合字符和属性
mov [gs:edi], ax
; 更新列号
inc ecx
cmp ecx, LINE_WIDTH
jb .no_newline
; 处理换行
xor ecx, ecx ; 列号清零
inc ebx ; 行号增加
.no_newline:
pop eax
pop edi
ret
sys_get_key:
call get_key
ret
sys_clear_screen:
call clear_screen
xor eax, eax
ret
sys_run_program:
; EBX=文件名指针
push esi
mov esi, ebx
; 这里需要实现文件加载和执行逻辑
; call do_run
pop esi
xor eax, eax
ret
写一个小程序测试一下
a.asm
org 0x100000
_start:
; 测试单个字符输出
mov eax, 0
mov dl, 'H'
mov dh, 0x0F
int 0x80
inc ecx
mov dl, 'e'
int 0x80
inc ecx
mov dl, 'l'
int 0x80
inc ecx
mov dl, 'l'
int 0x80
inc ecx
mov dl, 'o'
int 0x80
inc ecx
mov dl, '!'
int 0x80
inc ecx
mov dl, '!'
int 0x80
ret
msg db "Hello, World!", 0
目前只支持纯二进制文件,elf格式在搞定硬盘读写之后再弄
还有一个小瑕疵,由于我们使用自制文件系统和纯二进制文件,程序不能jmp(会导致跳转到内核的地址,而不是相对地址)
目前也没有什么好办法,加了org也没用,先将就一下
顺便实现期待已久的网络功能
network.asm
; network.asm - 完整网络协议栈实现
[bits 32]
KERNEL_CS equ 0x08
KERNEL_DS equ 0x10
; 网络相关定义
%define ETH_ALEN 6 ; 以太网地址长度
%define IP_ALEN 4 ; IP地址长度
%define ETH_HLEN 14 ; 以太网头部长度
%define IP_HLEN 20 ; IP头部长度
%define ICMP_HLEN 8 ; ICMP头部长度
%define ARP_HLEN 28 ; ARP包长度
; 协议类型
%define ETH_P_IP 0x0800 ; IP协议
%define ETH_P_ARP 0x0806 ; ARP协议
%define IP_PROTO_ICMP 1 ; ICMP协议
%define IP_PROTO_TCP 6 ; TCP协议
%define IP_PROTO_UDP 17 ; UDP协议
; ICMP类型
%define ICMP_ECHO_REPLY 0
%define ICMP_ECHO_REQUEST 8
; 网卡I/O基地址 (假设使用NE2000兼容网卡)
%define NIC_IO_BASE 0x300
%define NIC_IRQ 10
; 数据结构
struc eth_header
.dest_mac: resb ETH_ALEN
.src_mac: resb ETH_ALEN
.ethertype: resw 1
endstruc
struc ip_header
.ver_ihl: resb 1
.tos: resb 1
.tot_len: resw 1
.id: resw 1
.frag_off: resw 1
.ttl: resb 1
.protocol: resb 1
.check: resw 1
.saddr: resb IP_ALEN
.daddr: resb IP_ALEN
endstruc
struc icmp_header
.type: resb 1
.code: resb 1
.checksum: resw 1
.unused: resw 1
.unused2: resw 1
endstruc
struc arp_header
.htype: resw 1
.ptype: resw 1
.hlen: resb 1
.plen: resb 1
.oper: resw 1
.sha: resb ETH_ALEN
.spa: resb IP_ALEN
.tha: resb ETH_ALEN
.tpa: resb IP_ALEN
endstruc
[section .data]
; 网络配置
my_mac db 0x52, 0x54, 0x00, 0x12, 0x34, 0x56 ; 默认MAC
my_ip db 192, 168, 1, 2 ; 默认IP
netmask db 255, 255, 255, 0 ; 子网掩码
gateway db 192, 168, 1, 1 ; 网关
ttl_msg db " TTL=", 0
; ARP缓存 (简单实现)
arp_cache:
times 16 db 0 ; 每个条目20字节(IP+MAC+状态)
; 接收缓冲区
packet_buffer:
times 2048 db 0
; 发送缓冲区
tx_buffer:
times 2048 db 0
; 中断描述符表 (IDT)
align 8
idt:
times 256 dq 0 ; 256个门描述符,每个8字节
idt_ptr:
dw 256*8 - 1 ; IDT界限 = 大小 - 1
dd idt ; IDT线性基地址
ping_timeout dd 0 ; 超时计数器
ping_seq dw 0 ; 当前序列号
ping_count db 0 ; 已接收的ping回复计数
ping_received db 0 ; 接收到ping回复标志
; 消息文本
ping_timeout_msg db " Request timed out", 0
ping_stats_msg db "Packets: Sent=%d, Received=%d", 0
net_init_msg db "Initializing network...", 0
net_ready_msg db "Network ready", 0
reset_fail_msg db "NIC reset failed!", 0
arp_req_msg db "ARP request sent", 0
ping_sent_msg db "Ping sent to ", 0
ping_recv_msg db "Ping reply from ", 0
net_err_msg db "Network error", 0
no_nic_msg db "Error: No NIC detected at I/O base 0x300", 0
reset_fail_detail_msg db "Reset failed, status: ", 0
nic_present db "NE2000 is ready",0
[section .text]
extern print_str, put_char
global init_network, send_packet, receive_packet
global do_ping_impl, net_interrupt_handler
; 初始化网络
init_network:
pushad
xor ecx, ecx
inc ebx
; Display initialization message
mov esi, net_init_msg
call print_str
; Initialize NIC
call nic_init
test eax, eax
jz .error
; Set up network interrupt
mov al, NIC_IRQ
mov bl, 0x8E ; Interrupt gate, DPL=0
mov esi, net_interrupt_handler
call set_interrupt_gate
; Enable IRQ in PIC (Critical!)
mov dx, 0x21 ; PIC1 data port
in al, dx
and al, ~(1 << (NIC_IRQ % 8)) ; Clear bit to enable interrupt
out dx, al
; Display ready message
mov esi, net_ready_msg
call print_str
popad
ret
.error:
mov ah, 0x0C ; Red color
xor ecx, ecx
inc ebx
mov esi, net_err_msg
call print_str
popad
ret
; NIC Initialization
nic_init:
pushad
; 1. 验证网卡存在
mov dx, NIC_IO_BASE + 0x01
in al, dx
cmp al, 0xFF
je .no_nic
; 2. 发送复位命令(带双重验证)
mov dx, NIC_IO_BASE + 0x18
mov al, 0x80
out dx, al
; 3. 延长等待时间(约2秒)
mov ecx, 2000000
.reset_wait:
in al, dx
test al, 0x80
jz .reset_done
pause
loop .reset_wait
; 4. 显示详细错误信息
mov esi, .reset_fail_msg
call print_str
in al, dx
call print_hex
; 5. 尝试软件复位
mov dx, NIC_IO_BASE + 0x1F
mov al, 0x00
out dx, al
jmp .error
.reset_done:
; 6. 初始化关键寄存器
mov dx, NIC_IO_BASE + 0x0E ; DCR
mov al, 0x58
out dx, al
mov dx, NIC_IO_BASE + 0x0C ; RCR
mov al, 0x20
out dx, al
mov esi, .success_msg
call print_str
popad
mov eax, 1
ret
.no_nic:
mov esi, .no_nic_msg
call print_str
.error:
popad
xor eax, eax
ret
.no_nic_msg db "Error: NE2000 compatible NIC not detected at I/O base 0x300", 0
.reset_fail_msg db "NIC reset failed, status: 0x", 0
.success_msg db "NE2000 NIC initialized successfully", 0
; 网络中断处理
net_interrupt_handler:
pushad
; 检查中断源
mov dx, NIC_IO_BASE + 0x0E
in al, dx
test al, al
jz .done
; 处理接收中断
test al, 0x01
jz .no_rx
call handle_receive
.no_rx:
; 确认中断
out dx, al
.done:
popad
iret
; 接收处理
handle_receive:
pushad
; 检查是否有数据包
mov dx, NIC_IO_BASE + 0x0C
in al, dx
test al, 0x01
jz .done
; 读取数据包长度
mov dx, NIC_IO_BASE + 0x0B
in al, dx
movzx ecx, al
; 读取数据包
mov dx, NIC_IO_BASE + 0x10
mov edi, packet_buffer
rep insb
; 处理数据包
call process_packet
.done:
popad
ret
; 处理接收到的数据包
process_packet:
pushad
; 检查以太网类型
mov esi, packet_buffer
mov ax, [esi + eth_header.ethertype]
xchg al, ah ; 转换为网络字节序
cmp ax, ETH_P_IP
je .ip_packet
cmp ax, ETH_P_ARP
je .arp_packet
jmp .done
.ip_packet:
; 处理IP包
add esi, ETH_HLEN
mov al, [esi + ip_header.protocol]
cmp al, IP_PROTO_ICMP
je .icmp_packet
jmp .done
.icmp_packet:
; 处理ICMP包
add esi, IP_HLEN
mov al, [esi + icmp_header.type]
cmp al, ICMP_ECHO_REPLY
je .ping_reply
jmp .done
.ping_reply:
; 检查是否是我们的ping回复
; 比较标识符和序列号(简化处理)
mov ax, [esi + icmp_header.unused]
cmp ax, 0x1234 ; 与我们发送的标识符比较
jne .done
; 设置接收标志
mov byte [ping_received], 1
; 显示回复信息
push esi
mov esi, ping_recv_msg
call print_str
; 显示源IP
mov esi, packet_buffer + ETH_HLEN + ip_header.saddr
call print_ip
; 显示TTL
mov esi, ttl_msg ; " TTL="
call print_str
mov al, [packet_buffer + ETH_HLEN + ip_header.ttl]
call print_dec
call newline
pop esi
jmp .done
.arp_packet:
; 处理ARP包
add esi, ETH_HLEN
mov ax, [esi + arp_header.oper]
xchg al, ah
cmp ax, 1 ; ARP请求
je .arp_request
cmp ax, 2 ; ARP回复
je .arp_reply
jmp .done
.arp_request:
; 处理ARP请求
call handle_arp_request
jmp .done
.arp_reply:
; 处理ARP回复
call handle_arp_reply
.done:
popad
ret
; 发送数据包
send_packet:
pushad
push es
; 设置发送缓冲区
mov esi, [esp + 44] ; 数据指针
mov ecx, [esp + 48] ; 数据长度
; 检查长度
cmp ecx, 2048
ja .error
; 复制数据到发送缓冲区
mov edi, tx_buffer
rep movsb
; 发送数据包
mov dx, NIC_IO_BASE + 0x04 ; 发送命令端口
mov al, 0x01 ; 发送命令
out dx, al
pop es
popad
ret
.error:
pop es
popad
xor eax, eax
ret
; 处理ARP请求
handle_arp_request:
pushad
; 检查是否是我们的IP
mov esi, packet_buffer + ETH_HLEN + arp_header.tpa
mov edi, my_ip
mov ecx, IP_ALEN
repe cmpsb
jne .done
; 构造ARP回复
mov edi, tx_buffer
; 以太网头部
mov esi, packet_buffer + eth_header.src_mac
mov ecx, ETH_ALEN
rep movsb ; 目标MAC
mov esi, my_mac
mov ecx, ETH_ALEN
rep movsb ; 源MAC
mov ax, ETH_P_ARP
xchg al, ah
stosw ; 以太网类型
; ARP头部
mov ax, 0x0001 ; 硬件类型(以太网)
stosw
mov ax, ETH_P_IP ; 协议类型(IP)
stosw
mov al, ETH_ALEN ; 硬件地址长度
stosb
mov al, IP_ALEN ; 协议地址长度
stosb
mov ax, 0x0200 ; 操作码(回复)
stosw
; 发送方MAC和IP
mov esi, my_mac
mov ecx, ETH_ALEN
rep movsb
mov esi, my_ip
mov ecx, IP_ALEN
rep movsb
; 目标MAC和IP
mov esi, packet_buffer + eth_header.src_mac
mov ecx, ETH_ALEN
rep movsb
mov esi, packet_buffer + ETH_HLEN + arp_header.spa
mov ecx, IP_ALEN
rep movsb
; 发送ARP回复
mov ecx, edi
sub ecx, tx_buffer
push ecx
push tx_buffer
call send_packet
.done:
popad
ret
; 处理ARP回复
handle_arp_reply:
; 更新ARP缓存 (简化实现)
ret
; 发送ping请求
do_ping_impl:
pushad
push es
push ds
; 设置内核数据段
mov ax, KERNEL_DS
mov ds, ax
mov es, ax
; 初始化计数器
mov word [ping_seq], 0
mov byte [ping_count], 0
; 解析目标IP
mov esi, [esp + 44] ; 获取IP字符串指针
call parse_ip
test eax, eax
jz .error
; 保存目标IP指针
mov edx, eax
; 发送4个ping请求(标准ping行为)
mov ecx, 4
.send_loop:
; 构造ICMP包
call build_icmp_packet
; 发送数据包
push ecx
push edx
mov ecx, edi
sub ecx, tx_buffer ; 计算包长度
push ecx
push tx_buffer
call send_packet
add esp, 8
; 显示发送消息
mov ah, 0x0F
inc ebx
mov ecx, 0
push esi
mov esi, ping_sent_msg
call print_str
pop esi
;mov esi, [esp + 44] ; 获取IP字符串指针
call print_str
; 等待回复(约1秒)
mov dword [ping_timeout], 0
.wait_reply:
inc dword [ping_timeout]
cmp dword [ping_timeout], 1000000 ; 超时值,根据CPU速度调整
jae .timeout
; 检查是否有接收到的包
cmp byte [ping_received], 0
jne .got_reply
; 短暂延迟
push ecx
mov ecx, 1000
.delay:
nop
loop .delay
pop ecx
jmp .wait_reply
.timeout:
; 显示超时信息
inc ebx
mov ecx, 0
mov esi, 0
mov esi, ping_timeout_msg
call print_str
jmp .next_ping
.got_reply:
; 已收到回复,计数器递增
inc byte [ping_count]
mov byte [ping_received], 0
.next_ping:
call newline
pop edx
pop ecx
dec ecx ; 递减计数器
jnz .send_loop ; 如果ecx≠0则继续循环
; 显示统计信息
mov esi, ping_stats_msg
call print_str
movzx eax, byte [ping_count]
push eax
push 4
call print_dec ; 实现打印十进制数的函数
add esp, 8
jmp .done
.error:
mov esi, net_err_msg
call print_str
.done:
pop ds
pop es
popad
ret
; 构建ICMP包
build_icmp_packet:
mov edi, tx_buffer
; 1. 以太网头部
; 源MAC
mov esi, my_mac
mov ecx, ETH_ALEN
rep movsb
; 目标MAC (广播)
mov al, 0xFF
mov ecx, ETH_ALEN
rep stosb
; 以太网类型(IP)
mov ax, ETH_P_IP
xchg al, ah
stosw
; 2. IP头部
mov al, 0x45 ; 版本4 + 头部长度5字
stosb
xor al, al ; 服务类型
stosb
mov ax, (IP_HLEN + ICMP_HLEN + 32) ; 总长度
xchg al, ah
stosw
mov ax, [ping_seq] ; 使用序列号作为标识
stosw
xor ax, ax ; 分片偏移
stosw
mov al, 64 ; TTL
stosb
mov al, IP_PROTO_ICMP ; 协议
stosb
xor ax, ax ; 校验和(先置0)
stosw
; 源IP
mov esi, my_ip
mov ecx, IP_ALEN
rep movsb
; 目标IP
mov esi, edx ; 之前保存的目标IP指针
mov ecx, IP_ALEN
rep movsb
; 计算IP校验和
push edi
mov esi, tx_buffer + ETH_HLEN
mov ecx, IP_HLEN
call checksum
mov [esi + ip_header.check], ax
pop edi
; 3. ICMP头部
mov al, ICMP_ECHO_REQUEST ; 类型
stosb
xor al, al ; 代码
stosb
xor ax, ax ; 校验和(先置0)
stosw
mov ax, 0x1234 ; 标识符(可以固定)
stosw
mov ax, [ping_seq] ; 序列号
stosw
inc word [ping_seq] ; 递增序列号
; ICMP数据 (32字节测试数据)
mov ecx, 32
mov al, 'A'
.icmp_data:
stosb
inc al
loop .icmp_data
; 计算ICMP校验和
mov esi, tx_buffer + ETH_HLEN + IP_HLEN
mov ecx, ICMP_HLEN + 32
call checksum
mov [esi + icmp_header.checksum], ax
ret
; 计算校验和
checksum:
xor eax, eax
xor edx, edx
.loop:
lodsw
add eax, edx
mov edx, eax
shr edx, 16
and eax, 0xFFFF
loop .loop
add eax, edx
mov edx, eax
shr edx, 16
add eax, edx
not ax
ret
; 解析IP地址
parse_ip:
pushad
mov edi, .ip_buffer
mov ecx, 4
.parse_loop:
call atoi
stosb
cmp byte [esi], '.'
jne .parse_done
inc esi
loop .parse_loop
.parse_done:
popad
mov eax, .ip_buffer
ret
.ip_buffer:
db 0, 0, 0, 0
; 辅助函数
print_ip:
pushad
mov ecx, 4
.print_loop:
lodsb
call print_dec
cmp ecx, 1
je .no_dot
mov al, '.'
call put_char
.no_dot:
loop .print_loop
popad
ret
print_dec:
pushad
xor ah, ah
mov bl, 100
div bl
test al, al
jz .no_hundreds
add al, '0'
call put_char
.no_hundreds:
mov al, ah
xor ah, ah
mov bl, 10
div bl
test al, al
jnz .has_tens
test ah, ah
jz .done
.has_tens:
add al, '0'
call put_char
mov al, ah
add al, '0'
call put_char
.done:
popad
ret
newline:
push eax
mov al, 0x0A
call put_char
pop eax
ret
; 设置中断门
; 输入: AL=中断号, BL=属性, ESI=处理程序地址
set_interrupt_gate:
pushad
; 计算IDT中的偏移量 (中断号*8)
xor ah, ah
shl eax, 3 ; 每个门描述符8字节
add eax, idt ; 加上IDT基地址
; 设置偏移量低16位
mov [eax], si
mov word [eax+2], KERNEL_CS ; 选择子
; 设置属性
mov byte [eax+4], 0 ; 保留
mov byte [eax+5], bl ; 属性
; 设置偏移量高16位
shr esi, 16
mov [eax+6], si
popad
ret
; 字符串转整数 (简单实现)
; 输入: ESI=字符串指针
; 输出: EAX=数值
atoi:
push ebx
push ecx
push edx
xor eax, eax
xor ebx, ebx
xor ecx, ecx
.convert:
lodsb
test al, al
jz .done
cmp al, '0'
jb .invalid
cmp al, '9'
ja .invalid
; 数字字符,转换为数值
sub al, '0'
imul ebx, 10
add ebx, eax
jmp .convert
.invalid:
xor ebx, ebx
.done:
mov eax, ebx
pop edx
pop ecx
pop ebx
ret
; 打印十六进制数的函数
print_hex:
pushad
mov ecx, 8 ; 处理8个十六进制字符(32位)
.hex_loop:
rol eax, 4 ; 循环左移4位
mov ebx, eax
and ebx, 0x0F ; 取最低4位
mov bl, [hex_chars + ebx] ; 转换为ASCII字符
mov ah, 0x0F ; 设置显示属性
call put_char ; 确保put_char已定义
loop .hex_loop
popad
ret
hex_chars db '0123456789ABCDEF'
make运行一下
程序运行正常
ping一下
网络功能似乎不可用,没有正确复位
看一下VMware运行
爆错似乎不一样,因为VMware 没有正确配置网卡
qemu应该这样运行
run : a.img
qemu-system-i386 \
-m 16M \
-device ne2k_isa,iobase=0x300,irq=10,mac=52:54:00:12:34:56 \
-netdev user,id=net0,hostfwd=tcp::8080-:80 \
-net nic,netdev=net0 \
-drive file=a.img,format=raw,if=floppy \
-boot a \
-serial stdio \
-monitor telnet:127.0.0.1:1234,server,nowait
我们还修复了许多bug
例如滚屏
一开始还以为使gs没有指向显存
测试后才发现是判断的问题