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


    操作系统是如何工作的

    目录

    1.虚拟一个x86的CPU硬件平台

    2.代码范例

    3.代码分析

    3.1进程的启动

    3.2进程的切换

    4.总结

    1.虚拟一个x86的CPU硬件平台

    • 进入到实验楼目录,输入以下命令

    $cd LinuxKernel/linux-3.9.4

    $make allnoconfig

    $make

    $qemu -kernel arch/x86/boot/bzImage

    $vim mymain.c

    $vim myinterrupt.c

    运行结果如下图所示:

    修改代码之后重新make,运行结果如下图:

    2.代码范例

    • 进程的启动(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];    
    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;      
        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].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];
        }
        /* start process 0 by task[0] */
        pid = 0;                                              
        my_current_task = &task[pid];
        asm volatile(
        	"movl %1,%%esp
    	" 	/*将进程原堆栈的栈底的地址存入ESP寄存器中*/
        	"pushl %1
    	" 	        /*将当前ESP寄存器的值入栈*/
        	"pushl %0
    	" 	        /*将当前进程的EIP寄存器的值入栈*/
        	"ret
    	" 	            /*让入栈的进程EIP保存到EIP寄存器中*/
        	"popl %%ebp
    	"        /*这里不会被执行,只是一种编码习惯,与前面的push结对出现*/
        	: 
        	: "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);
            }     
        }
    }
    
    • 进程的切换(myinterrupt.c,主要增加了my schedule(void)函数)
    #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 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<<<
    ");
            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)                  
        { 
            asm volatile(   
                "pushl %%ebp
    	"          /* 将当前进程的EBP入栈 */
                "movl %%esp,%0
    	"     /* 将当前进程的ESP保存到PCB  */
                "movl %2,%%esp
    	"     /* 将next进程的栈顶地址放入ESP */
                "movl $1f,%1
    	"            /* 保存当前进程的EIP */
                "pushl %3
    	"                /* 把即将进行的进程的代码位置标号1入栈 */ 
                "ret
    	"                           /* 出栈标号1到EIP*/
                "1:	"                               /* 标号1,next进程开始执行的位置 */
                "popl %%ebp
    	"           /* 恢复EBP的值*/
                : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
                : "m" (next->thread.sp),"m" (next->thread.ip)
            ); 
            my_current_task = next; 
            printk(KERN_NOTICE ">>>switch %d to %d<<<
    ",prev->pid,next->pid);      
        }
        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
    	"           /* 将当前进程的EBP入栈 */
                "movl %%esp,%0
    	"      /* 将当前进程的ESP保存到PCB  */
                "movl %2,%%esp
    	"      /* 将next进程的栈顶地址放入ESP */
                "movl %2,%%ebp
    	"      /* 将next进程的栈底地址放入EBP */
                "movl $1f,%1
    	"             /* 将当前EIP的值放入PCB */ 
                "pushl %3
    	"                  /* 把即将进行的进程的代码入口地址入栈 */ 
                "ret
    	"                             /* 把即将进行的进程的代码入口地址存入EIP */
                : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
                : "m" (next->thread.sp),"m" (next->thread.ip)
            );          
        }   
        return; 
    }
    

    3.代码分析

    3.1进程的启动

    asm volatile(
        	"movl %1,%%esp
    	" 	
        	"pushl %1
    	" 	        
        	"pushl %0
    	" 	       
        	"ret
    	" 	           
        	"popl %%ebp
    	"       
        	: 
        	: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)	/* input c or d mean %ecx/%edx*/
          );
    

    将进程0的堆栈栈底存入ESP寄存器中,将当前ESP寄存器的值入栈,相应的ESP寄存器指向的位置也发生了变化,将当前进程的EIP寄存器(0号进程的起点位置)的值入栈,相应的ESP寄存器指向的位置也发生了变化,让入栈的进程EIP保存到EIP寄存器中,相应的ESP寄存器指向的位置也发生了变化。

    3.2进程的切换

    if(next->state == 0)                  
        { 
            asm volatile(   
                "pushl %%ebp
    	"         
                "movl %%esp,%0
    	"    
                "movl %2,%%esp
    	"    
                "movl $1f,%1
    	"           
                "pushl %3
    	"               
                "ret
    	"                          
                "1:	"                              
                "popl %%ebp
    	"          
                : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
                : "m" (next->thread.sp),"m" (next->thread.ip)
            ); 
            my_current_task = next; 
            printk(KERN_NOTICE ">>>switch %d to %d<<<
    ",prev->pid,next->pid);      
        }
        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
    	"          
                "movl %%esp,%0
    	"      
                "movl %2,%%esp
    	"     
                "movl %2,%%ebp
    	"     
                "movl $1f,%1
    	"            
                "pushl %3
    	"                 
                "ret
    	"                            
                : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
                : "m" (next->thread.sp),"m" (next->thread.ip)
            );          
        }   
        return; 
    }
    

    从进程1被调度开始分析堆栈变化,进程1从来没有被执行过,所以先执行else中的语句。先保留进程0的EBP到堆栈中,接着保存进程0的ESP到PCB中,保留完现场后,将进程1的栈顶地址,堆栈基地址分别载入到ESP寄存器,EBP寄存器中,接着把进程1的代码入口地址入栈,让入栈的进程1的代码入口地址保存到EIP寄存器中,相应的ESP寄存器指向的位置也发生了变化,到这里就开始执行进程1了,如果执行了进程1的过程中发生了进程的调度,就要重新执行进程0了,此时执行if中的语句,pre进程变成了进程1,next进程变成了进程0,要先保存进程1的EBP到堆栈中,接着保存进程1的ESP到PCB中,保留完现场后,将进程0的栈顶地址放入到ESP寄存器中,ESP寄存器此时指向了进程0的栈顶,保存进程1的EIP值,下次恢复进程1后会在标号1开始执行,接着压入进程0的堆栈,ESP指向了进程0的堆栈栈顶,并将进程0的栈顶数据存入到EBP寄存器中,到这里就恢复了进程0的环境,就可以开始执行进程0了。

    4.总结

    操作系统是运行在相应的硬件平台上的一组软件的集合,它的任务是负责进程的创建,运行和调度,操作系统的正常工作离不开存储程序计算机,函数调用堆栈机制和中断的支持,当一个进程正在执行时,进来一个中断,操作系统先将当前进程堆栈中的ESP,EBP指针保存在当前进程的堆栈中,对进程之前的一个状态进行保存,以便从中断返回后继续执行之前任务,EIP指向中断处理的入口,然后进入中断处理程序,操作系统调用schedule函数来进行调度,进入另外一个进程的堆栈中,恢复现场,开始执行,执行完该进程后,恢复前一个进程的现场。

  • 相关阅读:
    用C#新建网站的方法
    zhngutils.js
    jQuery源代码学习jQuery对象构建
    前端性能书单
    预则成,不预则废
    表格
    js延时周期执行setTimeout;setInterval;clearTimeout;clearInterval
    url备份
    前端性能集合(各种测试各种资源...)
    Scrum开发模式
  • 原文地址:https://www.cnblogs.com/traceurli/p/13870870.html
Copyright © 2020-2023  润新知