在我们的操作系统中,已经存在的3个进程是运行在ring1上的,它们已经不能任意地使用某些指令,不能访问某些权限更高的内存区域,但如果一项任务需要这些使用指令或者内存区域时,只能通过系统调用来实现,它是应用程序和操作系统之间的桥梁。
所以,一件事情就可能是应用程序做一部分,操作系统做一部分。这样,问题就又涉及特权级变换。
很明显,这已经难不倒我们了,因为进程的切换就是不停地在重复这么一个特权级变换的过程。在那里,触发变换的是外部中断,我们把这个诱因换一下就可以了,变成"int nnn",一切就都解决了。
我们来实现一个叫做int get_ticks()的函数,用这个函数来得到当前总共发生了多少次时钟中断。设置一个全局变量ticks,每发生一次时钟中断,它就加1,进程可以随时通过get_ticks()这个系统调用来得到这个值。
见syscall.asm:
_NR_get_ticks equ 0 ; 要跟 global.c 中 sys_call_table 的定义相对应! INT_VECTOR_SYS_CALL equ 0x90 global get_ticks ; 导出符号 bits 32 [section .text] get_ticks: mov eax, _NR_get_ticks int INT_VECTOR_SYS_CALL ret
这里将系统调用对应的中断号设为0x90,它只要不和原来的中断号重复即可。
马上来定义INT_VECTOR_SYS_CALL对应的中断门,protect.c:
PUBLIC void init_prot() { ... init_idt_desc(INT_VECTOR_SYS_CALL, DA_386IGate, sys_call, PRIVILEGE_USER); ... }
这样我们就将第INT_VECTOR_SYS_CALL号中断与sys_call对应起来了。这里的call [sys_call_table+eax*4](调用的是sys_call_table[eax]),与irq_table类似,sys_call_table是一个函数指针数组,每一个成员都指向一个函数,用以处理相应的系统调用。
extern sys_call_table global sys_call sys_call: call save sti call [sys_call_table + eax * 4] mov [esi + EAXREG - P_STACKBASE], eax cli ret
前面eax已被赋值为_NR_get_ticks(即0),而sys_call_table[0]已经初始化为sys_get_ticks,所以call [sys_call_table+eax*4]这一句调用的便是sys_get_ticks。
mov [esi+EAXREG-P_STACKBASE],eax是把函数sys_call_table[eax]的返回值放在进程表中eax的位置,以便进程P被恢复执行时eax中装的是正确的返回值。
现在可以在进程中添加调用get_ticks的代码了:
void TestA() { int i = 0; while (1) { get_ticks(); disp_str("A"); disp_int(i++); disp_str("."); delay(1); } }
别忘了在kernel.asm和syscall.asm中导入和导出相应符号。
修改
void TestA() { int i = 0; while (1) { disp_str("A"); disp_int(get_ticks()); disp_str("."); delay(1); } }
运行如下,第一个是A0x0,第二个是A0x27,两次之间的“#”恰好是39个,我们的get_ticks一切正常:
【源码】