纯汇编自制操作系统(四、应用程序等的实现)

发布于:2025-06-03 ⋅ 阅读:(21) ⋅ 点赞:(0)

本项目已在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没有指向显存

测试后才发现是判断的问题


网站公告

今日签到

点亮在社区的每一天
去签到