linux内核分析第二周-完成一个简单的时间片轮转多道程序内核代码
中断时计算机运行的一个非常重要的功能。之所以重要,是因为由于种种原因,计算机不能将一个程序从头执行到尾不间断,而是可能会出现很多像等待输入设备输出设备的过程,如果没有中断系统,CPU只能等待,造成资源浪费。中断就是当出现需要时,CPU会暂时停止当前程序的执行,转而执行新的程序。多个程序交替执行,能够大大提高运行的效率。
中断是如何实现的呢?通过分析《Linux内核分析》第二周课程提供的一个小小的内核模拟程序mykernel,我们能够很容易地理解中断实现的过程。
mykernel程序有三个源文件,分别是mypcb.h,mymain.c和myinterrupt.c,其中mypcb.h是头文件,定义了一些结构和函数。mymain.c包含模拟多个进程运行的函数。myinerrupt.c包含了模拟中断的函数。
1. mypcb.h
首先来看mypcb.h。其中定义了两个结构和一个函数。
struct Thread {
unsigned long ip;
unsigned long sp;
};
第一个是结构Thread,里面有两个变量,ip和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;

第二个是结构PCB,PCB结构定义了进程管理块,包括6各变量:(1)pid进程标识符;(2)state状态,-1表示不可运行,0表示可运行,>0表示停止;(3)定义了一个栈空间;(4)一个Thread变量;(5)任务入口点;(6)下一个PCB的指针。
#define MAX_TASK_NUM 4
#define KERNEL_STACK_SIZE 1024*8 void my_schedule(void);
还定义了一个my_schedule函数,以及两个宏定义。
2. mymain.c
tPCB task[MAX_TASK_NUM];
tPCB * my_current_task = NULL;
volatile int my_need_sched = 0;
首先定义了3个全局变量,两个PCB结构,一个是所有的进程集合,一个是当前的进程。
void my_process(void); void __init my_start_kernel(void){};
然后是两个函数,my_process和my_start_kernel。
(1)my_start_kernel函数
这个函数可以分为三部分来解析。

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。pid代表了进程号,0是第一个。state代表运行状态,初始化为可运行。Thread的ip就是进程入口点,其实就是进程运行的起点。sp实际上是定义了一段进程的栈空间。最后定义了下一个PCB的链接先指向自己。

/*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];
}

第二部分,是根据第一个进程0初始化余下的进程。因为我们设置最大进程数为4,所以这里实际上是设置了进程1-3的数据结构的值。

/* 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*/
);

最后一个部分,是从进程0号开始运行。这里使用了内联汇编编程,实际上就是将进程0的thread.sp的值赋给esp,将当前运行的地址保存到栈中,这样如果切换的话就可以保证下一个进程结束时回到原来的位置执行。
总而言之,my_start_kernel函数实现了定义进程数组,并运行第一个进程。
(2)my_process函数

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

my_process函数很简单,就是建立一个循环不断运行进程,并输出表明进程正在运行的语句。这里注意有一个my_schedule()函数,实际上这个函数是在myinterrupt.c中实现的,主要作用是切换进程。
3. myinterrupt.c
extern tPCB task[MAX_TASK_NUM];
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0;
首先定义了一些全局变量。然后主要实现了两个函数:my_time_handler和my_schedule,其中my_time_handler实现了中断,而my_schedule实现了中断之后进程的切换。
(1)my_time_handler函数

void my_timer_handler(void)
{
#if 1
if(time_count%1000 == 0 && my_need_sched != 1)
{
printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
my_need_sched = 1;
}
time_count ++ ;
#endif
return;
}

这个函数也很简单,就是每1000毫秒的时候产生一个中断,产生中断之后把my_need_sched设置为1,这样mymain.c中的my_process函数就会调用my_schedule函数来进行进程切换。
(2)my_schedule函数
这个函数才是重点,实现了时间片轮转的中断处理过程。

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;

首先是初始化next和prev两个PCB结构。

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

这一段是循环运行代码,就是当下一个进程的state状态是可运行时,说明这个进程之前已经在运行了,此时可以继续执行,就切换到下一个进程,这中间有一段内联汇编,实现了保存栈地址和栈指针,这样进程切换回来的时候就可以正常运行。然后根据之前保存的栈地址恢复执行。

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

