5. 进程的产生 缘起缘灭,一切因缘而且。了解linux进程管理的机制,首先得搞清楚进程因何而起,这个因缘就是do_fork函数。而调用do_fork的地方首先是fork系统调用,其次是内核创建内核线程的kernel_thread。还有在哪里会调用这个函数?想必也是盘古开天辟地之前,宇宙混沌之初的事情了吧,已经超出了缘起缘灭能够解释的清楚的了,不再追究下去。下面我们看一下do_fork的真实面目。
总的来说do_fork完成4项事情,首先它需要检查外部传入的各种控制flag是否自洽,同时进行一些权限的检查。然后,调用copy_process完成实际task struct和thread info的创建工作。虽然我们说进程缘起于do_fork,但是,do_fork是一个大boss,它只管framework的事情咯,实际工作要靠下面的小boss copy_process来完成。经过copy_process的塑造之后,一个新的进程有胫骨和躯干,但是这个时候新进程还是孤魂一枚,游离于三界之外,必须通过另外一员大神wake_up_new_task,将新的进程送入人间,也就是runqueues。这样do_fork的主要工作就完成了,之后do_fork判断原来进程是否设置了CLONE_VFORK的标记,如果设置了休眠就等待子进程将自己唤醒。
上面就将创建进程的过程说完了,也许你觉得这个也太简单了吧。不对,将上面的过程展开,就是一个非常复杂的过程。下面着重介绍copy_process和wake_up_new_task的过程。
Copy_process主要完成了进程的管理结构体的创建和初始化。这部分的内容涉及整个linux操作系统,我们不可能也没有必要一口气把这个过程啃下来。可以细水长流(最近老是听到这个词,主要用在受到排挤和打击感到心情郁闷自我鼓励勉励的场合),慢慢消化。所以我只将这个过程的重要部分分析。
首先还是各种标记自洽性检查
然后调用p = dup_task_struct(current);复制task struct 和thread info结构体
对task struct结构体的初始化
调用sched_fork对task 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_thread。copy_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时间并设置重新调度
最后将这个新的进程插入到运行队列中去。
最后调用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. */ /*设置last和next,在选择被调度的进程时有用*/ 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); /*此处也是这个函数的精髓,如果p的vruntime比curr的vruntime小了一个 颗粒的时间,就可以发生抢占,设置重新调度的标记*/ if (wakeup_preempt_entity(se, pse) == 1) { resched_task(curr); break; } se = parent_entity(se); pse = parent_entity(pse); } } |
阅读(2387) | 评论(0) | 转发(3) |