你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

Linux内核——为何将主调度器全局导出

[复制链接]
gaosmile 发布时间:2020-4-12 14:05
[导读] Linux内核代码庞大,阅读内核书籍总觉得云山雾绕,纸上得来终觉浅,希望通过阅读代码撰写笔记,尝试将这美人神秘的面纱掀开一角,管中窥豹,见一点真容。水平所限,错误难免,恳请交流指正。
前情提要

《阅读内核系列之EXPORT_SYMBOL展开》将EXPORT_SYMBOL(schedule)展开:

asmlinkage __visible void __sched schedule(void)
{
struct task_struct *tsk = current;
   
sched_submit_work(tsk);
do {
  preempt_disable();
  __schedule(false);
  sched_preempt_enable_no_resched();
} while (need_resched());
}
EXPORT_SYMBOL(schedule);

微信图片_20200412140228.png

全部展开后,得到了什么呢(前文中__EXPORT_SYMBOL(sym, sec)  sec弄错了,修正如下)?

    extern typeof(schedule) schedule;                                    \
    extern __visible void *__crc_schedule __attribute__((weak));         \
    static const unsigned long __kcrctab_schedule                        \
    __used                                                               \
    __attribute__((section("___kcrctab" "" "+" "schedule"), unused))     \
    = (unsigned long) &__crc_schedule;      
   static const char __kstrtab_schedule[]                                \
    __attribute__((section("__ksymtab_strings"), aligned(1)))            \
    = "_" "schedule";                                                    \
    extern const struct kernel_symbol __ksymtab_schedule;                \
    __visible const struct kernel_symbol __ksymtab_schedule              \
    __used                                                               \
    __attribute__((section("___ksymtab" "" "+" "schedule"), unused))     \
    = {
(unsigned long)&schedule, __kstrtab_schedule };   

这样还是不直观,去掉不必要的换行符,整理一下:

asmlinkage __visible void __sched schedule(void)
{
struct task_struct *tsk = current;
   
sched_submit_work(tsk);
do {
  preempt_disable();
  __schedule(false);
  sched_preempt_enable_no_resched();
} while (need_resched());
}
/*以下部分都属于EXPORT_SYMBOL(schedule)的展开*/
extern typeof(schedule) schedule;
extern __visible void *__crc_schedule __attribute__((weak));

static const unsigned long __kcrctab_schedule __used                 \
__attribute__((section("___kcrctab+schedule"), unused))    \
= (unsigned long) &__crc_schedule;     

static const char __kstrtab_schedule[] __attribute__((section("__ksymtab_strings"), aligned(1)))  = "_" "schedule";                                    

extern const struct kernel_symbol __ksymtab_schedule;

__visible const struct kernel_symbol __ksymtab_schedule __used __attribute__((section("___ksymtab" "" "+" "schedule"), unused)) = {
    (unsigned long)&schedule, __kstrtab_schedule
};

gcc相关知识点梳理

要理解上述代码,感觉还是很难,先来梳理一下其中一些关键字,好多没见过?憋急。

  • asmlinkage,其一:用于指定函数的参数都栈中,而不应在寄存器中;其二,指定一个函数为asmlinkage,则汇编代码中可以调用该函数。参考http://kernelnewbies.org/FAQ/asmlinkage

  • 阅读Linux内核代码,发现大量的文件名同名,如不理清其内在机理,这很让人头脑发胀。这里以linkage.h为例来探讨一下。

    看到有博文说asmlinkage其根源如下:

    #define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

    但仔细查看代码,这仅仅是对x86体系而言,而比如针对IA64(英特尔安腾架构(Intel Itanium architecture))而言:

    #define asmlinkage CPP_ASMLINKAGE __attribute__((syscall_linkage))

    所以不同的体系结构为实现前述目的是有差异的。

    微信图片_20200412140233.png
  • __attribute__  ,关键字__attribute__用来指定变量,函数参数或结构,联合以及在C ++中的类成员的特殊属性。__attribute__关键字后跟一个用双括号括起来的属性规范。当前通常为变量定义一些属性。为特定目标系统上的变量定义了其他属性。其他属性可用于函数(请参见“函数属性”),标签(请参见“标签属性”),枚举(请参见“枚举器属性”),语句(请参见“语句属性”)和类型(请参见“类型属性”)。有需要的时候可以去查阅gcc文档:       http://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html


  • typeof,是gcc的扩展关键字。参考gcc 9.3.0手册 P475:

    引用表达式类型的另一种方法是使用typeof。其语法看起来像sizeof,但是该构造在语义上类似于使用typedef定义的类型名称。有两种方式将参数写入typeof:使用表达式或类型。

    很可能没见过这种特性,那有啥妙用呢?

    #define SWAP(a, b)  {\
          typeof(a) _t=a;\
          a=b;\
          b=_t;}

    写的这么复杂干啥呢?其一、用大括号括起来定义的_b作用域限定了,不会有重名的问题,其二、利用typeof(a) _t=a,则可以获取a的类型,具有普适性。所谓普适性,即便对这个宏传入两个结构体也是运行的。如果不这么做,用函数实现需要做到普适性则比较麻烦,如果一定要做肯定也有办法,比如swap(void *a, void * b,int length),直接交换内存。但远不如这个宏来的简单。


    • 表达式的示例:typeof(x [0](1)) 假设x是一个指向函数的指针数组;则上述语句描述的类型是函数值的类型。

    • 类型名示例

      typeof (int *)

      这里描述的类型是指向int的指针类型。


  • __visible ,这在哪里实现的呢?这是将gcc的__externally_visible__属性利用宏转定义了,以增加可读性。用于声明全局可见。该宏定义位于:


       ./include/linux/compiler_attributes.h中

