Linux内核态抢占怎么实现

发布时间:2021-12-24 15:05:25 作者:iii
来源:亿速云 阅读:130

本篇内容介绍了“ Linux内核态抢占怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

1. 非抢占式和可抢占式内核的区别

为了简化问题,我使用嵌入式实时系统uC/OS作为例子。首先要指出的是,uC/OS只有内核态,没有用户态,这和Linux不一样。

多任务系统中,内核负责管理各个任务,或者说为每个任务分配CPU时间,并且负责任务之间的通讯。内核提供的基本服务是任务切换。调度(Scheduler),英文还有一词叫dispatcher,也是调度的意思。这是内核的主要职责之一,就是要决定该轮到哪个任务运行了。多数实时内核是基于优先级调度法的。每个任务根据其重要程度的不同被赋予一定的优先级。基于优先级的调度法指,CPU总是让处在就绪态的优先级***的任务先运行。然而,究竟何时让高优先级任务掌握CPU的使用权,有两种不同的情况,这要看用的是什么类型的内核,是不可剥夺型的还是可剥夺型内核。

非抢占式内核

非抢占式内核是由任务主动放弃CPU的使用权。非抢占式调度法也称作合作型多任务,各个任务彼此合作共享一个CPU。异步事件还是由中断服务来处理。中断服务可以使一个高优先级的任务由挂起状态变为就绪状态。但中断服务以后控制权还是回到原来被中断了的那个任务,直到该任务主动放弃CPU的使用权时,那个高优先级的任务才能获得CPU的使用权。非抢占式内核如下图所示。

非抢占式内核的优点有:

非抢占式内核的缺点有:

抢占式内核

使用抢占式内核可以保证系统响应时间。***优先级的任务一旦就绪,总能得到CPU的使用权。当一个运行着的任务使一个比它优先级高的任务进入了就绪态,当前任务的CPU使用权就会被剥夺,或者说被挂起了,那个高优先级的任务立刻得到了CPU的控制权。如果是中断服务子程序使一个高优先级的任务进入就绪态,中断完成时,中断了的任务被挂起,优先级高的那个任务开始运行。抢占式内核如下图所示。

抢占式内核的优点有:

抢占式内核的缺点有:

2. Linux下的用户态抢占和内核态抢占

Linux除了内核态外还有用户态。用户程序的上下文属于用户态,系统调用和中断处理例程上下文属于内核态。在2.6 kernel以前,Linux  kernel只支持用户态抢占。

2.1 用户态抢占(User Preemption)

在kernel返回用户态(user-space)时,并且need_resched标志为1时,scheduler被调用,这就是用户态抢占。当kernel返回用户态时,系统可以安全的执行当前的任务,或者切换到另外一个任务。当中断处理例程或者系统调用完成后,kernel返回用户态时,need_resched标志的值会被检查,假如它为1,调度器会选择一个新的任务并执行。

中断和系统调用的返回路径(return path)的实现在entry.S中(entry.S不仅包括kernel entry code,也包括kernel  exit code)。

2.2 内核态抢占(Kernel Preemption)

在2.6 kernel以前,kernel code(中断和系统调用属于kernel  code)会一直运行,直到code被完成或者被阻塞(系统调用可以被阻塞)。在 2.6 kernel里,Linux  kernel变成可抢占式。当从中断处理例程返回到内核态(kernel-space)时,kernel会检查是否可以抢占和是否需要重新调度。kernel可以在任何时间点上抢占一个任务(因为中断可以发生在任何时间点上),只要在这个时间点上kernel的状态是安全的、可重新调度的。

3. 内核态抢占的设计

3.1 可抢占的条件

要满足什么条件,kernel才可以抢占一个任务的内核态呢?

如何判断当前上下文(中断处理例程、系统调用、内核线程等)是没持有锁的?Linux在每个每个任务的thread_info结构中增加了preempt_count变量作为preemption的计数器。这个变量初始为0,当加锁时计数器增一,当解锁时计数器减一。

3.2 内核态需要抢占的触发条件

内核提供了一个need_resched标志(这个标志在任务结构thread_info中)来表明是否需要重新执行调度。

3.3 何时触发重新调度

set_tsk_need_resched():设置指定进程中的need_resched标志

clear_tsk need_resched():清除指定进程中的need_resched标志

need_resched():检查need_ resched标志的值;如果被设置就返回真,否则返回假

什么时候需要重新调度:

