Chinaunix首页 | 论坛 | 博客
  • 博客访问: 924456
  • 博文数量: 63
  • 博客积分: 568
  • 博客等级: 中士
  • 技术积分: 3435
  • 用 户 组: 普通用户
  • 注册时间: 2012-10-05 11:44
文章分类
文章存档

2016年(4)

2015年(6)

2014年(3)

2013年(27)

2012年(23)

分类: LINUX

2012-12-02 22:33:55

5. 进程的产生

缘起缘灭,一切因缘而且。了解linux进程管理的机制,首先得搞清楚进程因何而起,这个因缘就是do_fork函数。而调用do_fork的地方首先是fork系统调用,其次是内核创建内核线程的kernel_thread。还有在哪里会调用这个函数?想必也是盘古开天辟地之前,宇宙混沌之初的事情了吧,已经超出了缘起缘灭能够解释的清楚的了,不再追究下去。下面我们看一下do_fork的真实面目。

wps_clip_image-1432

总的来说do_fork完成4项事情,首先它需要检查外部传入的各种控制flag是否自洽,同时进行一些权限的检查。然后,调用copy_process完成实际task structthread info的创建工作。虽然我们说进程缘起于do_fork,但是,do_fork是一个大boss,它只管framework的事情咯,实际工作要靠下面的小boss copy_process来完成。经过copy_process的塑造之后,一个新的进程有胫骨和躯干,但是这个时候新进程还是孤魂一枚,游离于三界之外,必须通过另外一员大神wake_up_new_task,将新的进程送入人间,也就是runqueues。这样do_fork的主要工作就完成了,之后do_fork判断原来进程是否设置了CLONE_VFORK的标记,如果设置了休眠就等待子进程将自己唤醒。

上面就将创建进程的过程说完了,也许你觉得这个也太简单了吧。不对,将上面的过程展开,就是一个非常复杂的过程。下面着重介绍copy_processwake_up_new_task的过程。

Copy_process主要完成了进程的管理结构体的创建和初始化。这部分的内容涉及整个linux操作系统,我们不可能也没有必要一口气把这个过程啃下来。可以细水长流(最近老是听到这个词,主要用在受到排挤和打击感到心情郁闷自我鼓励勉励的场合),慢慢消化。所以我只将这个过程的重要部分分析。

首先还是各种标记自洽性检查

然后调用p = dup_task_struct(current);复制task struct thread info结构体

task struct结构体的初始化

调用sched_forktask struct结构中调度相关的一些变量进行初始化。

void sched_fork(struct task_struct *p, int clone_flags)
{
    int cpu = get_cpu();
 
    __sched_fork(p);
 
#ifdef CONFIG_SMP
    cpu = sched_balance_self(cpu, SD_BALANCE_FORK);
#endif 
    set_task_cpu(p, cpu);
 
    /*
     * Make sure we do not leak PI boosting priority to the child:
     */ 
    p->prio = current->normal_prio;
    if (!rt_prio(p->prio))
        p->sched_class = &fair_sched_class;
 
#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)
    if (likely(sched_info_on()))
        memset(&p->sched_info, 0, sizeof(p->sched_info));
#endif 
#if defined(CONFIG_SMP) && defined(__ARCH_WANT_UNLOCKED_CTXSW)
    p->oncpu = 0;
#endif 
#ifdef CONFIG_PREEMPT
    /* Want to start with kernel preemption disabled. */ 
    task_thread_info(p)->preempt_count = 1;
#endif 
    put_cpu();
}
 

根据标致,复制进程的各种资源

另外一个很重要的函数就是一个体系结构相关的函数Copy_threadcopy_thread,初始化了一下子进程的内核态栈,设置子进程的用户态栈地址,这个地址应该和父进程是一样的,利用写时复制技术,才分离实际物理地址空间。而子进程保存的cpu现场就全是空了,内核栈初始化为新分配的空间,返回地址为ret_from_fork

int 
copy_thread(int nr, unsigned long clone_flags, unsigned long stack_start,
            unsigned long stk_sz, struct task_struct *p, struct pt_regs *regs)
{
    struct thread_info *thread = task_thread_info(p);
    struct pt_regs *childregs = task_pt_regs(p);
 
    *childregs = *regs;
    childregs->ARM_r0 = 0;
    childregs->ARM_sp = stack_start;
 
    memset(&thread->cpu_context, 0, sizeof(struct cpu_context_save));
    thread->cpu_context.sp = (unsigned long)childregs;
    thread->cpu_context.pc = (unsigned long)ret_from_fork;
 
    if (clone_flags & CLONE_SETTLS)
        thread->tp_value = regs->ARM_r3;
 
    return 0;
}

