接上文 MIT XV6 - 1.1 Lab: Xv6 and Unix utilities - sleep 是怎樣練成的?
user/_sleep 是什么?
book-riscv-rev3.pdf 3.8節有對Xv6 binary文件的格式描述
Xv6 binaries are formatted in the widely-used ELF format, defined in (kernel/elf.h). An ELF binary
consists of an ELF header, struct elfhdr (kernel/elf.h:6), followed by a sequence of program
section headers, struct proghdr (kernel/elf.h:25). Each progvhdr describes a section of the
application that must be loaded into memory; xv6 programs have two program section headers:
one for instructions and one for data.
- 讓我們驗證一下他的文件格式
riscv64-unknown-elf-readelf -h user/_sleep ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 # ELF 文件的魔數,用于標識文件類型Class: ELF64 # 64 位 ELF 文件Data: 2's complement, little endian # 使用 2 的補碼表示數字,小端序存儲Version: 1 (current) # ELF 格式的當前版本OS/ABI: UNIX - System V # 目標操作系統為 UNIX System VABI Version: 0 # ABI 版本號Type: EXEC (Executable file) # 這是一個可執行文件Machine: RISC-V # 目標架構為 RISC-VVersion: 0x1 # ELF 文件版本Entry point address: 0x64 # 程序入口點的虛擬地址Start of program headers: 64 (bytes into file) # 程序頭表在文件中的偏移量Start of section headers: 31008 (bytes into file) # 節頭表在文件中的偏移量Flags: 0x5, RVC, double-float ABI # 標志位:RISC-V 壓縮指令集,雙精度浮點數 ABISize of this header: 64 (bytes) # ELF 頭的大小Size of program headers: 56 (bytes) # 每個程序頭的大小Number of program headers: 4 # 程序頭的數量Size of section headers: 64 (bytes) # 每個節頭的大小Number of section headers: 18 # 節頭的數量Section header string table index: 17 # 節頭字符串表的索引
我們知道,在Unix系統中,一個新進程的誕生是通過fork和exec配合得來的,通過fork創建一個副本,然后通過exec將指定的binary加載進內存空間中并開始執行,教材中也對此有所講解,依然是book-riscv-rev3.pdf 3.8節,簡單看一眼
-
讓我們看看我們的 user/_sleep
riscv64-unknown-elf-objdump -p user/_sleep user/_sleep: file format elf64-littleriscvProgram Header: 0x70000003 off 0x0000000000006af8 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**0filesz 0x000000000000005a memsz 0x0000000000000000 flags r-- LOAD off 0x0000000000001000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12filesz 0x0000000000001000 memsz 0x0000000000001000 flags r-x LOAD off 0x0000000000002000 vaddr 0x0000000000001000 paddr 0x0000000000001000 align 2**12filesz 0x0000000000000000 memsz 0x0000000000000020 flags rw- STACK off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**4filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
-
讓我們來看看AI是怎么解釋的
-
當然我們的 user/_sleep 里面還有更多東西
riscv64-unknown-elf-objdump -h user/_sleep Sections: Idx Name Size VMA LMA File off Algn # 列標題:索引、名稱、大小、虛擬地址、加載地址、文件偏移、對齊0 .text 0000082a 0000000000000000 0000000000000000 00001000 2**1 # 代碼段:包含可執行指令CONTENTS, ALLOC, LOAD, READONLY, CODE # 屬性:有內容、已分配、可加載、只讀、代碼1 .rodata 000007d0 0000000000000830 0000000000000830 00001830 2**3 # 只讀數據段:包含常量數據(如字符串常量)CONTENTS, ALLOC, LOAD, READONLY, DATA # 屬性:有內容、已分配、可加載、只讀、數據2 .data 00000000 0000000000001000 0000000000001000 00002000 2**0 # 數據段:包含已初始化的全局變量CONTENTS, ALLOC, LOAD, DATA # 屬性:有內容、已分配、可加載、數據3 .bss 00000020 0000000000001000 0000000000001000 00002000 2**3 # BSS段:包含未初始化的全局變量ALLOC # 屬性:已分配4 .debug_info 00000ff8 0000000000000000 0000000000000000 00002000 2**0 # 調試信息段:包含DWARF調試信息CONTENTS, READONLY, DEBUGGING, OCTETS # 屬性:有內容、只讀、調試用、字節數據5 .debug_abbrev 00000643 0000000000000000 0000000000000000 00002ff8 2**0 # 調試縮寫表:DWARF調試信息的縮寫表CONTENTS, READONLY, DEBUGGING, OCTETS # 屬性:有內容、只讀、調試用、字節數據6 .debug_loc 00001f21 0000000000000000 0000000000000000 0000363b 2**0 # 調試位置信息:變量位置信息CONTENTS, READONLY, DEBUGGING, OCTETS # 屬性:有內容、只讀、調試用、字節數據7 .debug_aranges 000000f0 0000000000000000 0000000000000000 00005560 2**4 # 調試地址范圍:用于快速定位調試信息CONTENTS, READONLY, DEBUGGING, OCTETS # 屬性:有內容、只讀、調試用、字節數據8 .debug_line 000011ab 0000000000000000 0000000000000000 00005650 2**0 # 調試行號信息:源代碼行號映射CONTENTS, READONLY, DEBUGGING, OCTETS # 屬性:有內容、只讀、調試用、字節數據9 .debug_str 000002e4 0000000000000000 0000000000000000 000067fb 2**0 # 調試字符串表:調試信息中的字符串CONTENTS, READONLY, DEBUGGING, OCTETS # 屬性:有內容、只讀、調試用、字節數據 10 .comment 00000019 0000000000000000 0000000000000000 00006adf 2**0 # 注釋段:包含編譯器版本等信息CONTENTS, READONLY # 屬性:有內容、只讀 11 .riscv.attributes 0000005a 0000000000000000 0000000000000000 00006af8 2**0 # RISC-V特定屬性段CONTENTS, READONLY # 屬性:有內容、只讀 12 .debug_frame 000004e0 0000000000000000 0000000000000000 00006b58 2**3 # 調試幀信息:用于棧回溯CONTENTS, READONLY, DEBUGGING, OCTETS # 屬性:有內容、只讀、調試用、字節數據 13 .debug_ranges 00000050 0000000000000000 0000000000000000 00007038 2**0 # 調試范圍信息:用于描述變量范圍CONTENTS, READONLY, DEBUGGING, OCTETS # 屬性:有內容、只讀、調試用、字節數據
我就不解釋了,這個東西給我一天也解釋不清楚,解釋不完全(是真的不清楚)
user/_sleep 做什么?
讓我們打開 user/sleep.asm 看一下匯編代碼
0: 1141 addi sp,sp,-162: e406 sd ra,8(sp)4: e022 sd s0,0(sp)6: 0800 addi s0,sp,16if (argc != 2)8: 4789 li a5,2a: 02f50063 beq a0,a5,2a <main+0x2a>{fprintf(2, "Usage: sleep <seconds>\n");e: 00001597 auipc a1,0x112: 82258593 addi a1,a1,-2014 # 830 <malloc+0x100>16: 853e mv a0,a518: 00000097 auipc ra,0x01c: 62e080e7 jalr 1582(ra) # 646 <fprintf>exit(1);20: 4505 li a0,122: 00000097 auipc ra,0x026: 2fc080e7 jalr 764(ra) # 31e <exit>}if (sleep(atoi(argv[1])) != 0)2a: 6588 ld a0,8(a1)2c: 00000097 auipc ra,0x030: 1ec080e7 jalr 492(ra) # 218 <atoi>34: 00000097 auipc ra,0x038: 37a080e7 jalr 890(ra) # 3ae <sleep>3c: cd19 beqz a0,5a <main+0x5a>{fprintf(2, "sleep: failed to sleep\n");3e: 00001597 auipc a1,0x142: 80a58593 addi a1,a1,-2038 # 848 <malloc+0x118>46: 4509 li a0,248: 00000097 auipc ra,0x04c: 5fe080e7 jalr 1534(ra) # 646 <fprintf>exit(1);50: 4505 li a0,152: 00000097 auipc ra,0x056: 2cc080e7 jalr 716(ra) # 31e <exit>}exit(0);5a: 4501 li a0,05c: 00000097 auipc ra,0x060: 2c2080e7 jalr 706(ra) # 31e <exit>
注意這一條指令
38: 37a080e7 jalr 890(ra) # 3ae <sleep>
這是一條跳轉指令,地址是 3ae,經過搜索可以看到 sleep 函數的匯編代碼
00000000000003ae <sleep>: # 函數入口點,地址為 0x3ae
.global sleep # 聲明 sleep 為全局符號
sleep: # 函數標簽li a7, SYS_sleep # 將系統調用號 13 (SYS_sleep) 加載到寄存器 a73ae: 48b5 li a7,13 # 機器碼:48b5 表示 li a7,13ecall # 執行系統調用指令3b0: 00000073 ecall # 機器碼:00000073 表示 ecallret # 從函數返回3b4: 8082 ret # 機器碼:8082 表示 ret
我們忽略其他各種分支判斷以及錯誤碼打印,exit調用等等, 可以看到sleep.c的核心就是在條件達成時,調用函數sleep,而 sleep函數內的實現就是利用 ecall 指令觸發系統調用,當系統調用完成后,函數返回。
瞎談
有大佬說過 Algorithms + Data Structures = Programs
那么從操作系統的角度來看,是不是 Data + SysCall = Programs 也是成立的?因為程序的一切的一切最終都是要把你想做的事情組織成一條條數據,通過系統調用的方式來達成目的?比如播放音樂、播放視頻、玩video game,畢竟系統調用應該是應用程序控制硬件資源的唯一途徑(吧?)。