Linux内核-抢占

本文深入探讨Linux内核抢占的概念,包括用户抢占与内核抢占的区别,抢占的实现机制,如__preempt_count的作用,系统调用和中断处理流程,以及抢占对SMP并发安全的影响。此外,还讨论了抢占相关的问题,如自旋锁和percpu变量的访问策略。

主要介绍内核抢占的相关概念和具体实现,以及抢占对内核调度和内核竞态和同步的一些影响。
(所用内核版本3.19.3)


1. 基本概念
  • 用户抢占和内核抢占
    • 用户抢占发生点
      • 当从系统调用或者中断上下文返回用户态的时候,会检查need_resched标志,如果被设置则会重新选择用户态task执行
    • 内核抢占发生点
      • 当从中断上下文返回内核态的时候,检查need_resched标识以及__preemp_count计数,如果标识被设置,并且可抢占,则会触发调度程序preempt_schedule_irq()
      • 内核代码由于阻塞等原因直接或间接显示调用schedule,比如preemp_disable时可能会触发preempt_schedule()
    • 本质上内核态中的task是共享一个内核地址空间,在同一个core上,从中断返回的task很可能执行和被抢占的task相同的代码,并且两者同时等待各自的资源释放,也可能两者修改同一共享变量,所以会造成死锁或者竞态等;而对于用户态抢占来说,由于每个用户态进程都有独立的地址空间,所以在从内核代码(系统调用或者中断)返回用户态时,由于是不同地址空间的锁或者共享变量,所以不会出现不同地址空间之间的死锁或者竞态,也就没必要检查__preempt_count,是安全的。__preempt_count主要负责内核抢占计数。

2. 内核抢占的实现
  • percpu变量__preempt_count
抢占计数8位, PREEMPT_MASK                     => 0x000000ff
软中断计数8位, SOFTIRQ_MASK                   => 0x0000ff00
硬中断计数4位, HARDIRQ_MASK                   => 0x000f0000
不可屏蔽中断1位, NMI_MASK                     => 0x00100000
PREEMPTIVE_ACTIVE(标识内核抢占触发的schedule)  => 0x00200000
调度标识1位, PREEMPT_NEED_RESCHED             => 0x80000000
  • __preempt_count的作用

    • 抢占计数
    • 判断当前所在上下文
    • 重新调度标识
  • thread_info的flags

    • thread_info的flags中有一个是TIF_NEED_RESCHED,在系统调用返回,中断返回,以及preempt_disable的时候会检查是否设置,如果设置并且抢占计数为0(可抢占),则会触发重新调度schedule()或者preempt_schedule()或者preempt_schedule_irq()。通常在scheduler_tick中会检查是否设置此标识(每个HZ触发一次),然后在下一次中断返回时检查,如果设置将触发重新调度,而在schedule()中会清除此标识。
// kernel/sched/core.c
// 设置thread_info flags和__preempt_count的need_resched标识
void resched_curr(struct rq *rq)
{
    /*省略*/
    if (cpu == smp_processor_id()) {
    // 设置thread_info的need_resched标识 
        set_tsk_need_resched(curr);
    // 设置抢占计数__preempt_count里的need_resched标识
        set_preempt_need_resched();
        return;
    }
    /*省略*/
}

//在schedule()中清除thread_info和__preempt_count中的need_resched标识
static void __sched __schedule(void)
{
    /*省略*/
need_resched:
    // 关抢占读取percpu变量中当前cpu id,运行队列
    preempt_disable();
    cpu = smp_processor_id(); 
    rq = cpu_rq(cpu);
    rcu_note_context_switch();
    prev = rq->curr;
    /*省略*/
    //关闭本地中断,关闭抢占,获取rq自旋锁
    raw_spin_lock_irq(&rq->lock);
    switch_count = &prev->nivcsw;
  // PREEMPT_ACTIVE 0x00200000
  // preempt_count = __preempt_count & (~(0x80000000))
  // 如果进程没有处于running的状态或者设置了PREEMPT_ACTIVE标识
  //(即本次schedule是由于内核抢占导致),则不会将当前进程移出队列
  // 此处PREEMPT_ACTIVE的标识是由中断返回内核空间时调用
  // preempt_schdule_irq或者内核空间调用preempt_schedule
  // 而设置的,表明是由于内核抢占导致的schedule,此时不会将当前
  // 进程从运行队列取出,因为有可能其再也无法重新运行。
    if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
    // 如果有信号不移出run_queue
        if (unlikely(signal_pending_state(prev->state, prev))) {
            prev
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值