操作系统进程切换的一些理解
作者:xujianguo 原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”
————————————————————————————————————————————————————————————————
实验目的:
运行并分析一个精简的操作系统内核,理解操作系统是如何工作的;
实验步骤:
1.使用实验楼的虚拟机打开shell:
2.cd mykernel 您可以看到qemu窗口输出的内容的代码mymain.c和myinterrupt.c;
3.完成一个简单的时间片轮转多道程序内核代码,代码见视频中,或从mykernel找。
修改相关文档mymain.c 和 myinterrupt.c,完成相关内核代码;
增添进程调度头文件mypcb.h,完善和调整实验。
4. 调试和测试:
cd .. 返回上级目录(方法来自讨论区,谢谢);
make 编译和测试。
5.验证和测试
qemu -kernel arch/x86/boot/bzImage
实验分析:
本次小实验主要是由以下几个文档控制的:
mymain.c,myinterrupt.c,mypcb.h
1. mypcb.h
重要头文件,主要定义了进程控制结构PCB、内核堆栈大小和调度算法的声明。
/* CPU-specific state of this task */
struct Thread {
unsigned long ip;
unsigned long sp;
};
typedef struct PCB{
int pid;
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
char stack[KERNEL_STACK_SIZE];
/* CPU-specific state of this task */
struct Thread thread;
unsigned long task_entry;
struct PCB *next;
} tPCB;
2.myinterrupt.c
主要定义时间中断函数和调度函数。
时间中断处理函数:
void my_timer_handler(void)
{
#if 1
if(time_count%1000 == 0 && my_need_sched != 1)
{
printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
my_need_sched = 1;
}
time_count ++ ;
#endif
return;
}/*这里为了明显看到中断的处理效果。可通过调节取模运算来增强实验效果。*/
调度函数:
...............................................
/* switch to next process */
asm volatile(
"pushl %%ebp\n\t" /* save ebp */
"movl %%esp,%0\n\t" /* save esp */
"movl %2,%%esp\n\t" /* restore esp */
"movl $1f,%1\n\t" /* save eip */
"pushl %3\n\t"
"ret\n\t" /* restore eip */
"1:\t" /* next process start here */
"popl %%ebp\n\t"
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
my_current_task = next;
printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
.............................................
3.mymain.c
是本次实验的入口文件,主要定义初始化及执行函数和示例进程函数。
初始化进程:
int pid = 0;
int i;
/* Initialize process 0*/
task[pid].pid = pid;
task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */
task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
task[pid].next = &task[pid];
示例进程函数:
void my_process(void)
{
int i = 0;
while(1)
{
i++;
if(i%10000000 == 0)
{
printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);
if(my_need_sched == 1)
{
my_need_sched = 0;
my_schedule();
}
printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
}
}
}
4.分析进程的启动和进程的切换机制
主要从my_start_kernel函数进入进程,通过上述初始化进程进行0号进程的初始化和调用;
同时利用类似的方法构建了n个pcb进程:
其中重要代码:
..................
task[i].state = -1;//进程初始化为unrunnable 状态
task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];//用这个字符数组作为运行栈
task[i].next = task[i-1].next;
task[i-1].next = &task[i];
使用了内联汇编来完成对0号进程的启动:
....................
my_current_task = &task[pid];
asm volatile(
"movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */
"pushl %1\n\t" /* push ebp */
"pushl %0\n\t" /* push task[pid].thread.ip */
"ret\n\t" /* pop task[pid].thread.ip to eip */
"popl %%ebp\n\t"
:
: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/
);
让esp 指向 0进程 栈顶(同时是栈底),将栈底ebp入栈,调用my_process 入口地址, 跳转到 my_process.
利用if....else来控制进程切换:
next->state = 0;
my_current_task = next;
printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
/* switch to new process */
asm volatile(
"pushl %%ebp\n\t" /* save ebp */
"movl %%esp,%0\n\t" /* save esp */
"movl %2,%%esp\n\t" /* restore esp */
"movl %2,%%ebp\n\t" /* restore ebp */
"movl $1f,%1\n\t" /* save eip */
"pushl %3\n\t"
"ret\n\t" /* restore eip */
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
将当前进程的state改为0(runnable[可运行状态]),并修改当前运行进程指针指向的PCB结构体,执行完后开始执行内联汇编语句,进行进程切换操作.进而调用进程调度函数。逆向完成返回操作。ret指令从栈里弹出数值到eip,并且令栈缩小。
总结:
我们实际生活中所使用的计算机由于各种需要和要求进而采用不同调度算法,但是进程的基本切换上述过程类似,不过更加复杂和效率更高。中断机制的使用也使得计算机能够从事更加复杂的运算和分析。进程的定义是:在自身的虚拟地址空间运行的一个独立的程序,从操作系统的角度来看,所有在系统上运行的东西,都可以称为进程。按照进程的功能和运行的程序分类,进程可划分为两大类:系统进程:可以执行内存资源分配和进程切换等管理工作,而且,该进程的运行不受用户的干预,即使是root用户也不能干预系统进程的运行;用户进程:通过执行用户程序、应用程序或内核之外的系统程序而产生的进程,此类进程可以在用户的控制下运行或关闭。
用户级应用程序,通过系统调用,进入内核空间。内核也要保存用户进程的一些寄存器、变量等,以便系统调用结束后回到用户空间继续执行。而“进程上下文”,就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈上的内容,当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。
参考文档:
http://blog.chinaunix.net/uid-27717694-id-3803944.html
http://www.2cto.com/os/201412/359261.html