第1次作业:操作系统Linux进程
一个进程在CPU上运行可以有两种运行模式(进程状态):用户模式和内核模式。如果当前运行的是用户程序(用户代码),那么对应进程就处于用户模式(用户态),如果出现系统调用或者发生中断,那么对应进程就处于内核模式(核心态)。
1.什么是进程
在百度上面进程是这么解释的:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
简单来说一个进程就是一个正在运行的程序。一个进程应该包含以下内容:
(1) 程序的代码,既然进程是一个正在运行的程序,自然需要程序的代码
(2) 程序的数据
(3) CPU寄存器的值,包括通用寄存器,程序计数器
(4) 堆( heap )是用来保存进程运行时动态分配的内存空间
(5) 栈( stack )有两个用途,1.保存运行的上下文信息。2.在函数调用时保存被调用函数的形参或者局部变量
(6) 进程所占用的一组系统资源,如打开的文件
2.进程的含义
2.1进程的状态
一般来说一个进程被创建开始,一直到它的生命结束为止,它只可能存在三个阶段:运行、就绪、阻塞。当然在具体实现中有的操作系统的状态个数可能不是三个。如可能把就绪和阻塞统称为暂停状态,或者是新增两个状态:创建和结束。但最核心的还是运行、就绪、阻塞。
2.1.1运行状态
进程占用着CPU,并且在CPU上运行。显然处于这种状态的进程数量<=CPU的数目。若只有一个CPU那么任何时刻最多只能有一个进程处于运行状态。
2.1.2就绪状态
进程已经具备了运行的条件,但由于CPU正忙着运行其他的进程,所以暂时不能运行。不过只要把CPU分给它,它就立刻可以运行。正所谓万事俱备只欠东风。
2.1.3阻塞状态
进程因为某种事件的发生暂时不能运行的状态。例如它正等待某个输入输出的操作完成,或者它与其他线程之间存在同步关系,需要等待其他进程给它输入数据。这种情况下即使CPU已经空闲下来,这个进程还是不能运行。
2.1.4状态切换
运行可转化为阻塞、就绪。
阻塞可转化为就绪。
就绪可转化为运行。
进程执行时,它会根据具体情况改变状态。进程状态是调度和对换的依据。Linux中的进程主要有如下状态,如表1所示。
表1 Linux进程的状态
内核表示 | 含义 |
TASK_RUNNING | 可运行 |
TASK_INTERRUPTIBLE | 可中断的等待状态 |
TASK_UNINTERRUPTIBLE | 不可中断的等待状态 |
TASK_ZOMBIE | 僵死 |
TASK_STOPPED | 暂停 |
TASK_SWAPPING | 换入/换出 |
2.2进程标识符
每个进程有进程标识符、用户标识符、组标识符,如表2所示。
表2 各种标识符
域名 |
含义 |
Pid |
进程标识符 |
Uid、gid | 用户标识符、组标识符 |
Euid、egid | 有效用户标识符、有效组标识符 |
Suid、sgid | 备份用户标识符、备份组标识符 |
Fsuid、fsgid | 文件系统用户标识符、文件系统组标识符 |
2.3linux进程组成
在Linux系统中进程由以下三部分组成:1.进程控制块 PCB ;2.数据段;3.正文段。
2.3.1进程控制块
Linux系统为了节省进程控制块所占的内存空间,把每个进程控制块分成两部分。一部分常驻内存,不管进程是否正占有处理器运行,系统经常会对这部分内容进行查询和处理,常驻部分内容包括:进程状态、优先数、过程特征、数据段始址、等待原因和队列指针等,这是进行处理器调度时必须使用的一些主要信息。另一部分非常驻内存,当进程不占有处理器时,系统不会对这部分内容进行查询和处理,因此这部分内容可以存放在磁盘的对换区中,它随用户的程序和数据部分换进或换出内存。
进程控制块( PCB )(系统为了管理进程设置的一个专门的数据结构,用它来记录进程的外部特征,描述进程的运动变化过程。系统利用 PCB 来控制 和管理进程,所以PCB是系统感知进程存在的唯一标志。进程与 PCB 是一 一对应的)。是进程存在和运行的唯一标志,在Linux中用task_struct这个结构体来表示。进程在操作系统中都有一个户口,用于表示这个进程。这个户口操作系统被称为 PCB (进程控制块)。 PCB 只是基本原理中的说法,对于一个真实的操作系统可能不叫 PCB ,比如Linux中叫做任务结构体( task struct )。示例如下
struct task_struct { volatile long state; pid_t pid; unsigned long timestamp; unsigned long rt_priority; struct mm_struct *mm, *active_mm }
PCB是进程的唯一标识。每生成一个新的进程,就要为它创造一个PCB然后对其进行初始化。若要撤销一个进程时只要回收PCB即可。进程切换就可以通过操作PCB进行。
2.3.2数据段
Linux系统把进程的数据段又划分成三部分:用户栈区(供用户程序使用的信息区);用户数据区(包括用户工作数据和非可重入的程序段);系统数据区(包括系统变量和对换信息)。
2.3.3正文段
正文段是可重入的程序,能被若干进程共享。为了管理可共享的正文段,Linux设置了一张正文表,每个正文段都占用一个表目,用来指出该正文段在内存和磁盘上的位置、段的大小以及调用该段的进程数等情况。
3.进程的组织方式
进程是一个动态的过程,既然会产生进程,那么进程也会消失,即死亡。每个进程都是有其父进程产生,若子进程结束后,父进程会回收其资源。若子进程的父进程先被结束,那么子进程就会进入孤儿态,其会被系统的1号进程回收其资源。 shell命令ps -lA可以查看当前系统的进程,例如:
4.进程的调度
4.1调度程序相关数据结构
4.1.1运行队列runqueue
runqueue 是2.6调度最重要的数据结构,系统中每个CPU拥有自己的运行队列,定义如下:
struct runqueue { spinlock_t lock; /* * nr_running and cpu_load should be in the same cacheline because * remote CPUs use both these fields when doing load calculation. */ unsigned long nr_running; unsigned long cpu_load; unsigned long long nr_switches; /* * This is part of a global counter where only the total sum * over all CPUs matters. A task can increase this counter on * one CPU and if it got migrated afterwards it may decrease * it on another CPU. Always updated under the runqueue lock: */ unsigned long nr_uninterruptible; unsigned long expired_timestamp; unsigned long long timestamp_last_tick; task_t *curr, *idle; struct mm_struct *prev_mm; prio_array_t *active, *expired, arrays[2]; int best_expired_prio; atomic_t nr_iowait; struct sched_domain *sd; /* For active balancing */ int active_balance; int push_cpu; task_t *migration_thread; struct list_head migration_queue; };
系统中每个可运行进程属于且只属于一个运行队列,所以每个进程只能运行在拥有该运行队列的CPU上,但是linux可以将进程从一个运行队列迁移到另一个运行队列。
4.1.2运行队列链表prio_array_t
struct prio_array { unsigned int nr_active; unsigned long bitmap[BITMAP_SIZE]; struct list_head queue[MAX_PRIO]; }; typedef struct prio_array prio_array_t; /* 进程描述符中的prio_array_t */ struct task_t{ .... prio_array_t *array; .... };
4.2调度程序使用的方法
scheduler_tick()
void scheduler_tick(void) { /* 获得CPU编号 */ int cpu = smp_processor_id(); /* 获得当前CPU的运行队列 */ runqueue_t *rq = this_rq(); /* 获得当前正在运行的进程 */ task_t *p = current; /* sched_clock()返回被转化为纳秒的64寄存器TSC * 更新timestampt_last_tick,此字段即为最后一次定时器中断的时间戳 */ rq->timestamp_last_tick = sched_clock(); /* 如果当前进程是当前CPU的swapper进程 */ if (p == rq->idle) { /* * 如果有其他进程,则设置TIF_NEED_RESCHED字段 * 如果没有待运行进程,标志为空闲状态退出 */ if (wake_priority_sleeper(rq)) goto out; /* 这个函数在后面介绍,维持多处理器中运行队列的平衡 */ rebalance_tick(cpu, rq, SCHED_IDLE); return; } /* * 如果当前进程所在运行队列链表(prio_array_t类型)不等于运行队列的运行队列, * 则说明当前进程应该停止运行,强制重新调度。 */ if (p->array != rq->active) { set_tsk_need_resched(p); goto out; } spin_lock(&rq->lock); /* 是否为实时进程 */ if (rt_task(p)) { /* 如果时间片轮转实时进程时间片用完 */ if ((p->policy == SCHED_RR) && !--p->time_slice) { /* 使用前面的公式计算时间片长度 */ p->time_slice = task_timeslice(p); /* 时间片用完,将标志位去除 */ p->first_time_slice = 0; /* 设置TIF_NEED_RESCHED标志 */ set_tsk_need_resched(p); /* 将进程加入活动进程队列队尾,使其他同优先级的实时进程得以运行 */ requeue_task(p, rq->active); } goto out_unlock; } /* 普通进程执行 */ if (!--p->time_slice) { /* 如果时间片用完,则将进程从活动进程队列中移除 */ dequeue_task(p, rq->active); /* 设置需要重新调度 */ set_tsk_need_resched(p); /* 重新计算动态优先级 */ p->prio = effective_prio(p); /* 重新分配时间片 */ p->time_slice = task_timeslice(p); /* 将第一次时间片标志位置0 */ p->first_time_slice = 0; /* 如果过期进程队列为空,则将最老被插入的进程的插入时间更新 */ if (!rq->expired_timestamp) rq->expired_timestamp = jiffies; /* * 将当前进程插入活动进程或者过期进程, * TASK_INTERACTIVE判断是否是交互式进程, * EXPIRED_STARVING判断过期进程是否等待太久或者有优先级更高的进程 * * 那么这个判断就是如果当前进程是批处理进程或者过期进程已经等待太长时间 * 或者等待进程中有优先级更高的进程时, * 将当前进程加入过期进程; * 否则将当前进程重新加入活动进程 */ if (!TASK_INTERACTIVE(p) || EXPIRED_STARVING(rq)) { /* 插入过期进程 */ enqueue_task(p, rq->expired); /* 更新过期进程的最高优先级 */ if (p->static_prio < rq->best_expired_prio) rq->best_expired_prio = p->static_prio; } else /* 插入活动进程 */ enqueue_task(p, rq->active); } else { /* 时间片未用完,检查剩余时间片是否过长,如果是则将当前进程置到队列队尾, * 然后设置重新调度标志位。 */ if (TASK_INTERACTIVE(p) && !((task_timeslice(p) - p->time_slice) % TIMESLICE_GRANULARITY(p)) && (p->time_slice >= TIMESLICE_GRANULARITY(p)) && (p->array == rq->active)) { requeue_task(p, rq->active); set_tsk_need_resched(p); } } out_unlock: spin_unlock(&rq->lock); out: rebalance_tick(cpu, rq, NOT_IDLE); }
维持当前最新的time_slice计数器
try_to_wake_up()
/*** * try_to_wake_up - 唤醒一个进程 * @p: 带唤醒进程 * @state: 可以被唤醒的进程状态 * @sync: 同步唤醒标志 * * 如果进程已经是激活状态则返回0 */ static int try_to_wake_up(task_t * p, unsigned int state, int sync) { int cpu, this_cpu, success = 0; unsigned long flags; long old_state; runqueue_t *rq; unsigned long load, this_load; struct sched_domain *sd; int new_cpu; /* task_rq_lock函数会禁用本地中断, * 并根据进程最后在哪个CPU上运行,获取该CPU运行队列 */ rq = task_rq_lock(p, &flags); /* 保存进程旧状态 */ old_state = p->state; /* 如果旧状态不属于允许唤醒进程的状态掩码,则无法唤醒 */ if (!(old_state & state)) goto out; /* 如果进程已经属于某个运行队列,则设置state为TASK_RUNNING后结束 */ if (p->array) goto out_running; /* 获取之前运行p的CPU和当前CPU编号 */ cpu = task_cpu(p); this_cpu = smp_processor_id(); /* 如果p进程正在运行, rq->curr == p */ if (unlikely(task_running(rq, p))) goto out_activate; new_cpu = cpu; /* 如果当前CPU和进程之前运行所在的CPU相同或者进程p不允许运行在当前CPU上 * 那么直接将p放在p原来运行的CPU上。*/ if (cpu == this_cpu || unlikely(!cpu_isset(this_cpu, p->cpus_allowed))) goto out_set_cpu; /* 进入这里则表示当前CPU和之前运行p的CPU不是同一个CPU,且p可以在当前CPU上运行 */ /* 获取运行p进程原CPU的负载猜测,两种算法取较小值 */ load = source_load(cpu); /* 获取当前CPU的负载猜测,两种算法取较大值 */ this_load = target_load(this_cpu); /* 如果设置同步标志则当前cpu负载降低 */ if (sync) this_load -= SCHED_LOAD_SCALE; /* 如果先前执行进程的CPU工作量远小于当前CPU,则运行在原CPU上 */ if (load < SCHED_LOAD_SCALE/2 && this_load > SCHED_LOAD_SCALE/2) goto out_set_cpu; new_cpu = this_cpu; for_each_domain(this_cpu, sd) { unsigned int imbalance; /* 当到达imbalance_pct限制时,开始被动平衡 */ imbalance = sd->imbalance_pct + (sd->imbalance_pct - 100) / 2; if ((sd->flags & SD_WAKE_AFFINE) && !task_hot(p, rq->timestamp_last_tick, sd)) { /* * 这个域有SD_WAKE_AFFINE标志,并且p进程的缓存可能可以用 */ if (cpu_isset(cpu, sd->span)) { goto out_set_cpu; } } else if ((sd->flags & SD_WAKE_BALANCE) && imbalance*this_load <= 100*load) { /* 这个域有SD_WAKE_BALANCE标志并且有不平衡发生 */ if (cpu_isset(cpu, sd->span)) { goto out_set_cpu; } } } new_cpu = cpu; /* 没找到合适的CPU,那么使用原CPU */ /* 将进程放进new_cpu对应的运行队列 */ out_set_cpu: /* 检查是否有空闲CPU,如果有空闲CPU直接让p运行在空闲CPU上 */ new_cpu = wake_idle(new_cpu, p); if (new_cpu != cpu) { set_task_cpu(p, new_cpu); task_rq_unlock(rq, &flags); /* might preempt at this point */ rq = task_rq_lock(p, &flags); old_state = p->state; if (!(old_state & state)) goto out; if (p->array) goto out_running; this_cpu = smp_processor_id(); cpu = task_cpu(p); } /* 处理进程激活的标志位 */ out_activate: if (old_state == TASK_UNINTERRUPTIBLE) { /* 进程已经不处于TASK_UNINTERRUPTIBLE状态,更新原运行队列的状态 */ rq->nr_uninterruptible--; p->activated = -1; } /* * activate_task函数的作用: * 1、获取以纳秒为单位的当前时间戳,如果CPU不是本地CPU会补偿偏差 * 2、调用recalc_task_prio重新计算出动态描述符,见下一个函数 * 3、设置p->activated字段的值 * 4、把当前进程描述符插入活动进程集合 * */ activate_task(p, rq, cpu == this_cpu); /* * 如果没有设置sync标志或者目标CPU不是本地CPU,检查新进程的动态优先级是否高于 * rq运行队列当前正在运行的进程,则强制调度。 */ if (!sync || cpu != this_cpu) { if (TASK_PREEMPTS_CURR(p, rq)) resched_task(rq->curr); } success = 1; out_running: p->state = TASK_RUNNING; out: task_rq_unlock(rq, &flags); return success; }
唤醒睡眠进程
recalc_task_prio()
static void recalc_task_prio(task_t *p, unsigned long long now) { unsigned long long __sleep_time = now - p->timestamp; unsigned long sleep_time; /* 计算上次睡眠时间 */ if (__sleep_time > NS_MAX_SLEEP_AVG) sleep_time = NS_MAX_SLEEP_AVG; else sleep_time = (unsigned long)__sleep_time; if (likely(sleep_time > 0)) { if (p->mm && p->activated != -1 && sleep_time > INTERACTIVE_SLEEP(p)) { /* 进程不是从uninterruptible状态被唤醒,并且睡眠时间大于交互式进程的睡眠时间 * 更新睡眠时间为最大睡眠时间减去一个标准进程的基本睡眠时间片长度,这是一个经验值 */ p->sleep_avg = JIFFIES_TO_NS(MAX_SLEEP_AVG - DEF_TIMESLICE); } else { /* 如果平均睡眠时间越短,sleep_time则越大 */ sleep_time *= (MAX_BONUS - CURRENT_BONUS(p)) ? : 1; if (p->activated == -1 && p->mm) { if (p->sleep_avg >= INTERACTIVE_SLEEP(p)) /* 如果进程原来处于TASK_UNINTERRUPTIBLE状态,且是交互式进程,则睡眠时间置为0 */ sleep_time = 0; else if (p->sleep_avg + sleep_time >= INTERACTIVE_SLEEP(p)) { /* 如果进程原来处于TASK_UNINTERRUPTIBLE状态,且是批处理进程,将平均睡眠时间设为极限值 */ p->sleep_avg = INTERACTIVE_SLEEP(p); sleep_time = 0; } } /* 加上睡眠时间 */ p->sleep_avg += sleep_time; if (p->sleep_avg > NS_MAX_SLEEP_AVG) p->sleep_avg = NS_MAX_SLEEP_AVG; } } /* 使用前文介绍的公式计算动态优先级 */ p->prio = effective_prio(p); }
更新进程的动态优先级
schedule()
asmlinkage void __sched schedule(void) { long *switch_count; task_t *prev, *next; runqueue_t *rq; prio_array_t *array; struct list_head *queue; unsigned long long now; unsigned long run_time; int cpu, idx; /* * Test if we are atomic. Since do_exit() needs to call into * schedule() atomically, we ignore that path for now. * Otherwise, whine if we are scheduling when we should not be. */ if (likely(!current->exit_state)) { if (unlikely(in_atomic())) { printk(KERN_ERR "scheduling while atomic: " "%s/0x%08x/%d\n", current->comm, preempt_count(), current->pid); dump_stack(); } } profile_hit(SCHED_PROFILING, __builtin_return_address(0)); need_resched: /* 关闭本地中断,当前current赋值给prev,释放内核锁 */ preempt_disable(); prev = current; release_kernel_lock(prev); need_resched_nonpreemptible: rq = this_rq(); now = sched_clock(); if (likely(now - prev->timestamp < NS_MAX_SLEEP_AVG)) run_time = now - prev->timestamp; else run_time = NS_MAX_SLEEP_AVG; run_time /= (CURRENT_BONUS(prev) ? : 1); spin_lock_irq(&rq->lock); if (unlikely(prev->flags & PF_DEAD)) prev->state = EXIT_DEAD; switch_count = &prev->nivcsw; if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) { switch_count = &prev->nvcsw; if (unlikely((prev->state & TASK_INTERRUPTIBLE) && unlikely(signal_pending(prev)))) /* 如果是可打断的状态且有信号到达,则进入重新修改为可运行状态 */ prev->state = TASK_RUNNING; else { if (prev->state == TASK_UNINTERRUPTIBLE) rq->nr_uninterruptible++; /* 如果prev是不可打断地状态则将其从运行队列中删除 * 函数让rq进程数减一,删除prev,使prev->array=NULL */ deactivate_task(prev, rq); } } cpu = smp_processor_id(); if (unlikely(!rq->nr_running)) { go_idle: /* 无可运行进程,从其他运行队列迁移一些可运行进程到本地 */ idle_balance(cpu, rq); if (!rq->nr_running) { /* 如果依然没有进程,则运行swapper进程 */ next = rq->idle; rq->expired_timestamp = 0; wake_sleeping_dependent(cpu, rq); /* swapper进程结束后也许有新的进程 */ if (!rq->nr_running) goto switch_tasks; } } else { if (dependent_sleeper(cpu, rq)) { /* 检查本CPU最高优先级的进程是否比其他CPU上正在运行的进程优先级低 * 如果是则运行swapper进程。 */ next = rq->idle; goto switch_tasks; } if (unlikely(!rq->nr_running)) goto go_idle; } /* 已经有可运行进程 */ array = rq->active; if (unlikely(!array->nr_active)) { /* 如果活动进程中没有进程,则将过期进程和活动进程交换 */ rq->active = rq->expired; rq->expired = array; array = rq->active; rq->expired_timestamp = 0; rq->best_expired_prio = MAX_PRIO; } /* 寻找最高优先级(数字最小)不为空的链表下标 */ idx = sched_find_first_bit(array->bitmap); /* 对应最高优先级的活动进程链表头 */ queue = array->queue + idx; next = list_entry(queue->next, task_t, run_list); if (!rt_task(next) && next->activated > 0) { unsigned long long delta = now - next->timestamp; /* 进程原来处于TASK_INTERRUPTIBLE,被系统调用或内核线程唤醒的 * 增加睡眠时间,间接增加动态优先级 */ if (next->activated == 1) delta = delta * (ON_RUNQUEUE_WEIGHT * 128 / 100) / 128; array = next->array; dequeue_task(next, array); recalc_task_prio(next, next->timestamp + delta); enqueue_task(next, array); } next->activated = 0; switch_tasks: /* 准备切换进程 */ /* 将next进程描述符装入硬件高速缓存 */ prefetch(next); /* 清除prev的TIF_NEED_RESCHEHD标志 */ clear_tsk_need_resched(prev); /* 记录CPU正在经历静态状态 */ rcu_qsctr_inc(task_cpu(prev)); /* 更新prev的平均睡眠时间 */ prev->sleep_avg -= run_time; if ((long)prev->sleep_avg <= 0) prev->sleep_avg = 0; prev->timestamp = prev->last_ran = now; if (likely(prev != next)) { /* 两进程不相等 */ next->timestamp = now; rq->nr_switches++; rq->curr = next; ++*switch_count; /* 建立next的地址空间,并调用switch_to函数进行进程切换 */ prev = context_switch(rq, prev, next); barrier(); /* 恢复地址空间,如果prev进程结束,则还要释放页表相关的描述符 */ finish_task_switch(prev); } else /* 两进程相等,不需要切换进程 */ spin_unlock_irq(&rq->lock); prev = current; if (unlikely(reacquire_kernel_lock(prev) < 0)) /* 如果获取了大内核锁,则继续调度,直到本进程主动释放大内核锁? */ goto need_resched_nonpreemptible; /* 打开内核抢占 */ preempt_enable_no_resched(); if (unlikely(test_thread_flag(TIF_NEED_RESCHED))) /* 如果设置了TTIF_NEED_RESCHED标志,则重新调度 */ goto need_resched; }
选择要被执行的新进程
load_balance()
维持多处理器系统中运行队列的平衡
5.操作系统进程模型的看法
操作系统(Operating System,简称OS)是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件,任何其他软件都必须在操作系统的支持下才能运行。
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
5.参考资料
https://blog.csdn.net/ahuang1900/article/details/39218077
https://blog.csdn.net/woshixuye/article/details/53931303
https://www.baidu.com/link?url=htdM6rR9GXF_4HA7Hg3zWXPc4fQMlecgqEZ_iTN83x9gJRERXMCvIGtjNuz-IOfr&wd=&eqid=99039a050000cb31000000065ae1c65a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
|
asmlinkage void__sched schedule(void)
{
long*switch_count;
task_t*prev,*next;
runqueue_t*rq;
prio_array_t*array;
structlist_head*queue;
unsignedlonglongnow;
unsignedlongrun_time;
intcpu,idx;
/*
* Test if we are atomic. Since do_exit() needs to call into
* schedule() atomically, we ignore that path for now.
* Otherwise, whine if we are scheduling when we should not be.
*/
if(likely(!current->exit_state)){
if(unlikely(in_atomic())){
printk(KERN_ERR"scheduling while atomic: "
"%s/0x%08x/%d\n",
current->comm,preempt_count(),current->pid);
dump_stack();
}
}
profile_hit(SCHED_PROFILING,__builtin_return_address(0));
need_resched:
/* 关闭本地中断,当前current赋值给prev,释放内核锁 */
preempt_disable();
prev=current;
release_kernel_lock(prev);
need_resched_nonpreemptible:
rq=this_rq();
now=sched_clock();
if(likely(now-prev->timestamp<NS_MAX_SLEEP_AVG))
run_time=now-prev->timestamp;
else
run_time=NS_MAX_SLEEP_AVG;
run_time/=(CURRENT_BONUS(prev)?:1);
spin_lock_irq(&rq->lock);
if(unlikely(prev->flags&PF_DEAD))
prev->state=EXIT_DEAD;
switch_count=&prev->nivcsw;
if(prev->state&&!(preempt_count()&PREEMPT_ACTIVE)){
switch_count=&prev->nvcsw;
if(unlikely((prev->state&TASK_INTERRUPTIBLE)&&
unlikely(signal_pending(prev))))
/* 如果是可打断的状态且有信号到达,则进入重新修改为可运行状态 */
prev->state=TASK_RUNNING;
else{
if(prev->state==TASK_UNINTERRUPTIBLE)
rq->nr_uninterruptible++;
/* 如果prev是不可打断地状态则将其从运行队列中删除
* 函数让rq进程数减一,删除prev,使prev->array=NULL */
deactivate_task(prev,rq);
}
}
cpu=smp_processor_id();
if(unlikely(!rq->nr_running)){
go_idle:
/* 无可运行进程,从其他运行队列迁移一些可运行进程到本地 */
idle_balance(cpu,rq);
if(!rq->nr_running){
/* 如果依然没有进程,则运行swapper进程 */
next=rq->idle;
rq->expired_timestamp=0;
wake_sleeping_dependent(cpu,rq);
/* swapper进程结束后也许有新的进程 */
if(!rq->nr_running)
gotoswitch_tasks;
}
}else{
if(dependent_sleeper(cpu,rq)){
/* 检查本CPU最高优先级的进程是否比其他CPU上正在运行的进程优先级低
* 如果是则运行swapper进程。
*/
next=rq->idle;
gotoswitch_tasks;
}
if(unlikely(!rq->nr_running))
gotogo_idle;
}
/* 已经有可运行进程 */
array=rq->active;
if(unlikely(!array->nr_active)){
/* 如果活动进程中没有进程,则将过期进程和活动进程交换 */
rq->active=rq->expired;
rq->expired=array;
array=rq->active;
rq->expired_timestamp=0;
rq->best_expired_prio=MAX_PRIO;
}
/* 寻找最高优先级(数字最小)不为空的链表下标 */
idx=sched_find_first_bit(array->bitmap);
/* 对应最高优先级的活动进程链表头 */
queue=array->queue+idx;
next=list_entry(queue->next,task_t,run_list);
if(!rt_task(next)&&next->activated>0){
unsignedlonglongdelta=now-next->timestamp;
/* 进程原来处于TASK_INTERRUPTIBLE,被系统调用或内核线程唤醒的
* 增加睡眠时间,间接增加动态优先级 */
if(next->activated==1)
delta=delta*(ON_RUNQUEUE_WEIGHT*128/100)/128;
array=next->array;
dequeue_task(next,array);
recalc_task_prio(next,next->timestamp+delta);
enqueue_task(next,array);
}
next->activated=0;
switch_tasks:
/* 准备切换进程 */
/* 将next进程描述符装入硬件高速缓存 */
prefetch(next);
/* 清除prev的TIF_NEED_RESCHEHD标志 */
clear_tsk_need_resched(prev);
/* 记录CPU正在经历静态状态 */
rcu_qsctr_inc(task_cpu(prev));
/* 更新prev的平均睡眠时间 */
prev->sleep_avg-=run_time;
if((long)prev->sleep_avg<=0)
prev->sleep_avg=0;
prev->timestamp=prev->last_ran=now;
if(likely(prev!=next)){
/* 两进程不相等 */
next->timestamp=now;
rq->nr_switches++;
rq->curr=next;
++*switch_count;
/* 建立next的地址空间,并调用switch_to函数进行进程切换 */
prev=context_switch(rq,prev,next);
barrier();
/* 恢复地址空间,如果prev进程结束,则还要释放页表相关的描述符 */
finish_task_switch(prev);
}else
/* 两进程相等,不需要切换进程 */
spin_unlock_irq(&rq->lock);
prev=current;
if(unlikely(reacquire_kernel_lock(prev)<0))
/* 如果获取了大内核锁,则继续调度,直到本进程主动释放大内核锁? */
gotoneed_resched_nonpreemptible;
/* 打开内核抢占 */
preempt_enable_no_resched();
if(unlikely(test_thread_flag(TIF_NEED_RESCHED)))
/* 如果设置了TTIF_NEED_RESCHED标志,则重新调度 */
gotoneed_resched;
}
|