驅(qū)動:
- 必做實驗一磅轻、二珍逸、四、五聋溜、十一
十天:
- 模塊谆膳、字符設(shè)備框架以及接口、led驅(qū)動
- platform總線 原子操作 自旋鎖 信號量 IO模型
外設(shè)驅(qū)動:按鍵驅(qū)動撮躁、蜂鳴器驅(qū)動漱病、ADC、I2C把曼、輸入子系統(tǒng)
學(xué)習(xí)驅(qū)動時需要的基礎(chǔ):
- 1杨帽、驅(qū)動接口的理解 %50
- 2、操作系統(tǒng)內(nèi)核機制 %30
- 3嗤军、硬件 %20
第一天的重點內(nèi)容:模塊注盈、字符設(shè)備框架
什么是驅(qū)動?
- driver駕駛員.在內(nèi)核中提供的一系列接口來操作硬件
現(xiàn)有內(nèi)核中我們通常以模塊的方式寫驅(qū)動叙赚。
- 1老客、什么是模塊 內(nèi)核中可以隨時添加和刪除的一部分代碼
- 2、為什么要用模塊 使用靈活方便震叮、可以規(guī)避版權(quán)
- 3胧砰、模塊和應(yīng)用程序什么區(qū)別?
應(yīng)用程序 | 模塊 | |
---|---|---|
運行空間 | 用戶空間 | 內(nèi)核空間 |
入口 | main | 加載函數(shù) |
調(diào)用的接口 | c庫或者系統(tǒng)調(diào)用 | 內(nèi)核函數(shù) |
釋放空間 | 自動釋放 | 必須手動釋放 |
系統(tǒng)調(diào)用的源代碼處于內(nèi)核空間苇瓣,但是千萬不要說系統(tǒng)調(diào)用是內(nèi)核函數(shù)
了解:內(nèi)核中可以使用模塊的部分包括——驅(qū)動尉间、文件系統(tǒng)、網(wǎng)絡(luò)協(xié)議棧
如何去操作一個驅(qū)動模塊?
模塊的三要素:
- 規(guī)避版權(quán)的宏
- 加載函數(shù)
- 卸載函數(shù)
- 如何規(guī)避版權(quán)?MODULE_LICENSE("GPL") 規(guī)避GPL版權(quán)哲嘲。如果不使用這個宏也可以編譯和執(zhí)行贪薪,但是內(nèi)核會抱怨(你玷污了內(nèi)核)。
加載函數(shù):
- 1眠副、自定義加載函數(shù)
- int 函數(shù)名(void) 建議函數(shù)名以_init結(jié)尾
- 這種情況下如果要想被內(nèi)核調(diào)用還需要使用一個內(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)));給默認加載函數(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é)果的作用是通過initcall_t類型定義了一個變量__initcall_hello_init6 __used __attribute__((__section__(.initcall6.init))),同時這個變量被賦值為hello_init
上面的變量最終編譯后會被放到.initcall6.init這個代碼分段中古掏。
進入到arch/arm/kernel/vmlinux.lds來尋找上面的分段
內(nèi)核在什么時候調(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, 這個符號就是我們的加載函數(shù)所在分段的起始地址
733 __initcall7_start,
734 __initcall_end,
735
};
-
b侦啸、默認加載函數(shù)
- int init_module(void)
-
2、卸載函數(shù)
- 自定義的卸載函數(shù) void 函數(shù)名(void) 建議函數(shù)名以_exit結(jié)尾
調(diào)用module_exit(自定義卸載函數(shù)名)來告訴內(nèi)核我們自定的函數(shù)是模塊的出口
默認的卸載函數(shù) void cleanup_module(void)
注意:在內(nèi)核中形參為void不能省略
自己寫一個模塊程序熟悉流程丧枪。
-
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ū)動程序的編譯:
- 1、直接將驅(qū)動程序放到內(nèi)核中指定的文件夾中恋博,將驅(qū)動的二進制內(nèi)容添加到uImage文件中
操作流程:寫好一個驅(qū)動程序齐佳,拷貝到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這個選項债沮,選中為*
- make uImage
- cp arch/arm/boot/uImage /tftpboot然后啟動開發(fā)板炼吴,如果驅(qū)動加載成功會在串口終端上看見hello init success
2、如果直接將驅(qū)動編譯到uImage文件中疫衩,無論這個驅(qū)動使用不使用都會占用內(nèi)存空間硅蹦,所以為了節(jié)省空間我們通常選擇編譯成模塊
- 編譯成模塊又分成兩種方法:
- 第一種:內(nèi)部編譯
- a、將驅(qū)動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 將我們添加的選項選為M
- e假褪、在頂層目錄執(zhí)行make modules 默認在drivers/char目錄下生成一個hello.ko的文件(這個文件模塊文件)
- f、拷貝到rootfs目錄下,然后去掛載開發(fā)板
- g近顷、在開發(fā)板上執(zhí)行insmod hello.ko
模塊文件名:hello.ko
- 模塊名:hello
- 模塊的命令:
insmod 模塊文件名 作用為模塊文件在內(nèi)核中分配空間
例子:insmod hello.ko
查看驅(qū)動的打印信息:dmesg如果執(zhí)行成功會打印hello init success
rmmod 模塊名 作用是將模塊從內(nèi)核中釋放掉
dmesg 會打印hello exit success
例如:rmmod hello
- 第二種:外部編譯(比較常用的方式生音,但這種方式比較難理解)
- 我們的驅(qū)動程序不需要拷貝到內(nèi)核源碼目錄下,任意存放就可以
- 要想寫一個外部編譯的Makefile先了解一個文件:/lib/modules/3.5.0-23-generic/build,這個文件是一個軟連接文件幕庐。
- 通過軟連接build查看到它的源路徑為/usr/src/linux-headers-3.5.0-23-generic,這文件夾相當(dāng)于是內(nèi)核源碼的頂層目錄久锥。
- 在/usr/src/linux-headers-3.5.0-23-generic目錄下有一個Makefile文件,
- 文件的1181行有一句話1181 # make M=dir modules 在編譯模塊時需要用M=模塊的絕對路徑,其中M不能變
$(shell uname -r) 這里的shell是Makefile的一個函數(shù)异剥,作用就是在Makefile調(diào)用shell命令
uname -r 顯示當(dāng)前操作系統(tǒng)的內(nèi)核版本
驗證過程:
sudo dmesg -c 清除內(nèi)核緩存區(qū)中的信息
sudo insmod hello.ko
dmesg
sudo rmmod hello
dmesg