一个简单的时间片轮转多道程序内核代码的分析

王宣  原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 
第一部分:环境搭建
环境可以直接用实验楼提供的环境,也可以按照孟宁老师github上的步骤来搭建,我是在自己的ubuntu上搭建的。
第二部分:实验分析
首先上截图

代码分析:
首先mymain.c里面的
#define MAX_TASK_NUM 10 //进程控制块的数目
#define KERNEL_STACK_SIZE 1024*8  //内核栈的大小
#define PRIORITY_MAX 30 //进程优先级
struct Thread {
    unsigned long	ip;//指令寄存器eip的值(我的理解是进程切换时,
			  //比如地址5,地址6,如果进程未被切换那么执行5处的代码后,
		          //接着就执行6处的代码,但在执行5后要切换进程.此时eip的值是6处代码的地址)
			   //那么此时把eip中的值放到当前进程的ip中,等下次轮到该进程执行时再使用
    unsigned long	sp;//保存进程将要切换时,当前进程esp的值,即栈顶地址
};
进程控制块结构的定义
typedef struct PCB{
    int pid; // 存放进程号
    volatile long state;//存放进程的状态
    char stack[KERNEL_STACK_SIZE];//内核栈
    struct Thread thread;
    unsigned long	task_entry;//进程执行的入口地址
    struct PCB *next;//下一个的进程的进程控制块地址
    unsigned long priority;// 进程优先级
    //todo add other attrubte of process control block
}tPCB;
上面是进程控制块的定义
下面是进程控制块的初始化等
__init my_start_kernel(void)函数创建若干个进程控制块
每个进程都有一个进程控制块PCB,里面存储进程的进程号,状态,进程的入口地址,ip的值,内核栈,内核栈栈顶的地址等
void __init my_start_kernel(void)
{
    int pid = 0;  //先初始化第一个进程控制块
    
    task[pid].pid = pid;//填入自身的进程号
    task[pid].state = 0;//状态设为0
    task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;//这里设置ip和程序入口地址
    task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];//内核栈的地址
    task[pid].next = &task[pid];//填入自身地址
    for(pid=1;pid<MAX_TASK_NUM;pid++)
    {
        memcpy(&task[pid],&task[0],sizeof(tPCB));
        task[pid].pid = pid;
        task[pid].state = -1;
        task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
	task[pid].priority=get_rand(PRIORITY_MAX);//优先级参数是根据内核的随机数生成的
    }
	
	task[MAX_TASK_NUM-1].next=&task[0];//相当于在尾节点填入首节点的地址,形成环
    //这里开始执行0号进程
    pid = 0;
    my_current_task = &task[pid];//my_current_task表示过当前运行进程,将0号进程赋值给它
asm volatile(
     "movl %1,%%esp\n\t" //%1指下面的"d" (task[pid].thread.sp),将当前进程内核栈的栈顶地址赋值给寄存器esp
     "pushl %1\n\t" //将sp的值压入当前进程内核栈中?感觉这里有点不对,好像应该插入 的是"movl %1,%%ebp\n\t"
					//因为0号进程还未运行时,ebp和esp的值是相同的,所以也应该对ebp赋值,如果是
					//"pushl %1\n\t",那么下面的ret\n\t 弹回到eip的值就是sp的值,而不是ip的值了
     "pushl %0\n\t" //将ip的值压栈,也就是0号进程的入口地址
     "ret\n\t" //将0号进程的入口地址弹回到eip中
     "popl %%ebp\n\t"//将之前压入的sp pop到ebp中
     :
     : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)	/* input c or d mean %ecx/%edx*/
);
所以void __init my_start_kernel(void)中就是对进程先初始化,在启动进程

接着就是执行了
void my_process(void)
{
    int i = 0;
    while(1)
    {
        i++;
        if(i%10000000 == 0)
        { 
            if(my_need_sched == 1)
            {
                my_need_sched = 0;
		sand_priority();
		my_schedule();  
	    }
        }
    }
}
因为进程初始化的时候
task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
填入的入口地址是my_process的地址,这个函数里面有个while死循环,当i一直自增,而此时中断函数
void my_timer_handler(void)
{
#if 1
    if(time_count%2000 == 0 && my_need_sched != 1)
    {
        my_need_sched = 1;
    }
    time_count ++ ;
#endif
    return;
}
中断函数里面的my_need_sched 初始化值为0,所以只要time_count自增到2000的倍数时,
my_need_sched的值就变为1,这个时候my_process函数里面满足条件i%10000000 == 0
就执行下面代码


my_need_sched = 0;//将my_need_sched再次变为0,使中断功能继续
sand_priority();//设置优先级


根据以上函数设置进程优先级
my_schedule();  
接着是my_schedule()函数


get_next()函数就是一个for循环比较得出优先级最高的进程地址,然后返回
接着就正式进行进程切换
 if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
    {//这个if里面的条件表示当前将要执行的进程已经运行过
     asm volatile(	
         "pushl %%ebp\n\t" //将当前ebp压入自己的内核栈中
         "movl %%esp,%0\n\t" //将当前的esp保存到prev->thread.sp中,
         "movl %2,%%esp\n\t" //将将要运行的进程的sp放到esp中,即栈顶指针
         "movl $1f,%1\n\t" //保存指令寄存器的值到当前进程的ip中,即prev->thread.ip
         "pushl %3\n\t"   //先将将要执行的进程的ip,压入当前进程的栈中
         "ret\n\t"       //将刚才压到栈中的ip弹回到eip寄存器中
         "1:\t"         //下一进程从这里开始
         "popl %%ebp\n\t"  //这里应该是下一进程的栈顶指针popl回ebp中
         : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
         : "m" (next->thread.sp),"m" (next->thread.ip)
     );
     my_current_task = next;
  }
 else
    {
	//将要切换的进程还未运行过的情况
        next->state = 0;
        my_current_task = next;
     //下面的汇编进行进程切换,保存现场//
     asm volatile(	
         "pushl %%ebp\n\t" //将ebp压入当前进程栈中
         "movl %%esp,%0\n\t" //将当前esp的值存到当前进程的sp中,为下次运行做准备
         "movl %2,%%esp\n\t" //将将要执行的进程的sp值放到esp中,
         "movl %2,%%ebp\n\t" //将将要执行的进程的sp的值放到ebp中
         "movl $1f,%1\n\t" //存储当前eip的值,也就是当前进程下次执行的代码的地址存到当前进程的ip中
         "pushl %3\n\t"   
         "ret\n\t"      //上面两句是将将要执行的ip赋值到eip寄存器中
         : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
         : "m" (next->thread.sp),"m" (next->thread.ip)
     );
    }
    return;	
}
主要的代码分析到这里就差不多结束了
第三部分:总结
之前对进程切换很迷糊,尤其对PCB这个东西,看fork()的时候,虽然能够理解,但是内部是怎么保存运行现场,怎么切换的,一直很晕,翻过那些讲linux的书,
然而看着就晕。。。这次通过这个精简过的程序代码,之前的很多的迷糊都明白了,虽然有些地方还是有些疑问,相信接下来的学习过程中会一点点的弄明白。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值