前言:本文为我学习孟宁老师的《庖丁解牛linux 内核》课程的简单总结,同时做为课间做业。 linux
唐建,《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000函数
一、概述学习
本文经过一个简单的自定义内核程序来简单描述内核中程序的相互切换过程,给你们展现内存多进程随着时间片轮转而来回切换this
的过程以及原理。线程
二、代码结构3d
咱们的例子代码结构:主要有两个文件mypcb.c myinterrupt.crest
mypcb.c文件内容: blog
myinteruppt.c文件内容:进程
my_start_kernel() ,系统的起点,完成系统环境的准备ip
my_process()这问进程的主体,咱们将起多个进程实例。
my_schedule() 系统进程调度器,咱们将在这个函数看到进程是怎么切换的。
my_time_handler() 时间中断函数,这个函数里面产生时间片,时间片到了后就须要进行进程切换了。
三、my_start_kernel() 系统环境的准备。
咱们先看下重要的结构体 ,进程的结构体,至关于系统的task_struct
struct Thread {
unsigned long ip;
unsigned long sp;
};
typedef struct PCB{
int pid; ——pid
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ ——当前进程调状态
unsigned long stack[KERNEL_STACK_SIZE]; ——进程的堆栈
/* CPU-specific state of this task */
struct Thread thread; ——线程,不用理会线程。主要看其内容ip=eip,sp=esp。就是进程当前执行到哪里了。
unsigned long task_entry; ——进程的入口
struct PCB *next; ——下一个进程
}tPCB;
咱们的linux 系统进程调度大致上就是操做task_struct,因此咱们这模拟的系统就是围绕这tpcb进行操做
回到正题,my_start_kernel以下图这个函数分三部分
(1)、0号进程的初始化
进程的入口为my_process,因此咱们一会主要看怎么跳转到进程的入口的。
这里task_entry 赋予了进程入口my_process()。同时sp=esp = 进程的栈底,由于是刚开始嘛,栈仍是空的。
(2)、fork多个进程,这里模拟了fork的过程,
(3)、咱们这着重讲这个,就是my_start_kernel怎么切换到0号进程的。
asm volatile(
"movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */ ——将esp 赋值为0号进程的sp,好了,这样栈就切换到0号进程的栈了
"pushl %1\n\t" /* push ebp */ ——将将由于0号进程还没开始跑呢,因此ebp=esp啊,因此这里其实是将ebp压栈
"pushl %0\n\t" /* push task[pid].thread.ip */ ——将ip=eip 将0号进程的eip入栈。
"ret\n\t" /* pop task[pid].thread.ip to eip */ ——ret= pop eip。准备切换到0号进程了,将0号进程的ip放入eip寄存器中。
"popl %%ebp\n\t" ——sp=ebp,准备栈底。到这里,ebp、eip、esp都准备好了,注意eip=my_process,
也就是已经切换到0号进程了。
:
: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/
);
四、时间片
如上图,实际上就是没1000个时钟就赋值my_need_sched=1,这个是调度标志位,表示要开始调度到下一个进程了。
五、my_process
前面咱们讲到0号进程已经开始跑起来了,也就是跑到了my_process这里。
如上图,能够看到这里是死循环,当发现调度标志被设置为1时,表示要开始调度了,这个进程不能再执行了,轮到别人了,因而调用my_schedule去切换到其余进程。
六、my_schedule,进程切换。
如今来到咱们的核心了,实际上就保存上一个进程的现场,而后将须要调度的进程的现场恢复到寄存器中,让寄存器按照这个进程的现场运行。
(1)、这个就是取出立刻须要进程的结构体
(2)、这个就是难点了
/* switch to next process */
asm volatile(
"pushl %%ebp\n\t" /* save ebp */ ——将本进程的ebp入栈
"movl %%esp,%0\n\t" /* save esp */ ——将esp保存到本进程中,用于下次执行
"movl %2,%%esp\n\t" /* restore esp */ ——将新进程的sp写入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)
);
这个实际上就是保存本进程的信息,而后将下一个进程的东西装入寄存器,并开始执行。
七、执行结果
能够推测,咱们的进程是按照序号顺序执行的