郑斌  原创作品转载请注明出处

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

第二周的实验内容分析

1.实验环境:实验楼中执行。

2.程序代码

整个程序主要由3个文件构成:

1)mypcb.h。 主要定义了进程结构体

2)mymain.c。 主要定义了my_start_kernel初始化第一个进程的函数,和my_process实现进程运行输出和调度。

3)  myinterrupt.c。  主要定义了my_time_handler函数通过计时器设置进程是否需要调度,实现分时,my_schedule函数实现进程调度,完成上一个进程的现场保存和转到下一个进程的功能。

具体代码如下:

mypcb.h

/**  linux/mykernel/mypcb.h**  Kernel internal PCB types**  Copyright (C) 2013  Mengning**/#define MAX_TASK_NUM        4
#define KERNEL_STACK_SIZE   1024*8/* 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;void my_schedule(void);

mymain.c

/**  linux/mykernel/mymain.c**  Kernel internal my_start_kernel**  Copyright (C) 2013  Mengning**/
#include <linux/types.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/tty.h>
#include <linux/vmalloc.h>#include "mypcb.h"tPCB task[MAX_TASK_NUM];
tPCB * my_current_task = NULL;
volatile int my_need_sched = 0;void my_process(void);void __init my_start_kernel(void)
{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];/*fork more process */for(i=1;i<MAX_TASK_NUM;i++){memcpy(&task[i],&task[0],sizeof(tPCB));task[i].pid = i;task[i].state = -1;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];}/* start process 0 by task[0] */pid = 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*/);
}
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);}     }
}

myinterrupt.c

#include <linux/types.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/tty.h>
#include <linux/vmalloc.h>#include "mypcb.h"extern tPCB task[MAX_TASK_NUM];
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0;/** Called by timer interrupt.* it runs in the name of current running process,* so it use kernel stack of current running process*/
void my_timer_handler(void)
{
#if 1if(time_count%1000 == 0 && my_need_sched != 1){printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");my_need_sched = 1;} time_count ++ ;
#endifreturn;
}void my_schedule(void)
{tPCB * next;tPCB * prev;if(my_current_task == NULL || my_current_task->next == NULL){return;}printk(KERN_NOTICE ">>>my_schedule<<<\n");/* schedule */next = my_current_task->next;prev = my_current_task;if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */{/* 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);       }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));          }   return;
}

 3.实验过程。

进入实验楼,在对应文件目录下,把3个文件编辑到mykernel文件夹下,然后make编译

4.程序代码分析

为了方便说明,本文中把代码中的进程根据下标叫为进程(pid),如进程(0)即为刚开始的0号进程。

整个代码部分,CPU从my_start_kernel开始执行

void __init my_start_kernel(void)
{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];

先是初始化了进程(0)的信息。这里主要关注两点:

1.task_entry=thread.ip=my_process。

我的理解是进程的eip,即执行内容为my_process函数. 前面加(unsigned long)表示该函数执行的入口地址。

2.thread.sp 的赋值表达式是(unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1]

我们回顾在mypcb.h中定义PCB部分

所以进程(0)的sp是从stack字符串的末尾开始的,符合第一周课程中的栈地址从高地址向低地址。

然后是复制另外几个进程的信息。

state = -1 表示开始未执行。

这里有个小细节挺有意思,开始乍一看我以为这样写没有进程的下一个进程是进程(0)。

然后分析task[i].next = task[i-1].next;

task[i-1].next = &task[i];

这两条语句,手写模拟后发现其实不是那样的,最后的数组next情况是

task.next[] task[0] task[1] task[2] task[3]

对应内容     task[1] task[2] task[3]  task[0]

然后执行

关键字说明:

asm表示嵌入式汇编,其中%0,%1等表示下面的参数,可以简单理解为函数参数,如此处%0表示task[pid].thread.ip,其他情况类似。

volatile表示编译器不需要优化的代码。

我们来观察其中的汇编代码部分。  注意当前的堆栈是进程(0)的堆栈空间

"movl %1,%%esp\n\t"   /* set task[pid].thread.sp to esp,此时esp是进程(0)的thread.sp*/
"pushl %1\n\t"            /* push ebp ,把进程(0)的thread.sp压栈,因为当前进程(0)的栈为空,ebp跟esp指向相同,所以相当于把ebp压栈*/
"pushl %0\n\t"            /* push task[pid].thread.ip ,把进程(0)的thread.ip压栈*/
"ret\n\t"             /* pop task[pid].thread.ip to eip ,eip为进程(0)的thread.ip,接下来开始执行my_process函数。*/
"popl %%ebp\n\t"  //我觉得这句代码并没有被执行
:
: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)   

下面来看my_process函数。

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);}     }
}

这段代码中定义了变量i初始值为0,然后一个循环体while(1)中i每次加1,当i为10000000才倍数时,输出“this is process +进程标号”

