Skip to content
GitLab
Projects Groups Topics Snippets
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in
  • P project210-USTC
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributor statistics
    • Graph
    • Compare revisions
  • Issues 0
    • Issues 0
    • List
    • Boards
    • Service Desk
    • Milestones
  • Packages and registries
    • Packages and registries
    • Package Registry
    • Terraform modules
  • Monitor
    • Monitor
    • Incidents
  • Analytics
    • Analytics
    • Value stream
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Commits
  • Issue Boards
Collapse sidebar
  • 叫什么名字好呢队
  • project210-USTC
  • Wiki
  • Home
  • 3.中断

3.中断 · Changes

Page history
Update 3.中断 authored Aug 14, 2023 by lqzzy's avatar lqzzy
Hide whitespace changes
Inline Side-by-side
home/3.中断.md 0 → 100644
View page @ cdb18a04
# 陷入,中断和驱动程序
运行进程时,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太久。
Clone repository
  • home
    • 1.简单的OS宏观理解
    • 2.内存管理
    • 3.中断
    • 4.调度
    • 5.文件系统