MIT XV6 - 1.1 Lab: Xv6 and Unix utilities - user/_sleep 是什么?做什么?

接上文 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.

  • 让我们验证一下他的文件格式

    bash 复制代码
    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 V
      ABI Version:                       0                              # ABI 版本号
      Type:                              EXEC (Executable file)         # 这是一个可执行文件
      Machine:                           RISC-V                         # 目标架构为 RISC-V
      Version:                           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 压缩指令集,双精度浮点数 ABI
      Size 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系统中,一个新进程的诞生是通过forkexec 配合得来的,通过fork 创建一个副本,然后通过exec 将指定的binary加载进内存空间中并开始执行,教材中也对此有所讲解,依然是book-riscv-rev3.pdf 3.8节,简单看一眼

  • 让我们看看我们的 user/_sleep

    bash 复制代码
    riscv64-unknown-elf-objdump -p user/_sleep 
    
    user/_sleep:     file format elf64-littleriscv
    
    Program Header:
    0x70000003 off    0x0000000000006af8 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**0
        filesz 0x000000000000005a memsz 0x0000000000000000 flags r--
    LOAD off    0x0000000000001000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12
        filesz 0x0000000000001000 memsz 0x0000000000001000 flags r-x
    LOAD off    0x0000000000002000 vaddr 0x0000000000001000 paddr 0x0000000000001000 align 2**12
        filesz 0x0000000000000000 memsz 0x0000000000000020 flags rw-
    STACK off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**4
        filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
  • 让我们来看看AI是怎么解释的

  • 当然我们的 user/_sleep 里面还有更多东西

    bash 复制代码
    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 看一下汇编代码

asm 复制代码
   0:	1141                	addi	sp,sp,-16
   2:	e406                	sd	ra,8(sp)
   4:	e022                	sd	s0,0(sp)
   6:	0800                	addi	s0,sp,16
    if (argc != 2)
   8:	4789                	li	a5,2
   a:	02f50063          	beq	a0,a5,2a <main+0x2a>
    {
        fprintf(2, "Usage: sleep <seconds>\n");
   e:	00001597          	auipc	a1,0x1
  12:	82258593          	addi	a1,a1,-2014 # 830 <malloc+0x100>
  16:	853e                	mv	a0,a5
  18:	00000097          	auipc	ra,0x0
  1c:	62e080e7          	jalr	1582(ra) # 646 <fprintf>
        exit(1);
  20:	4505                	li	a0,1
  22:	00000097          	auipc	ra,0x0
  26:	2fc080e7          	jalr	764(ra) # 31e <exit>
    }

    if (sleep(atoi(argv[1])) != 0)
  2a:	6588                	ld	a0,8(a1)
  2c:	00000097          	auipc	ra,0x0
  30:	1ec080e7          	jalr	492(ra) # 218 <atoi>
  34:	00000097          	auipc	ra,0x0
  38:	37a080e7          	jalr	890(ra) # 3ae <sleep>
  3c:	cd19                	beqz	a0,5a <main+0x5a>
    {
        fprintf(2, "sleep: failed to sleep\n");
  3e:	00001597          	auipc	a1,0x1
  42:	80a58593          	addi	a1,a1,-2038 # 848 <malloc+0x118>
  46:	4509                	li	a0,2
  48:	00000097          	auipc	ra,0x0
  4c:	5fe080e7          	jalr	1534(ra) # 646 <fprintf>
        exit(1);
  50:	4505                	li	a0,1
  52:	00000097          	auipc	ra,0x0
  56:	2cc080e7          	jalr	716(ra) # 31e <exit>
    }

    exit(0);
  5a:	4501                	li	a0,0
  5c:	00000097          	auipc	ra,0x0
  60:	2c2080e7          	jalr	706(ra) # 31e <exit>

注意这一条指令

asm 复制代码
  38:	37a080e7          	jalr	890(ra) # 3ae <sleep>

这是一条跳转指令,地址是 3ae,经过搜索可以看到 sleep 函数的汇编代码

asm 复制代码
00000000000003ae <sleep>:                      # 函数入口点,地址为 0x3ae
.global sleep                                  # 声明 sleep 为全局符号
sleep:                                         # 函数标签
 li a7, SYS_sleep                              # 将系统调用号 13 (SYS_sleep) 加载到寄存器 a7
 3ae:	48b5                li	a7,13          # 机器码:48b5 表示 li a7,13
 ecall                                         # 执行系统调用指令
 3b0:	00000073          	ecall              # 机器码:00000073 表示 ecall
 ret                                           # 从函数返回
 3b4:	8082                ret                # 机器码:8082 表示 ret

我们忽略其他各种分支判断以及错误码打印,exit 调用等等, 可以看到sleep.c 的核心就是在条件达成时,调用函数sleep ,而 sleep 函数内的实现就是利用 ecall 指令触发系统调用,当系统调用完成后,函数返回。

瞎谈

有大佬说过 Algorithms + Data Structures = Programs

那么从操作系统的角度来看,是不是 Data + SysCall = Programs 也是成立的?因为程序的一切的一切最终都是要把你想做的事情组织成一条条数据,通过系统调用的方式来达成目的?比如播放音乐、播放视频、玩video game,毕竟系统调用应该是应用程序控制硬件资源的唯一途径(吧?)。

相关推荐
LUCIAZZZ3 小时前
简单介绍分布式定时任务XXL-JOB
java·spring boot·分布式·spring·操作系统·定时任务
LUCIAZZZ16 小时前
Caffeine快速入门
java·后端·spring·缓存·操作系统·springboot
ssslar1 天前
MIT XV6 - 1.2 Lab: Xv6 and Unix utilities - pingpong
操作系统·risc-v·xv6
羑悻的小杀马特3 天前
【Linux篇章】Linux 进程信号1:解锁系统高效运作的 “隐藏指令”,开启性能飞跃新征程(精讲信号产生和保存)
linux·运维·服务器·学习·操作系统·进程信号
望获linux5 天前
北京亦庄机器人马拉松:人机共跑背后的技术突破与产业启示
linux·人工智能·机器人·操作系统·开源软件·rtos·具身智能
獭.獭.5 天前
Linux -- 操作系统
linux·操作系统·冯诺依曼体系
红白小蛋糕6 天前
《操作系统真象还原》调试总结篇
操作系统·内核·开发笔记
望获linux7 天前
望获实时Linux系统荣获人形机器人技术突破奖
linux·运维·机器人·操作系统·开源软件·rtos
ssslar8 天前
MIT XV6 - 1.1 Lab: Xv6 and Unix utilities - sleep 是怎样练成的?
操作系统·risc-v·xv6