您的位置:首页 > 编程语言

2、完成一个简单的时间片轮转多道程序内核代码

2017-03-04 19:51 513 查看
姓名:周毅

原创作品转载请注明出处 

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

一、基础知识

1、时间片轮转法是一种最常见的进程调度算法,它为每个进程分配一个时间片,然后每个进程轮流执行来完成并发执行;

2、进程的调度是通过中断完成,切换进程需要保留上一个进程的现场(本实验保留ebp、esp、eip);

3、PCB(process control block),进程控制块,是我们学习操作系统后遇到的第一个数据结构描述,它是对系统的进程进行管理的重要依据,和进程管理相关的操作无一不用到PCB中的内容。

4、中断指当出现需要时,CPU暂时停止当前程序的执行转而执行处理新情况的程序和执行过程。

二、实验过程

本次实验基于linux3.9.4完成。

在ubuntu下完成下面操作。

sudo apt-get install qemu # install QEMU
sudo ln -s /usr/bin/qemu-system-i386 /usr/bin/qemu
wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.9.4.tar.xz xz -d linux-3.9.4.tar.xz
tar -xvf linux-3.9.4.tar
cd linux-3.9.4
patch -p1 < ../mykernel_for_linux3.9.4sc.patch
make allnoconfig
make
qemu -kernel arch/x86/boot/bzImage


注:笔者实验时使用的是ubuntu16.04系统,make时会出现一个错误提示没有compiler-gcc5.h,这里提供下载地址http://pan.baidu.com/s/1geHcrmF;

本次实验代码为下面三个文件,注释讲解代码:

/*
*  linux/mykernel/mypcb.h
*
*  Kernel internal PCB types
*
*  Copyright (C) 2013  Mengning
*
*/
//本文件定义了进程控制块PCB,用来描述进程和保存进程状态
#define MAX_TASK_NUM        4            //最大进程数
#define KERNEL_STACK_SIZE   1024*2       //进程堆栈
/* CPU-specific state of this task */
struct Thread {//进程结构体,ip用来保存进程切换时的断点eip,sp用来保存切换时的栈顶指针esp
unsigned long		ip;
unsigned long		sp;
};

typedef struct PCB{    //进程控制块,含有进程的pid,状态state,进程堆栈空间stack,进程结构体,进程执行入口,以及下一个进程
int pid;
volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
unsigned long 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);  //进程调度(中断处理过程)

/*
*  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"
//该文件主要创建4个进程然后对它们的PCB进行初始化,最后开始执行第一个进程,当时间片用完后,中断来临,切换进程
tPCB task[MAX_TASK_NUM]; //创建4个进程控制块
tPCB * my_current_task = NULL;//当前执行进程
volatile int my_need_sched = 0;  //为1时表示时钟中断,需要切换进程

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;//进程执行入口为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++)//初始化另外3个进程
{
memcpy(&task[i],&task[0],sizeof(tPCB));//拷贝第一个进程PCB初始化另外3个
task[i].pid = i;//设置自己的pid
task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];//栈顶
task[i].stack[KERNEL_STACK_SIZE-2] = task[i].thread.sp;//入栈,实际上保存的是ebp
task[i].thread.sp -= 1;//因为入栈了,所以新的栈顶指针要-1
task[i].next = task[i-1].next;//进程PCB插入循环链表
task[i-1].next = &task[i];
}
/* start process 0 by task[0] *///开始执行第一个进程
pid = 0;
my_current_task = &task[pid];//当前执行进程为pid=0
asm volatile(            //因为要保存和切换进程,所以在C语言中插入汇编代码
"movl %1,%%esp\n\t" 	/* set task[pid].thread.sp to esp *///%1指PCB里的sp,将sp栈顶指针存入esp,设置栈顶esp
"pushl %1\n\t" 	        /* push ebp */                     //设置ebp,ebp存入栈顶
"pushl %0\n\t" 	        /* push task[pid].thread.ip *//函数my_process函数地址入栈,为了挑转执行该函数
"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受中断控制,在下一文件讲解
{
my_need_sched = 0;         //恢复my_need_sched
my_schedule();         //时间片执行完,切换进程,进程调度,在下一文件讲解
}
printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);//输出当前执行进程
}
}
}

/*
*  linux/mykernel/myinterrupt.c
*
*  Kernel internal my_timer_handler
*
*  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"

extern tPCB task[MAX_TASK_NUM];    //进程PCB链表
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 1
if(time_count%100 == 0 && my_need_sched != 1)
{
printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
my_need_sched = 1;                  //每个时间片完成后,发出中断消息,切换进程
}
time_count ++ ;
#endif
return;
}

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 *///若下一个进程可执行
{
my_current_task = next;   //修改当前执行进程为下一个
printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
/* switch to next process */ //保持现场
asm volatile(
"pushl %%ebp\n\t" 	    /* save ebp */ //保存当前进程ebp
"movl %%esp,%0\n\t" 	/* save esp */     //保存栈顶esp到PCB
"movl %2,%%esp\n\t"     /* restore  esp */ //设置下一个进程esp
"movl $1f,%1\n\t"       /* save eip */	   //保存当前进程断点,在标号1处
"pushl %3\n\t"                             //恢复下一个进程执行处
"ret\n\t" 	            /* restore  eip */ //执行下一个进程
"1:\t"                  /* next process start here */
"popl %%ebp\n\t"                           //当前进程再次被调度时,从此处执行,恢复ebp
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
}
return;
}

实验截图:

将上述三个文件放入mykernel后,需要重新make,然后调用模拟器
qemu -kernel arch/x86/boot/bzImage



三、总结

上述注释基本上把时间片轮转法细节论述了一遍:

1、每个进程都有自己的进程控制块PCB,PCB包含进程的关键信息如运行状态,进程调度信息,进程处理信息等,是一个进程存在的标志。

2、操作系统主要根据时间片来使多个进程并发运行,每个进程轮流执行一个时间片;

3、当前进程执行完后,即时间片中断来临时需要处理中断,然后切换到下一个可运行的进程;

4、处理时间片中断的过程实际上是保存当前进程的状态(本实验仅保存堆栈指针和中断点),然后恢复下一个进程的状态(堆栈指针和中断点),从而继续运行;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