1-驅動

驅動:
  • 必做實驗一、二、四、五、十一
十天:
  • 模塊、字符設備框架以及接口、led驅動
  • platform總線 原子操作 自旋鎖 信號量 IO模型
外設驅動:按鍵驅動、蜂鳴器驅動、ADC、I2C、輸入子系統
學習驅動時需要的基礎:
  • 1、驅動接口的理解 %50
  • 2、操作系統內核機制 %30
  • 3、硬件 %20
第一天的重點內容:模塊、字符設備框架
什麼是驅動?
  • driver駕駛員.在內核中提供的一系列接口來操作硬件
現有內核中我們通常以模塊的方式寫驅動。
  • 1、什麼是模塊 內核中可以隨時添加和刪除的一部分代碼
  • 2、爲什麼要用模塊 使用靈活方便、可以規避版權
  • 3、模塊和應用程序什麼區別?
應用程序 模塊
運行空間 用戶空間
入口 main
調用的接口 c庫或者系統調用
釋放空間 自動釋放

* 系統調用的源代碼處於內核空間,但是千萬不要說系統調用是內核函數

  • 瞭解:內核中可以使用模塊的部分包括——驅動、文件系統、網絡協議棧
如何去操作一個驅動模塊?
模塊的三要素:
  • 規避版權的宏
  • 加載函數
  • 卸載函數
  • 如何規避版權?MODULE_LICENSE(“GPL”) 規避GPL版權。如果不使用這個宏也可以編譯和執行,但是內核會抱怨(你玷污了內核)。
加載函數:
  • 1、自定義加載函數
  • int 函數名(void) 建議函數名以_init結尾
  • 這種情況下如果要想被內核調用還需要使用一個內核提供的接口: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)));給默認加載函數取別名爲initfn
module_init(initfn);告訴系統內核我們的自定義的模塊入口爲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

假設module_init(hello_init) <==> static initcall_t __initcall_hello_init6 __used __attribute__((__section__(.initcall6.init))) = hello_init
最終的結果的作用是通過initcall_t類型定義了一個變量__initcall_hello_init6 __used __attribute__((__section__(.initcall6.init))),同時這個變量被賦值爲hello_init
上面的變量最終編譯後會被放到.initcall6.init這個代碼分段中。
進入到arch/arm/kernel/vmlinux.lds來尋找上面的分段
內核在什麼時候調用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, 這個符號就是我們的加載函數所在分段的起始地址
733     __initcall7_start,
734     __initcall_end,
735
    };
  • b、默認加載函數
    • int init_module(void)
  • 2、卸載函數
    • 自定義的卸載函數 void 函數名(void) 建議函數名以_exit結尾
  • 調用module_exit(自定義卸載函數名)來告訴內核我們自定的函數是模塊的出口
  • 默認的卸載函數 void cleanup_module(void)
  • 注意:在內核中形參爲void不能省略

  • 自己寫一個模塊程序熟悉流程。

  • make tags 產生tags文件用於我們查看內核中的函數或者宏
    • 1、自定義模塊的入口函數
    • 2、自定義模塊的出口函數
    • 3、告訴內核我們的入口和出口是哪些自定義函數 module_init module_exit
    • 4、規避版權 MODULE_LICENSE
驅動程序的編譯:
  • 1、直接將驅動程序放到內核中指定的文件夾中,將驅動的二進制內容添加到uImage文件中
    操作流程:寫好一個驅動程序,拷貝到drivers/char目錄下
    • 修改drivers/char/Kconfig,添加:
    • config HELLO
    • tristate “my first driver hello”
  • 修改drivers/char/Makefile,在最後一行添加obj-$(CONFIG_HELLO) += hello.o
  • 回到頂層目錄執行make menuconfig,找到my first driver hello這個選項,選中爲*
  • make uImage
  • cp arch/arm/boot/uImage /tftpboot然後啓動開發板,如果驅動加載成功會在串口終端上看見hello init success
2、如果直接將驅動編譯到uImage文件中,無論這個驅動使用不使用都會佔用內存空間,所以爲了節省空間我們通常選擇編譯成模塊
  • 編譯成模塊又分成兩種方法:
  • 第一種:內部編譯
    • a、將驅動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、在頂層目錄執行make modules 默認在drivers/char目錄下生成一個hello.ko的文件(這個文件模塊文件)
  • f、拷貝到rootfs目錄下,然後去掛載開發板
  • g、在開發板上執行insmod hello.ko
模塊文件名:hello.ko
  • 模塊名:hello
  • 模塊的命令:

    • insmod 模塊文件名 作用爲模塊文件在內核中分配空間
    • 例子:insmod hello.ko
    • 查看驅動的打印信息:dmesg如果執行成功會打印hello init success

    • rmmod 模塊名 作用是將模塊從內核中釋放掉

    • dmesg 會打印hello exit success
    • 例如:rmmod hello
  • 第二種:外部編譯(比較常用的方式,但這種方式比較難理解)
  • 我們的驅動程序不需要拷貝到內核源碼目錄下,任意存放就可以
  • 要想寫一個外部編譯的Makefile先了解一個文件:/lib/modules/3.5.0-23-generic/build,這個文件是一個軟連接文件。
  • 通過軟連接build查看到它的源路徑爲/usr/src/linux-headers-3.5.0-23-generic,這文件夾相當於是內核源碼的頂層目錄。
  • 在/usr/src/linux-headers-3.5.0-23-generic目錄下有一個Makefile文件,
  • 文件的1181行有一句話1181 # make M=dir modules 在編譯模塊時需要用M=模塊的絕對路徑,其中M不能變
$(shell uname -r) 這裏的shell是Makefile的一個函數,作用就是在Makefile調用shell命令
uname -r 顯示當前操作系統的內核版本
驗證過程:
sudo dmesg -c 清除內核緩存區中的信息
sudo insmod hello.ko
dmesg

sudo rmmod hello
dmesg
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章