驅(qū)動(dòng):
- 必做實(shí)驗(yàn)一、二、四、五、十一
十天:
- 模塊、字符設(shè)備框架以及接口、led驅(qū)動(dòng)
- platform總線 原子操作 自旋鎖 信號(hào)量 IO模型
外設(shè)驅(qū)動(dòng):按鍵驅(qū)動(dòng)、蜂鳴器驅(qū)動(dòng)、ADC、I2C、輸入子系統(tǒng)
學(xué)習(xí)驅(qū)動(dòng)時(shí)需要的基礎(chǔ):
- 1、驅(qū)動(dòng)接口的理解 %50
- 2、操作系統(tǒng)內(nèi)核機(jī)制 %30
- 3、硬件 %20
第一天的重點(diǎn)內(nèi)容:模塊、字符設(shè)備框架
什么是驅(qū)動(dòng)?
- driver駕駛員.在內(nèi)核中提供的一系列接口來(lái)操作硬件
現(xiàn)有內(nèi)核中我們通常以模塊的方式寫驅(qū)動(dòng)。
- 1、什么是模塊 內(nèi)核中可以隨時(shí)添加和刪除的一部分代碼
- 2、為什么要用模塊 使用靈活方便、可以規(guī)避版權(quán)
- 3、模塊和應(yīng)用程序什么區(qū)別?
| 應(yīng)用程序 | 模塊 | |
|---|---|---|
| 運(yùn)行空間 | 用戶空間 | 內(nèi)核空間 |
| 入口 | main | 加載函數(shù) |
| 調(diào)用的接口 | c庫(kù)或者系統(tǒng)調(diào)用 | 內(nèi)核函數(shù) |
| 釋放空間 | 自動(dòng)釋放 | 必須手動(dòng)釋放 |
系統(tǒng)調(diào)用的源代碼處于內(nèi)核空間,但是千萬(wàn)不要說(shuō)系統(tǒng)調(diào)用是內(nèi)核函數(shù)
了解:內(nèi)核中可以使用模塊的部分包括——驅(qū)動(dòng)、文件系統(tǒng)、網(wǎng)絡(luò)協(xié)議棧
如何去操作一個(gè)驅(qū)動(dòng)模塊?
模塊的三要素:
- 規(guī)避版權(quán)的宏
- 加載函數(shù)
- 卸載函數(shù)
- 如何規(guī)避版權(quán)?MODULE_LICENSE("GPL") 規(guī)避GPL版權(quán)。如果不使用這個(gè)宏也可以編譯和執(zhí)行,但是內(nèi)核會(huì)抱怨(你玷污了內(nèi)核)。
加載函數(shù):
- 1、自定義加載函數(shù)
- int 函數(shù)名(void) 建議函數(shù)名以_init結(jié)尾
- 這種情況下如果要想被內(nèi)核調(diào)用還需要使用一個(gè)內(nèi)核提供的接口:module_init();
vim -t module_init 選擇5
297 #define module_init(initfn) \
298 static inline initcall_t __inittest(void) \
299 { return initfn; } \
300 int init_module(void) __attribute__((alias(#initfn)));
135 typedef int (*initcall_t)(void); <==> typedef int (*)(void) initcall_t
int init_module(void) __attribute__((alias(#initfn)));給默認(rèn)加載函數(shù)取別名為initfn
module_init(initfn);告訴系統(tǒng)內(nèi)核我們的自定義的模塊入口為initfn
vim -t module_init 選擇4
266 #define module_init(x) __initcall(x);
212 #define __initcall(fn) device_initcall(fn)
207 #define device_initcall(fn) __define_initcall(fn, 6)
176 #define __define_initcall(fn, id) \
177 static initcall_t __initcall_##fn##id __used \
178 __attribute__((__section__(".initcall" #id ".init"))) = fn
假設(shè)module_init(hello_init) <==> static initcall_t __initcall_hello_init6 __used __attribute__((__section__(.initcall6.init))) = hello_init
最終的結(jié)果的作用是通過(guò)initcall_t類型定義了一個(gè)變量__initcall_hello_init6 __used __attribute__((__section__(.initcall6.init))),同時(shí)這個(gè)變量被賦值為hello_init
上面的變量最終編譯后會(huì)被放到.initcall6.init這個(gè)代碼分段中。
進(jìn)入到arch/arm/kernel/vmlinux.lds來(lái)尋找上面的分段
內(nèi)核在什么時(shí)候調(diào)用module_init();?
- a、init/main.c中的start_kernel();
- ==> rest_init();
- ==>kernel_init
- ==> kernel_init_freeable()
- ==> do_basic_setup();
- ==>do_initcalls()
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1;level++)
725 static initcall_t *initcall_levels[] __initdata = {
726 __initcall0_start,
727 __initcall1_start,
728 __initcall2_start,
729 __initcall3_start,
730 __initcall4_start,
731 __initcall5_start,
732 __initcall6_start, 這個(gè)符號(hào)就是我們的加載函數(shù)所在分段的起始地址
733 __initcall7_start,
734 __initcall_end,
735
};
-
b、默認(rèn)加載函數(shù)
- int init_module(void)
-
2、卸載函數(shù)
- 自定義的卸載函數(shù) void 函數(shù)名(void) 建議函數(shù)名以_exit結(jié)尾
調(diào)用module_exit(自定義卸載函數(shù)名)來(lái)告訴內(nèi)核我們自定的函數(shù)是模塊的出口
默認(rèn)的卸載函數(shù) void cleanup_module(void)
注意:在內(nèi)核中形參為void不能省略
自己寫一個(gè)模塊程序熟悉流程。
-
make tags 產(chǎn)生tags文件用于我們查看內(nèi)核中的函數(shù)或者宏
- 1、自定義模塊的入口函數(shù)
- 2、自定義模塊的出口函數(shù)
- 3、告訴內(nèi)核我們的入口和出口是哪些自定義函數(shù) module_init module_exit
- 4、規(guī)避版權(quán) MODULE_LICENSE
驅(qū)動(dòng)程序的編譯:
- 1、直接將驅(qū)動(dòng)程序放到內(nèi)核中指定的文件夾中,將驅(qū)動(dòng)的二進(jìn)制內(nèi)容添加到uImage文件中
操作流程:寫好一個(gè)驅(qū)動(dòng)程序,拷貝到drivers/char目錄下- 修改drivers/char/Kconfig,添加:
- config HELLO
- tristate "my first driver hello"
- 修改drivers/char/Makefile,在最后一行添加obj-$(CONFIG_HELLO) += hello.o
- 回到頂層目錄執(zhí)行make menuconfig,找到my first driver hello這個(gè)選項(xiàng),選中為*
- make uImage
- cp arch/arm/boot/uImage /tftpboot然后啟動(dòng)開發(fā)板,如果驅(qū)動(dòng)加載成功會(huì)在串口終端上看見hello init success
2、如果直接將驅(qū)動(dòng)編譯到uImage文件中,無(wú)論這個(gè)驅(qū)動(dòng)使用不使用都會(huì)占用內(nèi)存空間,所以為了節(jié)省空間我們通常選擇編譯成模塊
- 編譯成模塊又分成兩種方法:
- 第一種:內(nèi)部編譯
- a、將驅(qū)動(dòng)hello.c放在drivers/char目錄下
- b、修改Kconfig文件
- config HELLO
- tristate "my first driver hello"
- c、修改drivers/char/Makefile,在最后一行添加obj-$(CONFIG_HELLO) += hello.o
- d、回到頂層目錄make menuconfig 將我們添加的選項(xiàng)選為M
- e、在頂層目錄執(zhí)行make modules 默認(rèn)在drivers/char目錄下生成一個(gè)hello.ko的文件(這個(gè)文件模塊文件)
- f、拷貝到rootfs目錄下,然后去掛載開發(fā)板
- g、在開發(fā)板上執(zhí)行insmod hello.ko
模塊文件名:hello.ko
- 模塊名:hello
- 模塊的命令:
insmod 模塊文件名 作用為模塊文件在內(nèi)核中分配空間
例子:insmod hello.ko
查看驅(qū)動(dòng)的打印信息:dmesg如果執(zhí)行成功會(huì)打印hello init success
rmmod 模塊名 作用是將模塊從內(nèi)核中釋放掉
dmesg 會(huì)打印hello exit success
例如:rmmod hello
- 第二種:外部編譯(比較常用的方式,但這種方式比較難理解)
- 我們的驅(qū)動(dòng)程序不需要拷貝到內(nèi)核源碼目錄下,任意存放就可以
- 要想寫一個(gè)外部編譯的Makefile先了解一個(gè)文件:/lib/modules/3.5.0-23-generic/build,這個(gè)文件是一個(gè)軟連接文件。
- 通過(guò)軟連接build查看到它的源路徑為/usr/src/linux-headers-3.5.0-23-generic,這文件夾相當(dāng)于是內(nèi)核源碼的頂層目錄。
- 在/usr/src/linux-headers-3.5.0-23-generic目錄下有一個(gè)Makefile文件,
- 文件的1181行有一句話1181 # make M=dir modules 在編譯模塊時(shí)需要用M=模塊的絕對(duì)路徑,其中M不能變
$(shell uname -r) 這里的shell是Makefile的一個(gè)函數(shù),作用就是在Makefile調(diào)用shell命令
uname -r 顯示當(dāng)前操作系統(tǒng)的內(nèi)核版本
驗(yàn)證過(guò)程:
sudo dmesg -c 清除內(nèi)核緩存區(qū)中的信息
sudo insmod hello.ko
dmesg
sudo rmmod hello
dmesg