【轉】在Linux下寫一個簡單的驅動程序


轉自:https://www.cnblogs.com/amanlikethis/p/4914510.html

  本文首先描述了一個可以實際測試運行的驅動實例,然后由此去討論Linux下驅動模板的要素,以及Linux上應用程序到驅動的執行過程。相信這樣由淺入深、由具體實例到抽象理論的描述更容易初學者入手Linux驅動的大門。

一、一個簡單的驅動程序實例

驅動文件hello.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>

#define    HELLO_MAJOR     231
#define    DEVICE_NAME     "HelloModule"

static int hello_open(struct inode *inode, struct file *file){
    printk(KERN_EMERG "hello open.\n");
    return 0;
}

static ssize_t hello_write(struct file *file, const char __user * buf, size_t count, loff_t *ppos){
    printk(KERN_EMERG "hello write.\n");
    return 0;
}

static struct file_operations hello_flops = {
    .owner  =   THIS_MODULE,
    .open   =   hello_open,     
    .write  =   hello_write,
};

static int __init hello_init(void){
    int ret;
    
    ret = register_chrdev(HELLO_MAJOR,DEVICE_NAME, &hello_flops);
    if (ret < 0) {
      printk(KERN_EMERG DEVICE_NAME " can't register major number.\n");
      return ret;
    }
    printk(KERN_EMERG DEVICE_NAME " initialized.\n");
    return 0;
}

