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


    《Linux内核原理与分析》第三周作业


    一.上周问题总结:

    • 第二周头脑风暴完成较慢
    • 虚拟机libc配置错误
    • 书本知识使用不够熟练

    二.本周学习内容:

    1.实验楼环境虚拟一个x86的CPU硬件平台

    在该环境下输入如下命令:

    cd LinuxKernel/linux-3.9.4
    qemu -kernel arch/x86/boot/bzImage
    

    实践截图如下:

    QEMU窗口截图如下:

    查看mymain.c:

    查看myinterrupt.c:

    2.实验楼环境完成一个简单的时间片轮转机制内核:

    代码如下:

    #define MAX_TASK_NUM 4
    #define KERNEL_STACK_SIZE 1024*8
    /* CPU-specific state of this task */
    struct Thread {
    unsigned long   ip;
    unsigned long   sp;
    };
    typedef struct PCB{
    int 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代码如下:

    #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;                                 #初始化0号进程
        int i;
        /* Initialize process 0*/
        task[pid].pid = pid;                    #设置进程id
        task[pid].state = 0;                     #设置进程状态为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];            #设置ESP指向栈顶
        task[pid].next = &task[pid];                #设置堆栈指针指向当前堆栈
    
        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
    	"     #将task[pid].thread.sp移入ESP
            "pushl %1
    	"          #将EBP入栈
            "pushl %0
    	"  #将task[pid].thread.ip入栈
            "ret
    	"   #将task[pid].thread.ip返回赋入eip
            "popl %%ebp
    	"    #将ebp压出栈
            : 
            : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)   #设置ecx和edx存储不同参数
        );
    }   
    void my_process(void)       
    {
        int i = 0;
        while(1)
        {
            i++;
            if(i%10000000 == 0)     #每计数10000000,会输出this is process 和当前进程任务号
            {
                printk(KERN_NOTICE "this is process %d -
    ",my_current_task->pid);
                if(my_need_sched == 1)   
    #判断是否需要切换进程,1需要切换,将状态值赋为0,并执行my_schedule(), 0不需要切换
                {
                    my_need_sched = 0;
                    my_schedule();
                }
                printk(KERN_NOTICE "this is process %d +
    ",my_current_task->pid);  #输出this is process 和当前进程任务号
            }     
        }
    }
    

    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;
    volatile int time_count = 0;
    #时钟中断程序
    void my_timer_handler(void)    
    {
    #if 1
        if(time_count%1000 == 0 && my_need_sched != 1)         
    #如果时钟计数可以整出1000,且不需要切换进程,打印输出my_timer_handler here,并切换状态
        {
            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;                   #用指针next指向下一个进程
        prev = my_current_task;                   #用指针prev指向当前进程
        if(next->state == 0)                    #判断下一进程曾执行过               
        {
            my_current_task = next; 
            printk(KERN_NOTICE ">>>switch %d to %d<<<
    ",prev->pid,next->pid);  
            /* switch to next process */
            asm volatile(   
                "pushl %%ebp
    	"       # 将ebp入栈保存
                "movl %%esp,%0
    	"     #保存当前进程ESP到PCB
                "movl %2,%%esp
    	"     #将next进程的栈顶地址存入ESP
                "movl $1f,%1
    	"       #保存当前EIP寄存器值到PCB    
                "pushl %3
    	"         #把下一个进程代码入口地址入栈
                "ret
    	"               #出栈进程的代码入口地址返回到EIP寄存器
                "1:	"                  #标号1,即next进程开始执行的位置
                "popl %%ebp
    	"         #恢复EBP寄存器的值
                : "=m" (prev->thread.sp),"=m" (prev->thread.ip)  
    #将当前和下一个进程栈顶地址和ip地址存入内存寄存器中,并根据变量%0%1%2%3命名
                : "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
    	"       #保存当前进程EBP到堆栈
                "movl %%esp,%0
    	"     #保存当前进程ESP到PCB
                "movl %2,%%esp
    	"      #将next进程的栈顶地址保存到ESP寄存器
                "movl %2,%%ebp
    	"     #将next进程的堆栈基地址保存到EBP寄存器
                "movl $1f,%1
    	"       #将当前EIP寄存器值存到PCB,标号为1
                "pushl %3
    	"           #把下一个进程入口地址入栈
                "ret
    	"               #将出栈进程的代码入口地址存到EIP寄存器
                : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
                : "m" (next->thread.sp),"m" (next->thread.ip)
            );          
        }   
        return; 
    }
    

    3.操作系统的工作原理:

    计算机三个法宝(3个关键性的方法机制):存储程序计算机、函数调用堆栈、中断机制

    3.1 堆栈:

    在计算机领域,堆栈是一个不容忽视的概念,堆栈是一种数据结构。堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。

    堆栈作用:
    • 记录函数调用框架
    • 传递函数参数
    • 保存返回值的地址
    • 提供函数内部局部变量的存储空间
    • ESP:堆栈指针
    • EBP:基址指针
      堆栈操作:
    • push:栈顶地址减少4个字节,并将操作数放进栈顶存储单元
    • pop:栈顶地址增加4个字节,并将栈顶存储单元的内容放入操作数
    其他关键寄存器:
    • CS:EIP:总是指向地址连续的下一条指令
    • 跳转/分支:执行这样的命令时,CS:EIP的值会根据程序需要被修改
    • call:将当前CS:EIP的值压入栈顶,CS:EIP指向被调用函数的入口地址
    • ret:从栈顶弹出原来保存在这里CS:EIP的值,放入CS:EIP中

    3.2 中断

    中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。

    中断分类:
    • 硬件中断(Hardware Interrupt)
    • 可屏蔽中断(maskable interrupt)。硬件中断的一类,可通过在中断屏蔽寄存器中设定位掩码来关闭。
    • 非可屏蔽中断(non-maskable interrupt,NMI)。硬件中断的一类,无法通过在中断屏蔽寄存器中设定位掩码来关闭。典型例子是时钟中断(一个硬件时钟以恒定频率—如50Hz—发出的中断)。
    • 处理器间中断(interprocessor interrupt)。一种特殊的硬件中断。由处理器发出,被其它处理器接收。仅见于多处理器系统,以便于处理器间通信或同步。
    • 伪中断(spurious interrupt)。一类不希望被产生的硬件中断。发生的原因有很多种,如中断线路上电气信号异常,或是中断请求设备本身有问题
    • 软件中断(Software Interrupt)。是一条CPU指令,用以自陷一个中断。由于软中断指令通常要运行一个切换CPU至内核态(Kernel Mode/Ring 0)的子例程,它常被用作实现系统调用(System call)。
    中断作用:
    • 提高计算机系统效率。计算机系统中处理机的工作速度远高于外围设备的工作速度。通过中断可以协调它们之间的工作。当外围设备需要与处理机交换信息时,由外围设备向处理机发出中断请求,处理机及时响应并作相应处理。不交换信息时,处理机和外围设备处于各自独立的并行工作状态
    • 维持系统可靠正常工作。现代计算机中,程序员不能直接干预和操纵机器,必须通过中断系统向操作系统发出请求,由操作系统来实现人为干预。主存储器中往往有多道程序和各自的存储空间。在程序运行过程中,如出现越界访问,有可能引起程序混乱或相互破坏信息。为避免这类事件的发生,由存储管理部件进行监测,一旦发生越界访问,向处理机发出中断请求,处理机立即采取保护措施。
    • 满足实时处理要求。在实时系统中,各种监测和控制装置随机地向处理机发出中断请求,处理机随时响应并进行处理。
    • 提供故障现场处理手段。处理机中设有各种故障检测和错误诊断的部件,一旦发现故障或错误,立即发出中断请求,进行故障现场记录和隔离,为进一步处理提供必要的依据。

    三.总结与疑难

    本次Linux学习主要学习了计算机的三大法宝,清楚它们各自的作用是什么。并且对C语言内嵌入汇编代码有了一定的了解,但距离自己编写代码还需要很长的时间来学习。

    QUSTION:

    • 对于进程的切换还不够熟练。

    四.下周计划

    • [ ] 完成书本上的课后习题
    • [ ] 自己尝试编写代码

    2019 年 09月 29日

  • 相关阅读:
    IIS网站或系统验证码不显示问题——"使用了托管的处理程序,但是未安装或未完整安装 ASP.NET"
    Windows Server 2008 R2 下配置证书服务器和HTTPS方式访问网站
    RESTful API 设计指南
    Js计算当前日,当前周开始结束时间,当前月份,当前年份
    highcharts
    CSS布局 ——从display,position, float属性谈起
    CSS的选择器
    DOC窗口之cd命令(windows)
    Tomcat的启动与关闭以及启动时一闪而过的解决方法
    为什么安装jdk时,会安装两个jre?不用配置jre的环境变量
  • 原文地址:https://www.cnblogs.com/Zxf313806994/p/11607767.html
Copyright © 2020-2023  润新知