1. 程式人生 > 實用技巧 >製作第一個Linux驅動程式

製作第一個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(struct
file *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機制使能時才能正常使用。