一个简单进程调度器的实现和分析
主体代码文件有三个,mypcb.h,myinterupt.h, mymain.h,mypcb定义了进程控制块结构,myinterupt实现了中断处理程序,mymain是实际入口点,以下代码省去了头文件部分,并有详细注释,下面的分析中只挑选关键部分进行分析
1 /* A simply process control block */ 2 3 #define MAX_TASK_NUM 10 //max number of tasks 4 #define KERNEL_STACK_SIZE 1024*8 //max kernel stack size of a single process, here is 8KiB 5 6 //This struct describes basic CPU info 7 struct Thread 8 { 9 unsigned long ip; //EIP Register, Points to next instruction 10 unsigned long sp; //ESP Register, Points to Stack head 11 }; 12 13 //This struct describes the structure of PCB 14 typedef struct PCB 15 { 16 int pid; //pid 17 volatile long state; //process state 18 /* -1 unrunnable, 19 * 0 runnable, 20 * >0 stopped */ 21 char stack[KERNEL_STACK_SIZE]; 22 //each PCB stack size is KERNEL_STACK_SIZE, here is 8KiB 23 struct Thread thread; //CPU info 24 unsigned long task_entry; //task execute entry 25 struct PCB *next; //all processes are in a circled linked list 26 }tPCB; 27 28 void my_schedule(void);
1 #include "mypcb.h" 2 3 extern tPCB task[MAX_TASK_NUM]; 4 extern tPCB *my_current_task; 5 extern volatile int my_need_sched; 6 volatile int time_count; 7 8 /* 9 * Called by timer interrupt. 10 */ 11 void my_timer_handler(void) 12 { 13 #if 1 14 //here defines the system circle count, after that count the processes should be resheduled 15 if(time_count % 10 == 0 && my_need_sched != 1) 16 { 17 printk(KERN_NOTICE ">>>my_timer_handler here<<<\n"); 18 my_need_sched = 1; 19 } 20 time_count ++; 21 #endif 22 return; 23 } 24 25 void my_schedule(void) 26 { 27 tPCB *next; 28 tPCB *prev; 29 30 printk(KERN_NOTICE "This is process %d, next is process %d", my_current_task->pid, my_current_task->next->pid); 31 32 if(my_current_task == NULL || my_current_task->next == NULL) 33 { 34 return; 35 } 36 37 printk(KERN_NOTICE ">>>my_schedule<<<\n"); 38 39 //Scheduler code below 40 next = my_current_task->next; 41 prev = my_current_task; 42 43 //state 0 means next process is runnable 44 if(next->state == 0) // -1 unrunnable, 0 runnable, >0 stopped 45 { 46 asm volatile( 47 //saving current task scene 48 "pushl %%ebp\n\t" //save ebp, aka current scene 49 "movl %%esp, %0\n\t"//save esp, also part of current scene 50 //changing to next task 51 "movl %2, %%esp\n\t"//set esp to the very value of next task 52 "movl $1f, %1\n\t" //save current EIP to memory 53 "pushl %3\n\t" //next task's EIP is now in stack 54 "ret\n\t" //reset EIP to next task's EIP, same as 'popl %eip' 55 "1:\t" //flag, next process start here 56 "popl %%ebp\n\t" // 57 :"=m"(prev->thread.sp), "=m"(prev->thread.ip) 58 :"m"(next->thread.sp), "m"(next->thread.ip) 59 ); 60 //The inline assembly did everything to cut the scene, now we are running 'next' task 61 my_current_task = next; 62 printk(KERN_NOTICE ">>>switch %d to %d<<<\n", prev->pid, next->pid); 63 } 64 else 65 { 66 next->state = 0; 67 my_current_task = next; 68 printk(KERN_NOTICE ">>>switch %d to %d<<<\n", prev->pid, next->pid); 69 //Switch to new process 70 asm volatile( 71 //Saving current task scene 72 "pushl %%ebp\n\t" //save ebp, same as above 73 "movl %%esp, %0\n\t"//save esp, same as above 74 //Changing to next task 75 "movl %2, %%esp\n\t"//reset esp, same as above 76 "movl %2, %%ebp\n\t"//set ebp to next->thread.sp, since next task is totally new, so its stack should be empty. aka esp == ebp 77 "movl $1f, %1\n\t" //save EIP 78 "pushl %3\n\t" //enstack next task's EIP, same as above 79 "ret\n\t" //reset EIP, same as above 80 :"=m"(prev->thread.sp), "=m"(prev->thread.ip) 81 :"m"(next->thread.sp), "m"(next->thread.ip) 82 ); 83 } 84 return; 85 }
#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;//Initializing process 0, root of everythingtask[pid].pid = pid;task[pid].state = 0; // -1 unrunnable, 0 runnable, >0 stoppedtask[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 processesfor(++pid; pid < MAX_TASK_NUM; ++pid){memcpy(&task[pid], &task[0], sizeof(tPCB)); //Just a simple memcpy, copies every thing of process 0//Reset PCB info, everything is similar as above, except this new process's state is 'unrunnable'task[pid].pid = pid;task[pid].state = -1; // -1 unrunnable, 0 runnable, >0 stoppedtask[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];//Link the new process to process listtask[pid].next = task[pid-1].next;task[pid-1].next = &task[pid];}//Starting process 0pid = 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 process 0's sp to stack//Redirect EIP to entry point of process 0"pushl %0\n\t" //push task[pid].thread.ip"ret\n\t" //Set actuall EIP via ret"popl %%ebp\n\t" //ebp is now process0's esp, aka the process is new ::"c"(task[pid].thread.ip),"d"(task[pid].thread.sp)); }//user process void my_process(void) {int i = 0;while(true){++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);}} }
mypcb.h中定义了pcb的数据结构,每个字段都已经用注释注明含义,此处不再赘述,需要注意的是tPCB.next如何指向下一个PCB从某种程度上影响了进程调度的方式,这儿采用时间片轮转调度法,因此在后面的mymain中将所有进程连接成了一个环形链表进行调度。
mymain是入口点,从整体结构上可以看出,系统启动时首先创建了0号进程,然后由0号进程fork出了所有其他进程,这个过程很简单,不赘述,需要详细说一下的是0号进程的启动过程,代码如下
1 //Starting process 0 2 pid = 0; 3 my_current_task = &task[pid]; 4 asm volatile( 5 "movl %1, %%esp\n\t" //set task[pid].thread.sp to esp 6 "pushl %1\n\t" //push process 0's sp to stack 7 //Redirect EIP to entry point of process 0 8 "pushl %0\n\t" //push task[pid].thread.ip 9 "ret\n\t" //Set actuall EIP via ret 10 "popl %%ebp\n\t" //ebp is now process0's esp, aka the process is new 11 : 12 :"c"(task[pid].thread.ip),"d"(task[pid].thread.sp) 13 );
这段代码首先将0号进程设置为当前进程,接下来的汇编才真正让0号进程跑了起来
L5,将0号进程的sp保存到esp中,即将栈顶挪到了0号进程栈的栈顶,但由于0号进程此时还未运行,实际上这儿也是其栈底
L6,继续将0号进程的sp入栈作为栈底
下面几行将程序执行流真正定向到了process 0
L8,L9,我们知道ret相当于popl %eip,所以现将进程0的eip入栈然后ret,不难理解,这是将0号进程的eip值实际注入到了cpu的eip寄存器中
L10,如上面所说,当前栈顶位置储存的是0号进程的栈底,所以这句重设ebp为0号进程的栈底
这段汇编执行完成后,CPU就会根据eip指向的0号进程的入口点继续执行了
以上就是系统启动的过程,下面的my_process则是一个进程的主体,这个进程干的事情很简单,计时,达到预设时间后调用中断处理函数做好准备切换到下一个进程,代码很简单不再赘述
下面要说的myinterupt是调度器的核心,内含了中断处理函数,也就是调度器真正实现保护现场和恢复现场的部分
time_handler不难理解,计时,到达预设时间时候通知用户进程准备进行调度,不详细解释
1 //Scheduler code below 2 next = my_current_task->next; 3 prev = my_current_task; 4 5 //state 0 means next process is runnable 6 if(next->state == 0) // -1 unrunnable, 0 runnable, >0 stopped 7 { 8 asm volatile( 9 //saving current task scene 10 "pushl %%ebp\n\t" //save ebp, aka current scene 11 "movl %%esp, %0\n\t"//save esp, also part of current scene 12 //changing to next task 13 "movl %2, %%esp\n\t"//set esp to the very value of next task 14 "movl $1f, %1\n\t" //save current EIP to memory 15 "pushl %3\n\t" //next task's EIP is now in stack 16 "ret\n\t" //reset EIP to next task's EIP, same as 'popl %eip' 17 "1:\t" //flag, next process start here 18 "popl %%ebp\n\t" // 19 :"=m"(prev->thread.sp), "=m"(prev->thread.ip) 20 :"m"(next->thread.sp), "m"(next->thread.ip) 21 ); 22 //The inline assembly did everything to cut the scene, now we are running 'next' task 23 my_current_task = next; 24 printk(KERN_NOTICE ">>>switch %d to %d<<<\n", prev->pid, next->pid); 25 } 26 else 27 { 28 next->state = 0; 29 my_current_task = next; 30 printk(KERN_NOTICE ">>>switch %d to %d<<<\n", prev->pid, next->pid); 31 //Switch to new process 32 asm volatile( 33 //Saving current task scene 34 "pushl %%ebp\n\t" //save ebp, same as above 35 "movl %%esp, %0\n\t"//save esp, same as above 36 //Changing to next task 37 "movl %2, %%esp\n\t"//reset esp, same as above 38 "movl %2, %%ebp\n\t"//set ebp to next->thread.sp, since next task is totally new, so its stack should be empty. aka esp == ebp 39 "movl $1f, %1\n\t" //save EIP 40 "pushl %3\n\t" //enstack next task's EIP, same as above 41 "ret\n\t" //reset EIP, same as above 42 :"=m"(prev->thread.sp), "=m"(prev->thread.ip) 43 :"m"(next->thread.sp), "m"(next->thread.ip) 44 ); 45 }
调度器首先将下一个进程记为next,并将当前进程记为prev,然后需要根据下一个进程的状态来进行处理,如果下一个进程正在运行,即状态为那么执行上面这段汇编
L10,L11两行进行的就是保存当前进程运行现场,L10将栈底设置到当前栈底,L11将esp保存到了内存中,这样就完成了当前运行现场,即调用栈的保护
L13由于next处于运行状态,所以首先要恢复其运行现场,和L11相反,这行是将next的esp从内存读取到了CPU的esp寄存器中
L14将现场保护完成后的EIP,即FLAG1的地址存入prev的ip中,记录了当前运行状态以便下次恢复,L15和L16和mymain中的类似,将next的ip值送入CPU的EIP寄存器中
L17处已经完成了现场保护
L18设置了下一个进程执行时的栈底,现场切换完成,下面将当前任务设置为next就完成了进程上下文的切换
如果下一个进程并未运行,是一个新进程,处理方式则略有不同
L33,34和L10,L11并无区别,保存现场
L37同上
L38有所区别,由于下一个进程是一个新进程,所以他的栈是空的,所以直接将ebp设置为esp就可以了
后续过程和继续运行一个已运行的进程一样,没有区别,不再赘述
这样两段代码就构成了进程调度器的主体,通过设置时间片大小可以获取不同的调度效果
程序的运行效果如下
可以看见10个进程按照环形顺序依次调度运行,说明这个简单的时间片轮转调度器工作正常
从这个实验也不难理解,为什么中断和进程上下文切换是操作系统最重要的一部分功能,我们知道操作系统存在的目的是统一管理计算机的资源,并按照一定的策略分配给用户进程使用,而中断和进程上下文切换就是实现这一管理功能的手段,没有这两项功能,操作系统对计算机资源的管理也就无从谈起了
云课堂作业
吴韬 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
转载于:https://www.cnblogs.com/current/p/4340578.html
一个简单进程调度器的实现和分析相关推荐
- python批量下载文件只有1kb_详解如何用python实现一个简单下载器的服务端和客户端...
话不多说,先看代码: 客户端: import socket def main(): #creat: download_client=socket.socket(socket.AF_INET,socke ...
- 如何用FFmpeg编写一个简单播放器详细步骤介绍
如何用FFmpeg编写一个简单播放器详细步骤介绍(转载) FFMPEG是一个很好的库,可以用来创建视频应用或者生成特定的工具.FFMPEG几乎为你把所有的繁重工作都做了,比如解码.编码.复用和解复用. ...
- 如何用 FFmpeg 编写一个简单播放器.pdf
An ffmpeg and SDL Tutorial.pdf 如何用 FFmpeg 编写一个简单播放器.pdf 中文版
- Verilog——一个简单仲裁器的实现
Verilog--一个简单仲裁器的实现 仲裁器基本功能 仲裁器(arbiter) 的主要功能是,多个source源同时发出请求时,根据当前的优先级来判断应响应哪一个source. 仲裁器分为轮询优先级 ...
- 一个简单的IPmsg程序源码分析(二)
离上篇一个简单的IPmsg程序源码分析(一)已经基本半个月(上篇最初发布在点点上面,后边纠结了一下还是选择了博客园),利用空闲的时间终于把源码的构架和一些细节基本都搞清楚了,总的来说是很简单的一个客户 ...
- 一个简单的时间片轮转多道程序内核代码分析
郑斌 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 第二周的实验内容分析 1. ...
- Linux内核分析2:一个简单的时间片轮转多道程序内核代码分析
Lab2:一个简单的时间片轮转多道程序内核代码 席金玉 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100002900 ...
- linux实验:基于mykernel的一个简单的时间片轮转多道程序内核代码分析
学号后三位:288 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ 1.mykernel mykernel是由中科大孟宁老师建立的用于开发 ...
- 一个简单限速器的java实现[1]
在日常开发过程中,经常遇到对资源使用频度的限制,例如:某个接口只允许每秒调用300次,或者某个资源对象只允许每秒使用300等等,下面是一个简单的限速器的java实现,它可以实现对一个资源在若干时间(毫 ...
最新文章
- iOS 储存用户信息设置封装 直接调用即可(部分是代码片段)
- python中如何创建包_如何在Python中创建命名空间包?
- Python 类的多态
- 代码管理 防止员工_低代码开发现形记
- [WCF 4.0新特性] 标准终结点与无(.SVC)文件服务激活
- xgboost相比传统gbdt有何不同?xgboost为什么快?xgboost如何支持并行?
- [视频演示].NET Core开发的iNeuOS物联网平台,实现从设备PLC、云平台、移动APP数据链路闭环...
- win10子系统linux编译ffmpeg
- 曾经拒绝马云的实习生 他说要开启云工作时代
- 虚函数virtual
- 洛谷 P2117 小Z的矩阵
- 强化学习与环境不确定_不确定性意识强化学习
- TCP安全测试指南-魔兽3找联机0day
- solidworks钣金件设计术语creo/ug适用
- VirtualBox导入虚拟机错误e_invalidarg (0x80070057) 使用管理员权限打开VirtualBox
- Leetcode高频题目整理(更新)
- 鹏业安装三维算量软件——批量修改工程量
- 微信小程序——云函数三方库request-promise的使用详解
- 学习GAN必须阅读的10篇论文
- vue-混入mixins
热门文章
- 关于华硕X205TA安装Linux操作系统的问题
- proteus中的各类开关及其使用
- 前端开发 (网页,网站,web标准)
- 【3维视觉】ShapeNet数据集介绍
- conda问题CondaHTTPError: HTTP 000 CONNECTION FAILED for url <https://mirrors.tuna.tsinghua.edu.cn/anac
- 彩虹云任务7.2的视频搭建教程+源码
- 彩虹云商城模板NiceShopV0.4完结版
- 计算几何基础算法几何C++实现
- linux 休眠定时唤醒_Linux 下定时唤醒计算机
- you-get 下载bilibili视频