操作系统是怎么工作的——mykernel环境的搭建
可以参见:https://github.com/mengning/mykernel
首先感谢:http://www.euryugasaki.com/archives/1014
1.搭建实验环境(实验环境centos6.5)
wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.9.4.tar.xz # download Linux Kernel 3.9.4 source code
wget --no-check-certificate https://raw.github.com/mengning/mykernel/master/mykernel_for_linux3.9.4sc.patch # downloadmykernel_for_linux3.9.4sc.patch
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命令,需要进行相关配置!
qemu -kernel arch/x86/boot/bzImage
ln -s /usr/bin/qemu-system-i386 /usr/bin/qemu
2.代码分析
2.1 mypcb.h
#define MAX_TASK_NUM 4
#define KERNEL_STACK_SIZE 1024*8 //进程控制块 /* CPU-specific state of this task */
struct Thread { //存储ip,sp
unsigned long ip;
unsigned long sp;
}; typedef struct PCB{
int pid; //进程的id
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; //指定的入口,平时入口为main函数
struct PCB *next; //进程用链表连接
}tPCB; void my_schedule(void); //函数,调度器
2.2 mymain.c
#include <linux/types.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/tty.h>
#include <linux/vmalloc.h> #include "mypcb.h" tPCB task[MAX_TASK_NUM]; //声明tPCB类型的数组
tPCB * my_current_task = NULL; //声明当前task的指针
volatile int my_need_sched = ; //是否需要调度 void my_process(void); void __init my_start_kernel(void)
{
int pid = ;
int i;
/* Initialize process 0*/
task[pid].pid = pid; //初始化0号进程
task[pid].state = ;/* -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-];//
task[pid].next = &task[pid]; //指向自己,系统启动只有0号进程
/*fork more process */
for(i=;i<MAX_TASK_NUM;i++)
{
memcpy(&task[i],&task[],sizeof(tPCB));
task[i].pid = i;
task[i].state = -;
task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-];
task[i].next = task[i-].next; //新进程加到进程链表尾部
task[i-].next = &task[i];
}
/* start process 0 by task[0] */
pid = ;
my_current_task = &task[pid];
asm volatile(
"movl %1,%%esp\n\t" /* set task[pid].thread.sp(%1) 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 ,ret之后0号进程正式启动*/
"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 = ;
while()
{
i++;
if(i% == ) //循环1000万次判断是否需要调度
{
printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);
if(my_need_sched == )
{
my_need_sched = ;
my_schedule();
}
printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
}
}
}
2.3 myinterrupt.c
#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];
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = ; /*
* 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% == && my_need_sched != ) //设置时间片的大小,时间片用完时设置调度的标志
{
printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
my_need_sched = ;
}
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 == )/* -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 */
"movl %%esp,%0\n\t" /* save esp */
"movl %2,%%esp\n\t" /* restore esp */
"movl $1f,%1\n\t" /* save eip,%1f指接下来的标号为1的位置 */
"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)
); }
else
{
next->state = ;
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)
);
}
return;
}
2.4总结如下
2.4.1
my_start_kernel()帮助我们创建进程;
my_timer_handler()来记录时间,触发调度;
my_start_kernel()中创建的0号进程的入口地址是my_process()。
2.4.2
my_process()作为每个进程的入口地址,开始逐个执行;
通过到达时间片的轮转时刻,my_process()会调用my_schedule()来保护进程堆栈现场,完成进程间的切换;
在mymain.c中实现内核的启动,通过my_start_kernel()来初始化进程;
2.4.3总体框架(不够完善)
2.4.4具体分析
2.4.4.1 首先看入口函数my_start_kernel()
int pid = ;
int i;
/* Initialize process 0*/
task[pid].pid = pid; //初始化0号进程
task[pid].state = ;/* -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-];//
task[pid].next = &task[pid]; //指向自己,系统启动只有0号进程
上述完成了对0号进程的初始化,包括
设置task[pid].state;
0号进程的入口地址为my_process();
task[0]的进程属性中ip被设置成了my_process()函数的入口地址,sp设置成了堆栈的首地址
/*fork more process */
for(i=;i<MAX_TASK_NUM;i++)
{
memcpy(&task[i],&task[],sizeof(tPCB));
task[i].pid = i;
task[i].state = -;
task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-];
task[i].next = task[i-].next; //新进程加到进程链表
task[i-].next = &task[i];
}
以0号进程为模板复制了MAX_TASK_NUM-1个进程,进程链表如下:
之后:
/* start process 0 by task[0] */
pid = ;
my_current_task = &task[pid];
asm volatile(
"movl %1,%%esp\n\t" /* set task[pid].thread.sp(%1) 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 ,ret之后0号进程正式启动*/
"popl %%ebp\n\t"
:
: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/
);
第一步(set task[pid].thread.sp(%1) to esp),将task[0].thread.sp拿去修改esp的值,这时候内核堆栈的栈顶被修改到了task[0]的sp位置;
第二步(push ebp),在task[0]的sp位置处压入ebp的值,来保护原来的内核堆栈;
第三步(push task[pid].thread.ip ,pop task[pid].thread.ip to eip),设置task[0].thread.ip的值给eip,这样就能够保证cpu下一步能够执行0号进程,完成了进入my_process()的过程。
注意:此时eip的值已经被修改,CPU进入my_process(),所以最后一句的popl ebp并不会被立即执行了。
2.4.4.2 再看my_process()与my_timer_handler()
void my_process(void)
{
int i = ;
while()
{
i++;
if(i% == ) //循环1000万次判断是否需要调度
{
printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);
if(my_need_sched == )
{
my_need_sched = ;
my_schedule();
}
printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
}
}
}
void my_timer_handler(void)
{
#if 1
if(time_count% == && my_need_sched != ) //设置时间片的大小,时间片用完时设置调度的标志
{
printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
my_need_sched = ;
}
time_count ++ ;
#endif
return;
}
经过前面所述步骤,程序执行转到了my_process()。由于这时候所有的进程只有task[0]才是执行态,my_need_sched == 0,无论如何,都不会触发时间片的轮转从而调度其他的进程。但是有my_timer_handler()函数(该函数会被linux内核自动调用),my_timer_handler()能够得以自动执行,每次执行时,会检查时间计数以及当前进程是否应该被调度,当满足条件后,会修改0号进程的my_need_sched值为1,0号进程就被暂停执行,进而调用my_schedule()函数。
2.4.4.3 最后分析核心函数my_schedule()
if(my_current_task == NULL
|| my_current_task->next == NULL)
{
return;
}
首先进行一个简单的判断(判断当前的任务和接下来要被执行的任务是否为空)
/* schedule */
next = my_current_task->next;
prev = my_current_task;
if(next->state == )/* -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 */
"movl %%esp,%0\n\t" /* save esp */
"movl %2,%%esp\n\t" /* restore esp */
"movl $1f,%1\n\t" /* save eip,%1f指接下来的标号为1的位置 */
"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)
); }
在上面的代码中,next指针指向了当前任务的下一个任务,prev指针指向了当前0号进程。由建立的进程链表知:下一个被调度的进程应该是task[3],而此时task[3]的状态是-1,会执行else中的部分:
else
{
next->state = ;
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)
);
}
task[3]的状态被更改为执行态,当前任务被修改为task[3]。
此时便开 始进行0号进程的现场保护工作,以便日后的调度。堆栈会保存ebp的值,同时将esp保存到0号进程的sp中。这是因为,当切换回0号进程的时候,可以通过0号进程内sp的值来寻找要执行的task[3]的进程堆栈。然后将task[3]的sp值设置到esp和ebp中,创建好了task[3]的执行堆栈。将task[3]执行任务的入口地址ip设置给eip,完成对任务的执行入口设置。这时候实际上后面的返回仍然不会被执行,因为在修改eip后,cpu又去执行下一步的my_process()了,因此这时候就会出现各种循环调用,利用时间片的统计,完成对进程之间的切换。
操作系统是怎么工作的——mykernel环境的搭建的更多相关文章
- Linux内核分析第二周学习总结:操作系统是如何工作的?
韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.函数调用堆栈 ...
- Linux内核分析之操作系统是如何工作的
在本周的课程中,孟老师主要讲解了操作系统是如何工作的,我根据自己的理解写了这篇博客,请各位小伙伴多多指正. 一.知识点总结 1. 三个法宝 存储程序计算机:所有计算机基础性的逻辑框架. 堆栈:高级语言 ...
- 《Linux内核分析》第二周笔记 操作系统是如何工作的
操作系统是如何工作的 一.函数调用堆栈 1.三个法宝 计算机是如何工作的?(总结)——三个法宝(存储程序计算机.函数调用堆栈.中断机制) 1)存储程序计算机工作模型,计算机系统最最基础性的逻辑结构: ...
- 《Linux内核分析》第二周:操作系统是如何工作的
杨舒雯 20135324 北京电子科技学院 杨舒雯 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1 ...
- 《Linux内核分析》第二周 操作系统是如何工作的?
[刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK TWO(2 ...
- Linux内核分析——操作系统是如何工作的
万子惠 + 原创作品转载请注明出处 + <Linux内核分析> 实验部分 使用实验楼的虚拟机打开shell 然后cd mykernel 您可以看到qemu窗口输出的内容的代码mymain. ...
- Linux操作系统学习_操作系统是如何工作的
实验五:Linux操作系统是如何工作的? 学号:SA1****369 操作系统工作的基础:存储程序计算机.堆栈(函数调用堆栈)机制和中断机制 首先要整明白的一个问题是什么是存储程序计算机?其实存储程序 ...
- Linux内核分析— —操作系统是如何工作的(20135213林涵锦)
mykernel实验指导(操作系统是如何工作的) 实验要求 运行并分析一个精简的操作系统内核,理解操作系统是如何工作的 使用实验楼的虚拟机打开shell cd LinuxKernel/linux-3. ...
- Linux内核分析第二周:操作系统是如何工作的
第一讲 函数调用堆栈 计算机是如何工作的? (总结)——三个法宝 1,存储程序计算机工作模型,计算机系统最最基础性的逻辑结构: 2,函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的时候堆 ...
随机推荐
- Import SHA2 SSL cert to Windows IIS7
Import SHA2 SSL cert to Windows IIS7 1.You will get 3 piece of certs from GeoTrust, and save them to ...
- 解读Nodejs多核处理模块cluster
来源: http://blog.fens.me/nodejs-core-cluster/ 从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发. ...
- 【VB技巧】VB ListView 控件功能使用详解
来源:http://lcx.cc/?i=494 ListView控件 在工具箱上击鼠标右键,选择快捷菜单的Components(部件)项,在控件列表中选择Microsoft Windows Commo ...
- C#将字符串转换为整型的三种方法的总结
在C#中,要将一个字符串或浮点数转换为整数,基本上有三种方法: (1)使用强制类型转换:(int)浮点数 (2)使用Convert.ToInt32(string) (3)使用int.Parse(str ...
- Hadoop学习1--解决启动过程中的问题
方法:http://www.aboutyun.com/thread-12694-1-1.html http://www.linuxidc.com/topicnews.aspx?tid=13 http: ...
- 黄聪:MYSQL5.6缓存性能优化my.ini文件配置方案
使用MYSQL版本:5.6 [client] …… default-character-set=gbk default-storage-engine=MYISAM max_connections=10 ...
- QRCode.jar生成二维码
参考http://www.oschina.net/code/snippet_2252392_45457 package com.ORcode; import java.awt.image.Buffer ...
- DBA_Oracle基本体系内存和进程结构(概念)
2014-08-05 Created By BaoXinjian
- 0814JavaScript简介、基本语法、运算符、转换
一.JavaScript简介 1.JavaScript是个什么东西? 它是个脚本语言,需要有宿主文件,它的宿主文件是HTML文件. 2.它与Java什么关系? 没有什么直接的联系,Java是Sun公司 ...
- pythomn
等我学号数据结构,明年就去找三胖 前端,写js相关代码.了解前端架构 而非页面设计 主要使用的是脚本语言 了解http web相关技术等 知道页面调优 浏览器加载方式等