linux 内核 禁止抢占,内核抢占实现(preempt)

一、什么叫抢占

所谓抢占,说白了就是进程切换。

linux的用户空间,进程A在执行中,来(硬?)中断打断A,从中断处理程序返回时,如果有更高优先级进程B在排队的话,那么执行进程B。 用户空间下进程总是可抢占的

在linux的内核空间就不一定了,linux 2.4是不可抢占的,实时性就会降低,如下面这个样子:

二、抢占的API

preempt_enable()  开启抢占

preempt_disable() 禁止抢占

内核中每个进程数据结构里有一个计数器preempt_count

抢占的开启与禁止,操作当前进程的preempt_count

内核在进行进程调度的时候,只要prempt_count为0,内核就可以进行抢占。

struct thread_info {

struct task_struct *task; /* main task structure */

............//省略

int     cpu;              /* cpu we're on */

int     preempt_count;    /* 0 => preemptable,  <0 => BUG */

};

#define preempt_enable() \

do { \

preempt_enable_no_resched(); \

barrier(); \

preempt_check_resched(); \

} while (0)

#define preempt_disable() \

do { \

inc_preempt_count(); \

barrier(); \

} while (0)

#define preempt_enable_no_resched() \

do { \

barrier(); \

dec_preempt_count(); \

} while (0)

#define inc_preempt_count() add_preempt_count(1)

#define dec_preempt_count() sub_preempt_count(1)

#define add_preempt_count(val) do { preempt_count() += (val); } while (0)

#define sub_preempt_count(val) do { preempt_count() -= (val); } while (0)

#define preempt_count() (current_thread_info()->preempt_count)

三、发生抢占的时机

linux进程调度的核心函数是 schedule(),进程调度就是在这里做的。

schedule的调用分为主动调用和被动调用。

主动调用是指内核显示的直接去调用shedule(),如当前进程调用了可休眠函数,里面会调用schedule

被动调用是指在系统调用、中断处理或异常处理结束之后,由相应的回调函数调用schedule

判断完当前进程是否可抢占,才会接着去调用schedule()

只看了看中断返回时schedule被动调用的情况

至于主动调用的地方就太多了,什么进程结束,pause等等,没耐心看了。。。

3.1 从中断返回时

首先是从中断处理程序do_IRQ()返回后,会调用ret_from_except() (看《PowerPC中断相关知识》)

ret_from_except()里要先check一下,判定前面被中断的执行体是运行在用户空间还是内核空间,

在决定返回到用户空间或内核空间

用户空间的话:(现在知道为什么用户空间程序总是可抢占了吧)

ret_from_except

--> user_exc_return

--> do_work

--> 调用 do_signal 和 schedule

内核空间的话:(编译内核时要打开可抢占选项才行)

ret_form_except

--> resume_kernel

--> preempt_schedule_irq

--> schedule

.globl ret_from_except

ret_from_except:

LOAD_MSR_KERNEL(r10,MSR_KERNEL)  //将MSR_KERNEL常量设置到MSR,以禁止外部中断

SYNC                             //Some chip revs have problems here...

MTMSRD(r10)                      //disable interrupts

lwz r3,_MSR(r1)                  //读栈中的MSR[PR],Returning to user mode?

andi. r0,r3,MSR_PR

beq resume_kernel

user_exc_return:                   //r10 contains MSR_KERNEL here

rlwinm r9,r1,0,0,(31-THREAD_SHIFT) //Check current_thread_info()->flags

lwz r9,TI_FLAGS(r9)

andi. r0,r9,(_TIF_SIGPENDING|_TIF_RESTORE_SIGMASK|_TIF_NEED_RESCHED)

bne do_work

restore_user:

#ifdef CONFIG_PREEMPT

b restore

resume_kernel:

rlwinm r9,r1,0,0,(31-THREAD_SHIFT) /* check current_thread_info->preempt_count */

lwz r0,TI_PREEMPT(r9)

cmpwi 0,r0,0                       /* if non-zero, just restore regs and return */

bne restore

lwz r0,TI_FLAGS(r9)

andi. r0,r0,_TIF_NEED_RESCHED

beq+ restore

andi. r0,r3,MSR_EE                /* interrupts off? */

beq restore                       /* don't schedule if so */

1: bl preempt_schedule_irq

rlwinm r9,r1,0,0,(31-THREAD_SHIFT)

lwz r3,TI_FLAGS(r9)

andi. r0,r3,_TIF_NEED_RESCHED

bne- 1b

#else

resume_kernel:

#endif /* CONFIG_PREEMPT */

do_work:            /* r10 contains MSR_KERNEL here */

andi.   r0,r9,_TIF_NEED_RESCHED

beq do_user_signal

do_resched:         /* r10 contains MSR_KERNEL here */

ori r10,r10,MSR_EE

SYNC

MTMSRD(r10)     /* hard-enable interrupts */

bl  schedule

recheck:

LOAD_MSR_KERNEL(r10,MSR_KERNEL)

SYNC

MTMSRD(r10)     /* disable interrupts */

rlwinm  r9,r1,0,0,(31-THREAD_SHIFT)

lwz r9,TI_FLAGS(r9)

andi.   r0,r9,_TIF_NEED_RESCHED

bne-    do_reschedandi.   r0,r9,_TIF_SIGPENDING

beq restore_user

do_user_signal:         /* r10 contains MSR_KERNEL here */

asmlinkage void __sched preempt_schedule_irq(void){

struct thread_info *ti = current_thread_info();

BUG_ON(ti->preempt_count || !irqs_disabled());

do {

add_preempt_count(PREEMPT_ACTIVE);

local_irq_enable();

schedule();

local_irq_disable();

sub_preempt_count(PREEMPT_ACTIVE);

barrier();

} while (unlikely(test_thread_flag(TIF_NEED_RESCHED)));

}

asmlinkage void __sched preempt_schedule(void){

struct thread_info *ti = current_thread_info();

//preempt_cout非0的话,就不调用schedule

if (likely(ti->preempt_count || irqs_disabled()))

return;

do {

add_preempt_count(PREEMPT_ACTIVE);

schedule();

sub_preempt_count(PREEMPT_ACTIVE);

barrier();

} while (unlikely(test_thread_flag(TIF_NEED_RESCHED)));

}

#########################################################################################;

内核中的执行路径主要有:

1  用户进程的内核态,此时有进程context,主要是代表进程在执行系统调用等。

还包括,内核中自己的进程,如 ksoftirqd 等等

2  中断或者异常或者自陷等,从概念上说,此时没有进程context,不能进行context switch。

3  bottom_half,从概念上说,此时也没有进程context。

4  同时,相同的执行路径还可能在其他的CPU上运行。

Linux2.6中网络代码中的preempt_enable/disable移到softirqd调用的地方原因是这样的.

一、部分softirq是isr处理之后调用的,

对于这部分代码,由于是在底半处理中运行,必须是是在运行进程系统调用之前返回的.

所以实际上preempt_disable(); preempt_enable();代码对于他们来说是没有意义的.

二、部分softirq是在ksoftirqd的内核线程运行的,

因为这个相当于运行在进程的内核空间,由于软中断都是对中断上半部的继续,

所以这些工作都需要尽快的完成.所以在softirqd运行的时候,禁止了preempt,

这样就可以保证softirq运行完之后才会调度下一个进程,因为softirq里面的所有函数都不会睡眠.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值