一个简单的时间片轮转多道程序内核代码分析
郑斌 原创作品转载请注明出处
《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
一个简单的时间片轮转多道程序内核代码分析相关推荐
- Linux内核分析2:一个简单的时间片轮转多道程序内核代码分析
Lab2:一个简单的时间片轮转多道程序内核代码 席金玉 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100002900 ...
- linux实验:基于mykernel的一个简单的时间片轮转多道程序内核代码分析
学号后三位:288 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ 1.mykernel mykernel是由中科大孟宁老师建立的用于开发 ...
- Linux内核分析:完成一个简单的时间片轮转多道程序内核代码
PS.贺邦 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 1.m ...
- linux内核实验一:一个简单的时间片轮转多道程序内核代码
学号517原创作品,转载请注明出处. 本实验资源来源: https://github.com/mengning/linuxkernel/ 一.mykernel 本次实验是在孟宁老师所建立的一个供用户开 ...
- linux内核计算代码时间,完成一个简单的时间片轮转多道程序内核代码
<Linux 内核分析>实验二:How Does a Operating System Work? 1.函数调用堆栈和中断 在上一节实验中我们已经明白了,存储程序计算机的运行原理,就是通过 ...
- linux操作系统分析实验—基于mykernel的时间片轮转多道程序实现与分析
linux操作系统分析实验-基于mykernel的时间片轮转多道程序实现与分析 学号384 原创作业转载请注明出处+中国科学技术大学孟宁老师的Linux操作系统分析 https://github.co ...
- 一个简单的IPmsg程序源码分析(二)
离上篇一个简单的IPmsg程序源码分析(一)已经基本半个月(上篇最初发布在点点上面,后边纠结了一下还是选择了博客园),利用空闲的时间终于把源码的构架和一些细节基本都搞清楚了,总的来说是很简单的一个客户 ...
- 一个简单的录音软件程序代码【C++】
一个简单的录音软件程序代码[C++]今天的院内绿草茵茵的 录音软件,岁月一去不回返,顽强拼搏,我将来的录音软件家是一栋三层的别墅,因为小树给我留下的是顽强拼搏,你见状,可你的眼睛好像在说,那盛夏的梧桐 ...
- 用opengl编写一个简单的画图软件示例代码
//用opengl编写一个简单的画图软件示例代码(存在闪烁问题) //本代码,抄写自一本教授opengl的书,可惜,里面的代码存在一些问题,导致不能正常显示,现在是增加了一些语句的代码 #includ ...
最新文章
- 2021中国大学排名发布:北京大学连续14年位居榜首
- FPGA笔试题解析(二)
- 《JavaScript面向对象精要》——1.8 原始封装类型
- AlertDialog的使用(二):分别创建
- CentOS Linux解决Device eth0 does not seem to be present
- 思科:四分之三的物联网项目将以失败告终
- swift Array 数组
- 湖北大学计算机学院胡院长,学院召开新一届领导干部任命宣布大会
- 关于变量的命名和属性(C#)
- ie浏览器中 textarea 不能自动换行
- activiti jsp 流程设计器_「Activiti精品 悟纤出品」Activiti插件来助你一臂之力 - 第327篇...
- 32位存储字长存储double_1GB多大?1GB与1MB的关系?详细数据存储单位转换来了...
- 2018年网络工程师考试提纲
- Node.js mzitu图片批量下载爬虫1.00
- 质心公式_No.217 质心位置的求法(基础篇)
- k8s/Kubernetes集群安装
- RabbitMQ深入学习指导
- Oracle数据库常用SQL语句查询
- PTA L1-059 敲笨钟
- 3阶差分方程在有重根下的一般计算公式的推导
热门文章
- 易中天与单田芳的区别在哪儿
- 37 | 案例篇:DNS 解析时快时慢,我该怎么办?
- 9.7 top:实时显示系统中各个进程的资源占用状况
- vba 数组赋值_VBA数组与字典解决方案第18讲:VBA中静态数组的定义及创建
- radio button html5,Tkinter Radiobutton单选框的用法
- Flink Forward Global 2021 议题征集ing!
- 从阿里云七代云服务器,谈云计算四大趋势
- 广播电视加速技术迭代,用新技术拥抱行业转型
- 10万人的大场馆如何“画座位”?
- 送给程序员终身受用的建议