当一个进程的管理结构体建立好之后,实际上它只是有了一个壳。如何才能被CPU调度到,并装入CPU来执行内,这就得先把进程放入到runqueue上面去。wake_up_new_task函数正是来完成这项工作。

wake_up_new_task主要完成将新创建的task放入运行队列,并检查新的进程是否可以抢占CPU,给新进程一个立即执行的机会。主调度器,主要指linux内核的调度框架,而CFS调度器就是指完全公平算法调度。

Task_new_fair完成将新建的进程放入到调度队列中去。

在该函数中首先调用update_curr跟新current进程中和runqueue中相关的统计量。Updat_curr主要更新了如下变量

然后调用place_entity给新的进程分配runtime时间。首先将cfs调度队列上的min_vruntime赋值给vruntime,不过没有这样的好事,还要在这个基础上加上一个vslice的时间。否则,用户可以通过不断的fork新的进程来占系统的便宜。

struct sched_entity *curr = cfs_rq->curr;
curr->sum_exec_runtime += delta_exec;
curr->vruntime += delta_exec_weighted;
 
update_min_vruntime(cfs_rq);
     cfs_rq->min_vruntime = max_vruntime(cfs_rq->min_vruntime, vruntime);
 
curr->exec_start = now;

如果调度策略设置新创建进程优先执行,并且current目前的virruntime比新的进程更加小。怎两个进程交换一下virruntime时间并设置重新调度

最后将这个新的进程插入到运行队列中去。

wps_clip_image-19399

最后调用check_preempt_wakeup判断新进程是否可以抢占curr进程的过程不再使用图来分析,直接贴上代码和注解

 
/*
* Preempt the current task with a newly woken task if needed:
*/ 
static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int sync)
{
    struct task_struct *curr = rq->curr;
    struct sched_entity *se = &curr->se, *pse = &p->se;
    struct cfs_rq *cfs_rq = task_cfs_rq(curr);
 
    update_curr(cfs_rq);
 
    /*如果p是个实时进程,毫不犹豫的设置重新调度标致*/ 
    if (unlikely(rt_prio(p->prio)))
    {
        resched_task(curr);
        return;
    }
 
    /*p的调度类都不是CFS,关我屁事,返回!*/ 
    if (unlikely(p->sched_class != &fair_sched_class))
        return;
 
    /*curr 等于p,本想检查一下p是否可以抢占cpu,不了curr自己就是p,返回吧!*/ 
    if (unlikely(se == pse))
        return;
 
    /*
     * Only set the backward buddy when the current task is still on the
     * rq. This can happen when a wakeup gets interleaved with schedule on
     * the ->pre_schedule() or idle_balance() point, either of which can
     * drop the rq lock.
     *
     * Also, during early boot the idle thread is in the fair class, for
     * obvious reasons its a bad idea to schedule back to the idle thread.
     */ 
    /*设置lastnext,在选择被调度的进程时有用*/ 
    if (sched_feat(LAST_BUDDY) && likely(se->on_rq && curr != rq->idle))
        set_last_buddy(se);
    set_next_buddy(pse);
 
    /*
     * We can come here with TIF_NEED_RESCHED already set from new task
     * wake up path.
     */ 
    /*如果进程已经设置了resched标致,返回吧*/ 
    if (test_tsk_need_resched(curr))
        return;
 
    /*
     * Batch and idle tasks do not preempt (their preemption is driven by
     * the tick):
     */ 
    if (unlikely(p->policy != SCHED_NORMAL))
        return;
 
    /* Idle tasks are by definition preempted by everybody. */ 
    /*IDLE进程能被所有进程抢占*/ 
    if (unlikely(curr->policy == SCHED_IDLE))
    {
        resched_task(curr);
        return;
    }
 
    if (!sched_feat(WAKEUP_PREEMPT))
        return;
 
    if (sched_feat(WAKEUP_OVERLAP) && (sync ||
                                       (se->avg_overlap < sysctl_sched_migration_cost &&
                                        pse->avg_overlap < sysctl_sched_migration_cost)))
    {
        resched_task(curr);
        return;
    }
 
    find_matching_se(&se, &pse);
 
    while (se)
    {
        BUG_ON(!pse);
        /*此处也是这个函数的精髓,如果pvruntimecurrvruntime小了一个
          颗粒的时间,就可以发生抢占,设置重新调度的标记*/ 
        if (wakeup_preempt_entity(se, pse) == 1)
        {
            resched_task(curr);
            break;
        }
 
        se = parent_entity(se);
        pse = parent_entity(pse);
    }
}
 

阅读(2333) | 评论(0) | 转发(3) |
给主人留下些什么吧!~~