riscv简单常用汇编指令xv6

文章目录

  • 前言
  • entry.S
    • mret
    • asm volatile
      • read csr
      • write csr
      • riscv常见csr寄存器
    • ecall, 系统调用指令
      • cpu执行异常处理指令的三种事件
  • 异常处理相关寄存器
    • 用户态trap
      • sret指令
      • 页表切换操作
      • 用户态系统调用过程总结
    • 内核态trap
    • 缺页异常
  • 中断与设备驱动
  • Locking
  • 调度
  • 文件系统
  • 操作系统拥有的资源
  • xv6系统启动过程
    • 1. 准备C代码执行环境
    • 2. 进入main
  • char *argv[] = { init, 0 };
  • ???
  • 其它C语言知识


前言

riscv

在目录下,执行make qemu-gdb ,进程会阻塞.
另开一个终端,在目录下,执行gdb-multiarch kernel/kernel
进入gdb后 b _entry将断点打在最开始处

退出qemu:
first press Ctrl + A (A is just key a, not the alt key),
then release the keys,
afterwards press X.


entry.S

.section .text

.section是一条伪指令,用于定义代码的段。该句告诉汇编器将接下来的指令放入.text段(代码段)。


.global _entry

_entry:

.global用于定义全局符号。在汇编中,符号是表示一个地址的标签。
该句表示将 _entry 这个符号标记为全局,这样它可以被其他模块或链接过程引用。通常,程序的入口点会被标记为全局,以确保链接器正确找到程序的起始地址。


la sp, stack0

la是加载地址(Load Address)的缩写,它将stack0的地址加载到栈指针寄存器sp中。


li a0, 1024*4

li是加载立即数(Load Immediate)的缩写,它将1024*4(4KB)加载到寄存器a0中。这个值通常用作栈的大小。


ld sp, 8(a0)

将存储在地址 8 + a0 处的数据加载到栈指针寄存器sp中


csrr a1, mhartid

csrr:这是一个用于读取 CSR(Control and Status Register) 寄存器的指令,其中 “csrr” 是 “CSR Read” 的缩写。
a1:这是目标寄存器,即读取到的 CSR 寄存器的值将被存储在寄存器 a1 中。
mhartid:这是一个 CSR 寄存器的符号名称,表示 Machine Hart ID。Hart 在 RISC-V 中指的是硬件线程,而 mhartid 包含了当前硬件线程的 ID。
这条指令的作用是将当前硬件线程的 ID 读取并存储到寄存器 a1 中。硬件线程 ID 通常用于区分不同的处理器核心或硬件线程,特别是在多核处理器系统中。寄存器 a1 在这里用于保存读取到的硬件线程 ID 的值。


addi a1, a1, 1

addi是加立即数的缩写,这里将a1中的值加1。


mul a0, a0, a1

mul是乘法指令,它将a0和a1的值相乘,结果存储在a0中。这个结果通常用作栈的总大小。


add sp, sp, a0

add是加法指令,它将sp和a0的值相加,结果存储在sp中。这样做是为了调整栈指针,使其指向新的栈顶。


call start

call是一个伪指令,实际上会生成一个jal(Jump and Link)指令,用于调用start函数。


spin:
        j spin

这是一个简单的自旋循环,通过无条件跳转指令j使程序在spin标签处不断循环执行。

mret

在RISC-V指令集中,mret是一条特权级别切换指令,用于从机器模式返回到先前的特权级别。这通常用于从机器级别返回到之前的机器模式上下文。

asm volatile("csrw mstatus, %0" : : "r" (x));
asm volatile("csrw mepc, %0" : : "r" (x));
asm volatile("mret");  // mstatus的特权级和mepc寄存器的值生效。及x特权,程序跳转到x处执行
  1. 将机器级别的程序计数器(PC)设置为机器模式下的值。
  2. 将机器级别的特权状态切换回到先前的特权级别(通常是用户模式或超级用户模式)。

这样,mret指令使得从高特权级别切换回低特权级别成为可能,比如从内核态返回到用户态。

asm volatile

read csr

asm volatile("csrr %0, mstatus" : "=r" (x) );

c内嵌汇编

asm volatile:这表示内嵌汇编是“volatile”的,这意味着编译器不会对其进行优化,以确保汇编代码的执行顺序不被改变。
“csrr %0, mstatus”:这是实际的汇编指令。csrr 是RISC-V指令集中的一个指令,用于从一个控制寄存器(CSR,Control and Status Register)中读取数据。mstatus 是一个CSR的名称,通常用于保存机器状态信息。

: "=r" (x):这是输出约束(output constraint),指定了汇编代码输出的位置。“=r” 表示将结果放在一个通用寄存器中,而 (x) 则是这个通用寄存器对应的C变量。


