|
|
# 陷入,中断和驱动程序
|
|
|
|
|
|
运行进程时,cpu 一直处于一个大循环中:取指,更新 PC,执行,取指……。但有些情况下用户程序需要进入内核,而不是
|
|
|
执行下一条用户指令。一种情况是系统调用,当用户程序执行ecall指令要求内核为其做某事时。另一种情况是异常:一条指令(用户或内核)做了一些非法的事情,如除以零或使用无效的虚拟地址。第三种情况是设备中断,当一个设备发出需要注意的信号时,例如当磁盘硬件完成一个读写请求时。
|
|
|
|
|
|
我们使用trap作为这些情况的通用术语。
|
|
|
|
|
|
## Traps from user space
|
|
|
|
|
|
在用户空间执行时,如果用户程序进行了系统调用(ecall指令),或者做了一些非法的事情,或者设备中断,都可能发生trap。来自用户空间的trap的处理路径是uservec(kernel/uservec.S:16),然后是usertrap(kernel/trap.c:37);返回时是usertrapret(kernel/trap.c:90),然后是userret(kernel/trampoline.S:16)。
|
|
|
|
|
|
|
|
|
首先是uservec,这是一个汇编程序
|
|
|
```asm
|
|
|
uservec:
|
|
|
csrrd $t0, LOONGARCH_CSR_SAVE0
|
|
|
#
|
|
|
# trap.c sets eentry to point here, so
|
|
|
# traps from user space start here,
|
|
|
# in privilege0, but with a
|
|
|
# user page table.
|
|
|
#
|
|
|
# CSR.SAVE points to where the process's p->trapframe is
|
|
|
# mapped into user space, at TRAPFRAME.
|
|
|
#
|
|
|
|
|
|
# swap a0 and SAVE1
|
|
|
# so that a0 is TRAPFRAME
|
|
|
csrwr $a0, LOONGARCH_CSR_SAVE1
|
|
|
|
|
|
# save the user registers in TRAPFRAME
|
|
|
st.d $ra, $a0, 0
|
|
|
st.d $tp, $a0, 8
|
|
|
st.d $sp, $a0, 16
|
|
|
st.d $a1, $a0, 32
|
|
|
st.d $a2, $a0, 40
|
|
|
st.d $a3, $a0, 48
|
|
|
st.d $a4, $a0, 56
|
|
|
st.d $a5, $a0, 64
|
|
|
st.d $a6, $a0, 72
|
|
|
st.d $a7, $a0, 80
|
|
|
st.d $t0, $a0, 88
|
|
|
st.d $t1, $a0, 96
|
|
|
st.d $t2, $a0, 104
|
|
|
st.d $t3, $a0, 112
|
|
|
st.d $t4, $a0, 120
|
|
|
st.d $t5, $a0, 128
|
|
|
st.d $t6, $a0, 136
|
|
|
st.d $t7, $a0, 144
|
|
|
st.d $t8, $a0, 152
|
|
|
st.d $r21, $a0,160
|
|
|
st.d $fp, $a0, 168
|
|
|
st.d $s0, $a0, 176
|
|
|
st.d $s1, $a0, 184
|
|
|
st.d $s2, $a0, 192
|
|
|
st.d $s3, $a0, 200
|
|
|
st.d $s4, $a0, 208
|
|
|
st.d $s5, $a0, 216
|
|
|
st.d $s6, $a0, 224
|
|
|
st.d $s7, $a0, 232
|
|
|
st.d $s8, $a0, 240
|
|
|
|
|
|
# save the user a0 in p->trapframe->a0
|
|
|
csrrd $t0, LOONGARCH_CSR_SAVE1
|
|
|
st.d $t0, $a0, 24
|
|
|
|
|
|
# restore kernel stack pointer from p->trapframe->kernel_sp
|
|
|
ld.d $sp, $a0, 248
|
|
|
|
|
|
# make tp hold the current hartid, from p->trapframe->kernel_hartid
|
|
|
ld.d $tp, $a0, 264
|
|
|
|
|
|
# restore kernel page table from p->trapframe->kernel_pgdl
|
|
|
ld.d $t1, $a0, 272
|
|
|
csrwr $t1, LOONGARCH_CSR_PGDL
|
|
|
invtlb 0x0,$zero,$zero
|
|
|
|
|
|
# jump to usertrap(), which does not return
|
|
|
b usertrap
|
|
|
```
|
|
|
实现了一系列的上下文切换操作,将用户态的寄存器状态保存到一个特定的数据结构(通常称为 trap frame)中,然后切换到内核态。
|
|
|
|
|
|
```c
|
|
|
//
|
|
|
// handle an interrupt, exception, or system call from user space.
|
|
|
// called from trampoline.S
|
|
|
//
|
|
|
void
|
|
|
usertrap(void)
|
|
|
{
|
|
|
int which_dev = 0;
|
|
|
|
|
|
if((r_csr_prmd() & PRMD_PPLV) == 0)
|
|
|
panic("usertrap: not from user mode");
|
|
|
|
|
|
struct proc *p = myproc();
|
|
|
|
|
|
// save user program counter.
|
|
|
p->trapframe->era = r_csr_era();
|
|
|
|
|
|
if(((r_csr_estat()& CSR_ESTAT_ECODE) >> 16) == 0xb){
|
|
|
// system call
|
|
|
|
|
|
if(p->killed)
|
|
|
exit(-1);
|
|
|
|
|
|
// sepc points to the ecall instruction,
|
|
|
// but we want to return to the next instruction.
|
|
|
p->trapframe->era += 4;
|
|
|
|
|
|
// an interrupt will change sstatus &c registers,
|
|
|
// so don't enable until done with those registers.
|
|
|
intr_on();
|
|
|
|
|
|
syscall();
|
|
|
} else if((which_dev = devintr()) != 0){
|
|
|
// ok
|
|
|
}
|
|
|
else {
|
|
|
printf("usertrap(): unexpected trapcause %p pid=%d\n", r_csr_estat(), p->pid);
|
|
|
printf(" era=%p badi=%p\n", r_csr_era(), r_csr_badi());
|
|
|
p->killed = 1;
|
|
|
}
|
|
|
|
|
|
if(p->killed)
|
|
|
exit(-1);
|
|
|
|
|
|
// give up the CPU if this is a timer interrupt.
|
|
|
if(which_dev == 2)
|
|
|
{
|
|
|
myproc()->user_time++;
|
|
|
yield();
|
|
|
}
|
|
|
|
|
|
usertrapret();
|
|
|
}
|
|
|
```
|
|
|
|
|
|
从 `usertrap` 的代码可以看出,总共有两种不同的用户中断(对应if 和 else if)
|
|
|
第一个是syscall,第二个是设备中断。
|
|
|
|
|
|
我们先来看syscall
|
|
|
|
|
|
### syscall
|
|
|
|
|
|
syscall的处理函数是 `syscall` ,它的实现在 `kernel/syscall.c` 中
|
|
|
```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_unlinkat] sys_unlinkat,
|
|
|
[SYS_link] sys_link,
|
|
|
// [SYS_mkdir] sys_mkdir,
|
|
|
[SYS_mkdirat] sys_mkdirat,
|
|
|
[SYS_close] sys_close,
|
|
|
[SYS_openat] sys_openat,
|
|
|
[SYS_uname] sys_uname,
|
|
|
[SYS_getppid] sys_getppid,
|
|
|
[SYS_brk] sys_brk,
|
|
|
[SYS_clone] sys_clone,
|
|
|
[SYS_execve] sys_execve,
|
|
|
[SYS_gettimeofday] sys_gettimeofday,
|
|
|
[SYS_dup3] sys_dup3,
|
|
|
[SYS_times] sys_times,
|
|
|
[SYS_wait4] sys_wait4,
|
|
|
[SYS_sched_yield] sys_sched_yield,
|
|
|
[SYS_nanosleep] sys_nanosleep,
|
|
|
[SYS_getdents] sys_getdents,
|
|
|
[SYS_getcwd] sys_getcwd,
|
|
|
// [SYS_mknod] sys_mknod,
|
|
|
[SYS_mmap] sys_mmap,
|
|
|
[SYS_munmap] sys_munmap,
|
|
|
[SYS_mount] sys_mount,
|
|
|
[SYS_umount] sys_umount,
|
|
|
};
|
|
|
void
|
|
|
syscall(void)
|
|
|
{
|
|
|
int num;
|
|
|
struct proc *p = myproc();
|
|
|
|
|
|
num = p->trapframe->a7;
|
|
|
// int a2 = p->trapframe->a2;
|
|
|
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
|
|
|
// printf("\na2: %d\n",a2);
|
|
|
// printf("\nsyscall: %d, proc name: %s, proc stat: %d\n", num, p->name, p->state);
|
|
|
p->trapframe->a0 = syscalls[num]();
|
|
|
} else {
|
|
|
printf("%d %s: unknown sys call %d\n",
|
|
|
p->pid, p->name, num);
|
|
|
p->trapframe->a0 = -1;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
这里 `syscalls` 为函数指针数组,数组的下标对应系统调用号,数组的值对应系统调用的处理函数。当用户程序执行ecall指令时,会将系统调用号保存到寄存器a7中,然后调用 `syscall` 函数,该函数会根据系统调用号调用相应的处理函数(如sys_exec等)。
|
|
|
|
|
|
这就是系统调用的过程。
|
|
|
|
|
|
### 设备中断
|
|
|
|
|
|
设备中断的处理函数是 `devintr` ,它的实现在 `kernel/trap.c` 中
|
|
|
```c
|
|
|
int
|
|
|
devintr()
|
|
|
{
|
|
|
uint32 estat = r_csr_estat();
|
|
|
uint32 ecfg = r_csr_ecfg();
|
|
|
|
|
|
|
|
|
if(estat & HWI_VEC & ecfg){
|
|
|
// this is a supervisor external interrupt, via PLIC.
|
|
|
// irq indicates which device interrupted.
|
|
|
uint64 irq = extioi_claim();
|
|
|
if(irq & (1UL << UART0_IRQ)){
|
|
|
uartintr();
|
|
|
|
|
|
// tell the apic the device is
|
|
|
// now allowed to interrupt again.
|
|
|
|
|
|
extioi_complete(1UL << UART0_IRQ);
|
|
|
} else if(irq){
|
|
|
printf("unexpected interrupt irq=%d\n", irq);
|
|
|
|
|
|
apic_complete(irq);
|
|
|
extioi_complete(irq);
|
|
|
}
|
|
|
return 1;
|
|
|
} else if(estat & ecfg & TI_VEC){
|
|
|
// software interrupt from a machine-mode timer interrupt,
|
|
|
// forwarded by timervec in kernelvec.S.
|
|
|
proc_tick();
|
|
|
if(cpuid() == 0){
|
|
|
clockintr();
|
|
|
}
|
|
|
w_csr_ticlr(r_csr_ticlr() | CSR_TICLR_CLR);
|
|
|
return 2;
|
|
|
} else {
|
|
|
return 0;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
设备中断有时钟中断还有来自uart的中断,我们先来看uart的handler函数
|
|
|
|
|
|
```c
|
|
|
// handle a uart interrupt, raised because input has
|
|
|
// arrived, or the uart is ready for more output, or
|
|
|
// both. called from trap.c.
|
|
|
void
|
|
|
uartintr(void)
|
|
|
{
|
|
|
// read and process incoming characters.
|
|
|
while(1){
|
|
|
int c = uartgetc();
|
|
|
if(c == -1)
|
|
|
break;
|
|
|
consoleintr(c);
|
|
|
}
|
|
|
|
|
|
// send buffered characters.
|
|
|
acquire(&uart_tx_lock);
|
|
|
uartstart();
|
|
|
release(&uart_tx_lock);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
这个函数的效果是把输入在console里的字符重新回显在console里。方便我们输入。
|
|
|
|
|
|
我们再看时钟中断
|
|
|
|
|
|
```c
|
|
|
void
|
|
|
clockintr()
|
|
|
{
|
|
|
acquire(&tickslock);
|
|
|
ticks++;
|
|
|
wakeup(&ticks);
|
|
|
release(&tickslock);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
每一次时钟中断,都把ticks加一,然后唤醒所有等待ticks的进程。以防止一个
|
|
|
进程占用cpu太久。 |