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

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

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

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

asmlinkage __visible void __sched schedule(void)5 k4 D) y% |' e6 |
{
3 o) ^/ \4 [- c6 @* Y% u- { struct task_struct *tsk = current;
) P- X2 r9 L% Y, J+ h# l6 H    2 J0 w1 t% P6 Z- T
sched_submit_work(tsk);
' ~: O- m/ p+ c( V; I, @ do {
; P& o) y3 @5 C+ p  K  preempt_disable();
) Q- U6 ~8 p! \8 v3 i. e+ T* B; l  __schedule(false);' y' I( k; ^( u. A2 g
  sched_preempt_enable_no_resched();
) x$ A. G0 V. s- t  z" A } while (need_resched());, d2 R) a* r% M0 r2 X% x
}
$ k$ g# j  ~7 k- c' }; r+ o/ DEXPORT_SYMBOL(schedule);
: q6 E$ E+ ]1 v- b
微信图片_20200412140228.png

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

    extern typeof(schedule) schedule;                                    \
8 J6 G8 I7 @' u! P/ T  X4 d$ {    extern __visible void *__crc_schedule __attribute__((weak));         \
, r' F8 Z9 h- r; p% }9 h1 ]: V    static const unsigned long __kcrctab_schedule                        \
% d' t% H% Y) b( |& w    __used                                                               \( t& B8 ^& H9 i* \
    __attribute__((section("___kcrctab" "" "+" "schedule"), unused))     \
; F; }+ e% v6 n" q3 _    = (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";                                                    \
0 I+ {% }+ z6 f+ }3 ^+ u    extern const struct kernel_symbol __ksymtab_schedule;                \# {& k4 z$ y: V
    __visible const struct kernel_symbol __ksymtab_schedule              \: M# }& @  n  C0 ?
    __used                                                               \
) k' ?5 x7 J$ P" @' t6 A    __attribute__((section("___ksymtab" "" "+" "schedule"), unused))     \
- H7 `$ h+ b! C5 H6 f8 Y    = {
(unsigned long)&schedule, __kstrtab_schedule };   
2 I7 h" t- P2 t

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

asmlinkage __visible void __sched schedule(void)
6 t# q/ W- G. o0 N3 l{5 A* j9 W* n! Y% L9 c. E
struct task_struct *tsk = current;
7 k5 g7 K# B) h' b$ j, b! p7 G    ; g3 J6 |9 S4 b2 ^
sched_submit_work(tsk);
4 s3 c' E. y5 [ 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();
, d& S! u3 ^. o0 |# n2 c2 p } while (need_resched());
3 {5 t$ ~( A% m* B}
- A8 j' a4 T( R7 _/*以下部分都属于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))    \
: G! s* S# z" M= (unsigned long) &__crc_schedule;     
* A8 l' _7 I* N8 H" f" B/ r; ~! m0 w( ~- I% Y
static const char __kstrtab_schedule[] __attribute__((section("__ksymtab_strings"), aligned(1)))  = "_" "schedule";                                    
; W6 G* s" M9 n& R+ ~! }4 ?, X0 \! {, P$ W( Y
extern const struct kernel_symbol __ksymtab_schedule;
5 n) }6 n! g9 t8 G( D% g7 _5 i
& ^- U8 ]! E+ J& Z8 g, m& W__visible const struct kernel_symbol __ksymtab_schedule __used __attribute__((section("___ksymtab" "" "+" "schedule"), unused)) = {
5 }/ a/ y+ m# d7 y    (unsigned long)&schedule, __kstrtab_schedule ! U. \. b4 s9 z$ o% R+ o+ M) m8 A
};

, g5 l  M6 O5 w6 i/ J9 @gcc相关知识点梳理

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

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

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

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

    #define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0))); w5 ]/ {+ w  t* w

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

    #define asmlinkage CPP_ASMLINKAGE __attribute__((syscall_linkage))
    / ?3 S% l: d8 B& F, h

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

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

    ' _; T# T1 k# ^3 G1 I& B1 K
  • typeof,是gcc的扩展关键字。参考gcc 9.3.0手册 P475:

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

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

    #define SWAP(a, b)  {\
    ( n1 I) V2 k: a; \  j% y      typeof(a) _t=a;\- F3 z1 \- n* B) P
          a=b;\/ N; d5 T. T6 j; d
          b=_t;}
    6 Z8 T9 o, _- R; d9 ]6 `+ p/ E0 O; ]

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


    : g9 T$ M5 k$ g7 c4 I2 y& n
    • 表达式的示例:typeof(x [0](1)) 假设x是一个指向函数的指针数组;则上述语句描述的类型是函数值的类型。

    • 类型名示例

      typeof (int *)

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


      , V! b! ]1 X3 Y4 a5 P' W: ]
  • __visible ,这在哪里实现的呢?这是将gcc的__externally_visible__属性利用宏转定义了,以增加可读性。用于声明全局可见。该宏定义位于:


    " m9 L6 S& I6 K4 t1 L& b0 F7 B

       ./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