mstatus, Machine Status Register是RISC-V架构中的一个控制和状态寄存器(CSR),用于保存和控制机器级别的状态信息。这个寄存器包含了一系列位,每一位都对应着不同的机器状态或控制标志。以下是一些mstatus寄存器中可能包含的一些位的常见含义:

  1. MIE(Machine Interrupt Enable):位3是MIE位,用于启用或禁用机器级中断。当MIE被置位时,机器级中断将被启用。

  2. MPIE(Machine Previous Interrupt Enable):位7是MPIE位,用于保存中断之前的MIE状态。在中断处理过程中,MPIE会被设置为MIE的值,以便在中断结束后还原中断前的MIE状态。

  3. MPP(Machine Previous Privilege):位11和12是MPP位,用于保存中断之前的特权级别。这两位共同表示中断之前的特权级别,可能是用户态(User)、监管态(Supervisor)或机器态(Machine)。

write csr

asm volatile("csrw mstatus, %0" : : "r" (x));

: : "r" (x): 这是内嵌汇编的输入输出约束部分。“r” (x) 表示将变量 x 放到一个通用寄存器中,供汇编代码使用。

#define MSTATUS_MPP_MASK (3L << 11)
#define MSTATUS_MPP_S (1L << 11)
mstatus_val &= ~MSTATUS_MPP_MASK;
mstatus_val |= MSTATUS_MPP_S;

riscv常见csr寄存器

RISC-V指令集中的CSR(Control and Status Register)寄存器用于控制和监视处理器的状态。以下是一些常见的CSR寄存器及其作用:

  1. mvendorid(Machine Vendor ID):用于标识实现RISC-V架构的硬件供应商。

  2. marchid(Machine Architecture ID):指定RISC-V的体系结构版本。

  3. mimpid(Machine Implementation ID):表示具体的硬件实现。

  4. mhartid(Machine Hart ID):用于标识处理器的硬件线程。

  5. mstatus(Machine Status):包含有关处理器当前状态的信息,例如中断使能和处理器模式。

  6. mie(Machine Interrupt Enable):用于启用或禁用中断。

  7. mcause(Machine Cause):标识最后一次异常或中断的原因。

  8. mtvec(Machine Trap Vector):包含异常处理程序的基址,用于处理中断和异常。

  9. mscratch:用于存储临时数据,例如异常处理时保存的寄存器值。

  10. mepc(Machine Exception Program Counter):保存导致异常的指令地址。

  11. mcounteren:用于控制计数器(cycle、time等)是否可由用户模式访问。

  12. mip(Machine Interrupt Pending):指示哪些中断是激活的。

  13. stap:配置和管理地址翻译和保护机制。具体来说,satp寄存器主要用于设置页表的基址以及选择页表的模式,从而启用虚拟地址到物理地址的转换。

asm volatile("csrw mepc %0"::"r"( (uint64)main ));

将main的地址写入mepc,当调用mret是以便程序“继续”从main执行

ecall, 系统调用指令

# RISC-V Assembly code example with ecall

.section .text
.globl _start

_start:
    # Your assembly code here

    # Make a system call
    li a7, 10      # 系统调用号存储在a7寄存器  scause寄存器存储事件号
    ecall

    # End of program
    j _start

ecall是一个用于发起系统调用(system call)的指令。系统调用是用户程序与操作系统进行交互的一种方式,允许用户程序请求操作系统提供的服务,如文件操作、进程管理等。

cpu执行异常处理指令的三种事件

  1. 系统调用,即,当用户程序执行ecall指令时。
  2. 非法指令,如除以0、无效的虚拟地址
  3. 设备中断,当设备信号需要被处理时,如硬件完成读写

异常处理相关寄存器

  • stvec: 内核将事件处理程序地址写到该寄存器,RISC-V跳转到stvec的地址处理事件
  • sepc: 当事件发生时,cpu保存pc指针(因为后面stvec的值会覆盖它)。sret(异常处理结束的返回指令)指令copy sepc到pc。内核能够写入sepc来控制sret的去向
  • scause: RISC-V将事件号写入这里
  • sscratch: 内核在这里放置一个值,该值在trap处理程序一开始就很有用
  • sstatus: SIE位控制设备中断开关,SPP位指示事件是来自用户模式还是管理员模式,并且控制sret的返回

以上寄存器在用户模式是不可读写的。
在机器模式下也有一组相似的控制寄存器来处理异常;
多核芯片的每个cpu都有它自己的一组寄存器,任意时刻都有可能有任意个cpu处理trap。

当需要强制中断时,RISC-V硬件为所有trap类型(除了定时器中断之外)做以下事情:

  1. 如果trap是设备中断,且sstatus SIE位被清零,以下几条都不做
  2. 通过清除sstatus SIE位关闭中断
  3. 复制pc到sepc
  4. 保存当前运行模式(用户模式或超级用户模式)到sstatus的SPP位
  5. 设置scause为trap原因
  6. 设为超级用户模式
  7. 复制stvec到pc
  8. 从新的pc处开始执行