若变量my_need_sched为1,把my_need_sched值置为0,然后执行my_schedule函数(该函数将在后面分析) 变量my_need_sched初始值为0,用来标记是否需要进行进程切换,0表示不用,1表示要切换。

再来看myinterrupt.c文件里的代码内容。

开头的变量定义有

extern tPCB task[MAX_TASK_NUM];
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0;

extern表示引用了其他地方(这里为mymain.c)定义了的变量。

函数my_timer_handler分析:

这里我简单理解为代码执行后,内核的定时器也是自动执行的,即my_timer_handler也是自动开始执行。

该函数的功能为每次计时器走一次,即该函数执行一次,time_count变量加1。当time_count为1000的倍数并且my_need_sched为0时候,输出">>>my_timer_handler here<<<",并把my_need_sched值置为1.表示当前进程需要被调度(这里体现了系统进程调度的分时处理思想)。

then是其中一个非常重要的调度函数my_schedule()。下面我们来进行代码分析。

void my_schedule(void)
{
//申明两个进程指针 prev和next,用来标记当前进程的pid和下一个进程的pidtPCB * next;
tPCB * prev;//特殊情况当前任务和下一个任务都不存在时返回处理if(my_current_task == NULL || my_current_task->next == NULL){return;}
printk(KERN_NOTICE ">>>my_schedule<<<\n");/* schedule */next = my_current_task->next;    //next指向当前进程的下一个进程prev = my_current_task;             //prev指向当前进程/*这里的if将在所有进程(这里是4个)都运行了才执行,前3次都跳转到else的代码部分,所以可以先看下面的else部分的分析后再回到这里*/
if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */{/* 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);
}/*程序初始状态只有进程(0)的state为0,其他进程都为-1(见初始化部分),所以if判断的前3次都将执行此部分,依次运行进程(1)、进程(2)、进程(3)并state都标记为0*/
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));          }   return;
}

my_schedule函数的功能为保存当前进程的现场,并调度运行下一个进程。

由于程序初始状态只有进程(0)的state为0,其他进程都为-1(见初始化部分),所以当

前3次调用 my_schedule函数时,if判断的都将执行else{}部分,依次运行进程(1)、进程(2)、进程(3)并state都标记为0。

接下来调用 my_schedule函数时,if判断都执行if(next->state == 0){}部分的代码。

我们先看来if判断中else{}部分的代码。以程序第一次调用my_schedule函数为例,此时进程(0)正在运行。所以栈空间是进程(0)的堆栈空间。

prev为task[0],即进程(0),next为task[1],即进程(1)

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,把ebp压栈 */"movl %%esp,%0\n\t"   /* save esp,保存进程(0)的thread.sp为esp*/"movl %2,%%esp\n\t"   /* restore  esp ,置当前esp为进程(1)的esp*/"movl %2,%%ebp\n\t"   /* restore  ebp ,进程(1)未开始执行,ebp和ebp指向相同,置当前ebp为进程(1)的ebp */"movl $1f,%1\n\t"   /* save eip ,保存进程(0)的eip为指向标记1:部分的下一条指令,在if的嵌入式汇编代码块中,至此进程(0)的ebp入栈,并保存了esp和eip*/"pushl %3\n\t"        /*把进程(1)的thread.ip压栈*/"ret\n\t"             /* restore  eip ,eip指向进程(1)的thread.ip,开始执行进程(1)*/: "=m" (prev->thread.sp),"=m" (prev->thread.ip): "m" (next->thread.sp),"m" (next->thread.ip));          }   

我们以第四次执行my_schedule函数时为例,将调用if(next->state == 0){}部分的代码,且接下来每次调用my_schedule函数时为例,都将调用if(next->state == 0){}部分的代码。

