|
[导读] Linux内核代码庞大,阅读内核书籍总觉得云山雾绕,纸上得来终觉浅,希望通过阅读代码撰写笔记,尝试将这美人神秘的面纱掀开一角,管中窥豹,见一点真容。水平所限,错误难免,恳请交流指正。 前情提要《阅读内核系列之EXPORT_SYMBOL展开》将EXPORT_SYMBOL(schedule)展开: asmlinkage __visible void __sched schedule(void)5 k4 D) y% |' e6 |{ struct task_struct *tsk = current; 2 J0 w1 t% P6 Z- T sched_submit_work(tsk); do { preempt_disable(); __schedule(false);' y' I( k; ^( u. A2 g sched_preempt_enable_no_resched(); } while (need_resched());, d2 R) a* r% M0 r2 X% x } EXPORT_SYMBOL(schedule);: q6 E$ E+ ]1 v- b
全部展开后,得到了什么呢(前文中__EXPORT_SYMBOL(sym, sec) sec弄错了,修正如下)? extern typeof(schedule) schedule; \extern __visible void *__crc_schedule __attribute__((weak)); \ static const unsigned long __kcrctab_schedule \ __used \( t& B8 ^& H9 i* \ __attribute__((section("___kcrctab" "" "+" "schedule"), unused)) \ = (unsigned long) &__crc_schedule; % p* h' S0 ]* y. S static const char __kstrtab_schedule[] \) A: F2 V9 g6 H/ @" q4 H __attribute__((section("__ksymtab_strings"), aligned(1))) \. Q8 p ?& k! ]1 f = "_" "schedule"; \ extern const struct kernel_symbol __ksymtab_schedule; \# {& k4 z$ y: V __visible const struct kernel_symbol __ksymtab_schedule \: M# }& @ n C0 ? __used \ __attribute__((section("___ksymtab" "" "+" "schedule"), unused)) \ = { (unsigned long)&schedule, __kstrtab_schedule }; 2 I7 h" t- P2 t 这样还是不直观,去掉不必要的换行符,整理一下: asmlinkage __visible void __sched schedule(void){5 A* j9 W* n! Y% L9 c. E struct task_struct *tsk = current; ; g3 J6 |9 S4 b2 ^ sched_submit_work(tsk); do {/ t' k! r9 a! k; o* \! X3 @ preempt_disable();: G, u2 d) p+ |) m% I& d __schedule(false);) l1 M( v$ j) v: @ sched_preempt_enable_no_resched(); } while (need_resched()); } /*以下部分都属于EXPORT_SYMBOL(schedule)的展开*/% Y. r) O' C1 P8 E extern typeof(schedule) schedule;6 R8 l9 ?+ {6 o s" O extern __visible void *__crc_schedule __attribute__((weak));( u% }5 K9 E5 T y/ V & l. Y D4 p; }. n. \7 ~ static const unsigned long __kcrctab_schedule __used \: t6 C& W4 K- k/ F, y __attribute__((section("___kcrctab+schedule"), unused)) \ = (unsigned long) &__crc_schedule; " f" B/ r; ~! m0 w( ~- I% Y static const char __kstrtab_schedule[] __attribute__((section("__ksymtab_strings"), aligned(1))) = "_" "schedule"; 4 ?, X0 \! {, P$ W( Y 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 ! U. \. b4 s9 z$ o% R+ o+ M) m8 A }; gcc相关知识点梳理 要理解上述代码,感觉还是很难,先来梳理一下其中一些关键字,好多没见过?憋急。
./include/linux/compiler_attributes.h中 #if __has_attribute(__externally_visible__)0 f/ D, ~$ R; B5 M( @( ^, {- G* K#define __visible __attribute__((__externally_visible__))5 V' K' Z7 X/ J) ^' O2 u0 i( O #else5 u+ Q9 L- a" V1 ^+ N0 p6 m #define __visible' N. O9 f) e, r5 q5 y$ C #endif
再看EXPORT_SYMBOL(schedule)展式 好了,前面的都整明白了,再来看前面的那段代码: asmlinkage __visible void __sched schedule(void){ struct task_struct *tsk = current;% y; A1 {( s& Y( Z; R- } sched_submit_work(tsk);/ X# E) d+ X% | do { preempt_disable();$ W1 M8 T0 ?/ \0 ? __schedule(false);/ v3 `8 T' F+ ?) g# G' Q" | sched_preempt_enable_no_resched();" F; x- Z; K' ?8 O/ d, s; K } while (need_resched()); } % [, r5 w& Z' H /*以下部分都属于EXPORT_SYMBOL(schedule)的展开*/ /*利用typeof全局声明schedule函数*/8 }6 H# \4 X1 a% A2 x" H) ^8 U6 Z extern typeof(schedule) schedule;% E9 Y5 h: s& f4 T, ]7 S7 b# c /*全局声明__crc_schedule,并声明为weak属性*/8 N& g4 Y( C6 ? E0 l extern __visible void *__crc_schedule __attribute__((weak));& g, m2 y1 ]5 Q5 C* k0 ~ 6 a. E, @ L/ E5 H, N4 C- C /*局部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"; % ]- ?( H/ |. Q/ V# w9 Z6 J; `* y extern const struct kernel_symbol __ksymtab_schedule; 3 A1 T+ H x' s, P- v6 J2 _6 B- @ ( p9 Y2 J) C6 a u9 m6 T8 g" z /*将schedule 及字符串属性利用kernel_symbol封装对外可见*/ __visible const struct kernel_symbol __ksymtab_schedule __used __attribute__((section("___ksymtab + schedule"), unused)) = { (unsigned long)&schedule, __kstrtab_schedule ; t# [- G3 }8 U% R }; 5 u- d% c% w ?: Q kernel_symbol 位于./include/linux/export.h 中: #ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS#include <linux/compiler.h> /*8 J' C! u* \* ?( U5 f/ i4 ? *将ksymtab条目作为一对相对引用链接:% q) a: z. T% g) z/ d5 d! {: {+ e0 { *在64位体系结构上,这将大小减小了一半,5 L% q, _/ \1 e& L' J& n *并且消除了需要在可重定位内核上进行运' B$ I/ ?8 k" O, L *行时处理的绝对重定位的需求。 */ #define __KSYMTAB_ENTRY_NS(sym, sec) \7 H6 }! A5 Q' Z- L8 [0 u) J7 p __ADDRESSABLE(sym) \ asm(" .section \"___ksymtab" sec "+" #sym "\", \"a\" \n" \( D, L/ F$ z4 u1 e' A8 D " .balign 4 \n" \ "__ksymtab_" #sym ": \n" \4 S/ x4 W F5 c/ ]. a3 i+ O " .long " #sym "- . \n" \ " .long __kstrtab_" #sym "- . \n" \ " .long __kstrtabns_" #sym "- . \n" \9 _8 X5 b$ ^4 T) a " .previous \n") #define __KSYMTAB_ENTRY(sym, sec) \ __ADDRESSABLE(sym) \$ F& Y7 r$ b# ~, r' @ asm(" .section \"___ksymtab" sec "+" #sym "\", \"a\" \n" \) U1 A5 J- Q5 ^7 ]' c " .balign 4 \n" \. A6 p$ T: q+ I+ e" H& Q z "__ksymtab_" #sym ": \n" \ " .long " #sym "- . \n" \ " .long __kstrtab_" #sym "- . \n" \! A& Z/ N; g. o# v+ d8 I5 y8 ]5 a. w " .long 0 \n" \ " .previous \n") G Q7 A* {: o: B struct kernel_symbol { int value_offset;: k+ S B7 S) S4 L* }0 U int name_offset;: }0 X* U+ O5 i" y n int namespace_offset;+ E8 e4 S& n; ]; B0 i& G8 z0 e/ H }; #else #define __KSYMTAB_ENTRY_NS(sym, sec) \, x- o d, N& i+ \# u) _ static const struct kernel_symbol __ksymtab_##sym \; f9 V5 V$ @% r' E% U __attribute__((section("___ksymtab" sec "+" #sym), used)) \/ F; n* w1 ?& \' w2 G$ L1 O __aligned(sizeof(void *)) \$ S! o. B: ^) Z' K i) ? = { (unsigned long)&sym, __kstrtab_##sym, __kstrtabns_##sym } #define __KSYMTAB_ENTRY(sym, sec) \. t" ^8 y: O3 f9 \6 N- Q7 s+ M static const struct kernel_symbol __ksymtab_##sym \ __attribute__((section("___ksymtab" sec "+" #sym), used)) \; ~6 f- O: E2 W. k3 U8 o1 E __aligned(sizeof(void *)) \ = { (unsigned long)&sym, __kstrtab_##sym, NULL }6 r" `" W; B! j; o/ v' t & ~( v7 p# z# X5 n. N struct kernel_symbol {0 D7 P" C0 l$ F# s& a" u' P* D unsigned long value;* A+ X& i0 d4 v% b const char *name;5 Q- t( E* S, |' Y, P const char *namespace;* R5 f1 v) ?9 E }; #endif! M- w* s7 X E$ O! X5 T 为何将主调度器全局导出 至此,调度对外导出就基本明晰了,但是进一步引申思考?为什么还要将调度器schedule以模块形式对外导出呢?EXPORT_SYMBOL对外导出,那么导出的作用域究竟多大呢,所包住的函数在内核代码中全局可见,也就意味着其他的内核模块可以使用该函数。但是貌似还是没有回答说为啥要将调度器对外导出,潜意识我们会认为调度器直接在后台像个勤劳的大管家,在哪里不停的忙活就完了,难不成其他模块还要主动去调用调度器不成。为了验证猜想,搜一下吧:
看来猜想没错,事实上:schedule就是主调度器的函数, 在内核中的许多地方, 如果要将CPU分配给与当前活动进程不同的另一个进程, 都会直接主动调用主调度器函数schedule.该函数完成如下工作:
{# q7 X, {0 j: ]- t; I9 Y /* 获取当前的进程 */ struct task_struct *tsk = current; 5 O6 y8 w: i( a /* 避免死锁 */ ]+ C! t0 g5 Z- c6 a; g sched_submit_work(tsk); do { preempt_disable(); /* 关闭内核抢占 */* ]8 S2 l% f9 Y# V, L& ^+ Q __schedule(false); /* 完成调度 */, J( E2 S% h) q4 J6 o; b sched_preempt_enable_no_resched(); /* 开启内核抢占 */1 ?" q, R7 A; z( p. _ } while (need_resched()); /* 如果该进程被其他进程设置了TIF_NEED_RESCHED标志,则函数重新执行进行调度 */# F7 |; b6 F8 R7 H1 D }8 l3 A2 I# E' r2 @ EXPORT_SYMBOL(schedule); 以./drivers/s390/crypto/ap_bus.c 的函数ap_poll_thread为例: /*ap_poll_thread():轮询完成的请求的线程。AP总线轮询线程6 ?) l4 W9 B9 f% p*该线程的目的是在循环中轮询存在的请求,如果有一个“空闲”的cpu,+ f5 l4 F3 a/ k& g *就不需要做什么。 只要有其他任务或所有消息都已传递,轮询就会停止。*/ static int ap_poll_thread(void *data) {& r# b$ p4 L6 k& C DECLARE_WAITQUEUE(wait, current);6 p& l7 E& f$ {/ M set_user_nice(current, MAX_NICE); set_freezable();& N9 I+ v- o% N( R; J while (!kthread_should_stop()) {, p& L4 x0 f. Z$ x add_wait_queue(&ap_poll_wait, &wait); set_current_state(TASK_INTERRUPTIBLE);: r6 K( I2 o) {3 R4 g, | if (ap_suspend_flag || !ap_pending_requests()) {* x* p+ [+ M# x7 ^ schedule();! E. A8 a& c, w* I- F o$ x# m try_to_freeze(); } set_current_state(TASK_RUNNING); remove_wait_queue(&ap_poll_wait, &wait); if (need_resched()) {3 O: k" E5 I6 g9 ], W/ V /*主动调用调度器*/ schedule(); try_to_freeze(); continue;2 P6 H' E3 i$ l7 l } ap_tasklet_fn(0); } return 0; }4 A$ K; B5 ]' C* v" E 关于内核调度器究竟如何工作,还没开始读,如有兴趣,请继续关注。 |
微信公众号
手机版