您的位置:首页 > 运维架构 > Linux

linux进程调度

2014-12-21 02:03 239 查看
本博文主要从三个方面简单介绍下linux进程调度,调度时机,调度策略,调度步骤三个方面简单介绍下linux系统额进程调度

1 . 什么是调度

调度就是在一组可运行状态的进程中选择一个最合适的进程来执行,这个是调度程序所要完成的基本操作

2. 调度策略

SCHED_NORMAL(SCHED_OTHER): 普 通 的 分时 进程

SCHED_FIFO : 先 入 先出 的 实 时 进程

SCHED_RR : 时 间 片 轮转 的 实 时 进程

SCHED_BATCH: 批 处理 进程

SCHED_IDLE:  只 在 系统空 闲 时 才能 够 被调度执行 的

3.调度类

linux内核将上面的5种调度方式划分成两张调度类

调度 类 的 引 入 增强 了 内核 调度程序 的 可 扩展性 , 这 些类( 调度程序 模 块 ) 封装 了调度 策略 ,并将 调度 策略模 块 化 

CFS  调度 类 (在 在 kernel/sched_fair.c  中实 现 ) 用 于以 下 调度 策略 :

SCHED_NORMAL 、SCHED_BATCH 和 和 SCHED_IDLE 。

实 时 调度 类 (在 在 kernel/sched_rt.c  中实 现 ) 用 于以 下 调度 策略 :

SCHED_RR 和 和 SCHED_FIFO  策略 。

备注:struct sched_class 是调度类结构体 ,其中 pick_next_task : 选择下 一 个 要 运 行 的进程,调度策略在这个函数里面体现出来

4.调度时机

1 、 主 动式

在内核 中 直 接调 用schedule() 这个函数来实现进程调度。比如在下面这种情况需要主动式调度, 当 进程 需 要 等待资 源 等 而暂 时 停 止 运 行 时 , 会把 状态 置于 挂起(睡眠) ,并 主 动 请求 调度 , 让 出CPU 。

主 动 放 弃cpu 例:

current->state = TASK_INTERRUPTIBLE; //将进程设置为可中断状态,此时就不会被调度程序唤醒

schedule();

2、 被 动式 ( 抢占)

用 户 抢占 :Linux2.4 、Linux2.6版本内核支持用户抢占

内核 抢占:Linux2.6支持内核抢占,Linux2.4内核不支持内核抢占

2.1用户抢占

用 户 抢占 发 生 在:

(1)从 系统调 用 返 回 用 户空间 。

(2)从 中 断处理 程序 返 回 用 户空间 

也就是在内核 即 将 返 回 用 户空间 的 时 候 , 如 果need_resched 标志 被 设置 , 会导 致schedule()被调 用, 此时就 会 发 生 用 户 抢占 。

用户抢占汇编代码分析

ENTRY(ret_from_exception) // 异常 返 回
get_thread_info tsk
mov why, #0
b ret_to_user
? __irq_usr: // 在用 户态 收到 中 断
usr_entry
kuser_cmpxchg_check
…… …… ……
b ret_to_user
ENTRY(ret_to_user)
ret_slow_syscall:
disable_irq @ disable interrupts
ldrr1, [tsk, #TI_FLAGS]
tstr1, #_TIF_WORK_MASK
bne work_pending<pre name="code" class="html">work_pending:
tstr1, #_TIF_NEED_RESCHED//<span style="font-size: 17.77777862548828px;">need_resched 标志 </span><span style="font-family: Arial, Helvetica, sans-serif;">是否</span><span style="font-family: Arial, Helvetica, sans-serif;">被 设置 </span>
bne work_resched
work_resched:
bl schedule


2.2 内核抢占

(1)在不 支持 内核 抢占 的 系统中 , 进程/ 线程一 旦运 行 于内核 空间 , 就 可以一 直 执行 , 直 到 它 主 动 放 弃 或 时 间片 耗尽 为止 。 这 样 一 些 非 常 紧急 的 进程 或 线程 将 长 时间 得不 到 运 行

(2) 在 支持 内核 抢占 的 系统中 , 更高 优先级 的 进程/ 线程可以 抢占 正 在 内核 空间 运 行 的 低 优先级 进程/

2.3内核抢占时机

在 支持 内核 抢占 的 系统中 , 某 些特例下 是 不 允许 内核 抢占 的

1)  内核 正进行中 断处理 。 进程调度 函 数schedule() 会对 此 作 出 判 断 , 如 果 是 在中 断 中调 用, 会 打 印 出 错

2)  内核 正 在 进行中 断 上 下 文 的Bottom Half( 中 断 的 底半部) 处理 。 硬 件 中 断 返 回前会 执行 软 中 断 , 此时仍 然 处于 中 断 上 下 文中

3)  进程 正 持 有spinlock 自旋锁 、writelock/readlock 读写锁 等 , 当 持 有 这 些 锁时 ,不应该 被 抢占 , 否 则 由于 抢占 将 导 致 其 他CPU 长 期 不 能 获 得 锁 而 死等 

