製作第一個Linux驅動程式
學習目標:
- 編寫一個簡單的Linux驅動程式,實現應用程式能通過系統呼叫,呼叫相應驅動函式,驅動程式中僅列印資訊,不實現一些特定功能
寫第一個驅動程式需要以下幾個步驟:
1)寫出open、read等系統呼叫在進入核心空間中呼叫的對應函式xxx_open、xxx_read
2)定義file_operations型別結構體,用寫好的xxx_open、xxx_read函式填充file_operations結構體成員
3)寫一個xxx_init函式呼叫register_chardev函式註冊填充好的file_operations型別結構體,目的時當呼叫它時將相關資訊告訴核心
4)通過module_init()來修飾xxx_init入口函式
5)寫驅動的xxx_exit出口函式,呼叫這個unregister_chrdev()函式解除安裝掛入核心中的file_operations型別結構體
6)通過module_exit()來修飾出口函式
7)模組許可證宣告, 最常見的是以MODULE_LICENSE( "GPL v2" )來宣告
1、建立一個驅動原始檔first_drv.c
程式碼如下:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include<linux/delay.h> #include <linux/device.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> //#include <asm/arch/regs-gpio.h> //#include <asm/hardware.h> int major;
static int first_drv_open(struct inode *inode, struct file *file); static ssize_t first_drv_read(structfile *file, char __user *buf, size_t count, loff_t *ppos); static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos); struct file_operations first_drv_fileop = { //---->① .owner = THIS_MODULE, /* 這是一個巨集,推向編譯模組時自動建立的__this_module變數 */ .open = first_drv_open, .read = first_drv_read, .write = first_drv_write, };
/*inode表示具體檔案,file結構用來追蹤執行時的資訊 */ static int first_drv_open(struct inode *inode, struct file *file) { printk("first_drv_open\n"); /*核心的列印用printk,而不是printf函式) return 0; } static ssize_t first_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { printk("first_drv_read\n"); return 0; } static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { printk("first_drv_read\n"); return 0; } static int first_drv_init(void) { major = register_chrdev(0, "first_drv", &first_drv_fileop); //---->② return 0; } static void first_drv_exit(void) { unregister_chrdev(major, "first_drv"); //---->③ } module_init(first_drv_init); //---->④ module_exit(first_drv_exit); //---->⑤ MODULE_LICENSE("GPL");
①定義file_operations結構體,並填充相關成員函式,如open時,通過系統呼叫,最終會呼叫到.open函式指標指向函式
②註冊file_operation結構體,將相關資訊告訴核心,只有註冊後file_operations內部成員才有效。register_chrdev函式第一項引數設定為0,讓系統自動分配主裝置號
③將相關資訊從核心中去除
④修飾函式,當執行insmod時,將呼叫first_drv_init函式(此例中,即呼叫register_chrdev函式)
⑤修飾函式,當執行rmmod時,將呼叫first_drv_exit函式(此例中,即呼叫unregister_chrdev函式)
2、建立一個編譯驅動的Makefile
KERN_DIR = /home/book/self_learn/01_linux_develop/02_embeded_dir/02_kernel/linux-3.4.2 #----->① all: make -C $(KERN_DIR) M=`pwd` modules #---->② clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += first_drv.o #----->③
①編譯驅動時依賴核心存放的目錄,核心必須是已經編譯好的
②make -C切換到核心目錄,呼叫核心目錄的Makefile;M='pwd',當用戶需要以核心為基礎編譯一個外部模組,需要將M='pwd'加入其中,表示到驅動的目錄中查詢編譯原始碼,modules是一個引數,就是告訴核心在編譯是將驅動編譯成模組,不編譯進核心
③表示將first_drv.o編譯成模組
3、執行make,編譯生成first_drv.ko
4、將first_drv.ko拷貝到跟檔案系統並載入驅動模組
執行insmod命令將first_drv.ko載入到核心
5、手動建立裝置節點
在註冊file_operations結構體時,我們選擇了讓系統自動分配主裝置號,在建立裝置節點之前,應先獲取系統自動分配的主裝置號。執行cat /proc/devices 命令可以看出系統自動分配主裝置號
系統自動分配的主裝置號時252,後面跟的是裝置名稱,裝置名稱是由register_chrdev傳入的第二個引數決定的。獲取主裝置號之後,執行 mknod /dev/first c 252 0 命令在dev目錄中建立裝置節點
6、編寫應用程式,進行驅動測試
應用程式碼first_drv_test.c:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { int fd; int val = 1; fd = open("/dev/first", O_RDWR); if(fd == -1) { printf("can't open...\n"); exit(EXIT_FAILURE); } read(fd, &val, sizeof(val)); exit(EXIT_SUCCESS); }
執行arm-linux-gcc -o first_drv_test first_drv_test.c編譯測試應該程式,並將編譯成功的應用程式拷貝到網路檔案系統中,執行應用程式
執行成功,由列印結果可以看出,應用程式中的open函式和read函式呼叫了對應驅動函式中的first_open和first_read,第一個驅動程式測試成功!
7、改進first_drv.c驅動程式
由上述操作結果可以看出,每載入一次驅動程式都要手動在/dev目錄中創將相應裝置節點,這樣做太麻煩了。可以使用自動建立裝置節點,Linux有udev、mdev的機制,而我們的ARM開發板上移植的busybox有mdev機制,然後mdev機制會通過/sys目錄中class類來找到相應類的驅動裝置來自動建立裝置節點 (前提需要有mdev)
1)首先建立一個class裝置類,class是C語言中的一個結構體,是軟體開發的一個裝置的高階檢視,它抽象出低階的實現細節,然後在class類下,建立一個class_device,即類下面建立類的裝置:
static struct class *first_drv_class; static struct class_device *first_drv_class_dev;
2)在first_drv_init後面新增如下程式碼
//建立類,它會在sys/class目錄下建立firstdrv_class這個類
firstdrv_class= class_create(THIS_MODULE,"firstdrv"); //在該類下建立裝置 firstdrv_class_devs=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"xyz");
注意:在高版本的linux核心中class_device_create用device_create函式代替
3)同理,在first_drv_exit中新增解除安裝類和裝置的函式,在解除安裝驅動程式後,自動刪除建立的裝置節點
class_device_unregister(firstdrv_class_devs); //登出類裝置,與class_device_create對應 class_destroy(firstdrv_class); //登出類,與class_create對應
4)按照上述過程,重新編譯驅動程式,並將編譯好的驅動程式載入到核心(注意在載入新驅動程式時,先解除安裝之前驅動程式,並刪除手動建立的裝置節點)
由此可見,第一個簡單驅動程式建立成功,值得注意的是,自動建立裝置節點,必須在mdev機制使能時才能正常使用。