zoukankan      html  css  js  c++  java
  • Linux内核分析作业 NO.2

    操作系统是如何工作的

    于佳心 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 

    本章所学内容主要围绕着三个方面。

    1.函数调用堆栈

    2.中断机制

    3.mykernel上实际操作构建内核

    操作系统的三个法宝指的是:存储程序计算机,函数调用堆栈,中断机制

    其中函数调用堆栈是高级语言的起点,它的作用是记录调用路径和参数(调用框架,传递参数,保存返回地址,提供局部变量空间)

    这里涉及到了esp,ebp两个相关寄存器,以及push、pop、call、ret寄存器,还有cs代码段寄存器及总是指向下一条指令地址的eip,这些内容在上一周的学习过程中都详细了解过了,所以在这里就不多讲了

    我们还学到了调用框架的固定格式

    建立框架:

    pushl %ebp
    movl %esp,%ebp

    拆除框架:

    movl %ebp,%esp
    popl %ebp
    ret

    它们的中间便是call要调用的内容

    关于另一个“法宝”——中断,我对它非常陌生。发生中断的原因是cpu内部做了工作,中断的出现来源于多道程序设计的出现,CPU把寄存器们压到一个叫内核的堆栈,把EIP指向中断处理程序的入口,保存现场。CPU和内核程序共同实现了保存现场和恢复现场。

    课程中我们主要分析了周期性的时间中断,即内核时间片轮转。

    另一个重要的东西是C代码中嵌入汇编代码——内嵌汇编语法

    asm(

             汇编语句模板;

             输出部分;

             输入部分;

             破坏描述部分);

    在前面如果需要可以加上volatile,这是指编译器不优化,后面的指令保持原样

    在转移的过程中会遇到好多限定符,在此不详述。

    操作系统除了“三大法宝”之外,还有“两把剑"——中断上下文切换和进程上下文切换

    在试验中,输入要求的代码来运行初始程序

    结果如下:

    为何会出现这个结果呢?我们来看系统中的代码

    我们可以看到图中有两个c程序,myinterrupt.c(时钟中断处理程序)和mymain.c(系统中唯一的一个进程)

    我们先来看mymain.c中的内容

    这个函数是操作系统的入口(在此之前的代码都是将操作系统初始化),每循环100000次,打印一个”my_start_kernel"。

    再看myinterrupt.c

    这是时间中断处理程序,每次时钟中断一次都调用一次上图的语句,在中断发生时进行中断处理动作

    这是一个最简单直接的中断处理程序了,接下来我们来看一个更复杂的例子

    mypcd.h

    定义一个进程控制块

    (程序中//的注释是自己加的,*/的注释是代码自带的,后面的代码都是)

    /*
     *  linux/mykernel/mypcb.h
     *
     *  Kernel internal PCB types
     *
     *  Copyright (C) 2013  Mengning
     *
     */
    
    #define MAX_TASK_NUM        4
    #define KERNEL_STACK_SIZE   1024*8
    
    /* CPU-specific state of this task */
    struct Thread {                                 //Thread用于存储eip和esp
        unsigned long        ip;
        unsigned long        sp;
    };
    
    typedef struct PCB{
        int pid;                                    //pid:进程的状态
        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);                         //调度器

    mymain.c

    /*
     *  linux/mykernel/mymain.c
     *
     *  Kernel internal my_start_kernel
     *
     *  Copyright (C) 2013  Mengning
     *
     */
    #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];                       //声明一个数组(task是数组)
    tPCB * my_current_task = NULL;                 //声明当前task的一个指针
    volatile int my_need_sched = 0;                //是否需要调度的标识
    
    void my_process(void);
    
    
    void __init my_start_kernel(void)              //初始化
    {
        int pid = 0;                               //先初始化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;                  //入口:my_process
        task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];              //栈顶:之前定义的stack
        task[pid].next = &task[pid];                                                             //指向它自己
        /*fork more process */                                                                   //初始化更多的进程
        for(i=1;i<MAX_TASK_NUM;i++)                                                              //共有MAX_TASK_NUM个进程
        {
            memcpy(&task[i],&task[0],sizeof(tPCB));                                              //复制了0号进程的状态
            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;                                                                                 //从0号进程开始运行
        my_current_task = &task[pid];
        asm volatile(                                                                            //嵌入的汇编代码,构建了cpu的运行环境,0号进程设立的堆栈和0号                                                                                               进程的入口
            "movl %1,%%esp
    	"     /* set task[pid].thread.sp to esp */                         //将第一号参数放入栈顶
            "pushl %1
    	"             /* push ebp */                                            //pushl %ebp(现在栈是空的,esp==ebp)
            "pushl %0
    	"             /* push task[pid].thread.ip */                            //将eip压栈
            "ret
    	"                 /* pop task[pid].thread.ip to eip */                       //pop eip,此后0号进程正式启动
            "popl %%ebp
    	"
            : 
            : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)    /* input c or d mean %ecx/%edx*/      //这里是第0号参数和第1号参数
        );
    }   
    void my_process(void)                                                                            //有主动调度的机制
    {
        int i = 0;
        while(1)
        {
            i++;
            if(i%100000 == 0)
            {
                printk(KERN_NOTICE "this is process %d -
    ",my_current_task->pid);                   //循环100000次有一次机会判断是否需要调度,调度完继续执行
                if(my_need_sched == 1)
                {
                    my_need_sched = 0;
                    my_schedule();
                }
                printk(KERN_NOTICE "this is process %d +
    ",my_current_task->pid);
            }     
        }
    }

    myinterrupt.c

    /*
     *  linux/mykernel/myinterrupt.c
     *
     *  Kernel internal my_timer_handler
     *
     *  Copyright (C) 2013  Mengning
     *
     */
    #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;                             //时间计数
    
    /*
     * Called by timer interrupt.
     * it runs in the name of current running process,
     * so it use kernel stack of current running process
     */
    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;                                  //发现my_need_sched==1,就会调度一次,执行my_schedule
        } 
        time_count ++ ;  
    #endif
        return;      
    }
    
    void my_schedule(void)
    {
        tPCB * next;
        tPCB * prev;
    
        if(my_current_task == NULL                            //出错处理,NULL说明有问题
            || my_current_task->next == NULL)
        {
            return;
        }
        printk(KERN_NOTICE ">>>my_schedule<<<
    ");
        /* schedule */
        next = my_current_task->next;                       //将当前进程的下一个进程赋给next
        prev = my_current_task;                             //将当前进程赋给prev
        if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */                          //判定:如果下一个进程正在执行,就用下面的汇编代码切换进程
    (进程间上下文的切换)
    { /* switch to next process */ asm volatile( //进程上下文切换的关键代码 "pushl %%ebp " /* save ebp */ //保存当前进程的ebp "movl %%esp,%0 " /* save esp */ //将当前进程的esp赋给0 "movl %2,%%esp " /* restore esp */ //将下一进程的esp放到esp里面 "movl $1f,%1 " /* save eip */ //保存eip "pushl %3 " //push下一进程的eip "ret " /* restore eip */ //下一个进程开始执行 "1: " /* next process start here */ "popl %%ebp " : "=m" (prev->thread.sp),"=m" (prev->thread.ip) //0,1 : "m" (next->thread.sp),"m" (next->thread.ip) //2,3 ); 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 " /* save ebp */ //保存ebp "movl %%esp,%0 " /* save esp */ //保存esp在当前程序的esp里 "movl %2,%%esp " /* restore esp */ //将下一个进程的esp保存到esp里 "movl %2,%%ebp " /* restore ebp */ //将下一个进程的ebp保存到ebp里(栈是空的 "movl $1f,%1 " /* save eip */ //将eip保存起来 "pushl %3 " //将当前进程的入口保存起来 "ret " /* restore eip */ : "=m" (prev->thread.sp),"=m" (prev->thread.ip) //0,1 : "m" (next->thread.sp),"m" (next->thread.ip) //2,3 ); } return; }

    总结:

    操作系统是如何工作的:我认为,操作系统的工作离不开“三大法宝和”两把剑“

    三大法宝中,存储程序计算机便是冯诺依曼体系结构,自不必说;函数调用堆栈记录了函数调用路径和参数,保存返回地址和临时需要用到的参数,提供局部变量空间;中断是为了多道程序设计的,当有多个程序需要执行时,每次执行的只能是一个程序,所以需要中断来调节,我们在这一章介绍的是时钟中断,即是否调用中断处理程序的判定是由时间来决定的,如果需要处理,CPU便将寄存器压入内核,将eip指向中断处理程序的入口。

    两把剑中,中断上下文切换就是保护现场和恢复现场,而进程上下文的切换则决定了进程的执行顺序,在上面都有可执行的代码,所以就不仔细讲了

    这一次学的东西比上一次难多了,比如说,那个分析代码的视频我就看了至少三遍才懂,而且也并没有完全理解透彻,想到以后的课程也许会更难,有点害怕。

    学校网不好,实验楼总是卡,但是如果用自己的虚拟机调配环境可能也要花好长时间,所以就非常缓慢的做了实验,其实感觉实验只是对所学课程的一个复习,这样挺好的,可以发现一些自己以为会了的东西其实不会。

    没什么可说的了,求大家高抬贵手。

  • 相关阅读:
    2、容器初探
    3、二叉树:先序,中序,后序循环遍历详解
    Hebbian Learning Rule
    论文笔记 Weakly-Supervised Spatial Context Networks
    在Caffe添加Python layer详细步骤
    论文笔记 Learning to Compare Image Patches via Convolutional Neural Networks
    Deconvolution 反卷积理解
    论文笔记 Feature Pyramid Networks for Object Detection
    Caffe2 初识
    论文笔记 Densely Connected Convolutional Networks
  • 原文地址:https://www.cnblogs.com/javablack/p/5245694.html
Copyright © 2011-2022 走看看