linux内核分析--操作系统是如何工作的?

时间:2021-03-18 15:48:37

一个简单的时间片轮转多道程序

  • 操作系统的“两把剑”:中断上下文(保存现场和恢复现场)和进程上下文的切换

    源代码的分析

    *使用的源代码为视频中所使用的精简内核的源代码

首先分析mypcd.h

struct Thread {
 unsigned long  ip;//point to cpu run address
 unsigned long  sp;//point to the thread stack's top address
  //todo add other attrubte of system thread
};

这是一个进程的结构体,其中的ip标记的是该进程的eip,而sp标记的是该进程的esp。

typedef struct PCB{
    int pid; // 进程的id
    volatile long state;    /*进程的状态 -1 unrunnable, 0 runnable, >0 stopped */
    char stack[KERNEL_STACK_SIZE];//进程的堆栈 each pcb stack size is 1024*8
    /* CPU-specific state of this task */
    struct Thread thread;
    unsigned long   task_entry;//进程的入口
    struct PCB *next;//以链表的形式连接下一个PCB
    unsigned long priority;//进程的优先度////////
 //todo add other attrubte of process control block
}tPCB;

这是一个进程控制块的结构体,记录着进程的的各种状态与信息。

void my_schedule(void);

该函数的功能为调度器,用于进程之间的调度运行。

接着分析mymain.c即进程的启动机制

void __init my_start_kernel(void)   
{
    int pid = 0;
 /* Initialize process 0*/
    task[pid].pid = pid;
    task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */
    // set task 0 execute entry address to my_process
    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(pid=1;pid<MAX_TASK_NUM;pid++) 创建进程
    {
        memcpy(&task[pid],&task[0],sizeof(tPCB));
        task[pid].pid = pid;
        task[pid].state = -1;
        task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
    task[pid].priority=get_rand(PRIORITY_MAX);//each time all tasks get a random priority
 }
    task[MAX_TASK_NUM-1].next=&task[0];
  printk(KERN_NOTICE "\n\n\n\n\n\n                system begin :>>>process 0 running!!!<<<\n\n");
 /* start process 0 by task[0] */
 pid = 0;
  my_current_task = &task[pid];
asm volatile(
  "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */
  "pushl %1\n\t" /* push ebp */
  "pushl %0\n\t" /* push task[pid].thread.ip */
  "ret\n\t" /* pop task[pid].thread.ip to eip */
  "popl %%ebp\n\t"
  :
   : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)    /* input c or d mean %ecx/%edx*/
);
}
  • 首先是先创建第一个进程,定义其进程编号为0,状态为可执行,设置进程入口的地址为该进程的eip,设置进程的堆栈为该进程的堆栈,task[0].next=&task[0];因为此时系统之中仅仅只要一个进程,该进程的下一个进程就只能暂且指向自己了。
  • 接着是创建更多的进程,步骤是通过一个for循环进程依次创建进程的编号、状态和堆栈。将新创建的进程加入上一个进程的尾部。
  • 接下来就是要启动0号进程也就是第一个进程,将该进程的堆栈标记so存入寄存器esp中,紧接着将ebp压入栈中,再将该进程的eip压入栈中,接着将myprocess的eip弹出给eip,也就是启动0号进程。
  • 当运行完后则需要运行popl %ebp退回上一状态。

*1f指的是标号1所在的位置。

    void my_process(void)
    {
    int i = 0;
    while(1)
    {
       i++;
       if(i%10000000 == 0)
       {
  
          if(my_need_sched == 1)
          {
               my_need_sched = 0;
        sand_priority();
        my_schedule();  
            }
        }
    }   
    }//end of my_process
  • 在myprocess的函数之中输出时采用了主动调度,循环1000万次才有一次机会判断一下是否需要调度。
  • 调度完切换回来时,则从myschedule();函数继续执行。

最后分析myinterrupt.c即进程的切换机制

void my_timer_handler(void)
{
#if 1
// make sure need schedule after system circle 2000 times.
if(time_count%2000 == 0 && my_need_sched != 1)
{
    my_need_sched = 1;
//time_count=0;
}
time_count ++ ;
#endif
return;
}
  • 该函数的作用是设置时间片的大小,时间片用完时设置一下调度标志,当进程调度发现状态为1时,则会执行一次my_process,而后执行一次my_schedule。

    void my_schedule(void)
    {
     tPCB * next;
     tPCB * prev;
      // if there no task running or only a task ,it shouldn't need schedule
      if(my_current_task == NULL
     || my_current_task->next == NULL)
     {
        printk(KERN_NOTICE "                time out!!!,but no more than 2 task,need not schedule\n");
     return;
     }
     /* schedule */
    
     next = get_next();
     prev = my_current_task;
     printk(KERN_NOTICE "                the next task is %d priority is %u\n",next->pid,next->priority);
     if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
     {//save current scene
      /* switch to next process */
      asm volatile( 
          "pushl %%ebp\n\t" /* save ebp */
          "movl %%esp,%0\n\t" /* save esp */
          "movl %2,%%esp\n\t" /* restore esp */
          "movl $1f,%1\n\t" /* save eip */  
          "pushl %3\n\t"
          "ret\n\t" /* restore eip */
          "1:\t" /* next process start here */
          "popl %%ebp\n\t"
         : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
       : "m" (next->thread.sp),"m" (next->thread.ip)
      );
     my_current_task = next;//switch to the next task
     printk(KERN_NOTICE "                switch from %d process to %d process\n                >>>process %d running!!!<<<\n\n",prev->pid,next->pid,next->pid);
    
    }
      else
     {
        next->state = 0;
        my_current_task = next;
        printk(KERN_NOTICE "                switch from %d process to %d process\n                >>>process %d running!!!<<<\n\n\n",prev->pid,next->pid,next->pid);
    
        /* switch to new process */
     asm volatile(  
     "pushl %%ebp\n\t" /* save ebp */
     "movl %%esp,%0\n\t" /* save esp */
     "movl %2,%%esp\n\t" /* restore esp */
     "movl %2,%%ebp\n\t" /* restore ebp */
     "movl $1f,%1\n\t" /* save eip */   
     "pushl %3\n\t"
     "ret\n\t" /* restore eip */
     : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
     : "m" (next->thread.sp),"m" (next->thread.ip)
      );
    }
    return; 
    }//end of my_schedule
  • 当前进程的下一个进程赋给next,当前进程赋给prev。如果下一个进程状态为0的话,即正在执行,则需要在两个正在运行的进程之间做进程上下文切换:
    • 保存当前进程的ebp
    • 将当前进程的esp赋给prev->thread.sp,从而将其保存起来
    • 将下一个进程的sp放置esp中
    • 保存当前进程的eip至当前进程所属的pcb中
    • 将下一个进程的eip保存至当前栈中
    • 通过ret,即可调用下一个进程
  • 若下一个进程的状态还未执行过,则进行以下步骤:
    • 将下一个进程设置为运行时状态,并将其设置为当前进程
    • 保存当前进程的ebp,将其压入栈中
    • 保存当前进程的esp至当前进程的pcb中
    • 由于该程序并未执行过,所以它的堆栈为空栈,空栈的创建则是通过将ebp和esp都赋予同一个值。
    • 保存当前进程的eip
    • 将当前进程的eip压入栈中
    • 通过ret,即可调用下一个进程

操作系统是如何工作的

  • 操作系统是控制应用程序执行和充当硬件系统和应用程序之间的界面的软件。
  • 操作系统是一个大型、复杂的系统软件,它负责计算机的全部软、硬件资源的分配、调度工作,控制和协调并发活动,实现信息的存取和保护。它提供用户接口,使用户获得良好的工作环境。操作系统使整个计算机系统实现了高效率和自动化,是现代计算机系统最关键和最核心的软件系统。
  • 操作系统能够完成程序创建、程序执行、、I/O设备访问、控制对文件的访问、系统访问、查错和纠错、簿记。
  • 操作系统的核心任务之一就是管理各种可获得的资源以及合理地调度它们。
  • 操作系统保存了一些队列,每个队列都是一组等待某些资源的进程。
    • 短程队列是由在主存中并处于就绪状态的进程组成。这些进程中的任何一个进程都可以作为处理器的下一个选用者,具体选择哪个进程由分派程序决定。(中断上下文)
    • 长程队列是一列等待使用系统的新进程。操作系统通过将一个进程从长程队列转移到短程队列,向系统增加任务。每个I/O设备都有一个I/O队列。等待使用某个设备的所有进程都排在该设备队列中。当中断发生时,操作系统得到处理器的控制权。一个进程可能会求助于操作系统提供的一些服务。在这种情况下,服务调度处理程序成为进入操作系统的入口。

实验楼截图

linux内核分析--操作系统是如何工作的?

linux内核分析--操作系统是如何工作的?

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