如何編寫一個簡單的Linux驅動(一)

前言

  最近在學習Linux驅動,記錄下自己學習的歷程。

驅動的基本框架

  Linux驅動的基本框架包含兩部分,「模組入口、出口的註冊」和「模組入口、出口函數的實現」,如下方程式碼。  

 1 static int __init shanwuyan_init(void)    //驅動入口函數
 2 {
 3     return 0;
 4 }
 5 
 6 static void __exit shanwuyan_exit(void)    //驅動出口函數
 7 {
 8 
 9 }
10 
11 module_init(shanwuyan_init);    //註冊入口函數
12 module_exit(shanwuyan_exit);    //註冊出口函數

  其中,module_init()和module_exit()兩個函數的作用是註冊驅動的入口「shanwuyan_init」和出口「shanwuyan_exit」。載入驅動時會運行入口函數,卸載驅動時會運行出口函數。入口函數的作用是載入驅動時做一些初始化工作,比如註冊設備、申請設備號、生成設備節點等等,其返回值為int類型;出口函數的作用是卸載驅動時做一些善後操作,比如註銷設備、註銷設備號、銷毀類等等。

一個基本驅動的編寫

  本文的主要目的是讓讀者了解驅動的基本框架,我們先不實現註冊設備、申請設備號、註銷設備等複雜的工作。

  為了讓驅動的載入和卸載工作更直觀得為程式設計師所觀察,我們可以在入口函數和出口函數中添加列印語句,這樣每次載入和卸載驅動的時候,程式設計師都能在終端觀察到相應的資訊,如下方程式碼。  

 1 static int __init shanwuyan_init(void)    //驅動入口函數
 2 {
 3     printk(KERN_EMERG "shanwuyan_init\r\n");
 4     return 0;
 5 }
 6 
 7 static void __exit shanwuyan_exit(void)    //驅動出口函數
 8 {
 9     printk(KERN_EMERG "shanwuyan_exit\r\n");
10 }

  「printk」函數是什麼?說到列印,有C語言基礎的讀者首先想到的可能就是「printf」函數,但是「printf」只能在應用層面工作,而設備驅動是工作在內核態下的,所以「printf」不能在設備驅動中工作。在內核態下的列印函數是「printk」函數。KERN_EMERG是列印優先順序,這裡採用了最高優先順序。

  再加上頭文件以及註冊用的函數,可以得到一個相對完整的程式碼。    

 1 /* 源程式碼文件名為:shanwuyan.c */
 2 #include <linux/module.h>
 3 #include <linux/kernel.h>
 4 #include <linux/init.h>
 5 #include <linux/fs.h>
 6 #include <linux/uaccess.h>
 7 
 8 static int __init shanwuyan_init(void)    //驅動入口函數
 9 {
10     printk(KERN_EMERG "shanwuyan_init\r\n");
11     return 0;
12 }
13 
14 static void __exit shanwuyan_exit(void)    //驅動出口函數
15 {
16     printk(KERN_EMERG "shanwuyan_exit\r\n");
17 }
18 
19 module_init(shanwuyan_init);    //註冊入口函數
20 module_exit(shanwuyan_exit);    //註冊出口函數

  該設備驅動實現的功能是:載入驅動時列印字元串「shanwuyan_init」,卸載驅動時列印字元串「shanwuyan_exit」。

Makefile文件的編寫

  Makefile文件沒什麼可說的,程式碼如下。  

 1 #!/bin/bash
 2 
 3 obj-m += shanwuyan.o    #此處要和你的驅動源文件同名
 4 
 5 KDIR := /home/topeet/Android/iTop4412_Kernel_3.0    #這裡是你的內核目錄
 6 
 7 PWD ?= $(shell pwd)
 8 
 9 all:
10     make -C $(KDIR) M=$(PWD) modules    #make操作
11 
12 clean:
13     make -C $(KDIR) M=$(PWD) clean    #make clean操作

應用

  編譯,並載入生成的「shanwuyan.ko」文件,載入驅動和卸載驅動的命令如下。  

1 insmod shanwuyan.ko #載入驅動
2 rmmod shanwuyan.ko #卸載驅動,如果該命令不起作用,請用下方的命令
3 rmmod shanwuyan    #卸載驅動

  進入到驅動文件所在的路徑下,並在命令行輸入載入驅動的命令「insmod shanwuyan.ko」,可以看到驅動入口函數列印出來的字元串資訊「shanwuyan_init」。

  但是終端還列印了兩行警告資訊「shanwuyan: module license ‘unspecified’ taints kernel」和「Disabling lock debugging due to kernel」,這是因為我們沒有在程式碼中加入同意開源協議,所以終端列印該資訊。需要注意的是,該警告資訊只有在系統啟動後第一次載入驅動時才會列印,卸載掉之後,如果不重啟系統,再載入驅動時就不會再列印這兩行警告資訊了。

  打開源文件,加入GPL開源協議,一個完整的基本驅動框架就完成了,全部程式碼如下。

 1 /* 源程式碼文件名為:shanwuyan.c */
 2 #include <linux/module.h>
 3 #include <linux/kernel.h>
 4 #include <linux/init.h>
 5 #include <linux/fs.h>
 6 #include <linux/uaccess.h>
 7 
 8 static int __init shanwuyan_init(void)    //驅動入口函數
 9 {
10     printk(KERN_EMERG "shanwuyan_init\r\n");
11     return 0;
12 }
13 
14 static void __exit shanwuyan_exit(void)    //驅動出口函數
15 {
16     printk(KERN_EMERG "shanwuyan_exit\r\n");
17 }
18 
19 module_init(shanwuyan_init);    //註冊入口函數
20 module_exit(shanwuyan_exit);    //註冊出口函數
21 
22 MODULE_LICENSE("GPL");    //同意GPL開源協議,就不會列印警告資訊了
23 MODULE_AUTHOR("shanwuyan");    //還可以再添加上作者名稱

  再次編譯,重啟系統,並載入驅動,這次不會再列印警告資訊了,只列印了我們在入口函數中寫的字元串,如下圖。

 

  使用「rmmod shanwuyan」命令卸載驅動,出現錯誤,如下圖。  

 

  這是我們需要創建文件夾「/lib/modules」,創建後再次卸載驅動,又出現了錯誤,如下圖。

  我們按照錯誤資訊,創建文件夾「/lib/modules/3.0.15」(根據內核版本的不同而不同),再次卸載驅動,成功,列印出來我們想要的字元串資訊「shanwuyan_exit」。