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函数来进行调度,进入另外一个进程的堆栈中,恢复现场,开始执行,执行完该进程后,恢复前一个进程的现场。

原文地址:https://www.cnblogs.com/traceurli/p/13870870.html