纯汇编自制操作系统(四、应用程序等的实现)
本项目已在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", 0cmd_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", 0align 8
idt:times 256 dq 0 ; 256个门描述符,每个8字节
idt_ptr:dw 256*8 - 1 ; IDT界限 = 大小 - 1dd 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_implglobal shell
shell:call init_timercall init_task_system;cmp ebx, 25;ja .scrollmov ecx, 0mov esi, msgmov ah, 0x0Fcall print_str; 初始化命令缓冲区mov edi, cmd_buffermov ecx, 18 ; 从第18列开始输入mov byte [edi], 0 ; 清空缓冲区mov al, ' 'mov ah, 0xFFcall put_char.input_loop:call get_keytest al, aljz .input_loopmov [current_line], ebxmov [current_column], ecx; 处理回车cmp al, 0x0Aje .execute; 处理退格cmp al, 0x08je .backspace; 存储并显示字符mov [edi], alinc edimov ah, 0x0Fcall put_charinc ecxmov al, ' 'mov ah, 0xFFcall put_charjmp .input_loop.backspace:; 退格处理cmp edi, cmd_bufferje .input_loop ; 忽略空退格mov al, ' 'mov ah, 0x0Fcall put_chardec edidec ecxmov al, ' 'mov ah, 0xFFcall put_charjmp .input_loop.scroll:call scroll_screenmov ebx, 24mov ecx, 0jmp shell
.execute:mov al, ' 'mov ah, 0x0Fcall put_char; 添加字符串结束符mov byte [edi], 0; 检查空命令mov esi, cmd_buffercall is_emptyje .empty_cmd; 跳过前导空格call skip_spacestest al, aljz .empty_cmdmov edi, cmd_taskcall cmd_cmpje do_task; 检查help命令mov edi, cmd_helpcall cmd_cmpje .show_help; 检查echo命令mov edi, cmd_echocall cmd_cmpje .do_echo; 检查echo命令mov edi, cmd_lscall cmd_cmpje do_lsmov edi, cmd_timecall cmd_cmpje do_timemov edi, cmd_catcall cmd_cmpje do_catmov edi, cmd_runcall cmd_cmpje do_runmov edi, cmd_sleepcall cmd_cmpje do_sleep;mov edi, cmd_write;call cmd_cmp;je do_write; 检查clear命令mov edi, cmd_clearcall cmd_cmpje .do_clearmov edi, cmd_pingcall cmd_cmpje do_ping; 未知命令处理jmp .do_run1
.cmd_error:inc ebxmov ecx, 0mov esi, not_msgmov ah, 0x0C ; 红色错误信息call print_str; 只显示命令部分(第一个空格前的内容)mov esi, cmd_buffercall print_command_partinc ebxjmp shell.do_run1:call skip_spacestest al, aljz .no_filename1; 读取文件到内存call fs_read_filejc .cmd_errorinc ebxmov ecx, 0; 设置新栈;mov ebp, 0x90000;mov esp, ebp; 跳转到二进制文件call esiinc ebxjmp shell.file_not_found1:inc ebxmov ecx, 0mov esi, no_file_msgmov ah, 0x0Ccall print_str; 显示尝试的文件名mov ecx, 16mov esi, cmd_buffermov ah, 0x0Fcall print_strinc ebxjmp shell.no_filename1:inc ebxmov ecx, 0mov esi, run_usage_msgmov ah, 0x0Ccall print_strjmp shell.empty_cmd:cmp ebx, 25ja .scrollinc ebxmov ecx, 0jmp shell.show_help:; 显示帮助信息inc ebxmov ecx, 0mov esi, help_msg1mov ah, 0x0A ; 绿色帮助信息call print_strinc ebxmov ecx, 0mov esi, help_msg2call print_strinc ebxmov ecx, 0mov esi, help_msg3call print_strinc ebxmov ecx, 0mov esi, help_msg4call print_strinc ebxmov ecx, 0mov esi, help_msg5call print_strinc ebxmov ecx, 0mov esi, help_msg6call print_strinc ebxmov ecx, 0mov esi, help_msg7call print_strinc ebxjmp shell.do_echo:; 跳过"echo"和后续空格add esi, 4call skip_spacestest al, aljz .no_args1 ; 无参数情况; 显示echo参数inc ebxmov ecx, 0mov ah, 0x0Fcall print_str.no_args1:inc ebx ; 换行jmp shell; === clear命令实现 ===
.do_clear:call clear_screenmov ebx, 0mov ecx, 0jmp shell; === 辅助函数 ===; 打印命令部分(第一个空格前的内容)
print_command_part:pushamov ecx, 26 ; 错误信息后位置
.loop:lodsbtest al, aljz .donecmp al, ' 'je .donemov ah, 0x0Fcall put_charinc ecxjmp .loop
.done:poparet; 检查字符串是否为空或只有空格
is_empty:push esi
.loop:lodsbcmp al, ' 'je .looptest al, alpop esiret; 跳过字符串中的空格
skip_spaces:lodsbcmp al, ' 'je skip_spacesdec esi ; 回退到第一个非空格字符ret; 命令比较函数
cmd_cmp:pusha
.compare:mov al, [esi]mov bl, [edi]; 检查命令是否结束(空格或字符串结束)cmp al, ' 'je .check_cmd_endtest al, aljz .check_cmd_end; 转换为小写比较cmp al, 'A'jb .no_change1cmp al, 'Z'ja .no_change1add al, 0x20
.no_change1:cmp bl, 'A'jb .no_change2cmp bl, 'Z'ja .no_change2add bl, 0x20.no_change2:cmp al, bljne .not_equalinc esiinc edijmp .compare.check_cmd_end:; 检查命令字符串是否也结束了cmp byte [edi], 0jne .not_equal.equal:popaxor eax, eax ; ZF=1ret.not_equal:popaor eax, 1 ; ZF=0ret; 显示固定数量的字符
print_nchars:pushamov ah, 0x0F
.loop:lodsbcall put_charloop .looppoparetprint_hex:pushadmov ecx, 8
.loop:rol eax, 4mov ebx, eaxand ebx, 0x0fmov bl, [hex_chars + ebx]mov ah, 0x0Fcall put_charloop .looppopadretdo_time:call get_timeinc ebx ; 换行mov ecx, 0mov esi, time_strmov ah, 0x0F ; 白色文字call print_strjmp shellget_time:pushad; 禁用NMI并读取小时mov al, 0x04 ; 小时寄存器or al, 0x80 ; 禁用NMIout 0x70, alin al, 0x71call bcd_to_asciimov [time_str], dhmov [time_str+1], dl; 读取分钟mov al, 0x02or al, 0x80out 0x70, alin al, 0x71call bcd_to_asciimov [time_str+3], dhmov [time_str+4], dl; 读取秒mov al, 0x00or al, 0x80out 0x70, alin al, 0x71call bcd_to_asciimov [time_str+6], dhmov [time_str+7], dlpopadretbcd_to_ascii:; 将AL中的BCD码转换为两个ASCII字符,存储在DH和DL中mov dh, alshr dh, 4add dh, '0'mov dl, aland dl, 0x0Fadd dl, '0'ret; === ls命令实现 ===
do_ls:call fs_list_files; 显示文件名inc ebxmov ecx, 0mov ah, 0x0Fcall print_str; 换行inc ebxmov ecx, 0jmp shell; === cat命令实现 ===
do_cat:; 跳过"cat"和空格add esi, 3call skip_spacestest al, aljz .no_filename; 直接调用文件系统call fs_read_filejc .file_not_found; 显示内容 (esi已指向内容字符串)inc ebxmov ecx, 0mov ah, 0x0Fcall print_str ; 直接打印整个内容inc ebxjmp shell.file_not_found:inc ebxmov ecx, 0mov esi, no_file_msgmov ah, 0x0Ccall print_str; 显示尝试的文件名mov ecx, 16mov esi, cmd_buffer+3mov ah, 0x0Fcall print_strinc ebxjmp shell.no_filename:inc ebxmov ecx, 0mov esi, cat_usage_msgmov ah, 0x0Ccall print_strjmp shell; === run命令实现 ===
; 常量定义
PROG_BASE equ 0x100000 ; 程序加载地址 (1MB)
PROG_STACK equ 0x9F000 ; 程序专用栈空间 (64KB)
MAX_SIZE equ 32768 ; 最大程序大小 (32KB)do_run:; 跳过"run"和空格add esi, 3call skip_spacestest al, aljz .no_filename; 读取文件到ESIcall fs_read_filejc .file_not_found; 保存所有寄存器状态pusha; 复制程序到固定地址mov edi, PROG_BASEmov ecx, MAX_SIZEcld ; 清除方向标志rep movsb ; 复制程序代码; 设置程序专用栈mov ebp, PROG_STACKmov esp, ebp; 准备调用环境xor eax, eaxxor ebx, ebxxor ecx, ecxxor edx, edxxor esi, esixor edi, edi; 跳转到程序push .return_point ; 返回地址push PROG_BASE ; 调用地址ret.return_point:; 恢复寄存器状态popajmp shell.file_not_found:mov esi, no_file_msgcall print_strjmp shell.no_filename:mov esi, run_usage_msgcall print_strjmp shell; === ping命令实现 ===
do_ping:; 跳过"ping"和空格add esi, 4call skip_spacestest al, aljz .no_ip; 调用网络ping功能push esi ; 压入IP字符串指针call do_ping_impladd esp, 4 ; 清理栈jmp shell.no_ip:; 显示用法错误inc ebxmov ecx, 0mov esi, ping_usage_msgmov ah, 0x0Ccall print_strjmp shell; === sleep命令实现 ===
do_sleep:add esi, 5 ; 跳过"sleep"call skip_spacestest al, aljz .invalidcall atoi ; 将参数转换为毫秒数push eaxcall sleep_ms ; 调用睡眠函数add esp, 4jmp shell.invalid:mov esi, sleep_usage_msgcall print_strjmp shellsleep_usage_msg db "Usage: sleep <milliseconds>", 0; === task命令实现 ===
do_task:add esi, 4 ; 跳过"task"call skip_spacestest al, aljz .invalid; 创建新任务call create_task; 显示任务启动信息mov esi, task_start_msgcall print_strmov eax, [current_pid]dec eaxcall print_deccall newlinejmp shell.invalid:mov esi, task_usage_msgcall print_strjmp shelltask_usage_msg db "Usage: task <command>", 0; === 数字转换函数 ===
; 输入:ESI=字符串指针
; 输出:EAX=数值
atoi:push ebxpush ecxpush edxxor eax, eax ; 清零结果xor ebx, ebx ; 临时存储字符.convert:lodsb ; 加载下一个字符test al, al ; 检查字符串结束jz .donecmp al, '0'jb .invalidcmp al, '9'ja .invalidsub al, '0' ; 转换为数字imul ebx, 10 ; 结果 *= 10add ebx, eax ; 结果 += 当前数字jmp .convert.invalid:xor ebx, ebx ; 无效输入返回0.done:mov eax, ebx ; 结果存入EAXpop edxpop ecxpop ebxret; === 命令解析和执行 ===
; 输入:ESI=命令字符串
parse_and_execute:pushad; 保存原始命令指针mov edi, esi; 跳过前导空格call skip_spacestest al, aljz .emptymov edi, cmd_taskcall cmd_cmpje do_task; 检查echo命令mov edi, cmd_echocall cmd_cmpje .do_echo; 检查echo命令mov edi, cmd_lscall cmd_cmpje do_lsmov edi, cmd_timecall cmd_cmpje do_timemov edi, cmd_catcall cmd_cmpje do_catmov edi, cmd_runcall cmd_cmpje do_runmov edi, cmd_sleepcall cmd_cmpje do_sleepmov edi, cmd_pingcall cmd_cmpje do_ping; 如果不是内置命令,尝试作为外部程序执行jmp do_run.empty:popadret.do_echo:add esi, 5 ; 跳过"echo "call print_strpopadret; === 十进制打印函数 ===
; 输入:EAX=要打印的数字
print_dec:pushadmov ebx, 10 ; 除数xor ecx, ecx ; 数字位数计数器.divide_loop:xor edx, edxdiv ebx ; EDX:EAX / EBXpush edx ; 保存余数inc ecxtest eax, eaxjnz .divide_loop.print_loop:pop eax ; 取出数字add al, '0' ; 转换为ASCIImov ah, 0x0F ; 显示属性call put_charloop .print_looppopadret; === 换行函数 ===
newline:xor ecx, ecxinc ebxret; 定义任务结构体
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_listmov dword [edi + task.pid], 1mov dword [edi + task.status], 1mov dword [current_task], ediinc dword [task_count]inc dword [current_pid]; 分配栈空间 (16KB)push 16384call mem_allocadd esp, 4add eax, 16384 - 32 ; 栈顶mov [edi + task.esp], eax; 设置初始上下文mov dword [eax + 0], 0x202 ; EFLAGS (IF=1)mov dword [eax + 4], shell ; EIPmov dword [eax + 8], 0 ; EAXmov dword [eax + 12], 0 ; EBXmov dword [eax + 16], 0 ; ECXmov dword [eax + 20], 0 ; EDXmov dword [eax + 24], 0 ; ESImov dword [eax + 28], 0 ; EDImov dword [eax + 32], 0 ; EBPpopadret; 初始化定时器 (PIT 8254)
init_timer:push eax; 设置PIT通道0为100Hzmov al, 0x36 ; 通道0,模式3,二进制计数out 0x43, almov ax, 11932 ; 1193182Hz / 100Hz = 11932out 0x40, al ; 低字节mov al, ahout 0x40, al ; 高字节; 设置IRQ0处理程序mov eax, timer_interruptmov [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=0shr eax, 16mov [idt + 8*0x20 + 6], word ax ; 高16位偏移pop eaxret; 定时器中断处理 (IRQ0)
timer_interrupt:pushad; 发送EOImov al, 0x20out 0x20, al; 更新系统时钟inc dword [ticks]; 检查是否需要调度cmp dword [task_count], 1jbe .no_schedule; 保存当前任务上下文mov edi, [current_task]mov [edi + task.esp], esp; 保存寄存器状态mov eax, [esp + 28] ; 从pushad中获取EFLAGSmov [edi + task.regs + 28], eaxmov [edi + task.regs + 0], eaxmov [edi + task.regs + 4], ebxmov [edi + task.regs + 8], ecxmov [edi + task.regs + 12], edxmov [edi + task.regs + 16], esimov [edi + task.regs + 20], edimov [edi + task.regs + 24], ebp; 调用调度器call schedule.no_schedule:popadiret; 任务调度器
schedule:push ebpmov ebp, esppush ebxpush esipush edi; 查找下一个就绪任务mov edi, [current_task]mov ecx, 16 ; 最大任务数.next_task:add edi, task_sizecmp edi, task_list + (16 * task_size)jb .check_taskmov edi, task_list.check_task:cmp dword [edi + task.status], 1 ; 检查是否运行中je .found_taskloop .next_task; 没有找到其他任务,继续运行当前任务mov edi, [current_task]jmp .switch_done.found_task:; 更新当前任务指针mov [current_task], edi; 加载新任务的页目录mov eax, [edi + task.cr3]test eax, eaxjz .no_pagingmov 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] ; EFLAGSpopfd.switch_done:pop edipop esipop ebxpop ebpret; 创建新任务
; 输入: ESI=命令字符串
create_task:pushad; 查找空闲任务槽mov edi, task_listmov ecx, 16
.find_slot:cmp dword [edi + task.status], 0je .found_slotadd edi, task_sizeloop .find_slot; 无可用槽位mov esi, task_full_msgcall print_strjmp .exit.found_slot:; 设置任务信息mov eax, [current_pid]mov [edi + task.pid], eaxinc dword [current_pid]mov dword [edi + task.status], 1 ; 运行中; 复制命令push esipush ediadd edi, task.cmdmov ecx, 64
.copy_cmd:lodsbtest al, aljz .copy_donestosbloop .copy_cmd
.copy_done:pop edipop esi; 分配栈空间 (16KB)push 16384call mem_allocadd esp, 4mov [edi + task.esp], eaxadd eax, 16384 - 32 ; 栈顶; 设置初始上下文mov dword [eax + 0], 0x202 ; EFLAGS (IF=1)mov dword [eax + 4], task_entry ; EIPmov dword [eax + 8], 0 ; EAXmov dword [eax + 12], 0 ; EBXmov dword [eax + 16], 0 ; ECXmov dword [eax + 20], 0 ; EDXmov dword [eax + 24], 0 ; ESImov dword [eax + 28], 0 ; EDImov dword [eax + 32], 0 ; EBP; 设置页目录 (如果启用分页)mov dword [edi + task.cr3], 0 ; 暂时不使用分页inc dword [task_count].exit:popadret; 任务入口点
task_entry:; 解析并执行命令mov esi, [current_task]add esi, task.cmdcall 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_freeadd esp, 4dec dword [task_count]; 切换到下一个任务call schedule; 这里不会执行,因为已经切换到其他任务popadret; 睡眠函数 (毫秒)
; 输入: 毫秒数 (栈上)
sleep_ms:push ebxmov ebx, [esp+8] ; 获取毫秒数; 简单延时实现(实际OS中应使用定时器中断)mov eax, 10000 ; 根据CPU速度调整mul ebxmov ecx, eax
.delay_loop:pauseloop .delay_looppop ebxret 4; 任务列表显示
do_tasks:pushadmov esi, task_listmov ecx, 16.task_loop:cmp dword [esi + task.status], 0je .next_task; 显示PIDmov eax, [esi + task.pid]call print_decmov al, ' 'call put_char; 显示状态mov eax, [esi + task.status]cmp eax, 1je .runningcmp eax, 2je .blockedmov al, '?'jmp .print_status
.running:mov al, 'R'jmp .print_status
.blocked:mov al, 'B'
.print_status:call put_charmov al, ' 'call put_char; 显示命令push esiadd esi, task.cmdcall print_strpop esicall newline.next_task:add esi, task_sizeloop .task_looppopadjmp shell; 任务管理相关消息
task_full_msg db "Error: No available task slots", 0
task_start_msg db "Started task PID: ", 0; === 光标闪烁任务 ===
; 在系统初始化时添加这个任务
init_cursor_task:push esipush edi; 查找空闲任务槽mov edi, task_listmov ecx, 16
.find_slot:cmp dword [edi + task.status], 0je .found_slotadd edi, task_sizeloop .find_slotjmp .exit ; 没有可用槽位.found_slot:; 设置任务信息mov eax, [current_pid]mov [edi + task.pid], eaxinc dword [current_pid]mov dword [edi + task.status], 1 ; 运行中; 设置任务命令mov byte [edi + task.cmd], 0 ; 空命令; 分配栈空间 (4KB)push 4096call mem_allocadd esp, 4mov [edi + task.esp], eaxadd eax, 4096 - 32 ; 栈顶; 设置初始上下文mov dword [eax + 0], 0x202 ; EFLAGS (IF=1)mov dword [eax + 4], cursor_task_entry ; EIPmov dword [eax + 8], 0 ; EAXmov dword [eax + 12], 0 ; EBXmov dword [eax + 16], 0 ; ECXmov dword [eax + 20], 0 ; EDXmov dword [eax + 24], 0 ; ESImov dword [eax + 28], 0 ; EDImov dword [eax + 32], 0 ; EBPinc dword [task_count].exit:pop edipop esiret; 光标任务入口点
cursor_task_entry:; 获取当前光标位置 (需要根据你的系统实现); 这里假设ebx=行, ecx=列mov ebx, [current_line]mov ecx, [current_column]; 无限循环实现闪烁
.cursor_loop:; 显示白色光标(0xFF空格)mov al, ' 'mov ah, 0xFFcall put_char_at; 延时约300mspush 300call sleep_msadd esp, 4; 显示黑色光标(0x00空格)mov al, ' 'mov ah, 0x00call put_char_at; 延时约300mspush 300call sleep_msadd esp, 4jmp .cursor_loop; 在指定位置显示字符
; 输入: ebx=行, ecx=列, al=字符, ah=属性
put_char_at:push edipush eax; 计算显存位置 (假设80x25文本模式)mov eax, ebxmov edi, 80mul ediadd eax, ecxshl eax, 1 ; 每个字符占2字节; 写入显存add eax, 0xB8000 ; 文本模式显存地址mov edi, eaxpop eaxmov [edi], axpop ediret; 全局变量
[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界限 = 大小 - 1dd 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 ; 0dd sys_get_key ; 1dd sys_clear_screen ; 2dd sys_run_program ; 3
SYS_CALL_MAX equ ($ - sys_call_table)/4[section .text]
; 内核入口点
_start:; 设置内核段寄存器mov ax, KERNEL_DSmov ds, axmov es, axmov fs, ax; 设置内核栈mov esp, stack_top; 初始化IDTcall init_idt; 初始化硬件call hide_cursorcall clear_screen; 显示欢迎消息mov ebx, 0 ; 行号mov ecx, 0 ; 列号mov esi, hello_msg ; 字符串地址mov ah, 0x0Fcall print_strxor ecx, ecxinc ebxcall init_networktest eax, eaxjz .init_failed ; 如果返回0表示初始化失败; 启动shellxor ecx, ecxmov ebx, 5call shelljmp $.init_failed:; 处理初始化失败mov esi, net_init_failed_msgcall print_strjmp $; ============ IDT初始化 ============
init_idt:; 1. 先清零IDTmov edi, idtmov ecx, 256xor eax, eaxrep stosd; 2. 设置系统调用中断门 (DPL=3允许用户程序调用)mov eax, syscall_handlermov 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, 16mov word [idt + 8*SYSCALL_INT + 6], ax ; 偏移高16位; 3. 加载IDTlidt [idt_ptr]ret; ============ 系统调用处理程序 ============
syscall_handler:pushad; 验证系统调用号范围cmp eax, SYS_CALL_MAXjae .invalid_call; 调用相应处理函数call [sys_call_table + eax*4]jmp .done.invalid_call:mov eax, -1 ; 无效调用号返回-1.done:mov [esp+28], eax ; 将返回值存入栈中的EAX位置popadiret; ============ 系统调用实现 ============
sys_print_str:call sys_print_charretsys_print_char:push edipush eax; 计算显存位置: (行*80 + 列)*2mov edi, ebximul edi, LINE_WIDTHadd edi, ecxshl edi, 1; 写入显存mov ax, dx ; 组合字符和属性mov [gs:edi], ax; 更新列号inc ecxcmp ecx, LINE_WIDTHjb .no_newline; 处理换行xor ecx, ecx ; 列号清零inc ebx ; 行号增加.no_newline:pop eaxpop ediretsys_get_key:call get_keyretsys_clear_screen:call clear_screenxor eax, eaxretsys_run_program:; EBX=文件名指针push esimov esi, ebx; 这里需要实现文件加载和执行逻辑; call do_runpop esixor eax, eaxret
写一个小程序测试一下
a.asm
org 0x100000
_start:; 测试单个字符输出mov eax, 0mov dl, 'H'mov dh, 0x0Fint 0x80inc ecxmov dl, 'e'int 0x80inc ecxmov dl, 'l'int 0x80inc ecxmov dl, 'l'int 0x80inc ecxmov dl, 'o'int 0x80inc ecxmov dl, '!'int 0x80inc ecxmov dl, '!'int 0x80retmsg 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
endstrucstruc 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
endstrucstruc icmp_header.type: resb 1.code: resb 1.checksum: resw 1.unused: resw 1.unused2: resw 1
endstrucstruc 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界限 = 大小 - 1dd 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:pushadxor ecx, ecxinc ebx; Display initialization messagemov esi, net_init_msgcall print_str; Initialize NICcall nic_inittest eax, eaxjz .error; Set up network interruptmov al, NIC_IRQmov bl, 0x8E ; Interrupt gate, DPL=0mov esi, net_interrupt_handlercall set_interrupt_gate; Enable IRQ in PIC (Critical!)mov dx, 0x21 ; PIC1 data portin al, dxand al, ~(1 << (NIC_IRQ % 8)) ; Clear bit to enable interruptout dx, al; Display ready messagemov esi, net_ready_msgcall print_strpopadret.error:mov ah, 0x0C ; Red colorxor ecx, ecxinc ebxmov esi, net_err_msgcall print_strpopadret; NIC Initialization
nic_init:pushad; 1. 验证网卡存在mov dx, NIC_IO_BASE + 0x01in al, dxcmp al, 0xFFje .no_nic; 2. 发送复位命令(带双重验证)mov dx, NIC_IO_BASE + 0x18mov al, 0x80out dx, al; 3. 延长等待时间(约2秒)mov ecx, 2000000
.reset_wait:in al, dxtest al, 0x80jz .reset_donepauseloop .reset_wait; 4. 显示详细错误信息mov esi, .reset_fail_msgcall print_strin al, dxcall print_hex; 5. 尝试软件复位mov dx, NIC_IO_BASE + 0x1Fmov al, 0x00out dx, aljmp .error.reset_done:; 6. 初始化关键寄存器mov dx, NIC_IO_BASE + 0x0E ; DCRmov al, 0x58out dx, almov dx, NIC_IO_BASE + 0x0C ; RCRmov al, 0x20out dx, almov esi, .success_msgcall print_strpopadmov eax, 1ret.no_nic:mov esi, .no_nic_msgcall print_str
.error:popadxor eax, eaxret.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 + 0x0Ein al, dxtest al, aljz .done; 处理接收中断test al, 0x01jz .no_rxcall handle_receive
.no_rx:; 确认中断out dx, al.done:popadiret; 接收处理
handle_receive:pushad; 检查是否有数据包mov dx, NIC_IO_BASE + 0x0Cin al, dxtest al, 0x01jz .done; 读取数据包长度mov dx, NIC_IO_BASE + 0x0Bin al, dxmovzx ecx, al; 读取数据包mov dx, NIC_IO_BASE + 0x10mov edi, packet_bufferrep insb; 处理数据包call process_packet.done:popadret; 处理接收到的数据包
process_packet:pushad; 检查以太网类型mov esi, packet_buffermov ax, [esi + eth_header.ethertype]xchg al, ah ; 转换为网络字节序cmp ax, ETH_P_IPje .ip_packetcmp ax, ETH_P_ARPje .arp_packetjmp .done.ip_packet:; 处理IP包add esi, ETH_HLENmov al, [esi + ip_header.protocol]cmp al, IP_PROTO_ICMPje .icmp_packetjmp .done.icmp_packet:; 处理ICMP包add esi, IP_HLENmov al, [esi + icmp_header.type]cmp al, ICMP_ECHO_REPLYje .ping_replyjmp .done.ping_reply:; 检查是否是我们的ping回复; 比较标识符和序列号(简化处理)mov ax, [esi + icmp_header.unused]cmp ax, 0x1234 ; 与我们发送的标识符比较jne .done; 设置接收标志mov byte [ping_received], 1; 显示回复信息push esimov esi, ping_recv_msgcall print_str; 显示源IPmov esi, packet_buffer + ETH_HLEN + ip_header.saddrcall print_ip; 显示TTLmov esi, ttl_msg ; " TTL="call print_strmov al, [packet_buffer + ETH_HLEN + ip_header.ttl]call print_deccall newlinepop esijmp .done.arp_packet:; 处理ARP包add esi, ETH_HLENmov ax, [esi + arp_header.oper]xchg al, ahcmp ax, 1 ; ARP请求je .arp_requestcmp ax, 2 ; ARP回复je .arp_replyjmp .done.arp_request:; 处理ARP请求call handle_arp_requestjmp .done.arp_reply:; 处理ARP回复call handle_arp_reply.done:popadret; 发送数据包
send_packet:pushadpush es; 设置发送缓冲区mov esi, [esp + 44] ; 数据指针mov ecx, [esp + 48] ; 数据长度; 检查长度cmp ecx, 2048ja .error; 复制数据到发送缓冲区mov edi, tx_bufferrep movsb; 发送数据包mov dx, NIC_IO_BASE + 0x04 ; 发送命令端口mov al, 0x01 ; 发送命令out dx, alpop espopadret.error:pop espopadxor eax, eaxret; 处理ARP请求
handle_arp_request:pushad; 检查是否是我们的IPmov esi, packet_buffer + ETH_HLEN + arp_header.tpamov edi, my_ipmov ecx, IP_ALENrepe cmpsbjne .done; 构造ARP回复mov edi, tx_buffer; 以太网头部mov esi, packet_buffer + eth_header.src_macmov ecx, ETH_ALENrep movsb ; 目标MACmov esi, my_macmov ecx, ETH_ALENrep movsb ; 源MACmov ax, ETH_P_ARPxchg al, ahstosw ; 以太网类型; ARP头部mov ax, 0x0001 ; 硬件类型(以太网)stoswmov ax, ETH_P_IP ; 协议类型(IP)stoswmov al, ETH_ALEN ; 硬件地址长度stosbmov al, IP_ALEN ; 协议地址长度stosbmov ax, 0x0200 ; 操作码(回复)stosw; 发送方MAC和IPmov esi, my_macmov ecx, ETH_ALENrep movsbmov esi, my_ipmov ecx, IP_ALENrep movsb; 目标MAC和IPmov esi, packet_buffer + eth_header.src_macmov ecx, ETH_ALENrep movsbmov esi, packet_buffer + ETH_HLEN + arp_header.spamov ecx, IP_ALENrep movsb; 发送ARP回复mov ecx, edisub ecx, tx_bufferpush ecxpush tx_buffercall send_packet.done:popadret; 处理ARP回复
handle_arp_reply:; 更新ARP缓存 (简化实现)ret; 发送ping请求
do_ping_impl:pushadpush espush ds; 设置内核数据段mov ax, KERNEL_DSmov ds, axmov es, ax; 初始化计数器mov word [ping_seq], 0mov byte [ping_count], 0; 解析目标IPmov esi, [esp + 44] ; 获取IP字符串指针call parse_iptest eax, eaxjz .error; 保存目标IP指针mov edx, eax; 发送4个ping请求(标准ping行为)mov ecx, 4
.send_loop:; 构造ICMP包call build_icmp_packet; 发送数据包push ecxpush edxmov ecx, edisub ecx, tx_buffer ; 计算包长度push ecxpush tx_buffercall send_packetadd esp, 8; 显示发送消息mov ah, 0x0Finc ebxmov ecx, 0push esimov esi, ping_sent_msgcall print_strpop 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], 0jne .got_reply; 短暂延迟push ecxmov ecx, 1000
.delay:noploop .delaypop ecxjmp .wait_reply.timeout:; 显示超时信息inc ebxmov ecx, 0mov esi, 0mov esi, ping_timeout_msgcall print_strjmp .next_ping.got_reply:; 已收到回复,计数器递增inc byte [ping_count]mov byte [ping_received], 0.next_ping:call newlinepop edxpop ecxdec ecx ; 递减计数器jnz .send_loop ; 如果ecx≠0则继续循环; 显示统计信息mov esi, ping_stats_msgcall print_strmovzx eax, byte [ping_count]push eaxpush 4call print_dec ; 实现打印十进制数的函数add esp, 8jmp .done.error:mov esi, net_err_msgcall print_str.done:pop dspop espopadret; 构建ICMP包
build_icmp_packet:mov edi, tx_buffer; 1. 以太网头部; 源MACmov esi, my_macmov ecx, ETH_ALENrep movsb; 目标MAC (广播)mov al, 0xFFmov ecx, ETH_ALENrep stosb; 以太网类型(IP)mov ax, ETH_P_IPxchg al, ahstosw; 2. IP头部mov al, 0x45 ; 版本4 + 头部长度5字stosbxor al, al ; 服务类型stosbmov ax, (IP_HLEN + ICMP_HLEN + 32) ; 总长度xchg al, ahstoswmov ax, [ping_seq] ; 使用序列号作为标识stoswxor ax, ax ; 分片偏移stoswmov al, 64 ; TTLstosbmov al, IP_PROTO_ICMP ; 协议stosbxor ax, ax ; 校验和(先置0)stosw; 源IPmov esi, my_ipmov ecx, IP_ALENrep movsb; 目标IPmov esi, edx ; 之前保存的目标IP指针mov ecx, IP_ALENrep movsb; 计算IP校验和push edimov esi, tx_buffer + ETH_HLENmov ecx, IP_HLENcall checksummov [esi + ip_header.check], axpop edi; 3. ICMP头部mov al, ICMP_ECHO_REQUEST ; 类型stosbxor al, al ; 代码stosbxor ax, ax ; 校验和(先置0)stoswmov ax, 0x1234 ; 标识符(可以固定)stoswmov ax, [ping_seq] ; 序列号stoswinc word [ping_seq] ; 递增序列号; ICMP数据 (32字节测试数据)mov ecx, 32mov al, 'A'
.icmp_data:stosbinc alloop .icmp_data; 计算ICMP校验和mov esi, tx_buffer + ETH_HLEN + IP_HLENmov ecx, ICMP_HLEN + 32call checksummov [esi + icmp_header.checksum], axret; 计算校验和
checksum:xor eax, eaxxor edx, edx
.loop:lodswadd eax, edxmov edx, eaxshr edx, 16and eax, 0xFFFFloop .loopadd eax, edxmov edx, eaxshr edx, 16add eax, edxnot axret; 解析IP地址
parse_ip:pushadmov edi, .ip_buffermov ecx, 4
.parse_loop:call atoistosbcmp byte [esi], '.'jne .parse_doneinc esiloop .parse_loop
.parse_done:popadmov eax, .ip_bufferret.ip_buffer:db 0, 0, 0, 0; 辅助函数
print_ip:pushadmov ecx, 4
.print_loop:lodsbcall print_deccmp ecx, 1je .no_dotmov al, '.'call put_char
.no_dot:loop .print_looppopadretprint_dec:pushadxor ah, ahmov bl, 100div bltest al, aljz .no_hundredsadd al, '0'call put_char
.no_hundreds:mov al, ahxor ah, ahmov bl, 10div bltest al, aljnz .has_tenstest ah, ahjz .done
.has_tens:add al, '0'call put_charmov al, ahadd al, '0'call put_char
.done:popadretnewline:push eaxmov al, 0x0Acall put_charpop eaxret; 设置中断门
; 输入: AL=中断号, BL=属性, ESI=处理程序地址
set_interrupt_gate:pushad; 计算IDT中的偏移量 (中断号*8)xor ah, ahshl eax, 3 ; 每个门描述符8字节add eax, idt ; 加上IDT基地址; 设置偏移量低16位mov [eax], simov word [eax+2], KERNEL_CS ; 选择子; 设置属性mov byte [eax+4], 0 ; 保留mov byte [eax+5], bl ; 属性; 设置偏移量高16位shr esi, 16mov [eax+6], sipopadret; 字符串转整数 (简单实现)
; 输入: ESI=字符串指针
; 输出: EAX=数值
atoi:push ebxpush ecxpush edxxor eax, eaxxor ebx, ebxxor ecx, ecx.convert:lodsbtest al, aljz .donecmp al, '0'jb .invalidcmp al, '9'ja .invalid; 数字字符,转换为数值sub al, '0'imul ebx, 10add ebx, eaxjmp .convert.invalid:xor ebx, ebx.done:mov eax, ebxpop edxpop ecxpop ebxret; 打印十六进制数的函数
print_hex:pushadmov ecx, 8 ; 处理8个十六进制字符(32位)
.hex_loop:rol eax, 4 ; 循环左移4位mov ebx, eaxand ebx, 0x0F ; 取最低4位mov bl, [hex_chars + ebx] ; 转换为ASCII字符mov ah, 0x0F ; 设置显示属性call put_char ; 确保put_char已定义loop .hex_looppopadrethex_chars db '0123456789ABCDEF'
make运行一下
程序运行正常
ping一下
网络功能似乎不可用,没有正确复位
看一下VMware运行
爆错似乎不一样,因为VMware 没有正确配置网卡
qemu应该这样运行
run : a.imgqemu-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没有指向显存
测试后才发现是判断的问题