注意到,cpu不会切换到内核页表,也不会切换到内核栈,也不会保存除了pc寄存器外的其他寄存器。内核软件必须完成这些工作。cpu最小化工作是为了为软件提供最大的灵活性,如某些系统为了性能可能不会进行页表切换。

用户态trap

.align 4确保接下来的指令或数据将被放置在4字节的边界上。其中.align为伪指令

csrw sscratch, a0 这条指令的作用是将 a0 寄存器的值写入到 sscratch CSR 中。这可以用于保存当前软件线程的一些临时状态,以备将来的恢复。在上下文切换时,操作系统可能会使用 sscratch 来保存当前线程的一些上下文信息。

sd ra, 40(a0) 的含义是将通用寄存器 ra 中的值存储到内存中,存储的目标地址是 a0 寄存器中的值加上 40。sd 指令: sd 是 “Store Doubleword” 的缩写,用于将一个双字(64位)的数据存储到内存中。40(a0): 这表示存储目标的内存地址。具体地,它是 a0 寄存器中的值加上 40。这是一种相对地址寻址方式,表示存储目标的地址是 a0 中的值加上 40。

在 RISC-V 架构中,这些寄存器具有以下作用:

  1. ra - Return Address: 这是返回地址寄存器,用于保存函数调用的返回地址。

  2. sp - Stack Pointer: 栈指针寄存器,指向当前函数的栈顶。

  3. gp - Global Pointer: 全局指针寄存器,通常用于访问全局数据区。

  4. tp - Thread Pointer: 线程(核id)指针寄存器,用于线程相关的指针。

  5. t0, t1, t2 - Temporary Registers: 临时寄存器,可以用于存储临时数据。

  6. s0, s1, …, s11 - Saved Registers: 保存寄存器,用于保存在函数调用中需要保留的寄存器值。

  7. a0, a1, …, a7 - Argument Registers: 参数寄存器,用于传递函数参数。

  8. t3, t4, t5, t6 - Temporary Registers: 更多的临时寄存器。

每个寄存器都有其特定的用途,例如用于保存返回地址、参数传递、临时存储等。在函数调用时,一些寄存器的值可能会被保存和恢复,以确保程序的正确执行。这些寄存器的具体用途也可能取决于特定的编程约定和编译器。

sfence.vma zero, zero 是 RISC-V 汇编中的一条指令,用于执行虚拟地址到物理地址的刷新操作。

  1. sfence.vma 指令: sfence.vma 是 “Store Fence Virtual Memory to Physical Memory” 的缩写。这是一种内存屏障指令,它确保在它之前的所有内存操作都完成,然后刷新(同步)虚拟地址到物理地址的映射。

  2. zero 寄存器: 在 RISC-V 中,zero 寄存器始终包含常量值零。在这里,两个 zero 寄存器作为参数,表示没有特定的虚拟地址范围需要刷新。

  3. 虚拟地址到物理地址的刷新: 在操作系统和硬件的交互中,虚拟地址和物理地址之间存在映射关系。这种刷新操作确保最新的虚拟地址到物理地址的映射关系被刷新,以反映最新的变化。这在多核系统中的共享内存环境中特别重要,以确保不同核之间的一致性。

总的来说,sfence.vma zero, zero 指令用于确保之前的内存操作已经完成,并刷新虚拟地址到物理地址的映射,以保持内存一致性。

sret指令

用于从中断或异常处理程序中返回到调用者
在执行该指令之前:

  1. 设置status寄存寄存器的 cpu运行模式,终端开关等
  2. 设置sepc寄存器
  3. 设置satp页表寄存器
  4. 切换页表
  5. 将a0寄存器设置为TRAPFRAME
  6. 恢复所有寄存器
  7. 调用sret (将spec寄存器的值写入pc寄存器等…程序从pc处开始执行) 返回用户模式

下面的c代码与对应的汇编

unsigned long int satp = 456;
((void (*)(unsigned long int))trampoline_userret)(satp);

// asm
        li      a5,456                       # 将立即数456写到寄存器a5
        sd      a5,-24(s0)                   # 将a5的值写入地址s0-24(改地址在栈上)
        lui     a5,%hi(trampoline_userret)
        addi    a5,a5,%lo(trampoline_userret)
        ld      a0,-24(s0)                   # 将之间保存的456加载到寄存器a0上
        jalr    a5                           # 跳转到a5(即,跳转到trampoline_userret)
// 

页表切换操作

sfence.vma zero, zero
csrw satp, TLB
sfence.vma zero, zero