#if __has_attribute(__externally_visible__)
#define __visible __attribute__((__externally_visible__))
#else
#define __visible
#endif

  • __sched,这个咋一看,也是一头雾水。找到出处:./include/sched/debug.h

    /* 声明存储位置在.sched.text中. */
    #define __sched         __attribute__((__section__(".sched.text")))

    类似地,还有

    #define __init_thread_info __attribute__((__section__(".data..init_thread_info")))
  • weak,若两个或两个以上全局符号(函数或变量名)名字一样,而其中之一声明为weak symbol(弱符号),则这些全局符号不会引发重定义错误。链接器会忽略弱符号,去使用普通的全局符号来解析所有对这些符号的引用,但当普通的全局符号不可用时,链接器会使用弱符号。当有函数或变量名可能被用户覆盖时,该函数或变量名可以声明为一个弱符号。当weak和alias属性连用时,还可以声明弱别名。

  • unused,附加到函数的此属性意味着如果该函数未被使用。GCC不会对此功能发出警告。

  • 两个以双引号的字符串,编译预处理时,会自动连接为一个字符串

    "_" "schedule" 变成 “_schedule”

  • __used, __unused__属性,在./include/compiler.h定义

    #define __used   __attribute__((__used__))

    该属性附加在函数上,表示即使未引用该函数,也必须将该函数链接在目标文件中。



再看EXPORT_SYMBOL(schedule)展式

好了,前面的都整明白了,再来看前面的那段代码:

asmlinkage __visible void __sched schedule(void)
{
struct task_struct *tsk = current;
   
sched_submit_work(tsk);
do {
  preempt_disable();
  __schedule(false);
  sched_preempt_enable_no_resched();
} while (need_resched());
}

/*以下部分都属于EXPORT_SYMBOL(schedule)的展开*/
/*利用typeof全局声明schedule函数*/
extern typeof(schedule) schedule;
/*全局声明__crc_schedule,并声明为weak属性*/
extern __visible void *__crc_schedule __attribute__((weak));

/*局部const定义__crc_schedule,指定存储位置*/
static const unsigned long __kcrctab_schedule __used                 \
__attribute__((section("___kcrctab + schedule"), unused))    \
= (unsigned long) &__crc_schedule;     

static const char __kstrtab_schedule[] __attribute__((section("__ksymtab_strings"), aligned(1)))  = "_schedule";                                    

extern const struct kernel_symbol __ksymtab_schedule;

/*将schedule 及字符串属性利用kernel_symbol封装对外可见*/
__visible const struct kernel_symbol __ksymtab_schedule __used __attribute__((section("___ksymtab + schedule"), unused)) = {
    (unsigned long)&schedule, __kstrtab_schedule
};

kernel_symbol 位于./include/linux/export.h 中:

#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
#include <linux/compiler.h>
/*
*将ksymtab条目作为一对相对引用链接:
*在64位体系结构上,这将大小减小了一半,
*并且消除了需要在可重定位内核上进行运
*行时处理的绝对重定位的需求。
*/

#define __KSYMTAB_ENTRY_NS(sym, sec)     \
__ADDRESSABLE(sym)      \
asm(" .section \"___ksymtab" sec "+" #sym "\", \"a\" \n" \
     " .balign 4         \n" \
     "__ksymtab_" #sym ":    \n" \
     " .long " #sym "- .    \n" \
     " .long __kstrtab_" #sym "- . \n" \
     " .long __kstrtabns_" #sym "- . \n" \
     " .previous     \n")


