• 2018-2019-1 20189204《Linux内核原理与分析》第三周作业


    OS是如何工作的

    学习任务:

    阅读学习教材「庖丁解牛Linux 」第2章
    学习蓝墨云班课中第三周视频「操作系统是如何工作的?」,并完成实验楼上配套实验二。

    云班课学习笔记:

    计算机三大法宝

    • 程序存储计算机 即冯诺依曼体系结构
    • 函数调用堆栈 高级语言可以运行的起点就是函数调用堆栈
    • 中断机制

    深入理解函数调用堆栈

    • 堆栈的功能
      • 记录函数调用的框架
      • 传递函数参数
      • 保存返回值地址
      • 提供函数内部局部变量的存储空间
    • 堆栈相关的寄存器
      • ESP:堆栈栈顶指针
      • EBP:堆栈栈底指针
    • 典型的堆栈操作——Push和Pop
      push和pop均是由两条指令组合而成的操作,压栈push,esp减少4个字节,所以是sub和mov的复合操作;pop增加四个字节,所以是add和mov的复合操作。
    • 其他关键寄存器
      • CS:EIP 总是指向下一条指令地址 CS(Code Segment)是代码段寄存器 EIP是指向下一条指令的地址
      • call:将CS:EIP压入栈顶,随后指向被调用函数的入口地址
      • ret:从栈顶弹出原来保存在这里的CS:EIP的值,放入CS:EIP中

    函数调用堆栈框架

    C语言中内嵌汇编语言的写法

    内嵌汇编的语法:
        _asm_ _volatile_ (
                        汇编语句模版;
                        输出部分;
                        输入部分;
                        破坏描述部分;
                         );
    

    asm 是GCC的关键字asm的宏定义,是内嵌汇编的关键字。
    _volatile_是GCC的关键字,告诉编译器不要优化代码,汇编指令保留原样。
    同时,%作为转义字符,寄存器前面会多一个转义符号
    %加一个数字代表输入、输入和破坏描述的编号。
    看一个DEMO

    #include <stdio.h>
    
    int main()
    {
        unsigned int val1 = 1;
        unsigned int val2 = 2;
        unsigned int val3 = 0;
        pritnf("val1:%d,val2:%d,val3:%d
    ",val1,val2,val3);
        asm volatile(
            "movl $0,%%eax
    	"
            "addl %1,%%eax
    	"
            "addl %2,%%eax
    	"
            "movl %%eax,%0
    	"
            :"=m"(val3)
            :"c"(vall),"d"(val2)
        );
        pritnf("val1:%d,val2:%d,val3:%d
    ",val1,val2,val3);
    
        return 0;
    }
    

    要特别注意,输出部分和输入部分从0开始编号,所以%1代表val1,%2代表val2,%0代表val3,c表示ecx存储器存储val1,d表示edx存储val2,=m表示内存变量存储val3

    利用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
    

    结果如图所示


    cd mykernel 到mykernel目录,然后查看Mymain.c源代码,可以看到如下代码

    有一个my_start_kernel()函数,故名思义,应该就是内核开始的函数。里面写了一个死循环,变量i不停的自加1,每当加到100000的整数倍的时候就打印出当前的i的值。
    查看myinterrrupt.c,结果如下

    定义了一个my_timer_handler()的函数
    只有一条打印语句, 这里有一点要注意,内核编程的打印语句是printk而不是printf。
    由于芯片不断发展,目前CPU处理速度很快,所以屏幕很快地交替打印出输出信息。

    下面分析mypcb.h,mymain.c,myinterrupt.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);
    

    文件的最后声明了一个schedule函数,即调度器。
    第二个main.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)
    {
        int pid = 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,实际上为mystartkernel
        task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
        task[pid].next = &task[pid];//刚一启动只有0号进程
        /*fork more process */
        for(i=1;i<MAX_TASK_NUM;i++)
        {
            memcpy(&task[i],&task[0],sizeof(tPCB));  //将0号进程状态copy过来
            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];  //新fork的进程加到进程列表的尾部
        } //创建了MAX_TASK_NUM个进程
    /* start process 0 by task[0] */
        pid = 0;
        my_current_task = &task[pid];  //当前的进程是0号进程
        asm volatile(
            "movl %1,%%esp
    	"     /* set task[pid].thread.sp to esp */    //第一号(task[pid].thread.sp)参数放到esp
            "pushl %1
    	"             /* push ebp */                       //当前堆栈空,esp==ebp
            "pushl %0
    	"             /* push task[pid].thread.ip */       //当前ip压栈
            "ret
    	"                 /* pop task[pid].thread.ip to eip */  //ret后0号进程正式启动
            "popl %%ebp
    	"
            : 
            : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)    /* input c or d mean %ecx/%edx*/
        );
    //构建起来了cpu的运行环境(0号进程设定的堆栈和0号进程的入口)
    };内核初始化完成,把0号进程启动起来了
    
    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里面的代码

    #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;   //把全局的东西extern来
    volatile int time_count = 0;   //时间计数
    void my_timer_handler(void)
    {
    #if 1
        if(time_count%1000 == 0 && my_need_sched != 1)  //时钟中断发生1000次并且my_need_sched不为1(设置时间片的大小,时间片用完时设置一下调度标志,时间片变小调度更频繁)
        {
            printk(KERN_NOTICE ">>>my_timer_handler here<<<
    ");
            my_need_sched = 1;  //当进程执行到的时候,发现为1,调度一次,执行一下my_schedule
        } 
        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;    //当前进程的下一个进程赋给next
    prev = my_current_task;
        if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
        {
            /* switch to next process */     //两个正在运行的进程之间做进程上下文切换
            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)
            ); //进程切换的关键代码
            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 */
                "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;
    }
    

    最后将这些代码复制到对应的文件里面去,保存(原本没有mypcb.h文件,所以要另外创建一个mypcb.h),再回到~/linux-3.9.4目录下,
    输入命令qemu -kernel arch/x86/boot/bzImage 但是发现结果仍然和刚才模拟计算机硬件平台时的结果一样,想了好久发现是自己没有编译~
    输入make 编译一遍,再执行命令qemu -kernel arch/x86/boot/bzImage
    就可运行刚刚的代码了,结果如图所示

    学习中的问题

    Mymain.c以及mypcb.c中的函数名void __init my_start_kernel(void) 是否是Linux内核中约定好的呢?

  • 相关阅读:
    .NET Core下的Socket示例.
    VS没办法调试,直接退出,报错:1. 使用调试生成配置或禁用调试选项“启用‘仅我的代码’”。。。
    2017年2月7日 今年第一天上班了
    .NET Core错误:The specified framework 'Microsoft.NETCore.App', version '1.0.0-rc2-3002702' was not found.
    KB2533623 下载
    Ajax Not Found,asp.net mvc 中
    JavaScript外部函数调用AngularJS的函数、$scope
    029医疗项目-模块三:药品供应商目录模块——供货商药品目录查询功能----------数据模型的分析(建表)
    028医疗项目-模块三:药品供应商目录模块——供货商药品目录查询功能----------需求分析
    50个查询系列-第10个查询:查询没有学全所有课的同学的学号、姓名;
  • 原文地址:https://www.cnblogs.com/bowendky/p/9866931.html
Copyright © 2020-2023  润新知