用户态系统调用过程总结

  1. 用户态执行
    #include "syscall.h"
    
    # exec(init, argv)
    .globl start
    start:
            la a0, init
            la a1, argv
            li a7, SYS_exec
            ecall
    # char init[] = "/init\0";
    init:
      .string "/init\0"
    
    # char *argv[] = { init, 0 };
    .p2align 2
    argv:
      .long init
      .long 0
    
    将init地址放入a0,argv放入a1,将调用号放入a7
  2. 执行ecall,即,程序从uservec处开始执行
  3. 程序执行到uint64 sys_exec(void);
    sys_exec会调用copyinstr,该函数会在内核页表下正确拷贝用户页表的参数
  4. 将调用结果放入a0
  5. 最后调用sret返回用户态

内核态trap

当在内核态发生trap时,

  1. 执行一些动作。由于stvec存的是kernelvec,所以pc指向kernelvec,即代码从该处执行

  2. 保存内核态寄存器到内核栈上

  3. 读取sepc(中断前执行的指令),sstatus获取与硬件相关的状态,scause获取trap原因

  4. 判断trap类型:a supervisor external interrupt, via PLIC;software interrupt from a machine-mode timer interrupt, forwarded by timervec in kernelvec.S.
    // returns 2 if timer interrupt,
    // 1 if other device,
    // 0 if not recognized.

    // 处理设备中断
    // give up the CPU if this is a timer interrupt.

  5. 设置sepc和sstatus寄存器为 3. 的值,即恢复之前的状态

  6. 函数执行完}后,继续从函数调用(call)后面的指令执行

  7. 恢复寄存器状态

  8. 调用sret,结束trap

在用户态时stvec = usertrap,当从用户态进入内核态时,stvec被设置为kernelvec。当setvec的usertrap执行时,中断自动关闭,直到启用。

缺页异常

scause寄存器知识缺页类型,stval寄存器包含未能被映射的地址

中断与设备驱动

设备驱动的执行分为两个部分:1.通过系统调用,内核线程的顶层操作,如read,write执行IO。此时代码请求硬件开始操作并等待其完成 2.设备完成操作并产生一个中断,此时驱动中断处理程序作为底层操作唤醒进程并执行后续操作

UART硬件在软件中显示为一组内存映射的控制寄存器。硬件设备映射到特定的物理地址上。操纵物理地址并非是与内存交互而是和设备。有几组UART控制寄存器,每个1字节。偏移量从#define UART0 0x10001000开始。

如何初始化UART控制台?
反应在软件上就是对相关寄存器(地址)进行读写控制即可
读取过程:
如LSR寄存器包含判断输入字符是否准备读取的位,如果字符可从RHR寄存器读取,每当从该寄存器读取一个字符时,UART硬件从内部FIFO中删除等待字符,并清除LSR中的“ready”位(当FIFO为空时)。UART的发送硬件和接收硬件是不相依赖的,如果软件向THR写入一个字节,UART就发送该字节。

// map major device number to device functions.
struct devsw {
  int (*read)(int, uint64, int);
  int (*write)(int, uint64, int);
  #define NDEV 10
} devsw[NDEV];
#define CONSOLE 1
devsw[CONSOLE].read = consoleread;
devsw[CONSOLE].write = consolewrite;

consoleread等待从内核缓冲区cons.buf中读取数据
当用户输入字符时,UART硬件向RISC-V请求中断,然后系统进入trap处理代码,改代码经过检查scause中断原因最终调用devintr->uartintr,最终通过读取RHR获取字符。consoleintr(控制台中断处理函数)负责将读的字符存入cons.buf,在满足条件时,consoleintr唤醒consoleread(如果有的话),uartintr结束即异常处理结束,consoleread负责将内核缓冲区数据拷贝到用户缓冲区,然后返回用户空间。

每当UART发送完一个字节,他会产生一个中断,uartintr调用uartstart检查设备是否完成发送,并处理设备下一个输出字符。因此,如果一个程序向控制台输出多个字节,一般第一个字节由uartputc调用uartstart完成,缓冲区剩余的字节以中断的方式由uartintr调用uartstart完成发送。

通常设备活动与程序活动通过buffer和中断实现结构。控制台驱动可以处理输入,即便没有进程在等待读取;随后的读取将会看到输入的数据。同样的,程序无需等待设备而输出数据。这种解耦允许进程并发的执行IO,进而提高性能,当设备十分慢时尤为重要。这种思想有时被称为IO并发。

驱动中的并发中断处理程序运行时再产生中断是不好的行为,中断处理程序应当做较少的工作,如仅将数据拷贝到缓冲区然后唤醒其他代码

