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


    一.mykernel实验指导(操作系统是如何工作的)

    使用实验楼的虚拟机打开shell

    然后 cd mykernel 可以看到 qemu 窗口输出的内容的代码 mymain.c 和 myinterrupt.c

     内核不停的执行my_start_kernel(),每隔一段时间被my_timer_handler()中断,然后执行一条打印语句:

    printk(KERN_NOTICE “ >>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<< ”)。

    二. 完成一个简单的时间片轮转多道程序内核代码

    2.1.打开mykernel中的mymain.c文件:

    根据https://github.com/mengning/mykernel上的mypcb.h、mymain.c、myinterrupt.c对kernel中的代码进行修改
    修改代码后重新进行make编译,结果如下:

    三. mykernel内核源代码分析

    3.1 myinterrupt.c

    1. /*
    2.  
      * linux/mykernel/myinterrupt.c
    3.  
      *
    4.  
      * Kernel internal my_timer_handler
    5.  
      *
    6.  
      * Copyright (C) 2013 Mengning
    7.  
      *
    8.  
      */
    9.  
      #include <linux/types.h>
    10.  
      #include <linux/string.h>
    11.  
      #include <linux/ctype.h>
    12.  
      #include <linux/tty.h>
    13.  
      #include <linux/vmalloc.h>
    14.  
       
    15.  
      #include "mypcb.h"
    16.  
       
    17.  
      extern tPCB task[MAX_TASK_NUM];
    18.  
      extern tPCB * my_current_task;
    19.  
      extern volatile int my_need_sched;
    20.  
      volatile int time_count = 0;
    21.  
       
    22.  
      /*
    23.  
      * Called by timer interrupt.
    24.  
      * it runs in the name of current running process,
    25.  
      * so it use kernel stack of current running process
    26.  
      */
    27.  
      void my_timer_handler(void)
    28.  
      {
    29.  
      #if 1
    30.  
      if(time_count%1000 == 0 && my_need_sched != 1)
    31.  
      {
    32.  
      printk(KERN_NOTICE ">>>my_timer_handler here<<< ");
    33.  
      my_need_sched = 1;
    34.  
      }
    35.  
      time_count ++ ;
    36.  
      #endif
    37.  
      return;
    38.  
      }
    39.  
       
    40.  
      void my_schedule(void)
    41.  
      {
    42.  
      tPCB * next;
    43.  
      tPCB * prev;
    44.  
       
    45.  
      if(my_current_task == NULL
    46.  
      || my_current_task->next == NULL)
    47.  
      {
    48.  
      return;
    49.  
      }
    50.  
      printk(KERN_NOTICE ">>>my_schedule<<< ");
    51.  
      /* schedule */
    52.  
      next = my_current_task->next;
    53.  
      prev = my_current_task;
    54.  
      if(next->state == 0)
    55.  
      {
    56.  
      /* switch to next process */
    57.  
      asm volatile(
    58.  
      "pushl %%ebp " /* 保存当前进程的ebp到自己的栈中。 save ebp */
    59.  
      "movl %%esp,%0 " /* 保存当前进程的esp到自己的栈中。 save esp */
    60.  
      "movl %2,%%esp " /* 从next->thread.sp中弹出下一个进程的esp。与第二句相对应。 restore esp */
    61.  
      "movl $1f,%1 " /* 将下一个进程的eip设置为1f。$1f就是指标号1:的代码在内存中存储的地址 save eip */
    62.  
      "pushl %3 " /* 将next->thread.ip压入当前进程的栈中。*/
    63.  
      "ret " /* 从当前进程的栈中弹出刚刚压入的next->thread.ip。完成进程切换。 restore eip */
    64.  
      "1: " /* 即$1f指向的位置。next process start here */
    65.  
      "popl %%ebp " /* 切换到的进程把ebp从栈中弹出至ebp寄存器。与第一句相对应。*/
    66.  
      : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
    67.  
      : "m" (next->thread.sp),"m" (next->thread.ip)
    68.  
      );
    69.  
      my_current_task = next; //当前进程切换为next
    70.  
      printk(KERN_NOTICE ">>>switch %d to %d<<< ",prev->pid,next->pid);
    71.  
      }
    72.  
      else
    73.  
      {
    74.  
      next->state = 0;
    75.  
      my_current_task = next;
    76.  
      printk(KERN_NOTICE ">>>switch %d to %d<<< ",prev->pid,next->pid);
    77.  
      /* switch to new process */
    78.  
      asm volatile(
    79.  
      "pushl %%ebp " /* save ebp */
    80.  
      "movl %%esp,%0 " /* save esp */
    81.  
      "movl %2,%%esp " /* restore esp */
    82.  
      "movl %2,%%ebp " /* restore ebp */
    83.  
      "movl $1f,%1 " /* 将要被切换出去的进程的ip设置为$1f。这样等一下它被切换回来时(一定是运行状态)肯定会进入if判断分支,可以从if中的标号1处继续执行。 save eip */
    84.  
      "pushl %3 " /* 将next->thread.ip(因为它还没有被运行过,所以next->thread.ip现在仍处于初始状态,即指向my_process(),压入将要被切换出去的进程的堆栈。*/
    85.  
      "ret " /* 将刚刚压入的next->thread.ip出栈至eip,完成进程切换。 restore eip */
    86.  
      : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
    87.  
      : "m" (next->thread.sp),"m" (next->thread.ip)
    88.  
      );
    89.  
      }
    90.  
      return;
    91.  

    3.2 mymain.c 

    1. /*
    2.  
      * linux/mykernel/mymain.c
    3.  
      *
    4.  
      * Kernel internal my_start_kernel
    5.  
      *
    6.  
      * Copyright (C) 2013 Mengning
    7.  
      *
    8.  
      */
    9.  
      #include <linux/types.h>
    10.  
      #include <linux/string.h>
    11.  
      #include <linux/ctype.h>
    12.  
      #include <linux/tty.h>
    13.  
      #include <linux/vmalloc.h>
    14.  
       
    15.  
       
    16.  
      #include "mypcb.h"
    17.  
       
    18.  
      tPCB task[MAX_TASK_NUM];
    19.  
      tPCB * my_current_task = NULL;
    20.  
      volatile int my_need_sched = 0;
    21.  
       
    22.  
      void my_process(void);
    23.  
       
    24.  
       
    25.  
      void __init my_start_kernel(void)
    26.  
      {
    27.  
      int pid = 0;
    28.  
      int i;
    29.  
      /* Initialize process 0*/
    30.  
      task[pid].pid = pid;//task[0].pid=0;
    31.  
      task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */
    32.  
      task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;//令0号进程的入口地址为my_process();
    33.  
      task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];//0号进程的栈顶为stack[]数组的最后一个元素
    34.  
      task[pid].next = &task[pid];//next指针指向自己
    35.  
      /*fork more process */
    36.  
      for(i=1;i<MAX_TASK_NUM;i++)//根据0号进程,复制出几个只是编号不同的进程
    37.  
      {
    38.  
      memcpy(&task[i],&task[0],sizeof(tPCB));//void *memcpy(void *dest, const void *src, size_t n);从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中。
    39.  
      task[i].pid = i;
    40.  
      task[i].state = -1;//这些进程的状态都设置为未运行。
    41.  
      task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];
    42.  
      task[i].next = task[i-1].next;//新创建的进程的next指向0号进程的首地址
    43.  
      task[i-1].next = &task[i];//前一个进程的next指向最新创建的进程的首地址,从而成为一个循环链表。
    44.  
      }
    45.  
      /* start process 0 by task[0] */
    46.  
      pid = 0;
    47.  
      my_current_task = &task[pid];//当前运行的进程设置为0号进程。
    48.  
      asm volatile(
    49.  
      "movl %1,%%esp " /* set task[pid].thread.sp to esp */
    50.  
      "pushl %1 " /* push ebp */
    51.  
      "pushl %0 " /* push task[pid].thread.ip */
    52.  
      "ret " /* pop task[pid].thread.ip to eip */
    53.  
      "popl %%ebp "
    54.  
      :
    55.  
      : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/
    56.  
      );
    57.  
      }
    58.  
      void my_process(void)
    59.  
      {
    60.  
      int i = 0;
    61.  
      while(1)
    62.  
      {
    63.  
      i++;
    64.  
      if(i%10000000 == 0)
    65.  
      {
    66.  
      printk(KERN_NOTICE "this is process %d - ",my_current_task->pid);//打印-号
    67.  
      if(my_need_sched == 1)//如果需要调度
    68.  
      {
    69.  
      my_need_sched = 0;
    70.  
      my_schedule();
    71.  
      }
    72.  
      printk(KERN_NOTICE "this is process %d + ",my_current_task->pid);//打印+号
    73.  
      }
    74.  
      }
    75.  
      }

    3.3 mypcb.h

    1. /*
    2.  
      * linux/mykernel/mypcb.h
    3.  
      *
    4.  
      * Kernel internal PCB types
    5.  
      *
    6.  
      * Copyright (C) 2013 Mengning
    7.  
      *
    8.  
      */
    9.  
      #define MAX_TASK_NUM 4
    10.  
      #define KERNEL_STACK_SIZE 1024*8
    11.  
       
    12.  
      /* CPU-specific state of this task */
    13.  
      struct Thread {
    14.  
      unsigned long ip;
    15.  
      unsigned long sp;
    16.  
      };
    17.  
       
    18.  
      typedef struct PCB{
    19.  
      int pid;
    20.  
      volatile long state;
    21.  
      char stack[KERNEL_STACK_SIZE];
    22.  
      /* CPU-specific state of this task */
    23.  
      struct Thread thread;
    24.  
      unsigned long task_entry;
    25.  
      struct PCB *next;
    26.  
      }tPCB;
    27.  
       
    28.  
      void my_schedule(void);

    五. 分析进程的启动和进程的切换机制

          内核启动__init my_start_kernel(void),创建了4个进程,分别是0,1,2,3号,设置0号为运行态,其它3个进程为未运行态。0号进程的入口都被初始化为task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;即指向my_process()
          0号进程的栈顶被初始化为 task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];之后的进程也都是根据0号进程复制得到,所以它们的起始入口也是my_process(),初始栈顶也是指向了自己的stack[KERNEL_STACK_SIZE-1];
    my_current_task = &task[pid];将当前进程设置为0号进程。然后从0号进程开始运行。
    “movl %1,%%esp ”
          将0号进程的栈顶放入esp寄存器。
    “pushl %1 ” /* push ebp */
          当前esp指向了stack数组的末尾,也就是栈顶,由于栈是空的,故有esp==ebp
    “pushl %0 " /* push task[pid].thread.ip */
    “ret ” /* pop task[pid].thread.ip to eip */
         这里是切换到0号进程的入口地址开始执行。

         切换到0号进程后,0号进程不断执行my_process()。之后,my_timer_handler()被内核调用,触发中断,my_need_sched = 1;将全局变量my_need_sched 设置为了1。在程序当中,在0号进程执行到了if(my_need_sched == 1)语句时就会进入这个if条件分支中,执行my_schedule();执行进程调度。

         0号进程的next指针指向的是1号进程,所以在my_schedule()中的next指针指向了1号进程,prev指针指向了0号进程。
         由于1号进程还没被运行过,所以会执行else条件分支:next->state = 0;//将1号进程设置为运行状态
    my_current_task = next;//当前进程切换为1号进程

    “pushl %%ebp ” /* save ebp */
     “movl %%esp,%0 ” /* save esp */
           将0号进程的ebp和esp都保存到0号进程的栈上。
    1. “movl %2,%%esp ” /* restore esp */
    2. “movl %2,%%ebp ” /* restore ebp */

          进程的切换情况大致上就是如上所述。

    1.  
  • 相关阅读:
    序列合并(luogu 1631)题解
    邮递员送信(luogu 1629)题解
    敲砖块(codevs 1257)题解
    货车运输(codevs 3287)题解
    分布式服务框架 Zookeeper -- 管理分布式环境中的数据
    dubbo学习
    第一章 1.20 多线程基础
    第一章 1.19 网络编程基础
    练习 : 面向对象
    练习 : 正则表达式
  • 原文地址:https://www.cnblogs.com/lmmn/p/13866655.html
Copyright © 2020-2023  润新知