prev为task[3],即进程(3),next为task[0],即进程(0)。

 if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */{/* switch to next process */asm volatile(    "pushl %%ebp\n\t"          /* save ebp ,保存进程(3)的ebp*/"movl  %%esp,%0\n\t"      /* save esp ,保存进程(3)的thread.sp为esp*/"movl  %2,%%esp\n\t"     /* restore  esp,置esp为进程(0)的thread.sp,恢复进程(0)现场的esp */"movl  $1f,%1\n\t"       /* save eip ,保存进程(3)的eip为指向标记1:部分的下一条指令                       至此进程(3)的ebp入栈,并保存了进程(3)的esp和eip*/"pushl %3\n\t"            /*把进程(0)的thread.ip入栈*/"ret\n\t"                 /* restore  eip ,eip指向进程(0)的thread.ip,开始恢复执行进程(0),查看上面分析可以看到即为从下面的汇编指令部分开始执行*/"1:\t"                  /* next process start here */"popl %%ebp\n\t"      /*进程(0)开始恢复执行的第一句,先把ebp出栈,这里提现了不同进程有各种工作的堆栈空间*/: "=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);       }

至此代码分析结束,接下来为进程的循环调度工作。

总结:

进程启动运行,会有自己的数据存储空间和堆栈空间。

内核会有定时器自动启动用以实现分时功能。

进程之间的切换要保存现场,包括esp,eip,ebp等。

转载于:https://www.cnblogs.com/terrry/articles/4333236.html

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

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

    Lab2:一个简单的时间片轮转多道程序内核代码 席金玉   <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100002900 ...

  2. linux实验:基于mykernel的一个简单的时间片轮转多道程序内核代码分析

    学号后三位:288 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ 1.mykernel mykernel是由中科大孟宁老师建立的用于开发 ...

  3. Linux内核分析:完成一个简单的时间片轮转多道程序内核代码

    PS.贺邦   原创作品转载请注明出处  <Linux内核分析>MOOC课程    http://mooc.study.163.com/course/USTC-1000029000 1.m ...

  4. linux内核实验一:一个简单的时间片轮转多道程序内核代码

    学号517原创作品,转载请注明出处. 本实验资源来源: https://github.com/mengning/linuxkernel/ 一.mykernel 本次实验是在孟宁老师所建立的一个供用户开 ...

  5. linux内核计算代码时间,完成一个简单的时间片轮转多道程序内核代码

    <Linux 内核分析>实验二:How Does a Operating System Work? 1.函数调用堆栈和中断 在上一节实验中我们已经明白了,存储程序计算机的运行原理,就是通过 ...

  6. linux操作系统分析实验—基于mykernel的时间片轮转多道程序实现与分析

    linux操作系统分析实验-基于mykernel的时间片轮转多道程序实现与分析 学号384 原创作业转载请注明出处+中国科学技术大学孟宁老师的Linux操作系统分析 https://github.co ...

  7. 一个简单的IPmsg程序源码分析(二)

    离上篇一个简单的IPmsg程序源码分析(一)已经基本半个月(上篇最初发布在点点上面,后边纠结了一下还是选择了博客园),利用空闲的时间终于把源码的构架和一些细节基本都搞清楚了,总的来说是很简单的一个客户 ...

  8. 一个简单的录音软件程序代码【C++】

    一个简单的录音软件程序代码[C++]今天的院内绿草茵茵的 录音软件,岁月一去不回返,顽强拼搏,我将来的录音软件家是一栋三层的别墅,因为小树给我留下的是顽强拼搏,你见状,可你的眼睛好像在说,那盛夏的梧桐 ...

  9. 用opengl编写一个简单的画图软件示例代码

    //用opengl编写一个简单的画图软件示例代码(存在闪烁问题) //本代码,抄写自一本教授opengl的书,可惜,里面的代码存在一些问题,导致不能正常显示,现在是增加了一些语句的代码 #includ ...

最新文章

  1. 2021中国大学排名发布:北京大学连续14年位居榜首
  2. FPGA笔试题解析(二)
  3. 《JavaScript面向对象精要》——1.8 原始封装类型
  4. AlertDialog的使用(二):分别创建
  5. CentOS Linux解决Device eth0 does not seem to be present
  6. 思科:四分之三的物联网项目将以失败告终
  7. swift Array 数组
  8. 湖北大学计算机学院胡院长,学院召开新一届领导干部任命宣布大会
  9. 关于变量的命名和属性(C#)
  10. ie浏览器中 textarea 不能自动换行
  11. activiti jsp 流程设计器_「Activiti精品 悟纤出品」Activiti插件来助你一臂之力 - 第327篇...
  12. 32位存储字长存储double_1GB多大?1GB与1MB的关系?详细数据存储单位转换来了...
  13. 2018年网络工程师考试提纲
  14. Node.js mzitu图片批量下载爬虫1.00
  15. 质心公式_No.217 质心位置的求法(基础篇)
  16. k8s/Kubernetes集群安装
  17. RabbitMQ深入学习指导
  18. Oracle数据库常用SQL语句查询
  19. PTA L1-059 敲笨钟
  20. 3阶差分方程在有重根下的一般计算公式的推导

热门文章

  1. 易中天与单田芳的区别在哪儿
  2. 37 | 案例篇:DNS 解析时快时慢,我该怎么办?
  3. 9.7 top:实时显示系统中各个进程的资源占用状况
  4. vba 数组赋值_VBA数组与字典解决方案第18讲:VBA中静态数组的定义及创建
  5. radio button html5,Tkinter Radiobutton单选框的用法
  6. Flink Forward Global 2021 议题征集ing!
  7. 从阿里云七代云服务器,谈云计算四大趋势
  8. 广播电视加速技术迭代,用新技术拥抱行业转型
  9. 10万人的大场馆如何“画座位”?
  10. 送给程序员终身受用的建议