计时器中断系统编程时钟硬件周期性的中断每个CPU
RISC-V要求定时器中断在机器模式下进行,而不是在监控模式下进行。RISCV机器模式在没有分页的情况下执行,并且有一组单独的控制寄存器,所以它不是在机器模式下运行普通xv6内核代码而实现的。因此,xv6处理定时器中断与上述trap机制完全分开。
计时器中断可以发生在任意时刻;在临界操作时内核无法关闭计时器中断。因此计时器中断处理程序需要接收这项工作,并且保证以一种不会被中断的内核代码的方式。实现计时器中断处理程序的基础方式时请求RISC-V引发一个“软中断”并立即返回。RISC-V以普通trap机制向内核发送软中断并允许内核关闭该中断。由计时器中断产生的软中断的异常处理程序在devintr中。
机器模式下的计时器中断处理代码为timervec。它仅仅保存了少部分的寄存器,通知CLINT(Core Local Interrupt)何时产生下一个计时器中断,请求RISC-V产生一个软中断,恢复寄存器然后返回。

  • sip:与软中断相关的寄存器

Locking

锁保证同一时间内只有一个CPU拥有锁,即同一时间只有一个核执行临界代码。

分两种情况考虑:

  1. 单核CPU,多线程
    只需保证在执行临界代码时不进行线程切换即可,但可以切换到不同进程的线程
  2. 多核CPU,多线程
    在1的基础上再加上,多个核心不能同时执行临界代码

系统中的锁

namedescription
bcache.lockProtects allocation of block buffer cache entries
cons.lockSerializes access to console hardware, avoids intermixed output
ftable.lockSerializes allocation of a struct file in file table
itable.lockProtects allocation of in-memory inode entries
vdisk_lockSerializes access to disk hardware and queue of DMA descriptors
kmem.lockSerializes allocation of memory
log.lockSerializes operations on the transaction log
pipe’s pi->lockSerializes operations on each pipe
pid_lockSerializes increments of next_pid
proc’s p->lockSerializes changes to process’s state
wait_lockHelps wait avoid lost wakeups
tickslockSerializes operations on the ticks counter
inode’s ip->lockSerializes operations on each inode and its content
buf’s b->lockSerializes operations on each block buffer

指令与内存序
为了提高性能,编译器可能不会按照代码书写的顺序编译成指令,CPU可能也不会按照指令顺序去执行。

__sync_synchronize()是内存屏障:告诉编译器和CPU禁止跨越屏障作指令重排序。

当acquire spinlock时不允许中断与yield
sleeplock, yield CPU和释放spinlock是原子操作???sleeplock允许中断,因此不能用在中断处理程序中,也不能用在spinlock的临界代码段

Pthreads(POSIX threads) has support for user-level locks, barriers, etc. Pthread also allows a programmer to optionally specify that a lock should be reentrant.

调度

多路复用的两种方式:1. sleep, wakeup机制,如等待设备或IO完成,等待子进程结束或sleep调用。2. 周期性的强迫进程发生切换。

每个核心都有自己一组寄存器,该组寄存器(struct context)被称为上下文。

当在swtch函数调用结束并非返回到sched函数而是scheduler函数。

每个物理核都有一个对应的全局变量struct cpu。

睡眠与唤醒机制
生产者与消费者模型机制

void V(struct semaphore *s)
{
    acquire(&s->lock);
    s->count += 1;
    wakeup(s);
    release(&s->lock);
}
void P(struct semaphore *s)
{
    acquire(&s->lock);
    while(s->count == 0)
    	sleep(s, &s->lock);  # 标记为SLEEPING态并放弃CPU
    s->count -= 1;
    release(&s->lock);
}

文件系统

设计文件系统面对的挑战

  • 文件系统需要磁盘上的数据结构来表示命名目录和文件的树,记录保存每个文件内容的块的标识,以及记录磁盘的哪些区域是空闲的
  • 文件系统需要支持crash recovery
  • 不同的进程可能会同时访问文件系统的文件,所以文件系统代码必须协调以维护不变量
  • 文件系统需要对高频访问数据进行内存缓存

a simple filesystem layer:
±----------------±----------------+
| layer name | description  |
±----------------±----------------+
| file descriptor |
| path name   |
| directory      | inode with special contents
| inode      | inode allocator, reading writing, metadata
| logging           | crash recovery
| buffer cache   | raw data
| disk                |
±----------------±----------------+

磁盘以sector为单位(一般为512Byte), block大小为sector的整数倍,sector有号码,block也有号码,根据sector号从硬盘中读写数据。

struct buf {
  int valid;   // 从硬盘中读完数据后赋为1
  // 读写数据前设为1,当硬盘完成操作后中断处理程序将其设为0
  int disk;    // does disk "own" buf? 
  uint dev;    // 设备号
  uint blockno;  // block 号码
  struct sleeplock lock;
  uint refcnt;
  struct buf *prev; // LRU cache list
  struct buf *next;
  uchar data[BSIZE];  // 从硬盘读写的数据,此为一个block
};

