ch8: 裝置驅動程式的基本知識 === ###### tags: `TWLKH` `Linux kernel` `Linux` >小弟不是電類的,若以下筆記有錯誤歡迎任何指教,謝謝。 環境 ``` ubuntuDistributor ID: Ubuntu Description: Ubuntu 16.04.1 LTS Release: 16.04 Codename: xenial kernel version:4.9.0-999-generic ``` [TOC] # 驅動裝置的基本概念 裝置驅動程式是為了隱藏硬體的複雜性和多變性,幫助使用者能夠簡單的呼叫函式即能完成複雜的工作,並和使用者的程式隔離,避免直接存取核心資料結構和硬體裝置。 >在本章裝置驅動程式(device driver)、可動態載入模組(loadable module)、可動態載入核心模組(loadable kernel device,LKM)、模組(module),通通都是指核心裝置驅動模組(kernel device driver module),因為好像沒有統一的術語。 ## 可動態載入模組 linux提供界面方便使用者在開機後動態的移除或加入模組到核心中,當核心完成啟動後,開始安裝動態模組(loadable module),或是透過script安裝模組,當模組需要使用時,才要求載入模組,那麼動態載入模組有什麼好處呢? 1. 可以任意將模組插入或移除正在運行的裝置 2. 可以直接更新模組,不須重新啟動 3. 嵌入式裝置的容量有限,可以將模組放在其他儲存裝置中 當然我們也可以透過重新編譯核心將驅動裝置直接編譯進核心,或是透過[Chapter 6 : User Space Initialization](https://hackmd.io/s/H1DqYq3bl#)將需要的模組和載入模組的script放進Initial RAM Disk這樣就可以直接在開機時啟動模組。 ## 裝置驅動程式架構 簡略分為兩類 |裝置|字元裝置(character devices)|區塊裝置(block devices)| |:-------|:-------------------------|:--------------------| |讀寫方式|serial|以block為單位,進行讀寫| |舉例|keyboard,arduino|硬碟,隨身碟| ## 裝置驅動程式範例 project tree: ![Uploading file..._ronik77fp]() hello.c ```clike= #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> static int __init hello_init(void) //初始化 { printk(KERN_INFO "Hello world!"); return 0; } static void __exit hello_exit(void) //還原初始化 { printk(KERN_INFO "bye bye world"); } module_init(hello_init); module_exit(hello_exit); MODULE_AUTHOR("splasky"); MODULE_DESCRIPTION("Hello world example!"); MODULE_LICENSE("GPL"); ``` 接下來我們要將寫好的模組加入到kernel中,步驟如下 1. 下載linux kernel source code ``` wget wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.8.13.tar.xz tar Jxvf linux-4.8.13 cd linux 4.8.13 ``` 2. 進入.../driver/char 並建立hello directory,這個資料夾就是我們等等要將我們寫的模組source code放置的位置 ``` cd drivers/char mkdir hello cp hello.c .../drivers/char/hello ``` 3. 在hello中新增編譯hello模組的makefile ``` splasky@splasky-XPS13→ [~/workspace/linux-4.8.13] $ cat drivers/char/hello/Makefile obj-$(CONFIG_HELLO) +=hello.o ``` 4. 在核心組態設定檔中加入選單表示hello模組,可以指定是否內建或是可動態載入核心模組 ``` splasky@splasky-XPS13→ [~/workspace/linux-4.8.13] $ cat drivers/char/Kconfig |head -n 20 # # Character device configuration # menu "Character devices" source "drivers/tty/Kconfig" config HELLO tristate "Enable Hello" default m help Example Hello module ``` 修改完後就可以在menuconfig看見核心組態檔(Kconfig)的編譯選項 ![](https://i.imgur.com/52Aaix5.png) 5. 修改.../drivers/char/Makefile,決定建構時要不要包含此目錄 ``` splasky@splasky-XPS13→ [~/workspace/linux-4.8.13] $ cat drivers/char/Makefile # # Makefile for the kernel character device drivers. # obj-y += mem.o random.o ... obj-$(CONFIG_HELLO) += hello/ ``` 到這邊修改完後我們就可以開始建構驅動程式模組了! ## 建構與安裝驅動程式模組 ``` splasky@splasky-XPS13→ [~/workspace/linux-4.8.13] $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- defconfig *** Default configuration is based on 'multi_v7_defconfig' # # configuration written to .config # splasky@splasky-XPS13→ [~/workspace/linux-4.8.13] $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- modules ...compiling... splasky@splasky-XPS13→ [~/workspace/linux-4.8.13] $ ls drivers/char/hello/ hello.c hello.ko hello.mod.c hello.mod.o hello.o Makefile modules.order Module.symvers ``` hello.ko模組就是我們的模組 >不知道有沒有其他方法可以直接上傳模組到qemu虛擬機裡,或是直接透過qemu掛載模組,小弟這邊是透過NFS掛載於qemu,想知道一般大家都是怎麼做的? >如何掛載NFS可以參考[這裡](https://balau82.wordpress.com/2010/04/27/linux-nfs-root-under-qemu-arm-emulator/) ## 載入模組 接下來我們嘗試在qemu上掛載我們編譯好的hello.ko 首先先更改console的級別才能將log的訊息列印到console上 ``` $ cat /proc/sys/kernel/printk 4 4 1 7 $ cat /proc/sys/kernel/printk 7 4 1 7 ``` 更詳習的規則可以參考[這裡](http://man7.org/linux/man-pages/man2/syslog.2.html) 接著掛載module ``` $ cp mnt/hello.ko /lib/modules/4.8.13/kernel/ $ depmod $ modprobe hello Hello world!$ ``` 就可以發現init的message列印於當前console下 移除已掛載的module也很簡單 ``` modprobe -r hello ``` ## 模組參數 現在讓我們為模組增加參數,讓其可以透過insmod輸入模組參數 ``` splasky@school->[~/workspace/module] (master) 166h10m $ cat src/hello2.c ``` ```clike= #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> static int debug_enable = 0; //宣告靜態變數debug_enable module_param(debug_enable, int, 0); MODULE_PARM_DESC(debug_enable, "Enable module debug mode."); static int __init hello_init(void) { printk(KERN_INFO "debug mode is %s\n", debug_enable ? "enable" : "disable"); return 0; } static void __exit hello_exit(void) { printk(KERN_INFO "bye bye world\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_AUTHOR("splasky"); MODULE_DESCRIPTION("Hello world example!"); MODULE_LICENSE("GPL"); ``` module_param定義於.../include/linux/moduleparam.h中,由modules.h引入,作用是將模組參數向核心模組子系統做註冊 MODULE_PARM_DESC向核心註冊參數說明的字串 接著讓我們看看如何在載入模組時使用引數 ``` splasky@school->[~/workspace/module] (master) 166h26m $ sudo insmod src/hello2.ko debug_enable=1 splasky@school->[~/workspace/module] (master) 166h27m $ dmesg |tail -n 2 [451967.837106] bye bye world [451972.059004] debug mode is enable ``` 也可以直接引入 ``` splasky@school->[~/workspace/module] (master) 166h28m $ sudo insmod src/hello2.ko splasky@school->[~/workspace/module] (master) 166h28m $ dmesg |tail -n 2 [452079.503340] bye bye world [452085.328312] debug mode is disable ``` # 模組工具 模組工具在前一小節已經介紹過,如果你想要更詳細的說明請參考manual ## insmod insmod 接收一個模組的==絕對路徑==,並將模組載入核心 ``` sudo insmod src/hello2.ko ``` ## lsmod 將載入到核新的模組清單印出 ``` splasky@school->[~/workspace/module] (master) 166h28m $ sudo lsmod [sudo] password for splasky: Module Size Used by hello2 16384 0 cdc_acm 36864 0 btrfs 987136 0 ufs 73728 0 gf128mul 16384 1 lrw ... ``` 最右邊的欄位Uesd by表示該模組正在使用中,並列出相依性 ## modprobe modprobe可以幫我們解決模組間相依的問題,舉例來說上例的gf128mul需要lrw模組才能使用,modprobe能幫我解決這個問題,並以正確的順序載入。 ``` modprobe gf128mul ``` 會將lrw與gf128mul都載入 ``` modprobe -r gf128mul ``` 則會將兩個模組都移除 :::info 使用modeprobe載入module時要先將module複製至/lib/modules/$(uname -r)/kernel 下 ::: ## depmod modprobe必須依賴modules.dep來知道模組間的相依關係,而depmod負責為我們產生這個檔案,這個檔案內含一份核心建構系統有啟用的模組清單。 這個檔案放至於/lib/modules/4.4.0-($uname -r)/modules.dep 下 ``` cat /lib/modules/4.4.0-51-generic/modules.dep ``` 格式為 \[模組\]:\[關聯模組\] ## rmmod 移除核心中的模組,不需要路徑,不會移除相依的模組 ``` rmmod hello2 ``` ## modinfo 檢視module的詳細資訊 ``` splasky@school->[~/workspace/module] (master) 167h38m $ modinfo hello2 filename: /lib/modules/4.4.0-51-generic/kernel/hello2.ko license: GPL description: Hello world example! author: splasky srcversion: CE729C455AA625EF69A6DDB depends: vermagic: 4.4.0-51-generic SMP mod_unload modversions parm: debug_enable:Enable module debug mode. (int) ``` # 與驅動程式溝通 接下來我們將介紹如何由module提供介面函式給user space application ## 驅動程式的檔案系統操作函式 ```clike= #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #define DEVICE_NAME "hello3" static int debug_enable = 0; module_param(debug_enable, int, 0); MODULE_PARM_DESC(debug_enable, "Enable module debug mode."); struct file_operations hello_fops; static int hello_open(struct inode *inode, struct file *file) { printk("Hello open: successful\n"); return 0; } static int hello_release(struct inode *inode, struct file *file) { printk("Hello release: successful\n"); return 0; } static ssize_t hello_read(struct file *file, char *buf, size_t count, loff_t *ptr) { printk("Hello read: returning zero bytes\n"); return 0; } static ssize_t hello_write(struct file *file, const char *buf, size_t count, loff_t *ppos) { printk("Hello write: accepting zero bytes\n"); return 0; } static long hello_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { printk("Hello ioctl:cmd=%ud, arg=%lu\n", cmd, arg); return 0; } static int __init hello_init(void) { int ret=0; printk(KERN_INFO "debug mode is %s\n", debug_enable ? "enable" : "disable"); ret = register_chrdev(234, DEVICE_NAME, &hello_fops); if (ret < 0) { printk(KERN_ALERT"ERROR registering hello device\n"); goto hello_fail; } printk(KERN_INFO "Hello module registered successfully!\n"); /* init process here */ return 0; hello_fail: return ret; } static void __exit hello_exit(void) { printk(KERN_INFO "Hello exit\n"); } /* 透過建立一個file operation 來和character裝製做connect */ struct file_operations hello_fops = { owner: THIS_MODULE, read: hello_read, write: hello_write, unlocked_ioctl: hello_ioctl, open: hello_open, release: hello_release, }; module_init(hello_init); module_exit(hello_exit); MODULE_AUTHOR("splasky"); MODULE_DESCRIPTION("Hello world example!"); MODULE_LICENSE("GPL"); ``` ## 裝置節點與mknod 裝置節點是一種特殊的檔案類型,linux用這種檔案來代表裝置,幾乎所有linux distribution都將裝置節點放在/dev下,我們會透過mknod產生裝置節點。 ``` mknod /dev/hello3 c 234 0 ``` mknod \[裝置節點位置\] \[檔案類型\] \[主編號\] \[次編號\] 我們透過裝置節點來跟已安裝的驅動程式做銜接,當程式透過open系統呼叫時,核心會透過主編號(此範例中為234)將驅動程式和裝置節點做連接,作業系統並不理會次編號,會把次編號直接傳給驅動裝置,讓一個驅動程式可以處理多個子裝置。 :::info 裝置節點一般是透過udev產生,後面章節會介紹到,另外本章的範例是透過手動直接把裝置主編號直接指定在source code中,這樣做並不好,應該透過kernel指定一個給驅動程式,詳細作法Linux Device Drivers的第三章有說明到。 ::: >還要再研究 ## 整合為一體 現在我們創立一個use-hello3.c 來運用我們寫的驅動程式 ```clike= #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char *argv[]) { /* our file descriptor */ int fd=0,rc=0; char *rd_buf[16]; printf("%s: entered\n",argv[0]); /* open device */ fd = open("/dev/hello3",O_RDWR); if(fd==-1){ perror("open failed"); rc=fd; exit(-1); } printf("open successful!\n"); /* Issue a read */ rc=read(fd,rd_buf,0); if(rc==-1){ perror("read failed"); close(fd); exit(-1); } printf("read:returning %d bytes!\n",rc); close(fd); return 0; } ``` 最後執行我們的use-hello ``` splasky@splasky-XPS13→ [~/workspace/kernel_module/module] (master) 237h25m $ ls -l /dev/hello* ls: cannot access '/dev/hello*': No such file or directory splasky@splasky-XPS13→ [~/workspace/kernel_module/module] (master) 237h26m $ sudo mknod /dev/hello3 c 234 0 [sudo] password for splasky: splasky@splasky-XPS13→ [~/workspace/kernel_module/module] (master) 237h26m $ sudo insmod src/hello3.ko splasky@splasky-XPS13→ [~/workspace/kernel_module/module] (master) 237h27m $ sudo ./use-hello ./use-hello: entered open successful! read:returning 0 bytes! splasky@splasky-XPS13→ [~/workspace/kernel_module/module] (master) 237h29m $ dmesg | tail -n 8 [ 193.437511] hello3: loading out-of-tree module taints kernel. [ 193.437724] hello3: module verification failed: signature and/or required key missing - tainting kernel [ 193.438665] debug mode is disable [ 193.438678] Hello module registered successfully! [ 204.570530] Hello open: successful [ 204.570534] Hello read: returning zero bytes [ 204.570537] Hello release: successful [ 300.054926] mce: [Hardware Error]: Machine check events logged ``` # GPL https://www.gnu.org/licenses/quick-guide-gplv3.html # 作者推薦的參考書目 [Linux Device Drivers](http://www.oreilly.com.tw/product_linux.php?id=a184):台灣:Linux 驅動程式, 3/e (Linux Device Drivers, 3/e) [Essential Linux Device Drivers](https://www.amazon.com/Essential-Device-Drivers-Sreekrishnan-Venkateswaran/dp/0132396556)有中文版 [Filesystem Hierarchy Standard](https://zh.wikipedia.org/wiki/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84%E6%A0%87%E5%87%86) # 參考資料 [Loda's blog](http://loda.hala01.com/2009/04/linux-%E5%8B%95%E6%85%8B%E8%BC%89%E5%85%A5module%E4%BB%8B%E7%B4%B9/) [The Linux Kernel Module Programming Guide](http://www.tldp.org/LDP/lkmpg/2.6/html/lkmpg.html) [開機流程、模組管理與 Loader](http://linux.vbird.org/linux_basic/0510osloader.php#kernel) >本參考資料中也含有模組工具的使用 [Linux Modules(1.1)module parameters](http://nano-chicken.blogspot.tw/2011/01/linux-modules11module-parameters.html)