該文內(nèi)容源于朱有鵬老師的課程,按照自己的理解進(jìn)行匯總,方便查閱。如有侵權(quán),請(qǐng)告知?jiǎng)h除。
1 驅(qū)動(dòng)的概念
- 驅(qū)動(dòng)一詞的字面意思
- 物理上的驅(qū)動(dòng)
- 硬件中的驅(qū)動(dòng)
- linux內(nèi)核驅(qū)動(dòng)
??軟件層面的驅(qū)動(dòng)廣義上就是指:這一段代碼操作硬件去動(dòng),所以這一段代碼就叫硬件的驅(qū)動(dòng)程序。(本質(zhì)上是電力提供了動(dòng)力,而驅(qū)動(dòng)程序提供了操作邏輯方法)。
??狹義上驅(qū)動(dòng)程序就是專指操作系統(tǒng)中用來(lái)操控硬件的邏輯方法部分代碼。
2 linux體系架構(gòu)
(1)分層思想
(2)驅(qū)動(dòng)的上面是系統(tǒng)調(diào)用API
(3)驅(qū)動(dòng)的下面是硬件
(4)驅(qū)動(dòng)自己本身也是分層的
3 模塊化設(shè)計(jì)
3.1 微內(nèi)核和宏內(nèi)核
(1)宏內(nèi)核(又稱為單內(nèi)核):將內(nèi)核從整體上作為一個(gè)大過(guò)程實(shí)現(xiàn),并同時(shí)運(yùn)行在一個(gè)單獨(dú)的地址空間。所有的內(nèi)核服務(wù)都在一個(gè)地址空間運(yùn)行,相互之間直接調(diào)用函數(shù),簡(jiǎn)單高效。(緊耦合)
(2)微內(nèi)核:功能被劃分成獨(dú)立的過(guò)程,過(guò)程間通過(guò)IPC進(jìn)行通信。模塊化程度高,一個(gè)服務(wù)失效不會(huì)影響另外一個(gè)服務(wù)。典型如windows。(松耦合)
(3)linux:本質(zhì)上是宏內(nèi)核,但是又吸收了微內(nèi)核的模塊化特性,體現(xiàn)在以下2個(gè)層面:
靜態(tài)模塊化 : 在編譯時(shí)實(shí)現(xiàn)可裁剪,特征是想要功能裁剪改變必須重新編譯。
動(dòng)態(tài)模塊化 : zImage可以不重新編譯燒錄,甚至可以不關(guān)機(jī)重啟就實(shí)現(xiàn)模塊的安裝和卸載。
4 linux設(shè)備驅(qū)動(dòng)分類
4.1 驅(qū)動(dòng)分類
(1)分3類:字符設(shè)備驅(qū)動(dòng)、塊設(shè)備驅(qū)動(dòng)、網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)
(2)分類原則:設(shè)備本身讀寫操作的特征差異
4.2 三類驅(qū)動(dòng)程序詳細(xì)對(duì)比分析
(1)字符設(shè)備,準(zhǔn)確的說(shuō)應(yīng)該叫“字節(jié)設(shè)備”,軟件操作設(shè)備時(shí)是以字節(jié)為單位進(jìn)行的。典型的如LCD、串口、LED、蜂鳴器、觸摸屏······
(2)塊設(shè)備,塊設(shè)備是相對(duì)于字符設(shè)備定義的,塊設(shè)備被軟件操作時(shí)是以塊(多個(gè)字節(jié)構(gòu)成的一個(gè)單位)為單位的。設(shè)備的塊大小是設(shè)備本身設(shè)計(jì)時(shí)定義好的,軟件是不能去更改的,不同設(shè)備的塊大小可以不一樣。常見(jiàn)的塊設(shè)備都是存儲(chǔ)類設(shè)備,如:硬盤、NandFlash、iNand、SD····
(3)網(wǎng)絡(luò)設(shè)備,網(wǎng)絡(luò)設(shè)備是專為網(wǎng)卡設(shè)計(jì)的驅(qū)動(dòng)模型,linux中網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)主要目的是為了支持API中socket相關(guān)的那些函數(shù)工作。
4.3 為什么字符設(shè)備驅(qū)動(dòng)最重要
(1)常見(jiàn)大量設(shè)備都屬于字符設(shè)備
??linux一切皆文件,則linux下的字符設(shè)備驅(qū)動(dòng)也是以文件的形式呈現(xiàn)。既然是文件,那無(wú)外乎文件的一系列操作,其實(shí)我們之所以可以對(duì)文件進(jìn)行一系列操作最為本質(zhì)的原因就是文件系統(tǒng)的實(shí)現(xiàn)。在linux內(nèi)核中(中間抽象一層
,這就好比阿里當(dāng)年想把快遞拿下,搞出了菜鳥(niǎo)驛站,管你什么快遞,都放到我這里,我來(lái)為用戶服務(wù)!),在各種類型的文件系統(tǒng)上在加一層,抽象出了VFS
,也正是VFS的實(shí)現(xiàn)對(duì)于我們來(lái)說(shuō)管你底層是什么類型的文件系統(tǒng)對(duì)上完全沒(méi)有影響。
從架構(gòu)圖上可以清晰的發(fā)現(xiàn),字符設(shè)備驅(qū)動(dòng)的架構(gòu)是如此簡(jiǎn)單,當(dāng)應(yīng)用層操作的時(shí)候通過(guò)系統(tǒng)調(diào)用轉(zhuǎn)到內(nèi)核層,內(nèi)核中實(shí)現(xiàn)文件操作的時(shí)候就是驅(qū)動(dòng)代碼實(shí)現(xiàn),并沒(méi)有調(diào)用任何文件系統(tǒng), 相當(dāng)于我們直接和vfs對(duì)接就ok!
(2)舉例說(shuō)明非標(biāo)準(zhǔn)類型字符設(shè)備驅(qū)動(dòng)
基于ZYNQ平臺(tái)開(kāi)發(fā)的字符驅(qū)動(dòng),屬于非標(biāo)準(zhǔn)類型字符設(shè)備驅(qū)動(dòng)。
5 驅(qū)動(dòng)程序的安全性要求
5.1 驅(qū)動(dòng)是內(nèi)核的一部分
- 驅(qū)動(dòng)已經(jīng)成為內(nèi)核中最龐大的組成部分
- 內(nèi)核會(huì)直接以函數(shù)調(diào)用的方式調(diào)用驅(qū)動(dòng)代碼
- 驅(qū)動(dòng)的動(dòng)態(tài)安裝和卸載都會(huì)“更改”內(nèi)核
5.2 驅(qū)動(dòng)對(duì)內(nèi)核的影響
- 驅(qū)動(dòng)程序崩潰甚至?xí)?dǎo)致內(nèi)核崩潰
- 驅(qū)動(dòng)的效率會(huì)影響內(nèi)核的整體效率
- 驅(qū)動(dòng)的漏洞會(huì)造成內(nèi)核安全漏洞
5.3 常見(jiàn)驅(qū)動(dòng)安全性問(wèn)題
- 未初始化指針
- 惡意用戶程序
- 緩沖區(qū)溢出
- 競(jìng)爭(zhēng)狀態(tài)
6 驅(qū)動(dòng)應(yīng)該這么學(xué)
6.1 先學(xué)好C語(yǔ)言
6.2 掌握相關(guān)預(yù)備知識(shí)
- 硬件操作方面
- 應(yīng)用層API
6.3 驅(qū)動(dòng)學(xué)習(xí)階段
- 注重實(shí)踐,一步一步寫驅(qū)動(dòng)
- 框架思維,多考慮整體和上下層
- 先通過(guò)簡(jiǎn)單設(shè)備學(xué)linux驅(qū)動(dòng)框架
- 學(xué)會(huì)總結(jié)、記錄,這會(huì)有助于理解
6.4 驅(qū)動(dòng)開(kāi)發(fā)的準(zhǔn)備工作
(1)正常運(yùn)行l(wèi)inux系統(tǒng)的開(kāi)發(fā)板。要求開(kāi)發(fā)板中的linux的zImage必須是自己編譯的,不能是別人編譯的。
(2)內(nèi)核源碼樹(shù),其實(shí)就是一個(gè)經(jīng)過(guò)了配置編譯之后的內(nèi)核源碼。
(3)nfs掛載的rootfs,主機(jī)ubuntu中必須搭建一個(gè)nfs服務(wù)器。
6.4.1 驅(qū)動(dòng)開(kāi)發(fā)的步驟
- 驅(qū)動(dòng)源碼編寫、Makefile編寫、編譯
- insmod裝載模塊、測(cè)試、rmmod卸載模塊
6.4.2 實(shí)踐
1)copy原來(lái)提供的x210kernel.tar.bz2,找一個(gè)干凈的目錄(我的目錄/x210_driver/kernel),解壓之,并且配置編譯。編譯完成后得到了:1. 內(nèi)核源碼樹(shù)。2.編譯ok的zImage
2)fastboot將第1步中得到的zImage燒錄到開(kāi)發(fā)板中去啟動(dòng)(或者將zImage丟到tftp的共享目錄,uboot啟動(dòng)時(shí)tftp下載啟動(dòng)),將來(lái)驅(qū)動(dòng)編譯好后,就可以在這個(gè)內(nèi)核中去測(cè)試。因?yàn)檫@個(gè)zImage和內(nèi)核源碼樹(shù)是一伙的,所以驅(qū)動(dòng)安裝時(shí)版本校驗(yàn)不會(huì)出錯(cuò)。
6.5 最簡(jiǎn)單的模塊源碼分析
最簡(jiǎn)單的驅(qū)動(dòng)模塊源碼:
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
// 模塊安裝函數(shù)
static int __init chrdev_init(void)
{
printk(KERN_INFO "chrdev_init helloworld init\n");
return 0;
}
// 模塊卸載函數(shù)
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
}
module_init(chrdev_init); //執(zhí)行這個(gè)宏就相當(dāng)于進(jìn)到模塊安裝函數(shù)里面執(zhí)行函數(shù)
module_exit(chrdev_exit); //執(zhí)行這個(gè)宏就相當(dāng)于進(jìn)到模塊卸載函數(shù)里面執(zhí)行函數(shù)
// MODULE_xxx這種宏作用是用來(lái)添加模塊描述信息
MODULE_LICENSE("GPL"); // 描述模塊的許可證
MODULE_AUTHOR("aston"); // 描述模塊的作者
MODULE_DESCRIPTION("module test"); // 描述模塊的介紹信息
MODULE_ALIAS("alias xxx"); // 描述模塊的別名信息
6.5.1 常用的模塊操作命令
模塊命令功能
lsmod (list module, 將模塊列表顯示) 功能是打印出當(dāng)前內(nèi)核中已經(jīng)安裝過(guò)的模塊列表
insmod(install module,安裝模塊) 功能是向當(dāng)前內(nèi)核中去安裝一個(gè)模塊,用法是insmod xxx.ko
modinfo(module information,模塊信息) 功能是打印出一個(gè)內(nèi)核模塊的自帶信息,用法是modinfo xxx.ko
rmmod(remove module,卸載模塊) 功能是從當(dāng)前內(nèi)核中卸載一個(gè)已經(jīng)安裝了的模塊,用法是rmmod xxx(注意卸載模塊時(shí)只需要輸入模塊名即可,不能加.ko后綴)
modprobe、depmod 暫時(shí)用不到,后面再說(shuō)
6.5.2 模塊的安裝
(1) 先lsmod再insmod看安裝前后系統(tǒng)內(nèi)模塊記錄。實(shí)踐測(cè)試標(biāo)明內(nèi)核會(huì)將最新安裝的模塊放在lsmod顯示的最前面。
(2) insmod與module_init宏。模塊源代碼中用module_init宏聲明了一個(gè)函數(shù)(在我們這個(gè)例子里是chrdev_init函數(shù)),作用就是指定chrdev_init這個(gè)函數(shù)和insmod命令綁定起來(lái),也就是說(shuō)當(dāng)我們insmod module_test.ko時(shí),insmod命令內(nèi)部實(shí)際執(zhí)行的操作就是幫我們調(diào)用chrdev_init函數(shù)。照此分析,那insmod時(shí)就應(yīng)該能看到chrdev_init中使用printk打印出來(lái)的一個(gè)chrdev_init字符串,但是實(shí)際沒(méi)看到。原因是ubuntu中攔截了,要怎么才能看到呢?在ubuntu中使用dmesg命令就可以看到了。
(3) 模塊安裝時(shí)insmod內(nèi)部除了調(diào)用module_init宏所聲明的函數(shù)外,實(shí)際還做了一些別的事(譬如lsmod能看到多了一個(gè)模塊也是insmod在內(nèi)部做了記錄),但是我們就不用管了。
6.5.3 模塊的版本信息
- 使用modinfo查看模塊的版本信息
- 內(nèi)核zImage中也有一個(gè)確定的版本信息
- insmod時(shí)模塊的vermagic必須和內(nèi)核的相同,否則不能安裝,報(bào)錯(cuò)信息為:insmod: ERROR: could not insert module module_test.ko: Invalid module format
- 模塊的版本信息是為了保證模塊和內(nèi)核的兼容性,是一種安全措施
- 如何保證模塊的vermagic和內(nèi)核的vermagic一致?編譯模塊的內(nèi)核源碼樹(shù)就是編譯正在運(yùn)行的這個(gè)內(nèi)核的那個(gè)內(nèi)核源碼樹(shù)即可。說(shuō)白了就是模塊和內(nèi)核要同出一門。
6.5.4 模塊卸載
- module_exit和rmmod的對(duì)應(yīng)關(guān)系
- lsmod查看rmmod前后系統(tǒng)的模塊記錄變化
6.5.5 模塊中常用宏
宏名 | 作用 |
---|---|
MODULE_LICENSE(“GPL”) | 描述模塊的許可證 |
MODULE_AUTHOR(“aston”) | 描述模塊的作者 |
MODULE_DESCRIPTION(“module test”) | 描述模塊的介紹信息 |
MODULE_ALIAS(“alias xxx”) | 描述模塊的別名信息 |
注:模塊的許可證,一般聲明為GPL許可證,而且最好不要少,否則可能會(huì)出現(xiàn)莫名其妙的錯(cuò)誤(譬如一些明顯存在的函數(shù)提升找不到)。
6.5.6 函數(shù)修飾符
- __init,本質(zhì)上是個(gè)宏定義,在內(nèi)核源代碼中就有#define __init xxxx。這個(gè)__init的作用就是將被他修飾的函數(shù)放入.init.text段中去(本來(lái)默認(rèn)情況下函數(shù)是被放入.text段中)。整個(gè)內(nèi)核中的所有的這類函數(shù)都會(huì)被鏈接器鏈接放入.init.text段中,所以所有的內(nèi)核模塊的__init修飾的函數(shù)其實(shí)是被統(tǒng)一放在一起的。內(nèi)核啟動(dòng)時(shí)統(tǒng)一會(huì)加載.init.text段中的這些模塊安裝函數(shù),加載完后就會(huì)把這個(gè)段給釋放掉以節(jié)省內(nèi)存。2)
- __exit原理也一樣。
6.5.7 printk函數(shù)詳解
printk是內(nèi)核態(tài)信息打印函數(shù),功能和比標(biāo)準(zhǔn)C庫(kù)的printf類似。
函數(shù)原型:int printk(const char *fmt, …)
消息打印級(jí)別:fmt----消息級(jí)別:不同級(jí)別使用不同字符串表示,數(shù)字越小,級(jí)別越高
#define KERN_EMERG “<0>” 用于緊急消息, 常常是那些崩潰前的消息.
#define KERN_ALERT “<1>” 需要立刻動(dòng)作的情形.
#define KERN_CRIT “<2>” 嚴(yán)重情況, 常常與嚴(yán)重的硬件或者軟件失效有關(guān).
#define KERN_ERR “<3>” 用來(lái)報(bào)告錯(cuò)誤情況; 設(shè)備驅(qū)動(dòng)常常使用 KERN_ERR 來(lái)報(bào)告硬件故障.
#define KERN_WARNING “<4>” 有問(wèn)題的情況的警告, 這些情況自己不會(huì)引起系統(tǒng)的嚴(yán)重問(wèn)題.
#define KERN_NOTICE “<5>” 正常情況, 但是仍然值得注意. 在這個(gè)級(jí)別一些安全相關(guān)的情況會(huì)報(bào)告.
#define KERN_INFO “<6>” 信息型消息. 在這個(gè)級(jí)別, 很多驅(qū)動(dòng)在啟動(dòng)時(shí)打印它們發(fā)現(xiàn)的硬件的信息.
#define KERN_DEBUG “<7>” 用作調(diào)試消息.
(1)printk在內(nèi)核源碼中用來(lái)打印信息的函數(shù),用法和printf非常相似。
(2)printk和printf最大的差別:printf是C庫(kù)函數(shù),是在應(yīng)用層編程中使用的,不能在linux內(nèi)核源代碼中使用;printk是linux內(nèi)核源代碼中自己封裝出來(lái)的一個(gè)打印函數(shù),是內(nèi)核源碼中的一個(gè)普通函數(shù),只能在內(nèi)核源碼范圍內(nèi)使用,不能在應(yīng)用編程中使用。
(3)printk相比printf來(lái)說(shuō)還多了個(gè):打印級(jí)別的設(shè)置。printk的打印級(jí)別是用來(lái)控制printk打印的這條信息是否在終端上顯示的。應(yīng)用程序中的調(diào)試信息要么全部打開(kāi)要么全部關(guān)閉,一般用條件編譯來(lái)實(shí)現(xiàn)(DEBUG宏),但是在內(nèi)核中,因?yàn)閮?nèi)核非常龐大,打印信息非常多,有時(shí)候整體調(diào)試內(nèi)核時(shí)打印信息要么太多找不到想要的要么一個(gè)沒(méi)有沒(méi)法調(diào)試。所以才有了打印級(jí)別這個(gè)概念。
(4)操作系統(tǒng)的命令行中也有一個(gè)打印信息級(jí)別屬性,值為0-7。當(dāng)前操作系統(tǒng)中執(zhí)行printk的時(shí)候會(huì)去對(duì)比printk中的打印級(jí)別和我的命令行中設(shè)置的打印級(jí)別,小于我的命令行設(shè)置級(jí)別的信息會(huì)被放行打印出來(lái),大于的就被攔截的。
可以用cat /proc/sys/kernel/printk查看打印級(jí)別;
可以用echo 4 /proc/sys/kernel/printk來(lái)設(shè)置打印級(jí)別為4
譬如我的ubuntu中的打印級(jí)別默認(rèn)是4,那么printk中設(shè)置的級(jí)別比4小的就能打印出來(lái),比4大的就不能打印出來(lái)。
(5)ubuntu中這個(gè)printk的打印級(jí)別控制沒(méi)法實(shí)踐,ubuntu中不管你把級(jí)別怎么設(shè)置都不能直接打印出來(lái),必須dmesg命令去查看。
6.5.8 關(guān)于驅(qū)動(dòng)模塊中的頭文件
驅(qū)動(dòng)源代碼中包含的頭文件和原來(lái)應(yīng)用編程程序中包含的頭文件不是一回事。應(yīng)用編程中包含的頭文件是應(yīng)用層的頭文件,是應(yīng)用程序的編譯器帶來(lái)的(譬如gcc的頭文件路徑在 /usr/include下,這些東西是和操作系統(tǒng)無(wú)關(guān)的)。驅(qū)動(dòng)源碼屬于內(nèi)核源碼的一部分,驅(qū)動(dòng)源碼中的頭文件其實(shí)就是內(nèi)核源代碼目錄下的include目錄下的頭文件。所以包含頭文件時(shí)要像#include <linux/module.h> ,#include <linux/init.h> 一樣找到相應(yīng)路徑的頭文件再包含。
6.5.9 驅(qū)動(dòng)編譯的Makefile分析
makefile源碼
#ubuntu的內(nèi)核源碼樹(shù),如果要編譯在ubuntu中安裝的模塊就打開(kāi)這2個(gè)
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build
#開(kāi)發(fā)板的linux內(nèi)核的源碼樹(shù)目錄
KERN_DIR = /root/driver/kernel
obj-m += module_test.o
all:
make -C $(KERN_DIR) M=`pwd` modules
cp:
cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
(1)KERN_DIR,變量的值就是用來(lái)編譯這個(gè)模塊的內(nèi)核源碼樹(shù)的目錄,分為在Ubuntu中和開(kāi)發(fā)板中兩種
(2)obj-m += module_test.o,表示我們要將module_test.c文件編譯成一個(gè)模塊
(3)make -C $(KERN_DIR) M=pwd modules 這個(gè)命令用來(lái)實(shí)際編譯模塊,工作原理就是:利用make -C進(jìn)入到我們指定的內(nèi)核源碼樹(shù)目錄下,然后在源碼目錄樹(shù)下借用內(nèi)核源碼中定義的模塊編譯規(guī)則去編譯這個(gè)模塊,編譯完成后把生成的文件還拷貝到當(dāng)前目錄下,完成編譯。
(4)make clean ,用來(lái)清除編譯痕跡
總結(jié):模塊的makefile非常簡(jiǎn)單,本身并不能完成模塊的編譯,而是通過(guò)make -C進(jìn)入到內(nèi)核源碼樹(shù)下,借用內(nèi)核源碼的體系來(lái)完成模塊的編譯鏈接的。這個(gè)Makefile本身是非常模式化的,3和4部分是永遠(yuǎn)不用動(dòng)的,只有1和2需要?jiǎng)?。?nèi)核源碼樹(shù)的目錄,必須根據(jù)自己的編譯環(huán)境確定的。
參考資料
Makefile文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-702841.html
注:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-702841.html
- module_param(參考文末鏈接) // 即使文件已經(jīng)定義參數(shù)值,參數(shù)傳遞會(huì)覆蓋已經(jīng)定義的。
- 模塊化設(shè)計(jì),自己寫好的代碼按功能單獨(dú)封裝在一個(gè)KO里面,EXPORT_SYMBOL之后就將函數(shù)放到了全局符號(hào)表(一個(gè)鏈?zhǔn)浇Y(jié)構(gòu)),內(nèi)核可以找到相應(yīng)的函數(shù)。其他模塊可以調(diào)用它實(shí)現(xiàn)代碼的共用。有點(diǎn)類似于動(dòng)態(tài)庫(kù)。
到了這里,關(guān)于1. 驅(qū)動(dòng)開(kāi)發(fā)--基礎(chǔ)知識(shí)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!