基于Linux系统深入源码分析进程模型

1.进程概念

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。简而言之,一个进程就是一个正在执行程序的实例。

windows的进程:

2.linux系统简介

  Linux以它的高效性和灵活性著称,Linux模块化的设计结构,使得它既能在价格昂贵的工作站上运行,也能够在廉价的PC机上实现全部的Unix特性,具有多任务、多用户的能力。Linux是在GNU公共许可权限下免费获得的,是一个符合POSIX标准的操作系统。Linux操作系统软件包不仅包括完整的Linux操作系统,而且还包括了文本编辑器、高级语言编译器等应用软件。它还包括带有多个窗口管理器的X-Windows图形用户界面,如同我们使用Windows NT一样,允许我们使用窗口、图标和菜单对系统进行操作。

3.Linux系统组织进程

  • Linux通过task_struct结构体来描述一个进程的所有信息,结构体被定义在 include/linux/sched.h中。
  • task_struct结构体即是PCB。PCB是进程的唯一标识,PCB由链表实现(为了动态插入和删除)。进程创建时,为该进程生成一个PCB;进程终止时,回收PCB。
  • PCB相关
    • 进程状态(State)
    • 进程调度信息
    • 标识符
    • 进程通信有关信息
    • 进程链接信息
    • 时间和定时器信息
    • 文件系统信息
    • 虚拟内存信息
    • 页面管理信息
    • 对称多处理机(SMP)信息
    • 和处理器相关的环境(上下文)信息
    • 其它

 

4.进程的调度

4.1调度的定义:

当计算机系统十多道程序设计系统时,通常就会有多个进程或线程同时竞争CPU。只要有两个或更多的进程处于就绪状态,这种情形就会发生。如果只有一个CPU可用,那么就必须选择下一个要运行的进程。在操作系统中,完成选择工作的这一部分称为调度程序,该程序使用的算法称为调度算法

4.2Linux系统的两个调度算法

4.2.1LinuxO(1)调度器(O(1)scheduler)

       这个调度器能够在常数时间内执行任务调度,例如从执行队列中选择一个任务或将一个任务加入执行队列,这与系统中的任务总数无关。

      O(1)调度器优点:能够在常数时间内执行任务调度

      O(1)调度器缺点:使用启发式方法来确定一个任务的交互性,会使该任务的优先级复杂且不完善,从而导致在处理交互任务时性能很糟糕。

4.2.2CFS(完全公平调度器)

      它在Linux2.6.23版本中首次被集成到内核中     

      CFS的主要思想是使用一颗红黑树作为调度队列的数据结构。

      CFS的调度算法总结:该算法总是优先调度那些使用CPU时间最少的任务,通常是在树中最左边节点上的任务。CFS会周期性地根据任务已经运行的时间,递增他的虚拟时间运行值,并将这个值与树中当前最左节点的值进行比较,如果正在运行的任务仍具有较小虚拟运行时间值,那么它将继续运行,否则,它将被插入红黑树的适当位置,并且CPU将执行新的最左边节点上的任务。 

5.关于vruntime

vruntime是CFS的重要概念,简单地说,vruntime是该进程的运行时间,但这个时间是通过优先级等计算过的虚拟运行时间,并非物理时间。之前在进程的转换中提到过CPU分配时间片,在CFS中,已经不再使用时间片,而是按照虚拟运行时间来衡量进程此时是否该被调度。那么,虚拟时间是如何被计算出来的呢?kernel/sched.c中有一个用于处理时钟中断的函数,如下

void scheduler_tick(void)
{
…
    raw_spin_lock(&rq->lock);
    update_rq_clock(rq);
    update_cpu_load(rq);
    curr->sched_class->task_tick(rq, curr, 0); 
    raw_spin_unlock(&rq->lock);
…
}

其中,curr->sched_class->task_tick(rq, curr, 0);用于执行调度器,更新vruntime值。task_tike_fair函数则会在循环语句中,对所以有se变量调用entity_tick(),代码如下

for_each_sched_entity(se) {
        cfs_rq = cfs_rq_of(se);
        entity_tick(cfs_rq, se, queued);//Look at this line
    }
static void entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
{
    /*
     * Update run-time statistics of the 'current'.
     */
    update_curr(cfs_rq);    //Look at this line

#ifdef CONFIG_SCHED_HRTICK
    /*
     * queued ticks are scheduled to match the slice, so don't bother
     * validating it and just reschedule.
     */
    if (queued) {
        resched_task(rq_of(cfs_rq)->curr);
        return;
    }
    /*
     * don't let the period tick interfere with the hrtick preemption
     */
    if (!sched_feat(DOUBLE_TICK) &&
            hrtimer_active(&rq_of(cfs_rq)->hrtick_timer))
        return;
#endif

    if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT))
        check_preempt_tick(cfs_rq, curr);
}

重点是update_curr(cfs_rq);更新了就绪队列的vruntime值。ps:cfs_rq为普通进程的就绪队列,声明如下

    struct cfs_rq       *cfs_rq;
    /* rq "owned" by this entity/group: */
static inline void __update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr, unsigned long delta_exec)
{
    unsigned long delta_exec_weighted;

    schedstat_set(curr->exec_max, max((u64)delta_exec, curr->exec_max));

    curr->sum_exec_runtime += delta_exec;
    schedstat_add(cfs_rq, exec_clock, delta_exec);
    delta_exec_weighted = calc_delta_fair(delta_exec, curr);

    curr->vruntime += delta_exec_weighted;
    update_min_vruntime(cfs_rq); 
}

可以看到,update_curr函数首先累计了实际的运行时间(Line:7),然后调用calc_delta_fair(delta_exec,curr)对delta_exec进行加权计算(Line:9)(至于如何进行加权计算没有再深入研究),然后再将加权计算的结果累计加入vruntime(Line:11),最后更新并保存cfs_rq队列中的的最小虚拟运行时间(Line:12)。由此,我们可以大概看到vruntime产生的整个过程。

 

posted on 2018-05-08 19:16  yg12138  阅读(131)  评论(0编辑  收藏  举报