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


    一、学习笔记
    计算机的“三大法宝”
    1、程序存储计算机。
    2、函数调用对栈。堆栈的作用是:记录函数调用框架、传递函数参数、保存返回值地址、提供函数内部局部变量的存储空间。函数调用堆栈就是由多个逻辑上的栈堆叠起来的框架。
    3、中断机制。当一个中断信号发生时,CPU把当时正在执行的程序地CS:EIP寄存器和ESP寄存器等都压到了一个叫做内核堆栈的地方,然后把CS:EIP指向一个中断处理程序的入口,做保存现场的工作,之后执行其他程序,等重新回来时再恢复现场,恢复CS:EIP寄存器和ESP寄存器等,继续执行程序。

    堆栈相关寄存器:
    1、ESP:堆栈指针,指向堆栈栈顶。
    2、EBP:基址指针,至此昂堆栈栈底,在C语言中记录当前函数调用基址
    EIP寄存器:指向下一条指令的地址,一般为顺序执行,也会因指令而跳转执行。

    函数调用堆栈框架:
    call target //建立被调用者函数的堆栈框架
    Push %ebp
    Movl %esp, %ebp
    被调用者函数体
    //拆除堆栈框架
    Movl %ebp, %esp
    Pool %ebp
    ret
    函数调用堆栈框架的建立封装在enter中,拆除封装在leave中

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

    asm volatile (
    汇编语句模版;
    输出部分;
    输入部分;
    破坏描述部分;
    );
    其中,asm 是GCC的关键字asm的宏定义,是内嵌汇编的关键字。
    _volatile_是GCC的关键字,告诉编译器不要优化代码,汇编指令保留原样。
    同时,%作为转义字符,寄存器前面会多一个转义符号
    %加一个数字代表输入、输入和破坏描述的编号。
    二、实验报告

    在mykernel基础上构造一个简单的操作系统内核
    1、在实验楼中按教程命令代码搭建平台

    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

    运行后可以看到一直在执行my_start_kernel here 代码。
    进入mykernel目录,查看mymain.c 和 myinterrupt.c

    从上图所示的代码中看出,mymain.c一直在循环输出“my_start_kernel here”这句代码,而myinterrupt.c中的my_timer_hardler函数将上述函数打断,而执行自己的代码。

    将mykernel操作系统的代码进行扩展
    1、添加mypcb.h头文件,用来定义进程控制块
    2、修改mymain.c,作为内核代码的入口,负责初始化内核的各个组成部分
    3、修改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;
    unsigned long sp;
    };

    typedef struct PCB{
    int pid;
    volatile long state; /* -1 unrunnable, 0runnable, >0 stopped /
    unsigned long 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结构体:定义了进程控制块PCB,包括:pid:进程号;state:进程状态,在模拟系统中,所有进程控制块信息都会被创建出来,其初始化值就是-1,如果被调度运行起来,其值就会变成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];
    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;/
    -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 more process /
    for(i=1;i<MAX_TASK_NUM;i++)
    {
    memcpy(&task[i],&task[0],sizeof(tPCB));
    task[i].pid = i;
    //
    (&task[i].stack[KERNEL_STACK_SIZE-1] - 1) = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-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 " /
    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 /
    :
    : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /
    input c or d mean %ecx/%edx*/
    );
    }

    int i = 0;

    void my_process(void)
    {
    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);
    }
    }
    }
    其中void __init my_start_kernel(void)函数是负责初始化0号进程; for(i=1;i<MAX_TASK_NUM;i++)用于复制123号进程,并形成一个进程链表,每个进程都执行 my_process函数,在执行的时候会打印出当前进程的id号。
    其中第一个进程即0号线程的启动,采用了内嵌汇编代码完成:
    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 /
    :
    : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /
    input c or d mean %ecx/%edx*/
    );

    初始化0号进程的寄存器变化

    启动0号进程,开始执行my_process(void)函数的代码。
    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;

    /*

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

    endif

    return;
    }

    void my_schedule(void)
    {
    tPCB * next;
    tPCB * prev;

     if(next->state == 0)     {
        /* switch to next process */   
        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
    	" /* 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;
    

    }
    中断函数计数达到1000产生一个中断,将my_need_sched置1,此时my_process()将会调用进程的调度函数my_shcedule()。
    在my_shcedule()有两种情况,一种是next->state == 0表示下一个要切换的进程可运行,执行if中的语句,除此之外则为要切换的进程为第一次执行,执行else中的语句。
    堆栈地址分析正如课本庖丁解牛所讲解的那样。

    总结:这周主要学习了进程的切换的操作原理。操作系统内核从一个起始位置开始执行,完成初始化操作后,开始执行第一个进程。计算机为每个进程分配一个时间片,时间片结束时需要切换进程,保存当前进程执行的上下文环境,执行完后再返回原进程执行,从而完成进程调度,并能实现多道程序的并发执行。在学习过程中遇到了几个难点:例如,$1f的作 用,”movl $1f,%1 ”是将进程原来的ip(my_process)替换为$1f,使得它被切换回来(运行状态)进入if,可从标号1:处继续执行。产生中断时会进行中断处理程序,记得计算机组成原理中讲述中断处理程序属于系统调用,它与进程切换程序的衔接还需进一步探究理解。

  • 相关阅读:
    c#格林治时间实现
    K3WISE常用表
    读取单元格数据
    水晶报表使用方法
    vs2010下使用sqlite
    C#执行EXE程序
    SQLLITE HELPER
    SQL LITE安装
    C#多线程
    VS2012 快捷键
  • 原文地址:https://www.cnblogs.com/20189210mujian/p/9867625.html
Copyright © 2020-2023  润新知