中断时计算机运行的一个非常重要的功能。之所以重要,是因为由于种种原因,计算机不能将一个程序从头执行到尾不间断,而是可能会出现很多像等待输入设备输出设备的过程,如果没有中断系统,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内核分析第二周-完成一个简单的时间片轮转多道程序内核代码的更多相关文章

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

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

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

    ---恢复内容开始--- 20135125陈智威 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-10 ...

  3. 20135202闫佳歆--week2 一个简单的时间片轮转多道程序内核代码及分析

    一个简单的时间片轮转多道程序内核代码及分析 所用代码为课程配套git库中下载得到的. 一.进程的启动 /*出自mymain.c*/ /* start process 0 by task[0] */ p ...

  4. Linux内核分析第二周学习博客——完成一个简单的时间片轮转多道程序内核代码

    Linux内核分析第二周学习博客 本周,通过实现一个简单的操作系统内核,我大致了解了操作系统运行的过程. 实验主要步骤如下: 代码分析: void my_process(void) { int i = ...

  5. Linux内核设计第二周学习总结 完成一个简单的时间片轮转多道程序内核代码

    陈巧然 原创作品 转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.使用实验楼的虚拟机 ...

  6. linux内核分析作业:操作系统是如何工作的进行:完成一个简单的时间片轮转多道程序内核代码

    计算机如何工作 三个法宝:存储程序计算机.函数调用堆栈.中断机制. 堆栈 函数调用框架 传递参数 保存返回地址 提供局部变量空间 堆栈相关的寄存器 Esp 堆栈指针  (stack pointer) ...

  7. Linux内核分析第二周:操作系统是如何工作的

    第一讲 函数调用堆栈 计算机是如何工作的? (总结)——三个法宝 1,存储程序计算机工作模型,计算机系统最最基础性的逻辑结构: 2,函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的时候堆 ...

  8. Linux内核分析——第二周学习笔记20135308

    第二周 操作系统是如何工作的 第一节 函数调用堆栈 存储程序计算机:是所有计算机基础的框架 堆栈:计算机中基础的部分,在计算机只有机器语言.汇编语言时,就有了堆栈.堆栈机制是高级语言可以运行的基础. ...

  9. Linux内核分析 第二周

    Linux内核分析——完成一个简单的时间片轮转多道程序内核代码 张潇月+<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100 ...

随机推荐

  1. Xcode模版生成文件头部注释

    在使用Xcode创建工程或者新建类的时候,顶部都会有一些xcode帮我们生成的注释 //// MySingletonClass.h// 单例模式//// Created by mark on 15/8 ...

  2. MQTT协议笔记之发布流程

    MQTT协议笔记之发布流程 前言 这次要讲到客户端/服务器的发布消息行为,与PUBLISH相关的消息类型,会在这里看到. PUBLISH 客户端发布消息经由服务器分发到所有对应的订阅者那里.一个订阅者 ...

  3. Java虚拟机原理图解

    具体详情参考: http://blog.csdn.net/luanlouis/article/details/41046443 http://blog.csdn.net/luanlouis/artic ...

  4. LeetCode - Delete Duplicate Emails

    Discription:Write a SQL query to delete all duplicate email entries in a table named Person, keeping ...

  5. 使用springBoot进行快速开发

    springBoot项目是spring的一个子项目,使用约定由于配置的思想省去了以往在开发过程中许多的配置工作(其实使用springBoot并不是零配置,只是使用了注解完全省去了XML文件的配置),达 ...

  6. ST-LINK使用注意

    利用ST-LINK下载程序注意事项: 1.接线 按照上面图对着自己的开发板连接相应的引脚就可以了. 2.keil5配置 线连接完之后,要对自己的工程进行相关的 配置才能正确进行下载. 首先选择ST-L ...

  7. mysql查看线程详解(转载)

    如果有 SUPER 权限,则可以看到全部的线程,否则,只能看到自己发起的线程(这是指,当前对应的 MySQL 帐户运行的线程). mysql> show processlist; +—–+——— ...

  8. SQL Server 2008 R2数据库镜像部署图文教程

    数据库镜像是一种针对数据库高可用性的基于软件的解决方案.其维护着一个数据库的两个相同的副本,这两个副本分别放置在不同的SQL Server数据库实例中 概述 “数据库镜像”是一种针对数据库高可用性的基 ...

  9. spring的AOP个人理解和使用

    1什么是AOP:AOP是面向切面编程,也就是说面向某个功能模块编程,典型的应用就是Spring的声明式事务, Spring的AOP事务解析: 在以前的事务管理是要融合在逻辑代码中的,在逻辑代码中决定事 ...

  10. jenkins之配置git认证方式

    在使用jenkins构建时候需要从git下拉代码,需要配置认证方式 http拉取代码 配置用户名和密码认证 添加认证 设置用户名和密码 git认证 设置私钥认证