实验要求
基于mykernel 2.0编写一个操作系统内核
- 按照https://github.com/mengning/mykernel 的说明配置mykernel 2.0,熟悉Linux内核的编译;
- 基于mykernel 2.0编写一个操作系统内核,参照https://github.com/mengning/mykernel 提供的范例代码
- 简要分析操作系统内核核心功能及运行工作机制
实验过程
打开shell并依次输入下列命令
cd LinuxKernel/linux-3.9.4 rm -rf mykernel patch -p1 < ../mykernel_for_linux3.9.4sc.patch make allnoconfig make qemu -kernel arch/x86/boot/bzImage
在输入make命令后等待一段时间进行编译;时间较长
然后输入qemu -kernel arch/x86/boot/bzImage进行运行,效果图如下
可以看出,my_start_kernel here与my_time_handler here不断的循环输出出来
时间片轮转多道批处理程序内核代码分析
将孟老师提供的三个文件mymain.c myinterrupt.c mypcb.h覆盖到原目录
依次执行下列命令
make clean make allnoconfig make qemu -kernel arch/x86/boot/bzImage
得到运行结果如下
mypcb.h
/* * linux/mykernel/mypcb.h * * Kernel internal PCB types * * Copyright (C) 2013 Mengning * */ #define MAX_TASK_NUM 4 #define KERNEL_STACK_SIZE 1024*2 # unsigned long /* CPU-specific state of this task */ struct Thread { unsigned long ip; unsigned long sp; }; typedef struct PCB{ 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);
在这个文件里,定义了 Thread 结构体和PCB结构体, Thread 结构体用于存储当前进程中正在执行的线程的EIP和ESP寄存器的信息,PCB结构体中的各个字段含义如下:
pid:进程号
state:进程状态,-1表示不能运行,0表示可以运行,大于0表示暂停
stack:进程使用的堆栈
thread:当前正在执行的线程信息
task_entry:进程入口函数
next:指向下一个PCB,本次实验中中所有的PCB是以链表的形式组织起来的。
my_schedule函数声明,在myinterrupt.c中实现,用于进程切换。
mymain.h
/* * 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" tPCB task[MAX_TASK_NUM]; tPCB * my_current_task = NULL; volatile int my_need_sched = 0; 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; 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++) { memcpy(&task[i],&task[0],sizeof(tPCB)); task[i].pid = i; //*(&task[i].stack[KERNEL_STACK_SIZE-1] - 1) = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-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]; } /* start process 0 by task[0] */ pid = 0; my_current_task = &task[pid]; asm volatile( "movl %1,%%esp " /* set task[pid].thread.sp to esp */ "pushl %1 " /* push ebp */ "pushl %0 " /* push task[pid].thread.ip */ "ret " /* pop task[pid].thread.ip to eip */ : : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/ ); } int i = 0; void my_process(void) { while(1) { i++; if(i%10000000 == 0) { printk(KERN_NOTICE "this is process %d - ",my_current_task->pid); if(my_need_sched == 1) { my_need_sched = 0; my_schedule(); } printk(KERN_NOTICE "this is process %d + ",my_current_task->pid); } } }
my_start_kernel 是系统启动后最先调用的函数,在这个函数里完成了0号进程的初始化和启动,并创建了其它的进程PCB,以方便后面的调度。在模拟系统里,每个进程的函数代码都是一样的,即 my_process 函数,my_process 在执行的时候,会打印出当前进程的 id,从而使得我们能够看到当前哪个进程正在执行。
在汇编语言当中
%1指的是task[pid].thread.sp,%0是task[pid].thread.ip
“movl %1,%%esp
” 将原堆栈的栈顶放到sp寄存器中
“pushl %1
” 将ep寄存器的值存入栈
“pushl %0
” 将当前进程ip值入栈
这样0号进程开始启动,程序去执行my_process()
myinterrupt.c
/* * 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]; 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%1000 == 0 && my_need_sched != 1) { printk(KERN_NOTICE ">>>my_timer_handler here<<< "); 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<<< "); /* 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<<< ",prev->pid,next->pid); /* switch to next process */ asm volatile( "pushl %%ebp " /* save ebp */ "movl %%esp,%0 " /* save esp */ "movl %2,%%esp " /* restore esp */ "movl $1f,%1 " /* save eip */ "pushl %3 " "ret " /* restore eip */ "1: " /* next process start here */ "popl %%ebp " : "=m" (prev->thread.sp),"=m" (prev->thread.ip) : "m" (next->thread.sp),"m" (next->thread.ip) ); } return; }
mymain.c:负责完成各个进程的初始化并且启动0号进程;
myinterrupt.c:负责完成时钟中断的处理及进程的切换;
mypcb.h:负责完成进程控制块PCB结构体的定义。
总结
操作系统的工作主要依赖三项:
1.存储程序计算机
2.函数嗲用堆栈机制
3.中断支持
堆栈是C语言程序运行时必须使用的记录函数调用路径和参数存储的空间,堆栈的具体作用有:记录函数调用框架、传递函数参数、保存返回值的地址、提供内部局部变量的存储空间等。而中断的支持也不容忽视,有了中断才有了多道处理程序,在没有中断机制之前,计算机智能一个程序一个程序的运行,也就是批处理,而无法实现并发执行。有了中断机制之后,当中断信号发生时,CPU把当前正在执行的程序的EIP、ESP寄存器的内容都压到堆栈当中进行保存。之后转而执行其他的程序,等执行过后还能依靠堆栈来恢复现场,恢复EIP、ESP寄存器的值,进而继续执行中断前的程序。