Linux 驅(qū)動掛載順序分析
本文轉(zhuǎn)載自微信公眾號「嵌入式Linux系統(tǒng)開發(fā)」,作者Jasonangel 。轉(zhuǎn)載本文請聯(lián)系嵌入式Linux系統(tǒng)開發(fā)公眾號。
手把手教你分析 Linux 啟動流程
從上文可以得出,start_kernel 函數(shù)最后調(diào)用的是 rest_init 函數(shù),其實 rest_init 函數(shù)不光產(chǎn)生了最重要的 kernel_init (PID=1)和 kthreadd (PID=2)內(nèi)核進程。
kernel_init 最后演變?yōu)橛脩艨臻g init 進程(PID=1)。
rest_init 函數(shù)還有一個重要的分支:加載驅(qū)動模塊,調(diào)用流程如下:
- start_kernel
- |--->rest_init
- |--->kernel_init
- |--->kernel_init_freeable
- |--->do_basic_setup
- |--->driver_init
- |--->do_initcalls
- |--->do_initcall_level
- |--->do_one_initcall
注意,這里就是驅(qū)動的初始化和驅(qū)動模塊的加載。
我們知道在 rest_init 函數(shù)中,最重要的 1 號進程和 2 號進程都已經(jīng)起來了,也就是說系統(tǒng)已經(jīng)真正起來了。1 號 2 號進程起來之前,文件系統(tǒng)的掛載是在調(diào)用 rest_init 函數(shù)之前就掛載好了,此時加載驅(qū)動是可以的。
那么這里是如何掛載的呢?
流程中 driver_init 函數(shù)會對各個驅(qū)動入口函數(shù)進行初始化,也就是在內(nèi)存中對驅(qū)動初始化函數(shù)進行尋址。而 do_initcalls 函數(shù)中,會按照驅(qū)動的優(yōu)先級,對驅(qū)動一個一個進行掛載。
linux4.14/init/main.c
驅(qū)動的優(yōu)先級:Linux 把系統(tǒng)中需要掛載的各種東西,都分為14個等級,分別為 1--1s--2--2s--3--3s--4--4s--5--5s--6--6s--7--7s,數(shù)字越小優(yōu)先級越高,定義在:
linux4.14/include/linux/init.h
一般我們自己寫的驅(qū)動模塊,文件最后會聲明一個 module_init 和 module_exit ,實際上被定義為 device_initcall,優(yōu)先級為6,是要比架構(gòu)初始化模塊和文件系統(tǒng)模塊優(yōu)先級低。
如果驅(qū)動模塊之間有依賴,需要更改模塊掛載順序,有三種方式:
1、增加一個優(yōu)先級,比如 8?;蛘甙炎约旱尿?qū)動模塊聲明成其他優(yōu)先級,也就是不用 module_init 去聲明,可以用 fs_initcall 去聲明。
2、對于同一優(yōu)先級的驅(qū)動模塊,可以在 Makefile 中更改其編譯和鏈接的順序,就會切換其掛載的順序。(靜態(tài)編譯)
3、動態(tài)加載驅(qū)動模塊:等 Linux 系統(tǒng)起來以后,手動執(zhí)行 insmod 和 rmmod 即可掛載和卸載驅(qū)動,順序自己決定。測試成功后,再搞到內(nèi)核中靜態(tài)編譯。
雖然可以更改掛載順序,但還是希望大家寫驅(qū)動模塊的時候,能夠做到高內(nèi)聚、低耦合,自己的模塊最好不要依賴其他模塊,防止其他模塊加載失敗導(dǎo)致自己的模塊不可用。
如何看驅(qū)動掛載順序?有兩種方式:
1、找到編譯后的 Linux 內(nèi)核源碼,根目錄下面有個 System.map 文件,這里記載了 Linux 內(nèi)核所做的所有的事情,是按順序記載的(也有可能在其他輸出目錄)。
一共有三列:地址、區(qū)域、操作。在操作中我們可以看到我們聲明的驅(qū)動的名字。
2、如果你驅(qū)動模塊有加一些打印,可以直接看 log。