进程表在哪

进程切换的过程

spinlock

为什么spinlock中可以直接使用了push_off和pop_off来关闭/开启中断,而不需要进入内核?
spinlock中使用了push_off和pop_off来关闭/开启中断,这个中断包括外部中断和定时中断吗?
如果一个用户进程acquire陷入死循环,那么中断是否永远关闭?
如果一个用户进程acquire后还没有release,而另一个进程release锁,并且这两个进程的锁不是同一个,那么中断是否开启了?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void
push_off(void)
{
int old = intr_get(); // 获取是否开启中断
// noff: num of interrupts, intena: interrupts enable
intr_off(); // 关闭中断
if(mycpu()->noff == 0) // 如果是第一次关闭中断
mycpu()->intena = old; // 记录上一次是否开启中断
mycpu()->noff += 1; // 中断次数加一
}

void
pop_off(void)
{
struct cpu *c = mycpu(); // 获取CPU
if(intr_get()) // 是否开启中断
panic("pop_off - interruptible");
if(c->noff < 1) // 中断次数是否≤0,意味着没有中断
panic("pop_off");
c->noff -= 1; // 中断次数减一
if(c->noff == 0 && c->intena) // 如果中断次数变为0并且之前已经开启过中断
intr_on(); // 开启中断
}

#define SSTATUS_SIE (1L << 1) // Supervisor Interrupt Enable

static inline uint64
r_sstatus()
{
uint64 x;
asm volatile("csrr %0, sstatus" : "=r" (x) );
return x;
}

static inline void
w_mstatus(uint64 x)
{
asm volatile("csrw mstatus, %0" : : "r" (x));
}

// enable device interrupts
static inline void
intr_on()
{
w_sstatus(r_sstatus() | SSTATUS_SIE);
}

// disable device interrupts
static inline void
intr_off()
{
w_sstatus(r_sstatus() & ~SSTATUS_SIE);
}

在中断使能方面,MIE 、SIE 、UIE 分别提供了 machine mode 、supervisor mode 、user mode 的全局中断使能位;
User级中运行用户程序,Supervisor级中运行操作系统内核(和设备驱动),Machine级中运行BootLoader和其它固件。

  • machine mode 拥有所有的特权,一般在启动时候用于配置电脑的环境
  • supervisor mode 的权限相对低些,可以执行特权指令,例如是否使能中断等
  • user mode 的权限级别最低,完成一些特殊功能的时候需要通过系统调用进入 supervisor mode
  • 在 RISC-V 中,通过寄存器 sstatus 中保存的 SPP 位来判断是处于内核态 (1) 还是用户态 (0)
  • 中断分为外中断和内中断,外中断包括 I/O 中断、时钟中断等,内中断包括异常、系统调用和中止。

  • http://rcore-os.cn/rCore-Tutorial-deploy/docs/lab-1/guide/part-2.html

  • https://dingfen.github.io/risc-v/2020/08/05/riscv-privileged.html

kalloc

在kinit中freerange中end的地址是多少?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define KERNBASE 0x80000000L
#define PHYSTOP (KERNBASE + 128*1024*1024)

#define PGSIZE 4096 // bytes per page
#define PGROUNDUP(sz) (((sz)+PGSIZE-1) & ~(PGSIZE-1))

extern char end[]; // first address after kernel.
// defined by kernel.ld.

void
kinit()
{
initlock(&kmem.lock, "kmem");
freerange(end, (void*)PHYSTOP);
}

void
freerange(void *pa_start, void *pa_end)
{
char *p;
p = (char*)PGROUNDUP((uint64)pa_start);
for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)
kfree(p);
}

每个CPU都有自己的定时中断吗?

trap是怎么跳转到trampoline中的usertrap()的?

riscv为处理trap提供了sstatuts、scause、stvec、sepc、sscratch等寄存器,这些寄存器都会被中断隐指令所使用到:
(1)stvec寄存器存放着trap wrapper的地址。当trap发生时,stvec的值将被读入到pc中
(2)scause记录这个trap的发生原因。当trap发生时,这个寄存器被设置
(3)sepc保存pc的值。在PC的值被更新为stvec之前,PC值被保存在这个寄存器中
(4)sstatuts的SPP位记录着当前CPU所处的mode。
(5)sscratch记录着这个进程的trapframe地址。保存现场时,需要将寄存器保存在trapframe上。
总而言之,当trap发生时,首先会“执行中断隐指令”,完成关中断和设定trap相关寄存器等操作,而保护现场等操作,是由相应的指令执行的。

在内核初始化的时候,会设置stvec寄存器,因此将中断处理入口设置成了kernelvec。

1
2
3
4
5
void
trapinithart(void)
{
w_stvec((uint64)kernelvec);
}

在内核态的时候是怎么trap的?

kernelvec、kerneltrap

trampoline是什么

trampoline是跳板代码,包含以下这两段汇编:
1.uservec的作用是保存现在进程的栈帧,恢复kernel的tp、satp,切换到usertrap();usertrap()中通过yield()切换到调度器线程。
2.userret的作用是恢复用户的satp,恢复进程的栈帧,切换到usertrapret()。

为什么调度器是一个线程,而不是一个进程

首先要了解线程切换和进程切换要保存和恢复什么,
对于线程来说只需要保存栈帧(寄存器现场)就行,
对于进程来说要保存PCB(PCB中包含栈帧、打开的文件描述符等)、需要重新映射虚拟地址空间、进出OS内核、寄存器切换,
因此进程切换的代价要高于线性切换的代价。
在xv6中,通过保存和恢复栈帧来切换到调度器,并且调度器没有对应的PCB,所有调度器是一个线程。

在 usertrap函数中 p->trapframe->epc += 4;

epc = epc + 4,epc下一条指令是什么?是不是ecall的下一条指令?
syscall,ecall的下一条指令是ret

trap page是什么?有什么?

为什么fork父进程返回子进程的PID而子进程返回0?

对于父进程来说,fork返回子进程的pid,而子进程没有拷贝a0,而是把a0设成0,所以对于子进程来说返回0.

proc.c里面有exit()(sys_exit调用exit),提供给用户的系统调用接口也是exit,不会冲突吗?

同样的sys_wait调用wait,而提供给用户的系统调用接口也是wait,不会冲突吗?
同样的sys_kill调用kill,而提供给用户的系统调用接口也是kill,不会冲突吗?

文件描述符是不是proc结构体里面ofile(open file array)的下标?

是的,ofile[i]指向file struct,如果file struct的类型是fd_inode,那么file struct中的ip指向inde。
内核中有一个大小为100的ftable数组。

exec执行成功会不会有return?如果没有,那么是什么原因?

sys_exec和exec函数。