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下完成下面操作。
注:笔者实验时使用的是ubuntu16.04系统,make时会出现一个错误提示没有compiler-gcc5.h,这里提供下载地址http://pan.baidu.com/s/1geHcrmF;
本次实验代码为下面三个文件,注释讲解代码:
实验截图:
将上述三个文件放入mykernel后,需要重新make,然后调用模拟器
三、总结
上述注释基本上把时间片轮转法细节论述了一遍:
1、每个进程都有自己的进程控制块PCB,PCB包含进程的关键信息如运行状态,进程调度信息,进程处理信息等,是一个进程存在的标志。
2、操作系统主要根据时间片来使多个进程并发运行,每个进程轮流执行一个时间片;
3、当前进程执行完后,即时间片中断来临时需要处理中断,然后切换到下一个可运行的进程;
4、处理时间片中断的过程实际上是保存当前进程的状态(本实验仅保存堆栈指针和中断点),然后恢复下一个进程的状态(堆栈指针和中断点),从而继续运行;
原创作品转载请注明出处
《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、处理时间片中断的过程实际上是保存当前进程的状态(本实验仅保存堆栈指针和中断点),然后恢复下一个进程的状态(堆栈指针和中断点),从而继续运行;
相关文章推荐
- 完成一个简单的时间片轮转多道程序内核代码
- lab2:完成一个简单的时间片轮转多道程序内核代码
- 实验二:完成一个简单的时间片轮转多道程序内核代码
- 完成一个简单的时间片轮转多道程序内核代码
- Linux内核分析第二周学习博客——完成一个简单的时间片轮转多道程序内核代码
- 实验二:完成一个简单的时间片轮转多道程序内核代码
- Linux内核分析:完成一个简单的时间片轮转多道程序内核代码
- 完成一个简单的时间片轮转多道程序内核代码(一)
- Linux 内核分析 第二次作业 完成一个简单的时间片轮转多道程序内核代码
- Linux内核设计第二周学习总结 完成一个简单的时间片轮转多道程序内核代码
- 完成一个简单的时间片轮转多道程序内核代码
- Linux内核分析课程--完成一个简单的时间片轮转多道程序内核代码,理解操作系统是如何工作的
- linux内核分析第二周-完成一个简单的时间片轮转多道程序内核代码
- linux内核分析作业:操作系统是如何工作的进行:完成一个简单的时间片轮转多道程序内核代码
- 完成一个简单的时间片轮转多道程序内核代码
- 完成一个简单的时间片轮转多道程序内核代码
- Linux内核分析:完成一个简单的时间片轮转多道程序内核代码
- Linux内核分析,完成一个简单的时间片轮转多道程序内核代码
- 完成一个简单的时间片轮转多道程序内核代码(二)
- Linux内核分析—完成一个简单的时间片轮转多道程序内核代码