成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux內核是如何巧妙的初始化各個模塊的

系統 Linux
相信很多在研究linux內核源碼的同學,經常會發現一些模塊的初始化函數找不到調用者,比如下面的網絡模塊的初始化函數。一起來看看吧。

 [[378352]]

相信很多在研究linux內核源碼的同學,經常會發現一些模塊的初始化函數找不到調用者,比如下面的網絡模塊的初始化函數: 

  1. // net/ipv4/af_inet.c  
  2. static int __init inet_init(void)  
  3.  
  4.         ...  
  5.         /*  
  6.          *      Set the IP module up  
  7.          */  
  8.         ip_init();  
  9.         /* Setup TCP slab cache for open requests. */  
  10.         tcp_init();  
  11.         /* Setup UDP memory threshold */  
  12.         udp_init();  
  13.         ...  
  14.  
  15. fs_initcall(inet_init); 

即使你在整個內核代碼中搜索,也找不到任何地方調用這個函數,那這個函數到底是怎么調用的呢?

秘密就在這個函數之后的一行代碼里:

  1. fs_initcall( inet_init); 

在該行代碼中,fs_initcall是一個宏,具體定義如下: 

  1. // include/linux/init.h  
  2. #define ___define_initcall(fn, id, __sec) \  
  3.         static initcall_t __initcall_##fn##id __used \  
  4.                 __attribute__((__section__(#__sec ".init"))) = fn;  
  5. ...  
  6. #define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)  
  7. ...  
  8. #define fs_initcall(fn)                 __define_initcall(fn, 5) 

在該宏展開后,上面宏調用的結果,大致像下面這個樣子:

  1. static initcall_t __initcall_inet_init5 __attribute__((__section__(".initcall5.init"))) = inet_init; 

由上可見,fs_initcall宏最終是定義了一個靜態變量,該變量的類型是initcall_t,值是宏參數表示的函數地址。

initcall_t類型的定義如下:

  1. typedef int (*initcall_t)(void); 

由上可見,initcall_t是一個函數指針類型,它定義的變量會指向一個函數,該函數的參數要為空,返回類型要為int。

我們可以再看下上面的 inet_init 方法,該方法確實符合這些要求。

綜上可知,fs_initcall宏定義了一個變量 __initcall_inet_init5,它的類型為initcall_t,它的值為inet_init函數的地址。

到這里我相信很多同學會想,linux內核一定是通過這個變量來調用inet_init函數的,對嗎?

對,也不對。

對是因為內核確實是通過該變量指向的內存來獲取inet_init方法的地址并調用該方法的。

不對是因為內核并不是通過上面的__initcall_inet_init5變量來訪問這個內存的。

那不用這個變量,還能通過其他方式訪問這個內存嗎?

當然可以,這正是linux內核設計的巧妙之處。

我們再來看下上面的宏展開之后,靜態變量__initcall_inet_init5的定義,在該定義中有如下的一些代碼: 

  1. __attribute__((__section__(".initcall5.init"))) 

該部分代碼并不屬于c語言標準,而是gcc對c語言的擴展,它的作用是聲明該變量屬于 .initcall5.init這個section。

所謂section,我們可以簡單的理解為對程序所占內存區域的一種布局和規劃,比如我們常見的 section有 .text用來存放我們的代碼,.data或.bss用來存放我們的變量。

通過這些section的定義,我們可以把程序中的相關功能放到同一塊內存區域中,這樣來方便內存管理。

除了這些默認的section之外,我們還可以通過gcc的attribute來自定義section,這樣我們就可以把相關的函數或變量放到相同的section中了。

比如上面的__initcall_inet_init5變量就屬于.initcall5.init這個自定義section。

在定義了這些section之后,我們可以在鏈接腳本中告訴linker,這些section在內存中的位置及布局是什么樣子的。

對于x86平臺來說,內核的鏈接腳本是:

  1. arch/x86/kernel/vmlinux.lds.S 

在該腳本中,對.initcall5.init等這些section做了相關定義,具體邏輯如下: 

  1. // include/asm-generic/vmlinux.lds.h  
  2. #define INIT_CALLS_LEVEL(level)                                         \  
  3.                 __initcall##level##_start = .;                          \  
  4.                 KEEP(*(.initcall##level##.init))                        \  
  5.                 KEEP(*(.initcall##level##s.init))                       \  
  6. #define INIT_CALLS                                                      \  
  7.                 __initcall_start = .;                                   \  
  8.                 KEEP(*(.initcallearly.init))                            \  
  9.                 INIT_CALLS_LEVEL(0)                                     \  
  10.                 INIT_CALLS_LEVEL(1)                                     \  
  11.                 INIT_CALLS_LEVEL(2)                                     \  
  12.                 INIT_CALLS_LEVEL(3)                                     \  
  13.                 INIT_CALLS_LEVEL(4)                                     \  
  14.                 INIT_CALLS_LEVEL(5)                                     \  
  15.                 INIT_CALLS_LEVEL(rootfs)                                \  
  16.                 INIT_CALLS_LEVEL(6)                                     \  
  17.                 INIT_CALLS_LEVEL(7)                                     \  
  18.                 __initcall_end = .; 

由上可見,initcall相關的section有很多,我們上面例子中的.initcall5.init只是其中一個,除此之外還有 .initcall0.init,.initcall1.init等等這些section。

這些section都是通過宏INIT_CALLS_LEVEL來定義其處理規則的,相同level的section被放到同一塊內存區域,不同level的section的內存區域按level大小依次連接在一起。

對于上面的__initcall_inet_init5變量來說,它的section是.initcall5.init,它的level是5。

假設我們還有其他方法調用了宏fs_initcall,那該宏為該方法定義的靜態變量所屬的section也是.initcall5.init,level也是5。

由于該變量和__initcall_inet_init5變量所屬的initcall的level都相同,所以它們被連續放在同一塊內存區域里。

也就是說,這些level為5的靜態變量所占的內存區域是連續的,又因為這些變量的類型都為initcall_t,所以它們正好構成了一個類型為initcall_t的數組,而數組的起始地址也在INIT_CALLS_LEVEL宏中定義了,就是__initcall5_start。

如果我們想要調用這些level為5的initcall,只要先拿到__initcall5_start地址,把其當成元素類型為initcall_t的數組的起始地址,然后遍歷數組中的元素,獲取該元素對應的函數指針,就可以通過該指針調用對應的函數了。

來看下具體代碼: 

  1. // init/main.c  
  2. extern initcall_entry_t __initcall_start[];  
  3. extern initcall_entry_t __initcall0_start[];  
  4. extern initcall_entry_t __initcall1_start[];  
  5. extern initcall_entry_t __initcall2_start[];  
  6. extern initcall_entry_t __initcall3_start[];  
  7. extern initcall_entry_t __initcall4_start[];  
  8. extern initcall_entry_t __initcall5_start[];  
  9. extern initcall_entry_t __initcall6_start[];  
  10. extern initcall_entry_t __initcall7_start[];  
  11. extern initcall_entry_t __initcall_end[];  
  12. static initcall_entry_t *initcall_levels[] __initdata = {  
  13.         __initcall0_start,  
  14.         __initcall1_start,  
  15.         __initcall2_start,  
  16.         __initcall3_start,  
  17.         __initcall4_start,  
  18.         __initcall5_start,  
  19.         __initcall6_start,  
  20.         __initcall7_start,  
  21.         __initcall_end,  
  22. };  
  23. static void __init do_initcall_level(int level)  
  24.  
  25.         initcall_entry_t *fn;  
  26.         ...  
  27.         for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)  
  28.                 do_one_initcall(initcall_from_entry(fn));  
  29.  
  30. static void __init do_initcalls(void)  
  31.  
  32.         int level;  
  33.         for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)  
  34.                 do_initcall_level(level);  

在上面的代碼中,do_initcalls方法遍歷了所有的合法level,對于每個level,do_initcall_level方法又調用了該level里所有函數指針指向的函數。

我們上面示例中的inet_init方法就屬于level 5,也是在這里被調用到的。

linux內核就是通過這種方式來調用各個模塊的初始化方法的,很巧妙吧。

最后我們再來總結下:

1. 在各模塊的初始化方法之后,一般都會調用一個類似于fs_initcall(inet_init)的宏,該宏的參數是該模塊的初始化方法的方法名。

2. 該宏展開后的結果是定義一個靜態變量,該變量通過gcc的attribute來聲明其所屬的initcall level的section,比如inet_init方法對應的靜態變量就屬于.initcall5.init這個section。

3. 在linux的鏈接腳本里,通過INIT_CALLS_LEVEL宏告知linker,將屬于同一level的所有靜態變量放到連續的一塊內存中,組成一個元素類型為initcall_t的數組,該數組的起始地址放在類似__initcall5_start的變量中。

4. 在內核的初始化過程中,會通過調用 do_initcalls方法,遍歷各個level里的各個函數指針,然后調用該指針指向的方法,即各模塊的初始化方法。

各個模塊的初始化方法就是這樣被調用的。

希望你喜歡。 

 

責任編輯:龐桂玉 來源: 良許Linux
相關推薦

2025-02-13 11:11:53

Redis哨兵代碼

2025-01-03 08:53:59

2022-11-15 20:48:41

Linux

2017-09-13 14:28:02

Linux初始化系統運行級別

2011-03-16 10:52:20

2012-03-13 13:38:42

Java

2009-06-10 16:17:00

Netbeans JT初始化

2021-07-07 05:00:17

初始化源碼

2011-03-31 16:46:10

LinuxMySQL

2023-11-12 23:08:17

C++初始化

2009-03-27 18:27:48

2020-11-23 14:22:17

代碼Go存儲

2012-05-23 12:46:53

JavaJava類

2010-09-08 14:49:09

藍牙協議棧

2023-10-06 20:57:52

C++聚合成員

2009-09-02 16:52:55

C#數組初始化

2010-02-24 15:41:19

Linux Light

2013-03-04 11:10:03

JavaJVM

2022-08-03 11:00:20

Linux內核

2023-05-08 08:05:42

內核模塊Linux
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 99精品国产一区二区三区 | 日韩日b视频| 天天射影院 | 久久久亚洲一区 | 一区二区免费看 | 97在线超碰| 欧美日韩综合视频 | 国产特一级黄色片 | 成年人在线视频 | 国产色| 亚洲天堂一区二区 | 国产精品久久久久久婷婷天堂 | 日韩美av | 日韩成人免费在线视频 | 日韩av在线一区二区 | 国产精品高潮呻吟久久 | 国产精品99久久久久久宅男 | 日韩欧美高清 | 亚洲国产精品99久久久久久久久 | 久草资源 | 久久久视频在线 | 综合精品 | 欧美极品少妇xxxxⅹ免费视频 | 欧美男人天堂 | www久久久 | 亚洲精品国产一区 | 一区二区三区免费 | 国产精品一区在线 | 国产精品99一区二区 | 成人免费在线观看 | 精品日本中文字幕 | 久久合久久 | 99久久视频 | 国产精品福利视频 | 少妇黄色 | 日韩精品免费视频 | 欧美亚洲在线 | 国产一区二区三区视频 | 99精品一区二区 | 草久久 | 久久亚洲国产 |