#define __KSYMTAB_ENTRY(sym, sec)   \
__ADDRESSABLE(sym)      \
asm(" .section \"___ksymtab" sec "+" #sym "\", \"a\" \n" \
     " .balign 4         \n" \
     "__ksymtab_" #sym ":    \n" \
     " .long " #sym "- .    \n" \
     " .long __kstrtab_" #sym "- . \n" \
     " .long 0         \n" \
     " .previous         \n")


struct kernel_symbol {
int value_offset;
int name_offset;
int namespace_offset;
};
#else
#define __KSYMTAB_ENTRY_NS(sym, sec)             \
static const struct kernel_symbol __ksymtab_##sym      \
__attribute__((section("___ksymtab" sec "+" #sym), used)) \
__aligned(sizeof(void *))                     \
= { (unsigned long)&sym, __kstrtab_##sym, __kstrtabns_##sym }


#define __KSYMTAB_ENTRY(sym, sec)                 \
static const struct kernel_symbol __ksymtab_##sym      \
__attribute__((section("___ksymtab" sec "+" #sym), used)) \
__aligned(sizeof(void *))                     \
= { (unsigned long)&sym, __kstrtab_##sym, NULL }


struct kernel_symbol {
unsigned long value;
const char *name;
const char *namespace;
};
#endif

为何将主调度器全局导出

至此,调度对外导出就基本明晰了,但是进一步引申思考?为什么还要将调度器schedule以模块形式对外导出呢?EXPORT_SYMBOL对外导出,那么导出的作用域究竟多大呢,所包住的函数在内核代码中全局可见,也就意味着其他的内核模块可以使用该函数。但是貌似还是没有回答说为啥要将调度器对外导出,潜意识我们会认为调度器直接在后台像个勤劳的大管家,在哪里不停的忙活就完了,难不成其他模块还要主动去调用调度器不成。为了验证猜想,搜一下吧:

微信图片_20200412140237.png

看来猜想没错,事实上:schedule就是主调度器的函数, 在内核中的许多地方, 如果要将CPU分配给与当前活动进程不同的另一个进程, 都会直接主动调用主调度器函数schedule.该函数完成如下工作:

  • 确定当前就绪队列, 并在保存一个指向当前(仍然)活动进程的task_struct指针;
  • 检查死锁, 关闭内核抢占后调用__schedule完成内核调度;
  • 恢复内核抢占, 然后检查当前进程是否设置了重调度标志TLF_NEDD_RESCHED, 如果该进程被其他进程设置了TIF_NEED_RESCHED标志, 则函数重新执行进行调度。
asmlinkage __visible void __sched schedule(void)
{

    /*  获取当前的进程  */
    struct task_struct *tsk = current;

    /*  避免死锁 */
    sched_submit_work(tsk);
    do {
        preempt_disable();                    /*  关闭内核抢占  */
        __schedule(false);                    /*  完成调度  */
        sched_preempt_enable_no_resched();    /*  开启内核抢占  */
    } while (need_resched());   
    /*  如果该进程被其他进程设置了TIF_NEED_RESCHED标志,则函数重新执行进行调度    */
}
EXPORT_SYMBOL(schedule);

以./drivers/s390/crypto/ap_bus.c 的函数ap_poll_thread为例:

/*ap_poll_thread():轮询完成的请求的线程。AP总线轮询线程
*该线程的目的是在循环中轮询存在的请求,如果有一个“空闲”的cpu,
*就不需要做什么。 只要有其他任务或所有消息都已传递,轮询就会停止。*/

static int ap_poll_thread(void *data)
{
DECLARE_WAITQUEUE(wait, current);

set_user_nice(current, MAX_NICE);
set_freezable();
while (!kthread_should_stop()) {
  add_wait_queue(&ap_poll_wait, &wait);
  set_current_state(TASK_INTERRUPTIBLE);
  if (ap_suspend_flag || !ap_pending_requests()) {
   schedule();
   try_to_freeze();
  }
  set_current_state(TASK_RUNNING);
  remove_wait_queue(&ap_poll_wait, &wait);
  if (need_resched()) {
            /*主动调用调度器*/
   schedule();
   try_to_freeze();
   continue;
  }
  ap_tasklet_fn(0);
}

return 0;
}

关于内核调度器究竟如何工作,还没开始读,如有兴趣,请继续关注。


收藏 评论0 发布时间:2020-4-12 14:05

举报

0个回答

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版