• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

PA 操作系统的线程

武飞扬头像
zhuanwancaishi
帮助1

批处理系统

riscv 指令集提供了ecall指令,当cpu执行到这个ecall指令后,会跳转到mtvec这个寄存器指向的地址执行操作系统的代码,我们常说的系统调用就是被编译成ecall指令,riscv架构是这样的,不同指令集有不同的"ecall"指令。

内核线程与用户线程

记录一下南京大学ics实验课的PA4 第一阶段的内容,有一点自己浅薄的理解,忘记的时候可以回来来看看

关于线程

从微观的角度说,线程本质上就是一小段代码,可以被独立执行。我们知道代码的执行关键需要栈(内存的一部分)和寄存器(在CPU上),一个个函数等于就是把这么多代码分开了,每一小片代码由一个函数名区分。在线程的运行过程中,数据都在栈和寄存器上,一个线程可以由寄存器的状态来标识。那么怎么切换线程呢,很简单,就是把栈和寄存器上的数据"冻结"起来,这个操作我们有一个熟悉的名字,叫做上下文切换,这里的上下文指的是栈和寄存器。然后换成其他线程的,再执行其他线程的代码。怎么冻结起来?
当前线程 栈上 的数据好说,只要把栈指针指向内存中的其他地方就行了,这样新的线程就会使用这片新的内存区域,而不会影响之前线程栈上的数据了,那么CPU上的寄存器怎么冻结?,也很简单!就是把寄存器的值也压在栈上,这样就完美地保存了之前线程栈上与寄存器的数据。
所以当某个线程的执行被打断时,比如线程自己调用yield()进行自陷,马上跳转操作系统中用于切换保存上下文的代码中,这段代码的地址可以是提前约定好的。在南大的实验中,这段代码如下,函数名为__am_asm_trap()

#define concat_temp(x, y) x ## y
#define concat(x, y) concat_temp(x, y)
#define MAP(c, f) c(f)

#if __riscv_xlen == 32
#define LOAD  lw
#define STORE sw
#define XLEN  4
#else
#define LOAD  ld
#define STORE sd
#define XLEN  8
#endif

#define REGS(f) \
      f( 1)       f( 3) f( 4) f( 5) f( 6) f( 7) f( 8) f( 9) \
f(10) f(11) f(12) f(13) f(14) f(15) f(16) f(17) f(18) f(19) \
f(20) f(21) f(22) f(23) f(24) f(25) f(26) f(27) f(28) f(29) \
f(30) f(31)

#define PUSH(n) STORE concat(x, n), (n * XLEN)(sp);
#define POP(n)  LOAD  concat(x, n), (n * XLEN)(sp);

#define CONTEXT_SIZE  ((32   3   1) * XLEN)
#define OFFSET_SP     ( 2 * XLEN)
#define OFFSET_CAUSE  (32 * XLEN)
#define OFFSET_STATUS (33 * XLEN)
#define OFFSET_EPC    (34 * XLEN)

.align 3
.globl __am_asm_trap
__am_asm_trap:

#==========push the reigster to stack ================
  addi sp, sp, -CONTEXT_SIZE

  MAP(REGS, PUSH)

  csrr t0, mcause
  csrr t1, mstatus
  csrr t2, mepc

  STORE t0, OFFSET_CAUSE(sp)
  STORE t1, OFFSET_STATUS(sp)
  STORE t2, OFFSET_EPC(sp)

  # set mstatus.MPRV to pass difftest
  li a0, (1 << 17)
  or t1, t1, a0
  csrw mstatus, t1
#=======================================

#======== a0 is the parameter of __am_irq_handle ==========
  mv a0, sp  
#==========================================================


#========= save and switch the context =========
  jal __am_irq_handle
#===============================================

#======== a0 is the return value of __am_irq_handle and ====
#======  is also the another thread stack pointer
  mv sp, a0 
#===========================================================


#=========== load another thread's context ===========
  LOAD t1, OFFSET_STATUS(sp)
  LOAD t2, OFFSET_EPC(sp)
  csrw mstatus, t1
  csrw mepc, t2

  MAP(REGS, POP)

  addi sp, sp, CONTEXT_SIZE
  mret
#======================================================
学新通

用户线程和内核线程其实没有什么本质的区别,只不过他们的栈和代码段位于内存不同的地方而已。在南大的这个实验中,用PCB来描述一个用户线程或者内核线程。这个Context 指针其实就是栈顶指针。

#define STACK_SIZE (8 * PGSIZE)

typedef union {
  uint8_t stack[STACK_SIZE] PG_ALIGN;
  struct {
    Context *cp;
    AddrSpace as;
    // we do not free memory, so use `max_brk' to determine when to call _map()
    uintptr_t max_brk;
  };
} PCB;

PCB的结构如下图
学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhggjekf
系列文章
更多 icon
同类精品
更多 icon
继续加载