4)  内核 正 在 执行调度程序Scheduler 。 抢占 的 原因就 是为了进行 新 的 调度 , 没有 理由 将 调度程序 抢占 掉再 运 行调度程序

抢占时机判断:

为 保 证Linux 内核在 以 上 情况 下 不 会 被 抢占 ,抢占 式内核使用 了一 个 变 量preempt_count ,称 为 内核 抢占 计 数 。 这一 变 量被 设置 在 进程 的thread_info 结构中 。 每 当 内核 要进 入 以 上 几

种 状态 时 , 变 量preempt_count 就 加1 , 指 示内核不 允许抢占 。 每 当 内核从 以 上 几种 状态 退出时 , 变 量preempt_count 就 减1 , 同时 进行可 抢占 的 判 断 与调度,只要
preempt_count 不为0就不可被抢占。

内核抢占发生时机:

 1)中 断处理 程序 完 成, 返 回 内核 空间 之前 。

 2) 当 内核 代码 再 一 次具 有 可 抢占性 的 时候 , 如 解锁 及 使 能 软 中 断 等

代码示例:

中断返回时候:
__irq_svc: /* 内核 态接 收到 中 断:中断入口*/
svc_entry
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
/* 进 入 中 断 , 抢占 计 数 加1*/
#ifdef CONFIG_PREEMPT
get_thread_info tsk
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
add r7, r8, #1 @ increment it
str r7, [tsk, #TI_PREEMPT]
#endif
irq_handler /* 中 断处理*/
#ifdef CONFIG_PREEMPT
str r8, [tsk, #TI_PREEMPT] @ restore preempt count //获得<span style="font-size: 17.77777862548828px;">preempt_count 计数</span>
ldr r0, [tsk, #TI_FLAGS] @ get flags
teq r8, #0 @ if preempt count != 0//判断<span style="font-family: Arial, Helvetica, sans-serif;">preempt_count 计数</span>
movne r0, #0 @ force flags to 0
tst r0, #_TIF_NEED_RESCHED
blne svc_preempt
#endif
svc_preempt:
mov r8, lr
1: bl preempt_schedule_irq /* 调度*/
ldr  r0, [tsk, #TI_FLAGS]
tst r0, #_TIF_NEED_RESCHED
moveqpc, r8


/*自旋锁解锁的时候*/
<span style="font-size: 18px; font-family: Arial, Helvetica, sans-serif;">define preempt_enable() \</span>
do { \
preempt_enable_no_resched(); \
barrier(); \
preempt_check_resched(); \
} while (0)
#define preempt_enable_no_resched() \
do { \
barrier(); \
dec_preempt_count(); \
/* 抢占 计 数 减 一*/
} while (0)
#define preempt_check_resched() \
do { \
if
(unlikely(test_thread_flag(TIF_NEED_RES
CHED))) \
preempt_schedule(); \ /* 调度*/
} while (0)
void __lockfunc _spin_unlock(spinlock_t*lock)
{
<span style="white-space:pre">	</span>spin_release(&lock->dep_map, 1,_RET_IP_);
<span style="white-space:pre">	</span>_raw_spin_unlock(lock);
<span style="white-space:pre">	</span>preempt_enable();
}

3. 调度标志(TIF_NEED_RESCHED)

作 用:内核 提 供了一 个need_resched 标志 来 表 明 是 否 需 要重 新执行一 次 调度 。

设置 :当 某 个 进程 耗尽 它 的 时 间 片时 , 会 设置 这 个 标志 ;当 一 个 优先级 更高 的 进程进 入 可执行状态 的 时 候 , 也 会 设置 这 个 标志 

4.调度步骤

Schedule 函 数 工作 流 程 如下 :

1).  清 理当 前运 行中 的 进程 ;

2).  选择下 一 个 要 运 行 的 进程 ;(pick_next_task  函数会在所有TASK_RUNNING进程里面选择出一个进程)

/*
* Pick up the highest-prio task:
*/
static inline struct task_struct * pick_next_task(struct rq *rq)
{
const struct sched_class *class;
struct task_struct *p;

/*
* Optimization: we know that if all tasks are in
* the fair class we can call that function directly:
*/
if (likely(rq->nr_running == rq->cfs.nr_running)) { //如果nr_running==cfs.nr_running,则说明当前rq队列中是没有rt任务的,rt任务不在cfs队列中,它的优先级的设置和cfs不一样。
p = fair_sched_class.pick_next_task(rq); //在cfs队列中挑选即将被切换进去的进程
if (likely(p))
return p;
}

for_each_class(class) { //如果有其他方式的调度类也就是不止是只有cfs调度类,那么依次按照优先级由高到低使用调度类
p = class->pick_next_task(rq);//使用该调度来了获取最合适的进程
if (p)
return p;
}

BUG(); /* the idle class will always have a runnable task */
}


3).  设置新 进程 的 运 行 环境;

4).  进程 上 下 文 切 换 。




















内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息