3.4 抢占发生的时机(何时检查可抢占条件)

3.5 禁用/使能可抢占条件的操作

对preempt_count操作的函数有add_preempt_count()、sub_preempt_count()、inc_preempt_count()、dec_preempt_count()。

使能可抢占条件的操作是preempt_enable(),它调用dec_preempt_count()函数,然后再调用preempt_check_resched()函数去检查是否需要重新调度。

禁用可抢占条件的操作是preempt_disable(),它调用inc_preempt_count()函数。

在内核中有很多函数调用了preempt_enable()和preempt_disable()。比如spin_lock()函数调用了preempt_disable()函数,spin_unlock()函数调用了preempt_enable()函数。

3.6 什么时候不允许抢占

preempt_count()函数用于获取preempt_count的值,preemptible()用于判断内核是否可抢占。

有几种情况Linux内核不应该被抢占,除此之外,Linux内核在任意一点都可被抢占。这几种情况是:

4. Linux内核态抢占的实现

4.1 数据结构

[cpp] view plain copy

struct thread_info {        struct task_struct  *task;      /* main task structure */        struct exec_domain  *exec_domain;   /* execution domain */        /**        * 如果有TIF_NEED_RESCHED标志,则必须调用调度程序。        */        unsigned long       flags;      /* low level flags */        /**        * 线程标志:        *     TS_USEDFPU:表示进程在当前执行过程中,是否使用过FPU、MMX和XMM寄存器。        */        unsigned long       status;     /* thread-synchronous flags */        /**        * 可运行进程所在运行队列的CPU逻辑号。        */        __u32           cpu;        /* current CPU */        __s32           preempt_count; /* 0 => preemptable, <0 => BUG */           mm_segment_t        addr_limit; /* thread address space:                              0-0xBFFFFFFF for user-thead                              0-0xFFFFFFFF for kernel-thread                           */        struct restart_block    restart_block;            unsigned long           previous_esp;   /* ESP of the previous stack in case                              of nested (IRQ) stacks                           */        __u8            supervisor_stack[0];    };

4.2 代码流程

禁用/使能可抢占条件的函数

[cpp] view plain copy

#ifdef CONFIG_DEBUG_PREEMPT      extern void fastcall add_preempt_count(int val);      extern void fastcall sub_preempt_count(int val);    #else    # define add_preempt_count(val) do { preempt_count() += (val); } while (0)    # define sub_preempt_count(val) do { preempt_count() -= (val); } while (0)    #endif        #define inc_preempt_count() add_preempt_count(1)    #define dec_preempt_count() sub_preempt_count(1)       /**    * 在thread_info描述符中选择preempt_count字段    */    #define preempt_count() (current_thread_info()->preempt_count)       #ifdef CONFIG_PREEMPT       asmlinkage void preempt_schedule(void);        /**    * 使抢占计数加1    */    #define preempt_disable() \    do { \        inc_preempt_count(); \        barrier(); \    } while (0)       /**    * 使抢占计数减1    */    #define preempt_enable_no_resched() \    do { \        barrier(); \        dec_preempt_count(); \    } while (0)        #define preempt_check_resched() \    do { \        if (unlikely(test_thread_flag(TIF_NEED_RESCHED))) \            preempt_schedule(); \    } while (0)        /**    * 使抢占计数减1,并在thread_info描述符的TIF_NEED_RESCHED标志被置为1的情况下,调用preempt_schedule()    */    #define preempt_enable() \    do { \        preempt_enable_no_resched(); \        preempt_check_resched(); \    } while (0)        #else        #define preempt_disable()       do { } while (0)    #define preempt_enable_no_resched() do { } while (0)    #define preempt_enable()        do { } while (0)    #define preempt_check_resched()     do { } while (0)        #endif

设置need_resched标志的函数

[cpp] view plain copy

static inline void set_tsk_need_resched(struct task_struct *tsk)    {        set_tsk_thread_flag(tsk,TIF_NEED_RESCHED);    }        static inline void clear_tsk_need_resched(struct task_struct *tsk)    {        clear_tsk_thread_flag(tsk,TIF_NEED_RESCHED);    }

“ Linux内核态抢占怎么实现”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

推荐阅读:
  1. keepalived 非抢占模式 以及nginx状态监控
  2. Linux用户态与内核态通信的方式有哪些

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

linux

上一篇:GNOME Linux桌面的示例分析

下一篇:linux中如何删除用户组

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》