linux驱动加载 动态加载 静态加载 自动加载
概述
在咱們工作中,編譯驅動,加載驅動時最常見的事。但是內核是如何加載驅動的,有些事編譯到內核里面,有些事編譯成ko,有些還能放到root下,讓系統自動加載。
總的說來,在Linux下可以通過兩種方式加載驅動程序:靜態加載和動態加載。
靜態加載就是把驅動程序直接編譯進內核,系統啟動后可以直接調用。動態加載利用了Linux的module特性,可以在系統啟動后用insmod命令添加模塊(.ko),在不需要的時候用rmmod命令卸載模塊。
?
?
驅動加載
靜態加載過程
?
將模塊的程序編譯到Linux內核中,也就是咱們在編譯內核時選擇Y的模塊,靜態由do_initcall函數加載。先來看看initcall在哪里:
核心進程(/init/main.c)
start_kernel()//內核啟動的入口,負責初始化調度,中斷,內存,最后啟動1號進程 和2號進程
? ? ?rest_init()
? ? ? ? ? kernel_init()//這是誰? linux 1號進程,top一下系統就能找到1號進程了。
? ? ? ? ? ? ? do_basic_setup()
? ? ? ? ? ? ? ? ? ? do_initcalls()
?
do_initcalls中會定義的各個模塊加載順序,加載順序分為16個等級,加載時按照16個等級依次加載內核驅動。關于每個等級的定義參考/include/linux/init.h。舉個例子,在2.6.24的內核 中:gianfar_device使用的是arch_initcall,而gianfar_driver使用的是module_init,因為 arch_initcall的優先級大于module_init,所以gianfar設備驅動的device先于driver在總線上添加。
?
動態加載過程
將模塊的程序編譯成KO,也就是咱們在編譯內核時選擇M的模塊,通過insmod
、modprobe 或者udev動態加載到內核中。?insmod加絕對路徑/××.ko,而modprobe ××即可,不用加.ko或.o后綴,也不用加路徑;最重要的一點是:modprobe同時會加載當前模塊所依賴的其它模塊。
來看看insmod都做了什么:
Insmod
? ? insmod_main
? ? ? ? bb_init_module
? ? ? ? ? ? init_module
? ? ? ? ? ? ? ? sys_init_module(進入系統調用)
? ? ? ? ? ? ? ? ? ? ? sys_init_module()系統調用會調用module_init指定的函數進行模塊的初始化,至此ko加載完成。
?
?
驅動匹配
硬件固件設計
以PCI為例,所有PCI硬件上必須設計一系列寄存器,PCI通過這些寄存器獲取硬件信息,稱為PCI配置空間,PCI配置空間格式如下:
?
PCI標準規定每個設備的配置寄存器組最多可以有256字節的連續空間,其中開頭的64字節的用途和格式是標準的,成為配置寄存器組的“頭部”,這樣的頭部又有兩種,“0型”頭部用于一般的PCI設備,“1型”頭部用于PCI橋,無論是“0型”還是“1型”,其開頭的16個字節的用途和格式是共同的。其中Device ID 和 Vendor ID 位于前4個字節。
在系統中運行LSPCI -NN,會得到類似如下輸出:
?Ethernet controller [0200]: Intel Corporation 82545EM Gigabit Ethernet Controller (Copper) [8086:100f] (rev 01)
其中的8086是vendor ID,100f是Device ID。所以咱們常用的LSCPI實質上就是去硬件上讀取硬件的寄存器值,并且顯示出來。
軟件驅動設計
每一個硬件設備都有Verdon ID, Device ID, SubVendor ID等信息。所以每一個設備驅動程序,必須說明自己能夠為哪些Verdon ID, DevieceID, SubVendor ID的設備提供服務。以PCI設備為例,它是通過一個pci_device_id的數據結構來實現這個功能的。例如:RTL8139驅動的pci_device_id定義為:
?
Verdon ID可以理解為廠商ID,8086 代表Intel,1249代表三星等,Device?ID可以理解為產品ID,每款產品的ID不同。上面的信息說明,Verdon ID為0x10EC, Device ID為0x8139, 0x8138的PCI設備(SubVendor ID和SubDeviceID為PCI_ANY_ID,表示不限制。),都可以使用這個驅動程序(8139too)。
下面是內核設備與驅動匹配的代碼:
?
自動加載過程
自動加載屬于動態加載,加載的是ko文件,許多同學也沒搞清楚,系統啟動之后ko在什么時候,通過什么程序自動加載上的。
構建自動加載環境
?
編譯內核時,通過make modules_install INSTALL_MOD_PATH=XXX,會將所有選項為M的模塊編譯為KO, 并且發布到xxx/lib/modules/uname-r/下面。在模塊安裝的時候,depmod會根據模塊中的rtl8139_pci_tbl的信息,生成下面的信息,保存到/lib/modules/uname-r/modules.alias文件中,其內容如下:
?
alias pci:v000010ECd00008138sv*sd*bc*sc*i* 8139too alias pci:v000010ECd00008139sv*sd*bc*sc*i* 8139too ......
?
v后面的000010EC說明其Vendor ID為10EC,d后面的00008138說明Device ID為8139,而sv,和sd為SubVendor ID和SubDevice ID,后面的星號表示任意匹配。
另外在/lib/modules/uname-r/modules.dep文件中還保存這模塊之間的依賴關系,其內容如下:
?
8139too.ko:mii.ko
?
modules.dep由depmod工具生成,在使用 modprobe xxx加載驅動時, modprobe需要借助modules.dep文件來分析模塊之間的依賴關系,先加載依賴的ko,再加載真正需要加載的ko。
?
?
PCI掃描自動加載驅動
在內核啟動過程中,總線驅動程序會會總線協議進行總線枚舉(總線驅動程序總是集成在內核之中,不能夠按模塊方式加載,你可以通過make menuconfig進入Busoptions,這里面的各種總線,你只能夠選擇Y或N,而不能選擇M.),并且為每一個設備建立一個設備對象。每一個總線對象有一個kset對象,每一個設備對象嵌入了一個kobject對象,kobject連接在kset對象上,這樣總線和總線之間,總線和設備設備之間就組織成一顆樹狀結構。當總線驅動程序為掃描到的設備建立設備對象時,會初始化kobject對象,并把它連接到設備樹中,同時會調用kobject_uevent()把這個(添加新設備的)事件,以及相關信息(包括設備的VendorID,DeviceID等信息)通過netlink發送到用戶態中。
來看看這個過程:
subsys_initcall?(前面講到過驅動的16級加載機制,subsys_initcall處于16級中的第8級,arch\x86\pci\legacy.c 參照內核4.19)
pci_legacy_init
pcibios_scan_root
pci_scan_root_bus
pci_scan_slot
pci_scan_single_device
pci_device_add
device_add
?
device_add有兩個流程:
在用戶態的udevd檢測到這個事件,就可以根據這些信息,打開/lib/modules/uname-r/modules.alias文件,根據
?
alias pci:v000010ECd00008138sv*sd*bc*sc*i* 8139too
?
得知這個新掃描到的設備驅動模塊為8139too。于是modprobe就知道要加載8139too這個模塊了,同時modprobe根據 modules.dep文件發現,8139too依賴于mii.ko,如果mii.ko沒有加載,modprobe就先加載mii.ko,接著再加載 8139too.ko。
?
總結
以上是生活随笔為你收集整理的linux驱动加载 动态加载 静态加载 自动加载的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux网络编程、socket编程
- 下一篇: Linux库概念及相关编程(动态库、静态