基础知识:
一个任务的所有信息都存放在任务状态段中,任务状态段与相应的段描述符相关。任务段描述符只能存放在GDT中,TR寄存器16位可见部分存放TSS段选择符。
在任务切换过程中,首先,处理器中各寄存器的当前值被自动保存到TR所指定的TSS中;然后,下一任务的TSS的选择子被装入TR;最后,从TR所指定的TSS中取出各寄存器的值送到处理器的各寄存器中。由此可见,通过在TSS中保存任务现场各寄存器状态的完整映象,实现任务的切换。
在System_call.s中:
reschedule:
pushl $ret_from_sys_call
jmp _schedule
在schedule中并没有执行IRET,那么程序是怎么在完成
schedule的调用后,跳到ret_from_sys_call中执行的?
是不是在执行switch_to(next)中的"ljmp %0/n/t" 切换到新的进程,以后再切换到当前进程后,则继续执行switch_to(next)中以下的语句。执行完switch_to后返回到ret_from_sys_call,然后执行IRET返回进程0的用户态,for(;;) pause()。
拿由进程0的用户态切换到进程1为例:进程0执行系统调用fork(),在System_call.s中调用完sys_fork后,此时执行
reschedule:
pushl $ret_from_sys_call
jmp _schedule
在schedule中执行switch_to(next)的"ljmp %0/n/t" 切换到新的进程1的用户态。(因为进程1用户态程序、数据、堆栈,tast_struct中的TSS结构。都是拷贝进程0的,而TSS中的CS:IP要执行的则是判断fork()的返回值),因为在进程1中fork()的返回值为0,所以执行init();执行完后,通过TSS中的连接字段回到进程中switch_to(next)中"ljmp %0/n/t"以下的语句。执行完switch_to后返回到ret_from_sys_call。然后执行IRET返回进程0的用户态,在进程1中fork()的返回值为1,执行for(;;) pause()。
补充:schedule()
schedule()函数首先对所有任务(进程)进行检测,唤醒任何一个已经得到信号的任务。具体方法是 针对任务数组中的每个任务,检查其报警定时值 alarm。如果任务的 alarm 时间已经过期(alarm<jiffies), 则在它的信号位图中设置 SIGALRM 信号,然后清 alarm 值。jiffies 是系统从开机开始算起的滴答数(10ms/ 滴答)。在 sched.h 中定义。如果进程的信号位图中除去被阻塞的信号外还有其它信号,并且任务处于 可中断睡眠状态(TASK_INTERRUPTIBLE),则置任务为就绪状态(TASK_RUNNING)。
随后是调度函数的核心处理部分。这部分代码根据进程的时间片和优先权调度机制,来选择随后要 执行的任务。它首先循环检查任务数组中的所有任务,根据每个就绪态任务剩余执行时间的值 counter, 选取该值最大的一个任务,并利用 switch_to()函数切换到该任务。若所有就绪态任务的该值都等于零, 表示此刻所有任务的时间片都已经运行完,于是就根据任务的优先权值 priority,重置每个任务的运行时 间片值 counter,再重新执行循环检查所有任务的执行时间片值。