《Linux内核原理与分析》第三周作业

实验:基于kernel的简单的时间片轮转多道程序内核

1、实验要求

  • 完成一个简单的时间片轮转多道程序内核代码

2、实验过程

  • 进入实验楼的linux环境,打开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

执行的效果如下:

  • 在mykernel的基础上添加mypcb.h,修改mymain.c和myinterrupt.c文件,实现一个简单的操作系统内核,实现效果如下:

3、mykernel时间片轮转代码分析

mypcb.h

#define MAX_TASK_NUM        4
#define KERNEL_STACK_SIZE   1024*8

/* CPU-specific state of this task */
struct Thread {
    unsigned long       ip;   //对应eip
    unsigned long       sp;   //对应esp
};

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;   //入口
    struct PCB *next; 
}tPCB;

void my_schedule(void); //声明调度函数

本mypcb.h头文件主要定义了程序控制块PCB,包括:
pid:定义进程id
state:进程状态标记,-1是未运行,0为运行,>0为终止
stack:定义使用的堆栈
thread:定义线程
task_entry:进程入口
next:链表指向下一个PCB

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引用全局变量
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0;

void my_timer_handler(void) //时钟中断触发本函数
{
#if 1
    if(time_count%100 == 0 && my_need_sched != 1) //当时钟中断发生100次,并且my_need_sched不为1时,赋值为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);  
        /* 切换进程 */
        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)
        ); 
    
    }
    else
    {
        next->state = 0;
        my_current_task = next;
        printk(KERN_NOTICE ">>>switch %d to %d<<<
",prev->pid,next->pid);
        /* switch to new process */
        asm volatile(   
            "pushl %%ebp
	"       /* save ebp */
            "movl %%esp,%0
	"     /* save esp */
            "movl %2,%%esp
	"     /* restore  esp */
            "movl %2,%%ebp
	"     /* restore  ebp */
            "movl $1f,%1
	"       /* save eip */  
            "pushl %3
	" 
            "ret
	"               /* restore  eip */
            : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
            : "m" (next->thread.sp),"m" (next->thread.ip)
        );          
    }   
    return; 
}

本c文件中,定义了my_timer_handler和my_schedule两个函数调用,前者是当时钟中断发生100次,并且my_need_sched不为1时,赋值为1,是mymain.c中my_process函数判定主动调度的标志;后者是执行调度的具体过程,下面对切换进程的汇编代码进行分析:
"pushl %%ebp " /* save ebp / ebp入栈
"movl %%esp,%0 " /
save esp / 保存当前esp到进程的sp中
"movl %2,%%esp " /
restore esp / esp指向下一进程
"movl $1f,%1 " /
save eip / 将1f存储到进程的ip中,$1f是标号“1: ”处,再次调度到该进程时就会从1:开始执行
"pushl %3 " 下一进程的ip入栈
"ret " /
restore eip / eip指向下一进程的起始地址
"1: " /
next process start here */ 下一进程从此处开始执行
"popl %%ebp " 执行完后出栈释放空间
: "=m" (prev->thread.sp),"=m" (prev->thread.ip) 分别对于上面的%0,%1
: "m" (next->thread.sp),"m" (next->thread.ip) 分别对应上面的%2,%3

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];  //PCB的数组task
tPCB * my_current_task = NULL; //当前task指针
volatile int my_need_sched = 0; //是否需要调度

void my_process(void);  //my_process函数声明

void __init my_start_kernel(void) //mykernel内核代码的入口
{
    int pid = 0;
    int i;
    /* 初始化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其他进程 */
    for(i=1;i<MAX_TASK_NUM;i++)
    {
        memcpy(&task[i],&task[0],sizeof(tPCB));
        task[i].pid = i;
        task[i].state = -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];
    }
    
    /* 用task[0]开始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 */
        "popl %%ebp
	"
        : 
        : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)   /* input c or d mean %ecx/%edx*/
    );
}   

void my_process(void)
{
    int i = 0;
    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);
        }     
    }
}

本c文件中,有my_start_kernel和my_process两个函数,其中,前者为mykernel内核代码的入口函数,后者为进程的入口函数,进程在运行中输出当前进程号,并通过my_need_sched变量判断是否需要调度。
其中对0号进程的启动汇编代码进行分析:
"movl %1,%%esp " /* set task[pid].thread.sp to esp / 将当前进程0的sp赋给esp
"pushl %1 " /
push ebp / 进程0的sp入栈
"pushl %0 " /
push task[pid].thread.ip / 进程0的ip入栈
"ret " /
pop task[pid].thread.ip to eip / 将进程0的ip赋给eip
"popl %%ebp " 执行完其他进程,回到0号进程,出栈
:
: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /
input c or d mean %ecx/%edx*/ 输入,将0号进程的ip、sp值分别存入ecx、edx寄存器中,分别对应上面的%0,%1

4、问题与总结

本次实验没有遇到什么重大的问题,但是小毛病犯了一堆,比如在将代码拷入实验楼linux环境的vim中时,实验楼的粘贴板不知为何复制粘贴会缺失一段代码,后在编译c文件的时候总是报错,这个问题找了好久,后来发现是粘贴板粘贴的代码缺失。还有一个是在make时找不到文件,后来发现修改代码是在/mykernel目录下进行修改的,make编译内核需要在LinuxKernel/linux-3.9.4目录下进行,需要返回上一级菜单进行make。
总共来讲,本周各种事情比较多,学习的计划一拖再拖,推迟了好久才完成,以后一定要合理分配时间,这一点尤为重要。
还有,学习要认真仔细,尽量避免因为犯低级错误而白白消耗大量学习时间。

原文地址:https://www.cnblogs.com/intoxication/p/9866849.html