linux runqueue定义,Linux中多CPU的runqueue及抢占

一、引出

在在嵌入式操作系统中,很多线程都可以为实时任务,因为毕竟这些线程很少和人接触,而是面向任务的。所有就有一个抢占的时机问题。特别是2.6内核中引入了新的内核态抢占任务,所以就可以说一下这个内核态抢占的实现。

内核态抢占主要发生在两个时机,一个是主动的检测是否需要抢占,另一个就是在异常处理完之后的异常判断。

#define preempt_enable() \

do { \

preempt_enable_no_resched(); \

barrier(); \

preempt_check_resched(); \

} while (0)

这一点和用户态的pthread_setcanceltype中也有使用,也就是如果禁止线程的异步取消,在使能之后的第一时间判断线程是不是已经被取消,包括内核态对信号的处理也是如此,例如,当sigprocmask开启一个信号屏蔽之后,也需要在第一时间来判断系统中是否有未处理的信号,如果有则需要及时处理,这个操作是在sys_sigprocmask--->>>recalc_sigpending--->>>set_tsk_thread_flag(t, TIF_SIGPENDING)中完成。

另一个就是内核线程无法预测的中断或者异常处理结束之后的判断。

linux-2.6.21\arch\i386\kernel\entry.S:  ret_from_exception(ret_from_intr)--->>>>

#ifdef CONFIG_PREEMPT

ENTRY(resume_kernel)

DISABLE_INTERRUPTS(CLBR_ANY)

cmpl $0,TI_preempt_count(%ebp) # non-zero preempt_count ?首先判断线程是否禁止了抢占,如果禁止抢占,则不检测是否重新调度标志。

jnz restore_nocheck

need_resched:

movl TI_flags(%ebp), %ecx # need_resched set ?

testb $_TIF_NEED_RESCHED, %cl检测是否需要重新调度。

jz restore_all

testl $IF_MASK,PT_EFLAGS(%esp) # interrupts off (exception path) ?

jz restore_all

call preempt_schedule_irq 这里就是第二个抢占发生的时机,就是内核线程不可预测的时候发生的。

jmp need_resched

END(resume_kernel)

在preempt_schedule_irq中引入了一个比较常见的概念,就是这个PREEMPT_ACTIVE,

add_preempt_count(PREEMPT_ACTIVE);

/*

* We use bit 30 of the preempt_count toindicate that kernel

* preemption is occurring.  See include/asm-arm/hardirq.h.

*/

#define PREEMPT_ACTIVE 0x40000000

这个标志位在内核中的线程抢占统计中将会用到,在schedule函数中

switch_count = &prev->nivcsw;

if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {这里判断的是抢占标志是否被置位,如果没有置位,也就是如果不是被抢占,则认为是自愿放弃CPU,也就是Voluntary释放CPU,否则认为是被抢占。

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++;

deactivate_task(prev, rq);

}

}

通过/proc/$PID/status可以看到这个切换次数记录。

static inline void task_context_switch_counts(struct seq_file *m,

struct task_struct *p)

{

seq_printf(m, "voluntary_ctxt_switches:\t%lu\n"

"nonvoluntary_ctxt_switches:\t%lu\n",

p->nvcsw,

p->nivcsw);

}

从调度的代码中可以看到,如果线程禁止了抢占,那么线程是不能执行调度的,这样可以推出线程在关掉抢占之后不能睡眠,如果需要等待,应该应该用spinlock,在asmlinkage void __sched schedule(void)的开始有这个判断

if (unlikely(in_atomic() && !current->exit_state)) {

printk(KERN_ERR "BUG: scheduling while atomic: "

"%s/0x%08x/%d\n",

current->comm, preempt_count(), current->pid);

debug_show_held_locks(current);

if (irqs_disabled())

print_irqtrace_events(current);

dump_stack();

}

也就是,如果 线程是禁止抢占之后进行调度,后果是很严重的,直接内核就dump_stack了(但是没有panic,至少对386是如此)。这一点也容易理解,因为在自愿和非自愿的两个抢占判断中,都判断了线程的preempt_count的值,如果非零就退出,所以应该是不能走到这一步的。

二、多核中的运行队列

这个在大型服务器中是比较有用的一个概念,就是线程在CPU之间的均匀分配或者非均匀分配问题。目的就是让各个CPU尽量负载平衡,不要忙的忙死,闲的闲死。按照计算机的原始概念,CPU可以作为一个资源,然后等待使用这个资源的线程就需要排队。如果要排队,就需要有一个约定的地点让大家在这里排队,这样便于管理,比如说先来先服务,然后优先级的判断等。

