当前位置:首页 > 嵌入式 > 嵌入式客栈
[导读]关注、星标嵌入式客栈,干货及时送达 [导读] Linux内核代码庞大,阅读内核书籍总觉得云山雾绕,纸上得来终觉浅,希望通过阅读代码撰写笔记,尝试将这美人神秘的面纱掀开一角,管中窥豹,见一点真容。水平所限,错误难免,恳请交流指正。 前情提要 《阅读内核

关注、星标嵌入式客栈,干货及时送达

[导读] 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);

全部展开后,得到了什么呢(前文中__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,则汇编代码中可以调用该函数。参考https://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))

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

  • __attribute__  ,关键字__attribute__用来指定变量,函数参数或结构,联合以及在C ++中的类成员的特殊属性。__attribute__关键字后跟一个用双括号括起来的属性规范。当前通常为变量定义一些属性。为特定目标系统上的变量定义了其他属性。其他属性可用于函数(请参见“函数属性”),标签(请参见“标签属性”),枚举(请参见“枚举器属性”),语句(请参见“语句属性”)和类型(请参见“类型属性”)。有需要的时候可以去查阅gcc文档:       https://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对外导出,那么导出的作用域究竟多大呢,所包住的函数在内核代码中全局可见,也就意味着其他的内核模块可以使用该函数。但是貌似还是没有回答说为啥要将调度器对外导出,潜意识我们会认为调度器直接在后台像个勤劳的大管家,在哪里不停的忙活就完了,难不成其他模块还要主动去调用调度器不成。为了验证猜想,搜一下吧:

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

  1. 确定当前就绪队列, 并在保存一个指向当前(仍然)活动进程的task_struct指针;
  2. 检查死锁, 关闭内核抢占后调用__schedule完成内核调度;
  3. 恢复内核抢占, 然后检查当前进程是否设置了重调度标志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;
}

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

点击留言/查看留言

END

果喜欢右下点个在看,也会让我倍感鼓舞

往期精彩推荐




Linux 内核架构分析
优化嵌入式Linux的启动时间之启动脚本
优化嵌入式Linux的启动时间之内核
优化嵌入式Linux的启动时间之文件系统
优化嵌入式Linux的启动之工具链/应用程序优化
嵌入式Linux引导以及U-Boot移植介绍

关注置顶:扫描左下二维码关注公众号加星

讨论加群:扫描右下二维码添加,发送“加群”

关注

加群

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