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

操作系统是如何工作的

一:函数调用堆栈

计算机的三个“法宝”:

1.存储程序计算机:基本上是所有计算机的基础性框架。

2.函数调用堆栈:堆栈是C语言程序运行时必须使用的记录函数调用路径和参数存储的空间,堆栈的具体作用有:记录函数调用框架、传递函数参数、保存返回值的地址、提供函数内部局部变量的存储空间等。

3.中断机制:中断的流程大概是产生中断、响应中断、关闭中断、保护中断、识别中断源、现场保护、中断服务、现场恢复。

堆栈相关的寄存器:

1.ESP:堆栈指针

2.EBP:基址指针,在C语言中用作记录当前函数调用基址。

堆栈操作:

对于x86体系结构来讲,堆栈空间是从高地址向低地址增长的。

二:借助Linux内核部分源代码模拟存储程序计算机工作模型及时钟中断

实验步骤:(因为实验楼已经为我们搭配好了qemu环境,并且搭建了虚拟的x86 CPU。所以可以直接进行试验)

1.使用实验楼的虚拟机打开shell,并且进入到内核目录中:

    >cd ~/LinuxKernel/linux-3.9.4
    >rm -rf mykernel
    >patch -p1 < ../mykernel_for_linux3.9.4sc.patch
    >make allnoconfig
    >make

2.查看搭建起来的内核启动效果:

    > qemu -kernel arch/x86/boot/bzImage

3.进入到mykernel目录,查看mymain.c和myinterrupt.c:

    > cd mykernel

4.查看mymain.c文件:

    >vi mymain.c

在mymain.c中,my_start_kernel是操作系统的入口,在该函数体中,有一个循环,每循环100000次,就会打印一条语句。因为如今的CPU速度都很快,所以,呈现在我们眼前就是在很短的时间打印一次。

5.查看myinterrupt.c文件:

    > vi myinterrupt.c

在myinterrupt.c中,每次的始终中断,都会打印语句“my_timer_handler here”。

6.通过实验所给的代码链接,建立一个简单的时间片轮转多道程序:

7.分析进程的启动和进程的切换机制:

mypcb.h:

    #define MAX_TASK_NUM      4                                      //定义总有4个任务/进程

    #define KERNEL_STACK_SIZE   1024*2                      //每个进程的堆栈大小为2KB

    /*用于保有存当前进程的esp和eip*/

    struct Thread {

        unsigned long		ip;

        unsigned long		sp;

    };

    /*定义进程结构体*/

    typedef struct PCB{

        int pid;        //进程id

        volatile long state;	//进程状态

        unsigned long stack[KERNEL_STACK_SIZE];        //进程堆栈

        struct Thread thread;        //保存esp、eip

        unsigned long	task_entry;        //进程入口

        struct PCB *next;        //在进程链表中指向下一个进程

    }tPCB;

   /*声明调度函数*/

    void my_schedule(void);


   mymain.c:
    
    #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;

        /* 初始化0号进程*/

        task[pid].pid = pid;
    
        task[pid].state = 0;    

        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];        //进程链表中,next指针指向自己

        /*创建其他进程*/

        for(i=1;i<MAX_TASK_NUM;i++)

        {

             /*把0号进程的状态拷贝给当前进程,不同成员值再单独赋值*/
            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\n\t" 	/*把当前进程的栈指针赋给esp*/ 

	        "pushl %1\n\t" 	        /* 把当前进程的栈指保存到栈中 */

	        "pushl %0\n\t" 	        /* 保存当前进程的eip到栈中 */

	        "ret\n\t" 	            /* 返回操作 */

        	: 
        
        	: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)	/*ecx=当前进程的eip, 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);
               /* 判断是否需要切换进程,1需要切换,0不需要切换*/
                if(my_need_sched == 1)

                {

                    my_need_sched = 0;

    	            my_schedule();        //切换进程

    	        }

    	        printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);

            }     

        }

    }


myinterrupt:
    
    #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 1

    /*判断是否需要切换进程*/

        if(time_count%1000 == 0 && my_need_sched != 1)

        {

            printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");

    /*将进程切换标记为需要切换*/

            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<<<\n");

        next = my_current_task->next;

        prev = my_current_task;

        if(next->state == 0)        /*state:-1表示不可用,0表示正在执行,>0表示没有运行 */

        {        

            /*当前进程指针指向下一下进程*/

        	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" 	    /*保存当前进程的ebp*/

            	"movl %%esp,%0\n\t" 	/*保存当前的栈指针到上一个进程的sp中*/

    	        "movl %2,%%esp\n\t"    /*把下一个进程的sp赋给esp*/

    	        "movl $1f,%1\n\t"       /*保存当前进程的eip到上进一个进程的ip中*/

    	        "pushl %3\n\t"         /*将下一个进程的ip压到栈中*/

    	        "ret\n\t" 	            /* 返回操作 */

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

    }

三:总结

   通过本章的学习,我对计算机的三大法宝有了进一步的了解,清楚它们各自的作用是什么。并且对C语言内嵌入汇编代码有了一定的了解,但从解读内嵌代码到自行编写还需要很长的时间来学习。实验楼给我们提供了良好的实验环境,供我们学习如何进行借助Linux内核部分源代码模拟存储程序计算机工作模型及时钟中断,它的重难点就是进程的切换,进程在执行的过程中,当时间片用完需要进行进程切换时,预压先存储当前进程的上下文环境,下次被调度时,需要恢复进程的上下文环境,才可以实现多道程序的并发性执行。
posted @ 2019-09-28 00:05  蒋昕睿_20199307  阅读(162)  评论(0编辑  收藏  举报