. G, w  t  n4 A+ S  s, P4 m! z8 v9 o
  • __sched,这个咋一看,也是一头雾水。找到出处:./include/sched/debug.h

    /* 声明存储位置在.sched.text中. */( B9 Z# J' n) _  o
    #define __sched         __attribute__((__section__(".sched.text")))

    ' |/ O) z2 q( T/ i( B" j+ w

    类似地,还有

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

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

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

    "_" "schedule" 变成 “_schedule”

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

    #define __used   __attribute__((__used__))" P  g6 q; O2 r5 O, D' F8 A

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

    0 |  S5 {9 B2 P* _, Q
& X/ {0 e" P8 M- K) J
再看EXPORT_SYMBOL(schedule)展式

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

asmlinkage __visible void __sched schedule(void)
  N% q6 D, j; L! V5 }" E{
5 K$ Q, @; Q8 s) U struct task_struct *tsk = current;% y; A1 {( s& Y( Z; R- }
   
+ p& M5 Y" |" a1 \7 e# z$ m sched_submit_work(tsk);/ X# E) d+ X% |
do {
. g# Z4 H- ]/ y3 Z7 L# y  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());
' G& U+ l( a7 o, p2 D}
  Q. \* u; E# N9 O% [, r5 w& Z' H
/*以下部分都属于EXPORT_SYMBOL(schedule)的展开*/
# J6 M  S6 [* Z5 c/*利用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,指定存储位置*/
+ ~9 g- d$ A/ ~8 y0 {static const unsigned long __kcrctab_schedule __used                 \
5 Z( n+ W0 l& O  ~- J: e: z8 D% c__attribute__((section("___kcrctab + schedule"), unused))    \
0 H, k0 B$ p; @* x# p8 h' s/ [% @= (unsigned long) &__crc_schedule;     
1 X2 ]) Z& V' e! C* \1 j5 ^# D
# i4 X2 z6 X+ w5 fstatic const char __kstrtab_schedule[] __attribute__((section("__ksymtab_strings"), aligned(1)))  = "_schedule";                                    
9 z/ {2 G  _! F: v% ]- ?( 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封装对外可见*/
) x, z) K6 R- e7 z5 f__visible const struct kernel_symbol __ksymtab_schedule __used __attribute__((section("___ksymtab + schedule"), unused)) = {
/ L. {( |2 ~& S. \    (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
7 v# T* l( u- r3 Z! i#include <linux/compiler.h>
4 B; F' c2 Z7 w2 X: ?3 ?- H2 B( G/*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
*行时处理的绝对重定位的需求。
% ^  M' C3 U7 L: c*/

6 h2 ^, D, R1 F" x. H- |5 N( ?#define __KSYMTAB_ENTRY_NS(sym, sec)     \7 H6 }! A5 Q' Z- L8 [0 u) J7 p
__ADDRESSABLE(sym)      \
5 Q3 x8 T* L* A3 o! L( @ asm(" .section \"___ksymtab" sec "+" #sym "\", \"a\" \n" \( D, L/ F$ z4 u1 e' A8 D
     " .balign 4         \n" \
) t% G) B+ n3 Y$ X4 H     "__ksymtab_" #sym ":    \n" \4 S/ x4 W  F5 c/ ]. a3 i+ O
     " .long " #sym "- .    \n" \
! S+ p1 J7 G6 M) F     " .long __kstrtab_" #sym "- . \n" \
, C/ M- u8 }" B$ |- d! i     " .long __kstrtabns_" #sym "- . \n" \9 _8 X5 b$ ^4 T) a
     " .previous     \n")

, y/ \; d3 g) h6 |8 S# ?9 z6 @
1 {: ]4 V& {0 e# K#define __KSYMTAB_ENTRY(sym, sec)   \
5 Y, x/ b/ Y3 L8 r' s  M __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" \
) M# V4 B" G! r, }     " .long " #sym "- .    \n" \
2 `* V- Q7 d( @6 i     " .long __kstrtab_" #sym "- . \n" \! A& Z/ N; g. o# v+ d8 I5 y8 ]5 a. w
     " .long 0         \n" \
: z, Q3 J+ w, s5 M2 |! M$ j     " .previous         \n")
  G  Q7 A* {: o: B

$ V9 J1 a+ K/ t' bstruct kernel_symbol {
7 {2 _9 b2 W* o; }* \ 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
};
) x9 N" n0 U2 B( b; q#else
  {' D% `1 r8 }9 J#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 }

" x1 H* s& Z" j$ M
$ N7 e2 N  n& |7 s0 ]; k#define __KSYMTAB_ENTRY(sym, sec)                 \. t" ^8 y: O3 f9 \6 N- Q7 s+ M
static const struct kernel_symbol __ksymtab_##sym      \
, P( X' B+ z6 u% L __attribute__((section("___ksymtab" sec "+" #sym), used)) \; ~6 f- O: E2 W. k3 U8 o1 E
__aligned(sizeof(void *))                     \
! X! N; l+ }  ` = { (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
};
/ x- ]9 ~6 ^0 L9 j) S#endif
! M- w* s7 X  E$ O! X5 T
为何将主调度器全局导出

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

微信图片_20200412140237.png

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

  • 确定当前就绪队列, 并在保存一个指向当前(仍然)活动进程的task_struct指针;
  • 检查死锁, 关闭内核抢占后调用__schedule完成内核调度;
  • 恢复内核抢占, 然后检查当前进程是否设置了重调度标志TLF_NEDD_RESCHED, 如果该进程被其他进程设置了TIF_NEED_RESCHED标志, 则函数重新执行进行调度。! \+ G/ y& X& A4 t8 `
asmlinkage __visible void __sched schedule(void)/ y+ v* f7 }! ~. ^/ [& e& O% h9 ^
{# q7 X, {0 j: ]- t; I9 Y

; m# _/ N# A' k* L    /*  获取当前的进程  */
% u, F, ]6 c- F' Q/ o" k* }0 I" W    struct task_struct *tsk = current;
+ I4 G$ t7 z2 ~& \7 P5 O6 y8 w: i( a
    /*  避免死锁 */  ]+ C! t0 g5 Z- c6 a; g
    sched_submit_work(tsk);
/ f( D- {, C: P9 P7 W4 B3 ?- D; ]    do {
- U8 U9 M7 R/ ~. s( t- ?4 p) E, {        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());   
) }6 ^7 T- e* ?$ ]( l    /*  如果该进程被其他进程设置了TIF_NEED_RESCHED标志,则函数重新执行进行调度    */# F7 |; b6 F8 R7 H1 D
}8 l3 A2 I# E' r2 @
EXPORT_SYMBOL(schedule);

; L4 M4 |+ V7 v1 q: d( Q

以./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
*就不需要做什么。 只要有其他任务或所有消息都已传递,轮询就会停止。*/

8 @/ ~3 I7 m+ M* X, d. Mstatic int ap_poll_thread(void *data)
! f6 y+ Z' ?! X" F. E{& r# b$ p4 L6 k& C
DECLARE_WAITQUEUE(wait, current);6 p& l7 E& f$ {/ M

" \0 o  _1 f; y6 |  s set_user_nice(current, MAX_NICE);
; s4 x4 ?1 z) c& }. M 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);
4 a6 j- I- V8 K9 m  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();
* R0 |; y6 R5 |% U% B  }
* N; ]- L# U. ]: J$ [2 P$ o* X  set_current_state(TASK_RUNNING);
+ I5 e. E6 J! A% P& W' E9 ?, d5 T  remove_wait_queue(&ap_poll_wait, &wait);
) H. W  I$ [3 J( @  if (need_resched()) {3 O: k" E5 I6 g9 ], W/ V
            /*主动调用调度器*/
9 p# \  q! w0 Q4 D* B   schedule();
7 Q6 [, M* v0 s1 o   try_to_freeze();
, Q# @* C" M, c+ I' I4 l   continue;2 P6 H' E3 i$ l7 l
  }
2 r! @) i# h: H5 ~$ i! i  ap_tasklet_fn(0);
$ ^+ i; N+ L2 w; j }
8 l5 v9 A4 k5 f! h
7 Y$ H4 J/ A" F, T$ h return 0;
! V( @$ S& X1 ~* y}
4 A$ K; B5 ]' C* v" E

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


5 k# |4 z  _$ m
赞 收藏 评论0 发布时间:2020-4-12 14:05

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版