[修改回复]
删除回复
插入表情:
宋体
楷体
幼圆
黑体
隶书
华文行楷
方正舒体
Arial
Arial Black
Arial Narrow
Century Gothic
Comic Sans MS
#0000FF
#8A2BE2
#DEB887
#5F9EA0
#7FFF00
#000000
#D2691E
#FF7F50
#FF0000
#DC143C
#99ccff
字体颜色
#FFF8DC
#00FFFF
#EE82EE
#F5DEB3
#FFFFFF
#F5F5F5
#FFFF00
#9ACD32
使用帮助
6. 内核可抢占 Kernel 2.6的一大亮点就是内核可抢占,是Kernel 2.6进程调度优于2.4的一个重要 表现。 (1) 何时可以抢占内核 在前面我们已经讲了内核何时可以抢占:当内核进程没有访问内核的关键数据,也就 是内核没有被加锁,此时内核代码是可重入的,可以抢占内核。 对内核抢占加锁是通过preempt_disable()来实现的,这个宏只是简单的将preempt_count 增1就实现了内核的加锁,表明此时已经进入内核的关键数据区域,内核不可被抢占。 (2) 如何抢占内核 中断返回内核时 在前面介绍"preempt_count"的时候已经提到,内核能否抢占是通过操作preempt_count 来实现的。注意arch/i386/kernel/entry.S里面以下程序: ENTRY(resume_kernel) cmpl $0,TI_PRE_COUNT(%ebp) # non-zero preempt_count ? jnz restore_all need_resched: movl TI_FLAGS(%ebp), %ecx # need_resched set ? testb $_TIF_NEED_RESCHED, %cl jz restore_all testl $IF_MASK,EFLAGS(%esp) # interrupts off (exception path) ? jz restore_all movl $PREEMPT_ACTIVE,TI_PRE_COUNT(%ebp) sti call schedule movl $0,TI_PRE_COUNT(%ebp) cli jmp need_resched 程序中可以看出,在中断或者异常返回内核空间以后,首先检查preempt_count是否 为0,如果不为0,说明已经内核已经禁止被抢占;如果为0,则检查TIF_NEED_RESCHED 位,如果已经被置位则检查此次是否是通过中断(通过检查堆栈中EFLAGS的IF位来检 查发生此次"中断"前IF是否被置位,如果被置位说明是中断;否则说明是由异常返回 内核)返回内核空间的,如果是,则调用schedule()函数进行调度。可见,抢占内核 是发生在由中断返回内核空间的时候。 解锁时 解锁通过宏preempt_enable()来完成,此函数完成以下功能: a. 将当前进程的preempt_count减1 b. 检查TIF_NEED_RESCHED位,如果是0,则返回;否则调用函数preempt_schedule(), 此函数将preempt_count置为PREEMPT_ACTIVE(表明正在执行内核抢占),然后直 接调用schedule()进行调度。 内核代码中直接调用函数schedule() 这种情况下是没有任何保护措施的,也就是说调用的代码必须清楚此时进行内核抢占 是否安全。 7. 负载均衡 Kernel 2.6的负载均衡分为两种,一种是"pull",一种是"push"。 (1) pull 当一个CPU负载轻,而另外一个CPU负载过重的时候,调度器会从负载重的CPU把进程 pull过来,这个过程主要通过函数load_balance()完成。 load_balance()有两种工作方式,一种是当前CPU完全空闲,idle=1,另外一种是上 面仍有进程在运行,idle=0;当idle=1时,很多操作将变得很简单。 当idle=0时,不管当前CPU有多忙碌,定时器都将定期启动函数rebalance_tick(), 而该函数将每隔BUSY_REBALANCE_TICK时间就调用函数load_balance()来进行负载均 衡。load_balance()的函数流程如下: 1 找到最忙的CPU:取当前CPU负荷为当前负荷和历史负荷里面的最大者,取其他CPU 负荷为当前负荷和历史符合里面最小者;最忙的CPU的负荷必须比当前CPU的负荷高25 %,否则不进行迁移;对源、目的两个就绪队列加锁之后,再次检查源就绪队列负载 是否减小,如果是则退出负载均衡; 2 找到最忙CPU后,按照从expired队列到active队列,从高优先级到低优先级进程进 行迁移,不过以下几种进程不进行迁移: a. 当前正在执行的进程; b. 通过cpus_allowed明确表示不能迁移该CPU的进程; c. 被原来CPU切换下来的时间小于cache_decay_ticks(说明cache仍然活跃)。 3 进行任务迁移,主要进行以下操作 a. 将进程从原来CPU的相应就绪队列中删除; b. 将进程的CPU号设置为当前进程,并将任务添加到当前CPU的active array中; c. 赋值timestamp; d. 如果新迁移过来的任务比当前CPU正在运行的程序优先级更高,则置位TIF_NEED_RESCHED 。 4 如果此时负载仍然没有平衡(通过imbalance的值来反映),则重复上述过程,直 到平衡为止。 当idle=1时,有两个时机调用load_balance()函数: 定时器每隔IDLE_REBALANCE_TICK时间启动load_balance()函数; 在schedule()函数里,如果发现该CPU上rq为空,则主动调用load_balance()函数 进行负载均衡。 idle时候调用load_balance()的流程和idle=0是一样,只不过将idle作为参数传进去 ,idle=1可以简化很多判断,比如每次只迁移一个进程等。 (2) push 主要通过migration_thread()这个核心进程来将本CPU上的rq->migration_queue "push "到别的CPU上,在Kernel 2.6里,是选择第一个允许运行的CPU。该进程是调度策略 为SCHED_FIFO的实时核心进程,一般时候处于睡眠状态。 migration_queue是通过函数set_cpus_allowed()来改变运行的CPU,该函数构造一个 类型为migration_req_t的迁移队列,将其植入进程所在CPU的migration_queue。然 后唤醒migration_thread这个核心进程,由它来完成进程的迁移。 migration_thread()通过函数move_task_away()来完成实际的迁移工作,该函数主要 完成以下工作: a. 设置进程CPU号; b. 将进程从原来的就绪队列里面删除,并用函数activate_task()来将进程加入目标 CPU的就绪队列中; 如果迁移的进程比目标CPU的rq->curr的优先级要高,就调用函数resched_task()来 将目标CPU上的当前进程的TIF_NEED_RESCHED置为1。 参考资料: [1] Linus Tovalds, Linux内核源码 v2.6.4, from www.kernel.org [2] Robert Love, Linux Kernel Development [3] 杨沙洲, Linux 2.6 调度系统分析
不能为空
不能含有 ` 字符,字数8000以内
(CTRL+ENTER提交)
关闭窗口