定义struct buf[NBUF]; 通过bread(dev, blockno)获取到一个buf指针,并带有读取的硬盘数据。
bp = bread(dev, 1);其中bp->data的部分数据即为superblock的内容

iNode层:

// On-disk inode structure
struct dinode {
  short type;           // File type
  short major;          // Major device number (T_DEVICE only)
  short minor;          // Minor device number (T_DEVICE only)
  short nlink;          // Number of links to inode in file system
  uint size;            // Size of file (bytes)
  uint addrs[NDIRECT+1];   // Data block addresses
};
// 内存中的iNode在itable中
// in-memory copy of an inode
struct inode {
  uint dev;           // Device number
  uint inum;          // Inode number
  int ref;            // Reference count, if ref == 0 release this
  struct sleeplock lock; // protects everything below here
  int valid;          // inode has been read from disk?

  short type;         // copy of disk inode
  short major;
  short minor;
  short nlink;
  uint size;
  uint addrs[NDIRECT+1];
};

Path:
内存文件系统的iNode=1
root目录的iNode=2

mount -t tmpfs -o size=32M tmpfs /mnt/tmpfs/
ls -id /mnt/tmpfs/
ls -id /

File Descriptor:

struct file结构体可表示一个文件、管道、设备等系统资源,某个系统资源的struct file结构体可同时出现在不同进程的文件表中,一个进程的文件表可出现多次,在全局文件表ftable中也可出现多次。
filealloc将文件的ref+1, fileclose将文件的ref-1,当ref==0时释放该结构体对应的系统资源,filestate获取inode信息,fileread、filewrite根据struct file的类型调用对应的函数读写数据

操作系统拥有的资源

  1. 各种类型的锁
  2. 调度器
  3. 进程表
  4. 文件表
  5. 运行时创建的系统资源,比如管道等

xv6系统启动过程

调试所需gdb命令:

b [file:]line/symbol
info b
d num
打断点
显示所有断点
删除某个断点
info registers显示所有寄存器的值
display $sp
undisplay sp
显示sp寄存器的值
不再显示
p var打印某个变量…
si下一条汇编执行令
n下一行c代码
s下一行c代码,进入函数体
finish结束函数

1. 准备C代码执行环境


qemu -kernal将内核装载到0x80000000处,然后让每个hart(cpu核)跳转到这里执行
在这里插入图片描述

将stack0放在16的整数倍地址上
__attribute__ ((aligned (16))) char stack0[4096 * NCPU];

栈大小为4096
sp = 0x80008a10, stack0 = 0x80008a10

为何stack0的地址为0x80008a10,是有谁设置的?由编译器按顺序排列得到的吗?如何保证_entry在内核的首条代码处?

cpu1:sp(now) -> 0x8000aa10
  stacksize = 4k
cpu0:sp(now) -> 0x80009a10
  stacksize = 4k
all core of cpu: sp(before) -> 0x80008a10

对每个CPU核心的栈初始化之后跳转到C函数void start();处执行
在这里插入图片描述
mepc = 0x80000E72 (main)

start函数对寄存器等硬件资源进行设置后跳转至void main()处执行

2. 进入main

tips: 似乎不能通过si的方式直接进入main需要打断点
cpuid为0的核执行内核代码,其它核进入while(started == 0); ,当cpu0执行完内核代码后将started设为1,所有核心进入scheduler()死循环中。

  1. 控制台初始化
    devsw[CONSOLE].read = consoleread;
    devsw[CONSOLE].write = consolewrite;
  2. printfinit
    初始化printf函数的锁,当调用printf时都会获取锁保证字符串线程安全的输出至终端,只有在panic时无锁输出
  3. kinit(); // physical page allocator
    分配物理页表,将每个页的每个字节都写入垃圾直1
  4. kvminit(); // create kernel page table
    创建内核页表。
    从之前分配好的物理内存页中获取一个4k大小的块(一页)
    第二个参数指向一个从第三个参数为起始的物理地址,从第二个参数开始为虚拟地址创建PTEs
    最后为每个程序都分配好内核栈(vx6预先设定好最多只能有64个进程)
  5. kvminithart(); // turn on paging
    开启虚拟内存功能
  6. procinit(); // process table
    初始化进程表
  7. trapinit(); // trap vectors
  8. trapinithart(); // install kernel trap vector
    初始化内核态异常处理代码w_stvec((uint64)kernelvec);
  9. plicinit(); // set up interrupt controller
    设置中断控制器
    (uint32)(PLIC + UART0_IRQ4) = 1;
    (uint32)(PLIC + VIRTIO0_IRQ
    4) = 1;
  10. plicinithart(); // ask PLIC for device interrupts
  11. binit(); // buffer cache
  12. 文件系统相关
    iinit(); // inode table
    fileinit(); // file table
    virtio_disk_init(); // emulated hard disk
  13. 启动第一个用户进程:
    从而进程表中有了一个有效的进程
    #exec(init, argv)
    .globl start
    start:
    la a0, init
    la a1, argv
    li a7, SYS_exec
    ecall
    #for( ; ; ) exit(); 😉
    exit:
    li a7, SYS_exit
    ecall
    jal exit
    init:
    .string “/init\0”
  14. 进入调度器
    调度器的swtch(&c->context, &p->context);函数不会直接返回到下一句,而是…