当下一个进程的state不为0时,那么也就是说下一个进程还从来都没有执行过,所以这一段内联汇编的作用是开始执行一个新进程。
总而言之,在上述函数的作用下,成功地实现了时间片轮转的中断处理内核的功能。通过分析源码,我们了解到时间片轮转算法的具体方法,即每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。调度程序所要做的就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾。这是一种最古老,最简单,最公平且使用最广的算法。
linux内核分析第二周-完成一个简单的时间片轮转多道程序内核代码的更多相关文章
- Linux内核分析:完成一个简单的时间片轮转多道程序内核代码
PS.贺邦 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 1.m ...
- Linux内核分析—完成一个简单的时间片轮转多道程序内核代码
---恢复内容开始--- 20135125陈智威 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-10 ...
- 20135202闫佳歆--week2 一个简单的时间片轮转多道程序内核代码及分析
一个简单的时间片轮转多道程序内核代码及分析 所用代码为课程配套git库中下载得到的. 一.进程的启动 /*出自mymain.c*/ /* start process 0 by task[0] */ p ...
- Linux内核分析第二周学习博客——完成一个简单的时间片轮转多道程序内核代码
Linux内核分析第二周学习博客 本周,通过实现一个简单的操作系统内核,我大致了解了操作系统运行的过程. 实验主要步骤如下: 代码分析: void my_process(void) { int i = ...
- Linux内核设计第二周学习总结 完成一个简单的时间片轮转多道程序内核代码
陈巧然 原创作品 转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.使用实验楼的虚拟机 ...
- linux内核分析作业:操作系统是如何工作的进行:完成一个简单的时间片轮转多道程序内核代码
计算机如何工作 三个法宝:存储程序计算机.函数调用堆栈.中断机制. 堆栈 函数调用框架 传递参数 保存返回地址 提供局部变量空间 堆栈相关的寄存器 Esp 堆栈指针 (stack pointer) ...
- Linux内核分析第二周:操作系统是如何工作的
第一讲 函数调用堆栈 计算机是如何工作的? (总结)——三个法宝 1,存储程序计算机工作模型,计算机系统最最基础性的逻辑结构: 2,函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的时候堆 ...
- Linux内核分析——第二周学习笔记20135308
第二周 操作系统是如何工作的 第一节 函数调用堆栈 存储程序计算机:是所有计算机基础的框架 堆栈:计算机中基础的部分,在计算机只有机器语言.汇编语言时,就有了堆栈.堆栈机制是高级语言可以运行的基础. ...
- Linux内核分析 第二周
Linux内核分析——完成一个简单的时间片轮转多道程序内核代码 张潇月+<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100 ...
随机推荐
- mysql使用笔记(一)
一.安装 使用免安装的版本进行安装: 1. 解压到安装目录 2. 拷贝目录下的 my-default.ini 文件为 my.ini 文件 3. 修改my.ini 文件内容为 [client] port ...
- HBase的目录结构
根目录文件(Root-level files) 第一部分文件是被Hlog处理的write-ahead日志文件,这些日志文件被保存在HBase根目录下的.logs文件夹..logs目录下面为每一个HRe ...
- webpack.optimize.CommonsChunkPlugin
打包第三方控件:比如jquery,angular,bootstrap.... const CommonsChunkPlugin = require("webpack/lib/optimize ...
- sencha touch 入门系列 (九)sencha touch 视图组件简介
对于一个普通用户来说,你的项目就是一组简单的视图集合,用户直接通过跟视图进行交互来操作你的应用,对于一个开发人员来说,视图是一个项目的入口,虽然大部分时候最有价值的部分是在model层和control ...
- 学习POC框架pocsuite--编写hellowordPOC
在这里,首先向安全圈最大的娱乐公司,某404致敬. 参考博文 https://www.seebug.org/help/dev 向seebug平台及该文原作者致敬,虽然并不知道是谁 长话短说其实,可自由 ...
- Linux Netfilter注册钩子点
注册钩子点首先要包含响应的头文件,因为这应该已经属于对kernel的编程了. #include <linux/module.h> #include <linux/kernel.h&g ...
- 微信小游戏 小程序与小游戏获取用户信息接口调整 wx.createUserInfoButton
参考: 小程序•小故事(6)——微信登录能力优化 小程序•小故事(4)——获取用户信息 本篇主要是讲微信getUserInfo接口不再出现授权弹框 那么原来getUserInfo是怎么样?修改之后又是 ...
- Navicat 创建 Mysql 函数
1.点击新建函数 2.写函数,保存为v1 3.调用 SELECT id,v1(id) from 表
- IIS7设置IP地址和域名限制
在IIS中可以通过IP地址域名设置来控制拒绝或允许特定范围内的IP对网站的访问权限,下面简单介绍如何在IIS7.5中设置,如下图,是IIS7.5的主界面 一.安装“IP地址和域限制”功能 选定一个网站 ...
- 170512、java日志文件log4j.properties配置详解
一.Log4j配置 第一步:加入log4j-1.2.8.jar到lib下. 第二步:在CLASSPATH下建立log4j.properties.内容如下: 放在src下的话就不用配置 否则得去web. ...