|
[导读] 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相关知识点梳理 要理解上述代码,感觉还是很难,先来梳理一下其中一些关键字,好多没见过?憋急。
./include/linux/compiler_attributes.h中 #if __has_attribute(__externally_visible__)#define __visible __attribute__((__externally_visible__)) #else #define __visible #endif
再看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.该函数完成如下工作:
{ /* 获取当前的进程 */ 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; } 关于内核调度器究竟如何工作,还没开始读,如有兴趣,请继续关注。 |
微信公众号
手机版