潘聪 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
这次的实验主要是研究线程调度中的主动调度。
---------------------------------------------
一、基础结构
(1)进程:在mypcb.h中,进程由ip(eip)和sp(esp)共同组成。
(2)进程控制块(PCB):进程号pid,状态(错误/运行中/停止运行),进程堆栈(包括内核栈和用户栈,此处合二为一了,详见[1]),进程,任务入口,通过next指针组成一个链表。
二、初始化(__init my_start_kernel)
生成零号进程:进程号0,状态运行,任务入口和ip为my_process的地址(此处将函数名作为指针使用,返回函数地址),sp设置为栈顶,链表next指向自身。
生成其他进程:进程号依次分配,入口与零号进程相同,状态为未运行,sp设置为各自的栈顶(每个进程维护一个自身的栈),使链表连成环状链表。
当前任务设定为零号进程。
asm代码段:
1 asm volatile 2 ( 3 "movl %1, %%esp\n\t" //set task[pid].thread.sp to esp 4 "pushl %1\n\t" //push ebp 5 "pushl %0\n\t" //push task[pid].thread.ip 6 "ret\n\t" //pop task[pid].thread.ip to eip 7 "popl %%ebp\n\t" 8 : 9 : "c" (task[pid].thread.ip), "d" (task[pid].thread.sp) 10 );
将零号进程的栈顶指针esp赋值给系统的esp,并将esp压栈(可以理解为ebp压栈),并将零号进程的ip压栈,并使用ret指令从该ip开始执行指令。
理论上,如果这些进程不退出的话,最后一行popl永远不会被执行。
三、主进程方法(my_process)
每个指令周期进行一次循环,每10000000此循环后打印当前运行进程的进程号两次;若进程调度标识为1,在两次打印期间执行调度函数。
四、调度句柄(my_timer_handler)
每次中断后开始运行该函数,累积1000次中断后将调度标识置为1,从而使主进程方法运行调度函数。
五、调度函数(my_schedule)
定义两个tPCB类型的指针,分别指向当前进程和下一个进程。针对下个进程的运行状态不同(运行中或其他)分为两段汇编代码进行对应的进程上下文切换。
1)下个进程正在执行:
1 asm volatile 2 ( 3 "pushl %%ebp\n\t" //save ebp 4 "movl %%esp, %0\n\t" //save esp 5 "movl %2, %%esp\n\t" //restore esp 6 "movl $1f, %1\n\t" // save eip |1f mean label 1 7 "pushl %3\n\t" 8 "ret\n\t" //restore eip 9 "1:\t" //next process start here 10 "popl %%ebp\n\t" 11 : "=m" (prev->thread.sp), "=m" (prev->thread.ip) 12 : "m"(next->thread.sp), "m" (next->thread.ip) 13 );
将ebp压栈,当前进程的sp记录esp,当前进程的ip记录label1的地址,当进程切换返回后将栈中原ebp的内容重新弹回到ebp中。
除此之外,还将当前进程设为下一进程,并打印切换文字。
2)下个进程未执行:
在执行汇编命令之前,先将下一进程状态设置为运行中,并将其设为当前进程。
执行的汇编代码与1)类似,在此不进行赘述。
------------------------------------------------------------------------------
最后,make后使用命令qemu -kernel arch/x86/boot/bzImage,生成图见下图:
--------------------------------------------
[1]从迷你型linux内核理解进度调度的原理http://itdreamerchen.com/%E4%BB%8E%E8%BF%B7%E4%BD%A0%E5%9E%8Blinux%E5%86%85%E6%A0%B8%E7%90%86%E8%A7%A3%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6%E7%9A%84%E5%8E%9F%E7%90%86/