linux-0.00很简单,两个进程task0,task1,task0打印A,task1打印B,在时钟中断中调度进程运行。
据说后来linus丢失了这个最初的版本,这里是赵炯博士写的linux-0.00版本。
我把源代码上传到了csdn的资源里面,在这里下载,可以用bochs模拟,赶紧试试吧。
简单说一下,就两个文件,boot.s和head.s。
head.s被编译成system模块
boot.s编译出的MBR用于将编译出的system模块从软盘中读到内存0x10000处,然后跳到0x10000执行。
head.s编译出的system模块设置好task0的tss0和ldt0,以及task1的tss1和ldt1后,初始化idt和gdt,最后在idt中填入两个中断门描述符:时钟中断timer_interrupt和系统调用中断system_interrupt。
然后构造出task0的人工堆栈环境,使用iret指令跳到task0出执行。
多任务实现的关键代码在时钟中断里,如下
/* Timer interrupt handler */ .align 2 timer_interrupt: push %ds pushl %edx pushl %ecx pushl %ebx pushl %eax movl $0x10, %eax #数据段描述符 mov %ax, %ds movb $0x20, %al #向8253发送EOI outb %al, $0x20 movl $1, %eax cmpl %eax, current #current == x 表示当前运行的是taskx (task0/task1) je 1f movl %eax, current #从task0切到task1 ljmp $TSS1_SEL, $0 #执行这句完之后,就会跳到task1代码处执行,task0的tss0的eip处将被填入下一句代码"jmp 2f"的地址,等到切换到task0的时候,从jmp 2f处开始执行 jmp 2f 1: movl $0, current #从task1切到task0 ljmp $TSS0_SEL, $0 #同上,task1的tss1中的eip将会存入"popl %eax"的地址 2: popl %eax popl %ebx popl %ecx popl %edx pop %ds iret
注意跳转的这两句ljmp。让我们来模拟一下:
0. 首先执行的是task0。
1. task0运行过程中发生时钟中断,根据逻辑
ljmp $TSS1_SEL, $0跳入到task1的代码中去,此时会把下一条语句即jmp 2f的地址存入到task0的tss中的eip条目中。
2. 从task1的tss1中恢复各个寄存器,使task1运行。
3. task1运行过程中发生时钟中断,根据逻辑
ljmp $TSS0_SEL, $0跳到task0的代码中去,此时会把下一条语句即popl %eax存入到task1的tss中的eip条目中。
4. cpu根据task0的tss中的eip条目恢复task0,即task0从jmp 2f往下执行,最后执行iret返回到task0的代码中。
5. task0运行过程中再次发生时钟中断,这时候恢复task1的寄存器上下文,从popl %eax出继续往下执行。
6 ....
这样就形成了task0和task1的多任务运行,对于task0来说,它完全感觉不到task1的存在,每次它的寄存器上下文都能被完整的恢复,对于task1来说也一样。
时钟中断这个过程是可以重入的,可以把这个过程理解成task0和task1共用(代码共用)的一个私有(执行流私有)过程。