2020-2021-1 20209311《Linux内核原理与分析》第三周作业

2020-2021-1 20209311《Linux内核原理与分析》第三周作业

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

1.实验内容

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

2.实验过程

使用实验楼的虚拟机打开shell,依次输入如下命令:

rm -rf mykernel
patch -p1 < ../mykernel_for_linux3.9.4sc.patch
make allnoconfig
make
qemu -kernel arch/x86/boot/bzImage

结果如图所示:

查看 mymain.c 和 myinterrupt.c ,结果如图:


替换 mymain.c 和 myinterrupt.c ,并加入mypcb.h。

mypcb.h

#define MAX_TASK_NUM        4
#define KERNEL_STACK_SIZE   1024*2

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;

mymain.c

#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;

void my_timer_handler(void)
{
    if(time_count%1000 == 0 && my_need_sched != 1)
    {
        printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
        my_need_sched = 1;
    }
    time_count ++ ;  
    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 == 0)/* -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 rbp of prev */
            "movl %%esp,%0\n\t"     /* save rsp of prev */
            "movl %2,%%esp\n\t"     /* restore  rsp of next */
            "movl $1f,%1\n\t"       /* save rip of prev */    
            "pushl %3\n\t"
            "ret\n\t"                 /* restore  rip of next */
            "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)
        );
    }  
    return;    
}

myinterrupt.c

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].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\n\t" 	/* set task[pid].thread.sp to rsp */
    	"pushl %1\n\t" 	        /* push rbp */
    	"pushl %0\n\t" 	        /* push task[pid].thread.ip */
    	"ret\n\t" 	            /* pop task[pid].thread.ip to rip */
    	: 
    	: "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 -\n",my_current_task->pid);
            if(my_need_sched == 1)
            {
                my_need_sched = 0;
        	    my_schedule();
        	}
        	printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
        }     
    }
}

mypcb.h中定义的结构体PCB是进程管理块,其中,pid代表进程的标识符;state代表进程状态,初始值是-1,如果被调度运行起来,其值就会变成0;stack代表进程的用户栈;thread代表正在执行的线程信息,sp、ip分别代表当前栈顶和执行位置,task_entry代表进程的入口,next指向下一个PCB。mymain.c初始化了四个进程,并将其连接起来作为调度程序序列;my_start_kernel函数的作用是初始化进程0,使pcb的指针指向进程0,随后初始化其余的进程并开始运行0号进程;my_process函数用来循环并输出语句以及切换进程。myinterrupt.c对时间中断函数进行了修改,并添加了每次时间中断发生时采取的动作,即my_schedule函数,用于mymain.c中进程的切换。

代码修改后的运行结果如下:

二、Linux知识学习

堆栈是计算机三大法宝之一,是C语言程序运行时必须使用的记录函数调用路径和参数存储空间。

1.堆栈相关的寄存器

  • ESP:堆栈指针。
  • EBP:基址指针,C语言中用来记录当前函数调用基址。

2.堆栈操作

  • push:栈顶地址减少4个字节(32位),并将操作数放入栈顶存储单元。
  • pop:栈顶地址增加4个字节(32位),并将栈顶存储单元的内容放入操作数。

3.其他关键寄存器

CS:EIP总是指向下一条的指令地址,这里用到了CS寄存器,也就是代码段寄存器和EIP总是指向下一条的指令地址。

  • 顺序执行:总是指向地址连续的下一条指令。
  • 跳转/分支:执行这样的指令时,CS:EIP的值会根据程序需要被修改。
  • call:将当前ES:EIP的值压入栈顶,CS:EIP指向被调用函数的入口地址。
  • ret:从栈顶弹出原来保存在这里的CS:EIP的值,放入CS:EIP中。

4.用堆栈来传递函数的参数

对32位的x86CPU来讲,通过堆栈来传递参数的方法时从右到左依次压栈,64位机器稍有不同。

5.函数传递返回值

EAX寄存器可以用来保存返回值;如果有多个返回值,EAX则会返回一个内存地址,这个内存地址可以指向很多的返回值。函数还可以使用参数传递返回值。

6.提供局部变量的空间

函数体内的局部变量是通过堆栈来存储的。

7.编译器使用堆栈的规则

不同版本的编译器对堆栈的使用规则不同;机器的处理器指令集不同时,汇编出的汇编代码也有所不同。

posted @ 2020-10-25 20:49  dkkk7  阅读(91)  评论(0编辑  收藏  举报