zoukankan      html  css  js  c++  java
  • 2018-2019-1 20189215《Linux内核原理与分析》第三周作业

    《庖丁解牛》第二章书本知识总结


    • 函数调用框架
      call指令有两个作用:
      (1) 将CS:EIP中下一条指令的地址A保存在栈顶;
      (2)设置CS:EIP指向被调用程序的第一行。
      ret指令在被调用函数完成之后,将地址A恢复到CS:EIP中。

    • 传递参数
      因为存储参数使用堆栈,根据后入先出的规则,传递参数的方法是从右到左依次压栈。

    • 计算机的3大法宝:存储程序计算机、函数调用堆栈机制、中断。

    • 内嵌汇编语法规则

    asm volatile (
    汇编语句:
    输出部分: //需要在行首加上冒号:
    输入部分: //需要在行首加上冒号:
    破环描述部分
    )
    

    (1)volatile是告诉编译器不要优化代码
    (2)汇编代码嵌入时和直接方式有些许不同,体现在%转义符号。寄存器前面会有两个%,而%加数字则表示在输出部分、输入部分、破环描述部分的编号,从零开始,最高到总个数-1。
    (3)一些符号

    "c"、"d"等符号分别表示寄存器ecxedx
    "r"表示将输入变量放到通用寄存器,也就是eaxebxecxedxesiedi中的一个。
    "="表示操作数在指令中是只写的,输出操作数。
    "+"表示输入输出操作数是读写类型的。
    "m"表示内存变量。

    实验:mykernel时间片轮转多道程序内核


    • 进入实验楼实验,在终端中分别输入以下命令
    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
    

    make过程如下图:

    make成功后mykernel运行:

    • 在mykernel的基础上添加mypcb.h,修改mymain.cmyinterrupt.c文件,实现一个简单的操作系统内核,设置为时钟中断发生100次时,将需要调度的flagmy_need_sched设置为1。运行结果如下:

    mykernel时间片轮转代码分析


    实验中,基于mykernel,添加了mypcb.h,修改mymain.cmyinterrupt.c文件,下面对这三个文件的代码进行分析。

    • 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); //声明调度函数
    

    这段代码主要定义了进程控制块PCB,包括:
    pid:进程id
    state:进程状态,初始化值就是-1,如果被调度运行起来,其值就会变成0 ,被终端后,其值>0
    stack:本进程使用的堆栈
    thread:当前正在执行的线程信息
    task_entry:进程入口函数
    next:指向下一个PCB,实验环境中所有的PCB是以链表的形式组织起来的。

    • 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);
            }     
        }
    }
    

    本实验环境中,mykernel是内核代码的入口,负责初始化内核的各个组成部分,在实际的Linux内核中,入口为init/main.c中的start_kernel(void)函数。
    在本实验环境中,每个进程的函数都是 my_process函数,进程在运行中打印当前进程号,并通过my_need_sched变量判断是否需要调度,这是在myinterrupt.c文件中进行赋值的一个变量。
    下面对0号进程的启动进行分析:
    1"movl %1,%%esp " %1指第2个输入输出"d" (task[pid].thread.sp),将进程的栈顶位置存入ESP寄存器
    2"pushl %1 " 将task[0].thread.sp压栈,即保存当前EBP值
    3"pushl %0 " 将task[0].thread.ip压栈,当前进程的EIP值入栈
    4"ret " 将栈顶值取出到EIP寄存器,即刚刚入栈的0号进程ip值
    5 "popl %%ebp " 在执行完其他进程之后,回到0号进程,释放栈空间
    6 : 没有输出
    7 : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) 输入,将0号进程的ip、sp值分别存入ecx、edx寄存器中。

    • 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;	
    }
    

    myinterrupt.c包含my_timer_handler和my_schedule两个函数。 my_timer_handler每隔100次将my_need_sched赋值为1,等待mymain.c中my_process函数的主动调度。my_schedule保存恢复进程上下文。
    切换进程的分析:
    01 "pushl %%ebp 保存当前ebp到堆栈中
    02 "movl %%esp,%0 " 保存当前ESP到当前进程sp中
    03 "movl %2,%%esp " esp指向下一个进程
    04 "movl $1f,%1 " 将1f存储到thread.ip中,$1f是标号“1: ”处,再次调度到该进程时就会从1:开始执行
    05 "pushl %3 " 将下一个进程的ip入栈
    06 "ret " eip指向下一个进程的起始地址,也做了一次出栈操作
    07 "1: " 标号1:,即next进程开始执行的位置
    08 "popl %%ebp " 待下一个进程执行完后释放栈空间,恢复现场
    09 : "=m" (prev->thread.sp),"=m" (prev->thread.ip) %0、%1分别对应,m代表内存变量
    10 : "m" (next->thread.sp),"m" (next->thread.ip) %2、%3分别对应

    遇到的问题


    1. 实验中修改代码后,make找不到文件。
      解决:修改代码是在/mykernel目录下进行修改的,make编译内核需要在LinuxKernel/linux-3.9.4目录下进行,修改完之后要cd ..
    2. 不懂myinterrupt.c文件中my_timer_handler函数在哪调用。
      解决:仔细看了书上的35页,在实验环境中已经配置好了一个时钟中断,在时钟中断发生的时候就会调用一次该函数。
    3. 不懂$1f的含义。
      解决:查阅资料得知,"movl $1f,%1 "是将进程原来的ip替换为$1f,是at&t的一种语法,f代表向后跳转(b表示向前,f表示向后),1f指的就是下一条指令,即当前进程的eip。
  • 相关阅读:
    Java代码打成jar后 classgetClassLoadergetResource("")返回为null
    springboot-yml内list、map组合写法
    rpc-java 生成代码路径设置
    Git操作 :从一个分支cherry-pick多个commit到其他分支
    使用maven插件生成grpc所需要的Java代码
    'Failed to import pydot. You must `pip install pydot` and install graphviz
    seasonal_decompose plot figsize
    Failed to install 'TwoSampleMR' from GitHub
    prophet Building wheel for fbprophet (setup.py) ... error
    python matplotlib 绘图线条类型和颜色选择
  • 原文地址:https://www.cnblogs.com/jsjliyang/p/9854589.html
Copyright © 2011-2022 走看看