static void __exit hello_exit(void){
    unregister_chrdev(HELLO_MAJOR, DEVICE_NAME);
    printk(KERN_EMERG DEVICE_NAME " removed.\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

  驅動文件主要包括函數hello_open、hello_write、hello_init、hello_exit,測試案例中並沒有賦予驅動模塊具有實際意義的功能,只是通過打印日志的方式告知控制台一些調試信息,這樣我們就可以把握驅動程序的執行過程。

  在使用printk打印的時候,在參數中加上了“KERN_EMERG”可以確保待打印信息輸出到控制台上。由於printk打印分8個等級,等級高的被打印到控制台上,而等級低的卻輸出到日志文件中。

編譯驅動所需的Makefile

ifneq ($(KERNELRELEASE),)
MODULE_NAME = hellomodule
$(MODULE_NAME)-objs := hello.o
obj-m := $(MODULE_NAME).o
else
KERNEL_DIR = /lib/modules/`uname -r`/build
MODULEDIR := $(shell pwd)

.PHONY: modules
default: modules

modules:
    make -C $(KERNEL_DIR) M=$(MODULEDIR) modules

clean distclean:
    rm -f *.o *.mod.c .*.*.cmd *.ko
    rm -rf .tmp_versions
endif

  編譯驅動文件需要一個合適的makefile,因為編譯驅動的時候需要知道內核頭文件,編譯規則等。

測試驅動的上層應用代碼hellotest.c

#include <fcntl.h>
#include <stdio.h>

int main(void)
{
    int fd;
    int val = 1;
    fd = open("/dev/hellodev", O_RDWR);
    if(fd < 0){
        printf("can't open!\n");
    }
    write(fd, &val, 4);
    return 0;
}

  上層測試案例中,首先打開設備文件,然后向設備中寫入數據。如此,則會調用驅動中對應的xxx_open和xxx_write函數,通過驅動程序的打印信息可以判斷是否真的如願執行了對應的函數。

二、驅動實例測試

  測試的方法整體來說就是,編譯驅動和上層測試應用;加載驅動,通過上層應用調用驅動;最后,卸載驅動。

1、編譯驅動

#make

  make命令,直接調用Makefile編譯hello.c,最后會生成“hellomodule.ko”。

2、編譯上層應用

#gcc hellotest.c -o hellotest

  通過這條命令,就能編譯出一個上層應用hellotest。

3、加載驅動

#insmod hellomodule.ko

  insmod加載驅動的時候,會調用函數hello_init(),打印的調試信息如下。

  此外,在"/proc/devices"中可以看到已經加載的模塊。

4、創建節點

  雖然已經加載了驅動hellomodule.ko,而且在/proc/devices文件中也看到了已經加載的模塊HelloModule,但是這個模塊仍然不能被使用,因為在設備目錄/dev目錄下還沒有它對應的設備文件。所以,需要創建一個設備節點。

#mknod /dev/hellodev c 231 0

  在/proc/devices中看到HelloModule模塊的主設備號為231,創建節點的時候就是將設備文件/dev/hellodev與主設備號建立連接。這樣在應用程序操作文件/dev/hellodev的時候,就會定位到模塊HelloModule。

/proc/devices 與 /dev的區別

  • /proc/devices中的設備是驅動程序生成的,它可產生一個major供mknod作為參數。這個文件中的內容顯示的是當前掛載在系統的模塊。當加載驅動HelloModule的時候,並沒有生成一個對應的設備文件來對這個設備進行抽象封裝,以供上層應用訪問。
  • /dev下的設備是通過mknod加上去的,用戶通過此設備名來訪問驅動。我以為可以將/dev下的文件看做是硬件模塊的一個抽象封裝,Linux下所有的設備都以文件的形式進行封裝。

5、上層應用調用驅動

#./hellotest

  hellotest應用程序先打開文件“/dev/hellodev”,然后向此文件中寫入一個變量val。期間會調用底層驅動中的hello_open和hello_write函數,hellotest的運行結果如下所示。

6、卸載驅動

#rmmod hellomodule

  insmod卸載驅動的時候,會調用函數hello_exit(),打印的調試信息如下。

總結一個模塊的操作流程:

  (1)通過insmod命令注冊module

  (2)通過mknod命令在/dev目錄下建立一個設備文件"xxx",並通過主設備號與module建立連接

  (3)應用程序層通過設備文件/dev/xxx對底層module進行操作

三、驅動模板

  從宏觀上把握了驅動程序的框架,然后再從細節上完善驅動的功能,這是開發驅動程序的一般步驟。驅動模板必備要素有頭文件、初始化函數、退出函數、版權信息,常用的擴展要素是增加一些功能函數完善底層驅動的功能。

1、頭文件

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>

init.h     定義了驅動的初始化和退出相關的函數
kernel.h     定義了經常用到的函數原型及宏定義
module.h   定義了內核模塊相關的函數、變量及宏

2、初始化函數

static int __init hello_init(void){
    int ret;
    ret = register_chrdev(HELLO_MAJOR,DEVICE_NAME,&hello_flops);
    if (ret < 0) {
          printk(KERN_EMERG DEVICE_NAME " can't register major number.\n");
          return ret;
    }
    printk(KERN_EMERG DEVICE_NAME " initialized.\n");
    return 0;
}
module_init(hello_init);

  當加載驅動到內核的時候,這個初始化函數就會被自動執行。

  初始化函數顧名思義是用來初始化模塊的,常用的功能是通過register_chrdev來注冊函數。內核分配了一塊內存(數組)專門用來存放字符設備的函數集,register_chrdev函數會在這個數組的HELLO_MAJOR位置將hello_flops中的內容進行填充,也就是將HelloModule的功能函數地址注冊到設備管理內存集中。

  形象的比喻好像是操作系統提供了很多的衣服架,注冊設備就好像是把一個衣服掛到某一個衣服架上。衣服上有許多口袋,就好像每一個模塊有許多功能程序接口。顯然,如果想使用設備的某個功能,就可以先找到對應的衣服架,然后找到相應的口袋,去調用對應的函數,執行動作。

3、退出函數

static void __exit hello_exit(void){
    unregister_chrdev(HELLO_MAJOR, DEVICE_NAME);
    printk(KERN_EMERG DEVICE_NAME " removed.\n");
}
module_exit(hello_exit);

  當卸載驅動的時候,退出函數便會自動執行,完成一些列清楚工作。

  在加載驅動的時候,我們向設備管理內存集中注冊了該模塊的相關功能函數。當卸載驅動的時候,就有必要將這個模塊占用的內存空間清空。這樣當其他的設備注冊的時候便有更多的空間可以選擇。

  形象的比喻是, 當卸載驅動的時候,就是把衣服從衣服架上取下來,這樣衣服架就騰空了。

4、版權信息

MODULE_LICENSE("GPL");

  Linux內核是按照GPL發布的,同樣Linux的驅動程序也要提供版權信息,否則當加載到內核中系統會給出警告信息。

5、功能函數

static int hello_open(struct inode *inode, struct file *file){
    printk(KERN_EMERG "hello open.\n");
    return 0;
}

static int hello_write(struct file *file, const char __user * buf, size_t count, loff_t *ppos){
    printk(KERN_EMERG "hello write.\n");
    return 0;
}

static struct file_operations hello_flops = {
    .owner  =   THIS_MODULE,
    .open   =   hello_open,     
    .write  =   hello_write,
};
  功能函數雖然不是一個驅動模板所必須的,但是一個有實際意義的驅動程序一定包含功能函數。功能函數實際上定義了這個驅動程序為用戶提供了哪些功能,也就是用戶可以對一個硬件設備可以進行哪些操作。
  常見的功能函數有xxx_open()、xxx_write()、xxx_read()、xxx_ioctl()、xxx_llseek()等。當上層應用調用open()、write()、read()、ioctl()、llseek()等這些函數的時候,經過層層調用最后到達底層,調用相應的功能函數。結構體file_operations中的成員定義了很多函數,實際應用可以只對其部分成員賦值,其定義如下。
  View Code

四、從上層應用到底層驅動的執行過程

 1、Linux系統的分層結構

  Linux系統的分層結構為:應用層 ----> 庫 ----> 內核 ----> 驅動程序 ----> 硬件設備。

2、從上層應用到底層驅動的執行過程

  以“open("/dev/hellodev", O_RDWR)”函數的執行過程為例來說明。

(1)應用程序使用庫提供的open函數打開代表hellodev的設備文件。

(2)庫根據open函數傳入的參數執行swi指令,這條指令會引起CPU異常,從而進入內核。

(3)內核的異常處理函數根據這些參數找到相應的驅動程序。

(4)執行相應的驅動程序。

(5)返回一個文件句柄給庫,進而返回給應用程序。

 3、驅動程序的執行特點

  與應用程序不同,驅動程序從不主動運行,它是被動的:根據應用程序的要求進行初始化,根據應用程序的要求進行讀寫。驅動程序加載進內核,只是告訴內核“我在這里,我能做這些工作”,至於這些工作何時開始,則取決於應用程序。

  驅動程序運行於“內核空間”,它是系統“信任”的一部分,驅動程序的錯誤有可能導致整個系統的崩潰。

 

參考資料:

             linux驅動開發框架

     《嵌入式Linux應用開發完全手冊》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM