Linux操作系统进程模型分析

发表于:2018-5-07 10:03

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:郑志煌    来源:51testing软件测试网采编

#
Linux
分享:
  Linux操作系统简介
  Linux拥有现代操作系统的功能,如真正的抢先式多任务处理,支持多用户内存,保护虚拟内存,支持SMP、UP,符合POSIX 标准联网、图形用户接口和桌面环境具有快速性、稳定性等特点。本文通过分析Linux内核源代码,说明了Linux作为操作系统内核是如何对进程组织,转换,调度的。
  进程的组织
  进程在内核中是以PCB的形式存在的,一个操作系统有很多进程,即就是很多PCB同时存在,如何有效的管理进程的组织方式,显然是一个很重要的问题,为了遍历内核中所有的进程,内核将这些PCB直接用双向链表链起来,task_struct 结构中的tasks字段就是用来连接所有进程描述符的。在这个链中,链表头是init_task描述符,它是所谓的0进程或者swapper进程的描述符,init_task.tasks.prev字段指向链表中最后插入的进程描述符的tasks字段。
  struct task_struct { 
  struct list_head run_list;
  struct list_head tasks; 
  struct task_struct *real_parent; /*real parent process (when being debugged) */
  struct task_struct *parent; /* parent process */
  struct list_head children; /* list of my children */
  struct list_head sibling; /* linkage in my parent's children list */
  struct task_struct *group_leader; /* threadgroup leader */
  /* PID/PID hash table linkage. */
  struct pid_link pids[PIDTYPE_MAX];
  struct list_head thread_group;
  };
  从以上源代码中的字段可知Linux是如何组织进程的。
  字段run_list:
  在内核中有一个队列是可运行队列,即就是保存了进程状态为:TASK_RUNNING的进程PCB,这个队列就是run_list字段,内核为每个优先级都设置了一个可运行队列,即如果一个进程的优先级为k(取值范围是0到139),那么内核就将此进程链入到优先权为k的可运行队列中,这样CPU可以从高优先级可可运行队列去选择要运行的进程,这样提高了效率,但为此却要把所有可运行的进程队列拆分成140个不同的队列。这些队列有一个单独的数据结构prio_array_t数据结构 来实现,定义如下(摘自linux2.6.11):
  typedef struct prio_array prio_array_t;
  struct prio_array {
           unsigned int nr_active;
           unsigned long bitmap[BITMAP_SIZE];
           struct list_head queue[MAX_PRIO];
  };
  字段 children、 sibling和thread_group:
  这三个字段分别是进程的子进程链表、兄弟链表和线程组链表。可以根据children和sibling字段找到一个进程家族的进程。进程在创建后必须处于某一个组中,通过thread_group来形成一个进程组。
  进程的转换
  Linux中进程切换过程与函数调用堆栈基本类似
  void my_schedule(void)
  {
      tPCB * next;
      tPCB * prev;
      if(my_current_task == NULL 
          || my_current_task->next == NULL)
      {
          return;
      }
      printk(KERN_NOTICE ">>>my_schedule<<<\n");
      /* schedule */
      next = my_current_task->next;
      prev = my_current_task;
      /*进程上下文的切换,与函数调用堆栈类似*/
      if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
      {
          /* switch to next process */
          asm volatile(
              /*保存当前进程的ebp esp*/  
              "pushl %%ebp\n\t"       /* save ebp */
              "movl %%esp,%0\n\t"     /* save esp */
              /*构建下一个进程的esp*/
              "movl %2,%%esp\n\t"     /* restore  esp */
              /*$1f是指下面标号为1的位置,就是下一个进程启动的位置*/
              "movl $1f,%1\n\t"       /* save eip */ 
              /*下一个进程eip压栈*/
              "pushl %3\n\t" 
              /*恢复现场:eip指向下下个进程的地址ebp恢复为第一条指令压栈保存的ebp*/
              "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; 
          printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);      
      }
      else
      {
          next->state = 0;//新的进程设置为正在运行状态
          my_current_task = next;
          printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
          /* switch to new process */
          asm volatile(   
              "pushl %%ebp\n\t"       /* save ebp */
              "movl %%esp,%0\n\t"     /* save esp */
              /*新的进程从来没有执行过,所以栈为空esp与ebp指向同一位置*/
              "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; 
  }
  切换过程:
  1.保存当前进程的堆栈环境(esp,eip,ebp),首先将当前进程的ebp压栈,并将esp和eip保存在当前进程的结构体中
  2.构建下一进程的堆栈环境。然后下一条进程的thread.sp读出,赋给esp;thread.ip读出赋给eip。若下一条进程是新的进程,处于未运行状态,则新进程堆栈为空,esp与ebp指向同一地址,这种情况与函数调用堆栈更加类似。若下一条进程处于运行态,那么ebp与当前进程的ebp指向同一地址。
  3.恢复堆栈环境,为下下个进程做准备。恢复eip,即把执行完成的进程的thread.ip(下一进程的指令地址)赋给eip,上例中所有进程的thread.ip设为my_process。
  如图所示: 
  
  进程的调度
  Linux系统进程提供了两种优先级,一种是普通的进程优先级,第二个是实时优先级。前者适用SCHED_NORMAL调度策略,后者可选SCHED_FIFO或SCHED_RR调度策略。任何时候,实时进程的优先级都高于普通进程,实时进程只会被更高级的实时进程抢占,同级实时进程之间是按照FIFO(一次机会做完)或者RR(多次轮转)规则调度的。
  Linux进程状态机:   
  个人看法
  在日常使用手机或者电脑的时候,经常在将应用关闭或退出后,仍然还有许多进程在后台运行,促使我对进程的存在产生了好奇心。现在明白,不仅仅在应用运行时会产生进程,计算机系统本身在运行过程中也会产生许多进程,就是这些进程使得计算机可以调用许多功能,与人进行交互。而通过对Linux操作系统进程的探索,使我对进程有了更进一步的了解,懂得了为什么计算机可以同时运行那么多程序,好的进程的组织,转换和调用模式可以极大地提升计算机的性能,帮助我们更好地完成任务。



上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
精选软件测试好文,快来阅读吧~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号