在内核里,这个队列就是每个CPU都定义的一个为struct rq 结构的runqueue变量,这个是每个CPU的一个排队区,可以认为是CPU的一个私有资源,并且是静态分配,每个CPU有天生拥有这么一个队列,拿人权的角度看,这个也就是CPU的一个基本权利,并且是一个内置权利。当CPU存在之后,它的runqueue就存在了。注意:这是一个容器,它是用来存放它的客户线程的,所以的线程在这里进行汇集和等待;对每个CPU来说,它的这个结构本身是不会变化的,变化的只是这个队列中的线程,一个线程可以在这个CPU队列里等待并运行,也可以在另一个CPU中运行,当然不能同时运行。这个变量的定义为

static DEFINE_PER_CPU(struct rq, runqueues);

现在,一个CPU需要服务的所有的线程都在这个结构里,所以也就包含了实时线程组和非实时线程组,它们在rq的体现为两个成员。

struct cfs_rq cfs;

struct rt_rq rt;

同一个CPU上的两个运行队列采用不同的调度策略,实时策略也就是内核中希望实现的O(1)调度器,所以它的内容中包含了100个实时队列结构。这个结构也和信号相同,首先有一个位图,表示这个优先级是否有可运行线程,然后有一个指针数组,指向各个优先级的就绪线程,前者用于快速判断最高优先级队列下表,后者用于真正取出该优先级的线程。

对于cfs调度,它一般是为了保证系统中线程对用户的及时响应,也就是说这个线程和用户交互,不能让用户感觉到某个任务有“卡”的感觉。保证这个流畅的方法就是快速切换,从而在某个时间段内所有的cfs任务都可以被运行一次。也就是不会出现某个任务跑的很欢乐,另外某个跑的很苦逼。

这个的实现就是大家经常说的内核红黑树结构,很多地方都有说明。这里注意红黑树是一个有序树,有序就需要有键值,并且有键值的比较方法。在内核中这个键值就是每个线程的一个调度实体的vruntime成员,在linux-2.6.37.1\kernel\sched_fair.c中我们看到的键值比较为put_prev_task_fair--->>>put_prev_entity--->>>__enqueue_entity--->>>entity_key

static inline s64 entity_key(struct cfs_rq *cfs_rq, struct sched_entity *se)

{

return se->vruntime - cfs_rq->min_vruntime;

}

而这个调度实体是一个抽象的概念,它可能考虑到了任务组的调度吧。实时任务和cfs任务的调度实体结构并不相同,并且这个两个在task_struct结构中两个并不是一个union,而是实实在在两个独立的实体,在task_struct结构中可以看到:

struct sched_entity se;

struct sched_rt_entity rt;

这个可能是为了保证线程的优先级可以在运行时通过sys_sched_setscheduler来动态修改而设置的吧。

对于一个runqueue,它对应一个CPU,由于一个CPU上只能同时运行一个线程,所以一个runqueue只有一个curr,因为我们可以看到一个rq有一个curr结构

struct task_struct *curr, *idle, *stop;

注意的是,同一时间真正使用CPU的线程只有一个,但是一个CPU上可以有多个线程都是处于就绪状态,也就是running状态,我们可以看到这个running在rq、rt_rq、cfs_rq中都有相应的成员(nr_running)。这里说的running并不是他们在运行,而是可运行,他们是用来进行CPU之间负载均衡的,和是否正在CPU上运行没有直接关系。反过来,一个线程是否处于可运行状态,是通过p->se.on_rq 来判断的。

我们看一下系统唤醒一个线程时的操作:

wake_up_new_task--->>>activate_task--->>enqueue_task

p->se.on_rq = 1; 这里可以看到,实时任务也是用了task_struct中的struct sched_entity se;成员,所以可以认为这是一个线程固有的成员,而struct sched_rt_entity rt;是为rt线程专门另外设置的一个附加成员,它们不是互斥或者说可替代的,而是基础和附加属性的关系。

而对于某个CPU上正在运行的线程的判断则使用的是

static inline int task_current(struct rq *rq, struct task_struct *p)

{

return rq->curr == p;

}

而对于nr_running的设置为

wake_up_new_task--->>>activate_task--->>inc_nr_running(rq);

static void inc_nr_running(struct rq *rq)

{

rq->nr_running++;

}

标签:task,抢占,rq,runqueue,线程,中多,CPU,struct

来源: https://www.cnblogs.com/tsecer/p/10485824.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值