1. 中断控制器
1.1 基础接口
中断控制器PLIC需要封装出标准接口使用:
void os_int_init(void); void os_int_en(unsigned int vector, int en); unsigned int os_vector_get(void); void os_int_done(unsigned int vector);
- os_int_init:中断控制器初始化。
- os_int_en:使能或关闭某个中断向量中断。
- os_vector_get:获取当前中断号。
- os_int_done:中断处理结束。
具体的代码复用之前全志D1 BSP开发教程中的代码,基本没有什么变化。
1.2 特殊初始化
C906核的PLIC有一个特殊的设置,就是是否允许在S模式下访问PLIC寄存器,这需要在M模式中去使能,所以需要在系统进入M模式后使能这个功能:
// 使能S模式下对plic寄存器的访问,此接口只能在M模式下调用 void rv_plic_smode_access_en(void) { write32(1, PLIC_BASE + PLIC_CTRL_REG); }
2. 定时器
2.1 基础接口
需要使用定时器来作为tick源,主要是封装两个接口:
void os_timer_start(unsigned int tick); void os_timer_int_clear(void);
- os_timer_start:根据传入的系统tick数初始化定时器,并启动定时器
- os_timer_int_clear:清除定时器中断状态
代码同样复用D1 BSP中的,基本没做修改。
2.2 S模式中断入口处理修改
主要修改就是添加当前进程上下文的保存和恢复,保存的过程基本和进程上下文切换时保存步骤一样,不同的就是内核栈sepc位置在中断时保存的就是sepc寄存器中的值而不是进程上下文切换时保存ra值:
# S模式中断和异常入口. .globl rv_supervisor_trap_entry .align 4 rv_supervisor_trap_entry: # 保存寄存器上下文 addi sp, sp, -256 sd ra, 0(sp) sd sp, 8(sp) sd gp, 16(sp) sd tp, 24(sp) sd t0, 32(sp) sd t1, 40(sp) sd t2, 48(sp) sd s0, 56(sp) sd s1, 64(sp) sd a0, 72(sp) sd a1, 80(sp) sd a2, 88(sp) sd a3, 96(sp) sd a4, 104(sp) sd a5, 112(sp) sd a6, 120(sp) sd a7, 128(sp) sd s2, 136(sp) sd s3, 144(sp) sd s4, 152(sp) sd s5, 160(sp) sd s6, 168(sp) sd s7, 176(sp) sd s8, 184(sp) sd s9, 192(sp) sd s10, 200(sp) sd s11, 208(sp) sd t3, 216(sp) sd t4, 224(sp) sd t5, 232(sp) sd t6, 240(sp) # 保存中断返回地址 csrr a0, sepc sd a0, 248(sp) # 记录当前进程内核栈地址到进程的kstack成员中 la a0, proc_current # a0 = &proc_current ld a1, 0(a0) # a1 = proc_current sd sp, 0(a1) # proc_current->kstack = sp; # 调用C入口 call rv_supervisor_trap # 恢复寄存器上下文 ld ra, 0(sp) ld sp, 8(sp) ld gp, 16(sp) ld tp, 24(sp) ld t0, 32(sp) ld t1, 40(sp) ld t2, 48(sp) ld s0, 56(sp) ld s1, 64(sp) ld a0, 72(sp) ld a1, 80(sp) ld a2, 88(sp) ld a3, 96(sp) ld a4, 104(sp) ld a5, 112(sp) ld a6, 120(sp) ld a7, 128(sp) ld s2, 136(sp) ld s3, 144(sp) ld s4, 152(sp) ld s5, 160(sp) ld s6, 168(sp) ld s7, 176(sp) ld s8, 184(sp) ld s9, 192(sp) ld s10, 200(sp) ld s11, 208(sp) ld t3, 216(sp) ld t4, 224(sp) ld t5, 232(sp) ld t6, 240(sp) addi sp, sp, 256 sret
2.3 tick中断处理
中断trap处理中添加对是否是tick中断的判断,如果是则调用tick中断处理函数,并在中断处理结束后尝试调度:
if (is_int) { unsigned int vector; vector = os_vector_get(); if (vector == 75) { // 系统tick处理 os_timer_int_clear(); #ifdef KERN_TRAP_DEBUG pr_debug("tick interrupt.\n"); #endif os_proc_tick_handle(); } else { pr_debug("strap Interrupt : %s, vector %d.\n", interrupt_cause[scode], vector); } os_int_done(vector); // 尝试中断中调度 os_sched_int(); }
tick中断处理目前只是很简单的将当前进程的时间片减少:
// 系统tick中断处理,必须在中断关闭时调用 void os_proc_tick_handle(void) { struct proc_block *p = proc_current; if ((p->state == PROC_RUNNING) && p->tick) { p->tick--; } }
os_sched_int的逻辑基本和os_sched差不多,只不过切换时使用的是中断上下文切换接口:
// 尝试进程调度,在中断中掉用,中断必须关闭 void os_sched_int(void) { proc_ready = proc_find_new(); // 新进程不是当前进程就执行上下文切换 if (proc_ready != proc_current) { os_proc_int_ctx_sw(); } }
最后每个进程创建时默认的时间片目前设置为20:
static struct proc_block* proc_alloc(void) { struct proc_block *p; unsigned long flag; // 尝试寻找空闲的进程控制块 flag = os_int_disable(); for(p = proc_table; p < &proc_table[CONFIG_SYS_PROCESS_NUM]; p++) { if(p->state == PROC_UNUSED) goto found; } os_int_enable(flag); // 未找到空闲的进程控制块 return 0; // 找到空闲进程控制块后进行初步初始化 found: p->pid = nextpid++; p->state = PROC_USED; p->tick = PROC_TICK_DEFAULT; memset(p->kstack, 0, PAGE_SIZE); os_int_enable(flag); return p; }
3. 测试
在sinit函数中设置tick为1000并使能定时器中断:
os_int_init(); os_timer_start(1000); os_int_en(75, 1);
运行后会发现每个进程都会在打印时被打断并调度下一个进程执行:
2022年6月27日 22:41 1F
可以再加个清除中断接口