linux操作系统实验:简单的时间片轮转多道程序内核代码分析
原创作品转载请注明出处
参考:https://github.com/mengning/linuxkernel/
学号:094
0.实验步骤
- 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 # download Linux Kernel 3.9.4 source code
- wget https://raw.github.com/mengning/mykernel/master/mykernel_for_linux3.9.4sc.patch # download mykernel_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 -kernel arch/x86/boot/bzImage 从qemu窗口中您可以看到my_start_kernel在执行,同时my_timer_handler时钟中断处理程序周期性执行。
- cd mykernel 您可以看到qemu窗口输出的内容的代码mymain.c和myinterrupt.c
- 当前有一个CPU执行C代码的上下文环境,同时具有中断处理程序的上下文环境,我们初始化好了系统环境。
- 您只要在mymain.c基础上继续写进程描述PCB和进程链表管理等代码,在myinterrupt.c的基础上完成进程切换代码,一个可运行的小OS kernel就完成了。
一、实验环境准备
在virual box + centos7 内核4.0版本环境下,使用
wget https://download.qemu.org/qemu-3.1.0.tar.xz tar xvJf qemu-3.1.0.tar.xz cd qemu-3.1.0 ./configure make
(因为在centos中,qemu和qemu-kvm不兼容,已被移除,所以下源码编译)
3.1.0是最新版本,也可以在官网上https://download.qemu.org/下载不同版本,我先后试用了3.1.0和2.1.3版本
在./configure中可以指定一些参数,最重要的是--target-list设置编译的架构模式,默认是全部编译(很费时)
打开qemu-2.1.3,并编译完成后
其中i386-softmmu中qemu-system-i386就是我们需要用到的模拟工具,而且由于我选择默认编译所有架构,所以还会有许多,如qemu-system-x86_64
但是这里出现了一个问题,qemu-system-i386无法使用,找了许多方法都没有解决,推测可能是由于没有编译完所有的target-list导致的(我编到一半告诉我设备空间不够了)
后来选择在实验楼完成实验。
按部就班的做,完成准备工作,截图如下:
二、简单时间片轮转多道程序内核代码分析
从孟老师https://github.com/mengning/mykernel处,下载mymain.c myinterrupt.c mypcb.h。
覆盖原来的mykernel下的mymain.c myinterrupt.c 新建mypcb.h。
重新编译内核,在linuxKernel/linux-3.9.4文件夹下,执行下面的命令。
make allnoconfig
make
qemu -kernel arch/x86/boot/bzImage
运行结果如下:
源码分析:
mypcb.h:
1.定义进程控制块,包含进程的进程号pid,进程状态state,堆栈信息stack,线程信息thread,进程入口task_entry,下一个PCBnext。
2.定义线程信息,包含eip寄存器信息和esp寄存器信息。
3.my_schedule函数声明,在myinterrupt.c中实现,用于进程切换。
其主要代码如下:
1 /* 2 * linux/mykernel/mypcb.h 3 * 4 * Kernel internal PCB types 5 * 6 * Copyright (C) 2013 Mengning 7 * 8 */ 9 10 #define MAX_TASK_NUM 4 11 #define KERNEL_STACK_SIZE 1024*2 # unsigned long 12 /* CPU-specific state of this task */ 13 struct Thread { 14 unsigned long ip; 15 unsigned long sp; 16 }; 17 18 typedef struct PCB{ 19 int pid; 20 volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ 21 unsigned long stack[KERNEL_STACK_SIZE]; 22 /* CPU-specific state of this task */ 23 struct Thread thread; 24 unsigned long task_entry; 25 struct PCB *next; 26 }tPCB; 27 28 void my_schedule(void);
myinterrupt,c文件:
1 /* 2 * linux/mykernel/myinterrupt.c 3 * 4 * Kernel internal my_timer_handler 5 * 6 * Copyright (C) 2013 Mengning 7 * 8 */ 9 #include <linux/types.h> 10 #include <linux/string.h> 11 #include <linux/ctype.h> 12 #include <linux/tty.h> 13 #include <linux/vmalloc.h> 14 15 #include "mypcb.h" 16 17 extern tPCB task[MAX_TASK_NUM]; // 外部变量定义的声明 18 extern tPCB * my_current_task; // 外部变量定义的声明 19 extern volatile int my_need_sched; // 外部变量定义的声明 20 volatile int time_count = 0; // 时间计数器 21 22 /* 23 * 时钟中断处理函数 24 */ 25 void my_timer_handler(void) 26 { 27 #if 1 28 if(time_count%1000 == 0 && my_need_sched != 1) // 模拟时间片轮转 29 { 30 printk(KERN_NOTICE ">>>my_timer_handler here<<<\n"); 31 my_need_sched = 1; 32 } 33 time_count ++ ; 34 #endif 35 return; 36 } 37 38 void my_schedule(void) 39 { 40 tPCB * next; 41 tPCB * prev; 42 43 if(my_current_task == NULL 44 || my_current_task->next == NULL) 45 { 46 return; 47 } 48 printk(KERN_NOTICE ">>>my_schedule<<<\n"); 49 /* 进程调度*/ 50 next = my_current_task->next; 51 prev = my_current_task; 52 if(next->state == 0)/* 判断下一个进程是否可以运行 */ 53 { 54 my_current_task = next; 55 printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid); 56 /* 切换下一个进程 */ 57 asm volatile( 58 "pushl %%ebp\n\t" /* save ebp */ 59 "movl %%esp,%0\n\t" /* save esp */ 60 "movl %2,%%esp\n\t" /* restore esp */ 61 "movl $1f,%1\n\t" /* save eip */ 62 "pushl %3\n\t" 63 "ret\n\t" /* restore eip */ 64 "1:\t" /* next process start here */ 65 "popl %%ebp\n\t" 66 : "=m" (prev->thread.sp),"=m" (prev->thread.ip) 67 : "m" (next->thread.sp),"m" (next->thread.ip) 68 ); 69 } 70 return; 71 }
my_schedule方法实现进程切换,其核心为最后一段汇编代码:
"pushl %%ebp\n\t" 将ebp信息压栈;
"movl %%esp,%0\n\t" 将esp信息保存到prev->thread.sp(当前进程sp)中;
"movl %2,%%esp\n\t" 将next->thread.sp的值保存到esp寄存器中;
"movl $1f,%1\n\t" 将下一条指令的地址保存到prev->thread.ip(当前进程的ip)中
"pushl %3\n\t" 将下一个进程的ip的值压栈;
"ret\n\t" 通过ret指令从栈顶弹出原来保存在这里的eip的值,放入EIP中。
"popl %%ebp\n\t" 开始执行下一个进程,并且出栈下一个进程ebp信息。
mymain.c
1 /* 2 * linux/mykernel/mymain.c 3 * 4 * Kernel internal my_start_kernel 5 * 6 * Copyright (C) 2013 Mengning 7 * 8 */ 9 #include <linux/types.h> 10 #include <linux/string.h> 11 #include <linux/ctype.h> 12 #include <linux/tty.h> 13 #include <linux/vmalloc.h> 14 15 16 #include "mypcb.h" 17 18 tPCB task[MAX_TASK_NUM]; 19 tPCB * my_current_task = NULL; 20 volatile int my_need_sched = 0; 21 22 void my_process(void); 23 24 25 void __init my_start_kernel(void) 26 { 27 int pid = 0; 28 int i; 29 /* Initialize process 0*/ 30 task[pid].pid = pid; 31 task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */ 32 task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; 33 task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; 34 task[pid].next = &task[pid]; 35 /*fork more process */ 36 for(i=1;i<MAX_TASK_NUM;i++) 37 { 38 memcpy(&task[i],&task[0],sizeof(tPCB)); 39 task[i].pid = i; 40 task[i].state = -1; 41 task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1]; 42 task[i].next = task[i-1].next; 43 task[i-1].next = &task[i]; 44 } 45 /* start process 0 by task[0] */ 46 pid = 0; 47 my_current_task = &task[pid]; 48 asm volatile( 49 "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */ 50 "pushl %1\n\t" /* push ebp */ 51 "pushl %0\n\t" /* push task[pid].thread.ip */ 52 "ret\n\t" /* pop task[pid].thread.ip to eip */ 53 "popl %%ebp\n\t" 54 : 55 : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/ 56 ); 57 } 58 void my_process(void) 59 { 60 int i = 0; 61 while(1) 62 { 63 i++; 64 if(i%10000000 == 0) 65 { 66 printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid); 67 if(my_need_sched == 1) 68 { 69 my_need_sched = 0; 70 my_schedule(); 71 } 72 printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid); 73 } 74 } 75 }
my_start_kernel完成了0号进程的初始化和启动,并创建了其它的进程PCB,并以链表形式串联。
系统通过my_timer_handle函数实现对于my_process函数的控制。
my_start_kernel
汇编过程如下:
"movl %1,%%esp\n\t" 将0号进程的esp的值赋给esp寄存器
"pushl %1\n\t" 将0号进程此esp入栈,堆栈状态为进程0的堆栈
"pushl %0\n\t 将0号进程的eip的值压栈
"ret\n\t" 通过ret指令,让栈顶的eip的值出栈到eip寄存器中,完成进程0的启动
总结一下:
mypcb.h完成PCB结的定义;mymain.c完成进程的初始化并启动0号进程;myinterrupt.c完成时钟中断的处理和进程的切换。
学号094
- cd linux-3.9.4
- patch -p1 < ../mykernel_for_linux3.9.4sc.patch
- make allnoconfig
- make
- qemu -kernel arch/x86/boot/bzImage 从qemu窗口中您可以看到my_start_kernel在执行,同时my_timer_handler时钟中断处理程序周期性执行。
- cd mykernel 您可以看到qemu窗口输出的内容的代码mymain.c和myinterrupt.c
- 当前有一个CPU执行C代码的上下文环境,同时具有中断处理程序的上下文环境,我们初始化好了系统环境。
- 您只要在mymain.c基础上继续写进程描述PCB和进程链表管理等代码,在myinterrupt.c的基础上完成进程切换代码,一个可运行的小OS kernel就完成了。