可以参见: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环境的搭建的更多相关文章

  1. Linux内核分析第二周学习总结:操作系统是如何工作的?

    韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.函数调用堆栈 ...

  2. Linux内核分析之操作系统是如何工作的

    在本周的课程中,孟老师主要讲解了操作系统是如何工作的,我根据自己的理解写了这篇博客,请各位小伙伴多多指正. 一.知识点总结 1. 三个法宝 存储程序计算机:所有计算机基础性的逻辑框架. 堆栈:高级语言 ...

  3. 《Linux内核分析》第二周笔记 操作系统是如何工作的

    操作系统是如何工作的 一.函数调用堆栈 1.三个法宝 计算机是如何工作的?(总结)——三个法宝(存储程序计算机.函数调用堆栈.中断机制) 1)存储程序计算机工作模型,计算机系统最最基础性的逻辑结构: ...

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

    杨舒雯 20135324 北京电子科技学院 杨舒雯 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1 ...

  5. 《Linux内核分析》第二周 操作系统是如何工作的?

    [刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK TWO(2 ...

  6. Linux内核分析——操作系统是如何工作的

    万子惠 + 原创作品转载请注明出处 + <Linux内核分析> 实验部分 使用实验楼的虚拟机打开shell 然后cd mykernel 您可以看到qemu窗口输出的内容的代码mymain. ...

  7. Linux操作系统学习_操作系统是如何工作的

    实验五:Linux操作系统是如何工作的? 学号:SA1****369 操作系统工作的基础:存储程序计算机.堆栈(函数调用堆栈)机制和中断机制 首先要整明白的一个问题是什么是存储程序计算机?其实存储程序 ...

  8. Linux内核分析— —操作系统是如何工作的(20135213林涵锦)

    mykernel实验指导(操作系统是如何工作的) 实验要求 运行并分析一个精简的操作系统内核,理解操作系统是如何工作的 使用实验楼的虚拟机打开shell cd LinuxKernel/linux-3. ...

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

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

随机推荐

  1. 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 ...

  2. 解读Nodejs多核处理模块cluster

    来源: http://blog.fens.me/nodejs-core-cluster/ 从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发. ...

  3. 【VB技巧】VB ListView 控件功能使用详解

    来源:http://lcx.cc/?i=494 ListView控件 在工具箱上击鼠标右键,选择快捷菜单的Components(部件)项,在控件列表中选择Microsoft Windows Commo ...

  4. C#将字符串转换为整型的三种方法的总结

    在C#中,要将一个字符串或浮点数转换为整数,基本上有三种方法: (1)使用强制类型转换:(int)浮点数 (2)使用Convert.ToInt32(string) (3)使用int.Parse(str ...

  5. Hadoop学习1--解决启动过程中的问题

    方法:http://www.aboutyun.com/thread-12694-1-1.html http://www.linuxidc.com/topicnews.aspx?tid=13 http: ...

  6. 黄聪:MYSQL5.6缓存性能优化my.ini文件配置方案

    使用MYSQL版本:5.6 [client] …… default-character-set=gbk default-storage-engine=MYISAM max_connections=10 ...

  7. QRCode.jar生成二维码

    参考http://www.oschina.net/code/snippet_2252392_45457 package com.ORcode; import java.awt.image.Buffer ...

  8. DBA_Oracle基本体系内存和进程结构(概念)

    2014-08-05 Created By BaoXinjian

  9. 0814JavaScript简介、基本语法、运算符、转换

    一.JavaScript简介 1.JavaScript是个什么东西? 它是个脚本语言,需要有宿主文件,它的宿主文件是HTML文件. 2.它与Java什么关系? 没有什么直接的联系,Java是Sun公司 ...

  10. pythomn

    等我学号数据结构,明年就去找三胖 前端,写js相关代码.了解前端架构 而非页面设计 主要使用的是脚本语言 了解http web相关技术等 知道页面调优 浏览器加载方式等