char *argv[] = { init, 0 };

.p2align 2
argv:
.long init
.long 0

???

riscv xv6系统中,trampoline和trapframe的虚拟地址在用户页表和内核页表中是相同的吗
trampoline是内核页表和用户页表相同的地址
trapframe不同???但地址固定

当产生计时器中断时会执行timervec:代码执行完后会去哪执行?

其它C语言知识

// 存放系统调用的指针
static uint64 (*syscalls[])(void) = {
[SYS_fork]    sys_fork,
[SYS_exit]    sys_exit,
[SYS_wait]    sys_wait,
[SYS_pipe]    sys_pipe,
[SYS_read]    sys_read,
[SYS_kill]    sys_kill,
[SYS_exec]    sys_exec,
[SYS_fstat]   sys_fstat,
[SYS_chdir]   sys_chdir,
[SYS_dup]     sys_dup,
[SYS_getpid]  sys_getpid,
[SYS_sbrk]    sys_sbrk,
[SYS_sleep]   sys_sleep,
[SYS_uptime]  sys_uptime,
[SYS_open]    sys_open,
[SYS_write]   sys_write,
[SYS_mknod]   sys_mknod,
[SYS_unlink]  sys_unlink,
[SYS_link]    sys_link,
[SYS_mkdir]   sys_mkdir,
[SYS_close]   sys_close,
};
// 或者
int arr[] = {
    [1] = sys_fork,
    [2] = sys_exit,
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/445698.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker完整版(一)

Docker完整版&#xff08;一&#xff09; 一、Docker概述1.1、Docker简介1.2、Docker的用途1.3、容器与虚拟机的区别1.4、Docker系统架构1.5、Docker仓库 二、Docker引擎2.1、Docker引擎架构2.2、Docker引擎分类2.3、Docker引擎的安装2.4、Docker镜像加速器 三、Docker镜像3.1、…

Android 完整SDK项目中添加对应的JNI与底层通信

安卓应用发消息给底层 近日需要写一个安卓app和底层发消息&#xff0c;这就涉及到java如何到c层的一个逻辑&#xff0c;app已经写好&#xff0c;就差发个消息了。至于如何对接底层&#xff0c;得和写底层的人进一步沟通&#xff0c;本文笔者只写从java层通信到cpp&#xff0c;…

视频远程监控平台EasyCVR集成后播放只有一帧画面的原因排查与解决

智慧安防视频监控平台EasyCVR能在复杂的网络环境中&#xff08;专网、局域网、广域网、VPN、公网等&#xff09;将前端海量的设备进行统一集中接入与视频汇聚管理&#xff0c;平台可支持的接入协议包括&#xff1a;国标GB28181、RTSP/Onvif、RTMP&#xff0c;以及厂家的私有协议…

C++ 字符串OJ

目录 1、14. 最长公共前缀 2、 5. 最长回文子串 3、 67. 二进制求和 4、43. 字符串相乘 1、14. 最长公共前缀 思路一&#xff1a;两两字符串进行比较&#xff0c;每次比较过程相同&#xff0c;可以添加一个函数辅助比较&#xff0c;查找最长公共前缀。 class Solution { pu…

Vue.set:Vue中的数据绑定利器

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【JAVA/Web】数组转对象

一. 需求 数组转对象 数组结构 List:[{id:1,code:phone,value:10101001},{id:2,code:name,value:admin},{id:3,code:address,value:XXXXXX} ]二. 数组转对象&#xff08;键值对映射关系&#xff09; 对象结构 object:{phone:10101001,name:admin,address:XXXXXX }2.1 Java…

Java Socket:飞鸽传书的网络套接字

套接字&#xff08;Socket&#xff09;是一个抽象层&#xff0c;应用程序可以通过它发送或接收数据&#xff1b;就像操作文件那样可以打开、读写和关闭。套接字允许应用程序将 I/O 应用于网络中&#xff0c;并与其他应用程序进行通信。网络套接字是 IP 地址与端口的组合。 01、…

VUE3 使用axios网络请求

1.新建工程 参考&#xff0c;VUE3 环境搭建&#xff1a;https://blog.csdn.net/LQ_001/article/details/136293795&#xff0c;运行命令 vue create vue-demo 2.引入axios 不管何种引用&#xff0c;都要在工程中安装 axios 包。安装命令&#xff1a;npm install --save axio…

linux paddle For C++环境搭建

paddle介绍 Paddle是类似tesseract的文字识别ocr。因为tesseract-ocr的中文识别效果不好。因此才准备安装Paddle。Paddle最方便的安装方式的使用Python的包管理安装。pip3 install paddlepaddle。但我使用了一下感觉还是用C更加方便&#xff0c;QT OpenCV Paddle应当还不错。…

Go语言必知必会100问题-19 浮点数溢出问题

问题呈现 在Go语言中&#xff0c;有两种浮点数类型&#xff08;虚数除外&#xff09;&#xff1a;float32和float64. 浮点数是用来解决整数不能表示小数的问题。我们需要知道浮点数算术运算是实数算术运算的近似&#xff0c;下面通过例子说明浮点数运算采用近似值的影响以及如…

⭐每天一道leetcode:83.删除排序链表中的重复元素(简单;链表遍历、删除经典题目)

⭐今日份题目 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 示例1 输入&#xff1a;head [1,1,2] 输出&#xff1a;[1,2] 示例2 输入&#xff1a;head [1,1,2,3,3] 输出&#xff1a;[1,2,3] …

【R语言爬虫实战】抓取省市级城市常务会议内容

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

enumerate函数的用法

enumerate() 函数是 Python 内置函数之一&#xff0c;用于同时返回可迭代对象的索引和对应的值。 它的语法结构如下&#xff1a; enumerate(iterable, start0) iterable: 表示一个可迭代的对象&#xff0c;如列表、元组、字符串等。start: 可选参数&#xff0c;表示索引起始…

02hadoop伪分布式搭建

3. 环境安装 3.1 安装方式 单机模式 只能启动MapReduce 伪分布式 能启动HDFS、MapReduce 和 YARN的大部分功能 完全分布式 能启动Hadoop的所有功能 3.2 安装JDK 3.2.1 JDK安装步骤 下载JDK安装包&#xff08;下载Linux系统的 .tar.gz 的安装包&#xff09; https://www…

网络协议常见问题

网络协议常见问题 OSI&#xff08;Open Systems Interconnection&#xff09;模型OSI 封装 TCP/IP协议栈IP数据报的报头TCP头格式UDP头格式TCP (3-way shake)三次握手建立连接&#xff1a;为什么三次握手才可以初始化 Socket、序列号和窗口大小并建立 TCP 连接。每次建立TCP连接…

蓝桥杯递推与递归法|斐波那契数列|数字三角形|42点问题|数的计算|数的划分(C++)

递归是用来做dfs&#xff0c;是搜索算法的基础 递推是用来做dp部分&#xff0c;及部分其他算法&#xff0c;复杂度较低&#xff0c;不会出现爆栈问题递推法&#xff1a; 递推法是一种在数学和其他领域广泛应用的重要方法&#xff0c;它在计算机科学中被用作一种关键的数值求解…

自动化运维利器Ansible基础(环境部署)

Ansible 介绍及安装 1. 介绍 Ansible 是⼀个 IT ⾃动化⼯具。它能配置系统、部署软件、编 排更复杂的 IT 任务&#xff0c;如连续部署或零停机时间滚动更新。 Ansible ⽤ Python 编写&#xff0c;尽管市⾯上已经有很多可供选择的 配置管理解决⽅案&#xff08;例如 Salt、Pupp…

OpenAI GPT LLMs 高级提示词工程方法汇总

原文地址&#xff1a;An Introduction to Prompt Engineering for OpenAI GPT LLMs Github&#xff1a;Prompt-Engineering-Intro 2023 年 3 月 2 日 提示工程指南 | Prompt Engineering Guide Naive 提示词&#xff1a;带有提示的情感分类器 prompt Decide whether a T…

复合查询【MySQL】

文章目录 复合查询测试表 单表查询多表查询子查询单行子查询多行子查询IN 关键字ALL 关键字ANY 关键字 多列子查询 合并查询 复合查询 测试表 雇员信息表中包含三张表&#xff0c;分别是员工表&#xff08;emp&#xff09;、部门表&#xff08;dept&#xff09;和工资等级表&…

GEE:基于ERA5数据集(U和V风速分量)计算风速的幅值和风向

作者:CSDN @ _养乐多_ 本文将介绍使用Google Earth Engine (GEE)平台提供的API加载ERA5月度数据集,该数据集包含了从1979年至今的全球月度气象数据。然后,定义了一个数据计算函数,用于将U和V风速分量转换为风速的幅值和风向。 结果如下图所示, 文章目录 一、核心函数1…