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就完成了。

猜你喜欢

转载自www.cnblogs.com/gesiyao/p/10520510.html