2.制作第一個驅動程序


先講解驅動框架,然后寫出first_drv驅動程序,來打印一些信息

寫出first_drv驅動程序需要以下幾步:

(1)寫出驅動程序first_drv_open first_drv_write

(2)需要定義file_operations結構體來封裝驅動函數first_drv_open first_drv_write

對於字符設備來說,常用file_operations以下幾個成員:

(3)  模塊加載函數,通過函數 register_chrdev(major, “first_drv”, &first_drv_fops) 來

注冊字符設備

(4)寫驅動的first_drv_init 入口函數來調用這個register_chrdev()注冊函數,

(5)通過module_init()來修飾入口函數,使內核知道有這個函數

(6)寫驅動的first_drv_exit出口函數,調用這個unregister_chrdev()函數卸載,

(7) 通過module_exit()來修飾出口函數

(8) 模塊許可證聲明, 最常見的是以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 <asm/irq.h>

#include <asm/arch/regs-gpio.h>

#include <asm/hardware.h>

#include <asm/uaccess.h>

#include <asm/io.h>

 
/*1寫出驅動程序first_drv_open first_drv_write */

/*  inode結構表示具體的文件,file結構體用來追蹤文件在運行時的狀態信息。*/

static int first_drv_open(struct inode *inode, struct file  *file)
{
   printk(“first_drv_open\n”);      //打印,在內核中打印只能用printk()
   return 0;
}

/*參數filp為目標文件結構體指針,buffer為要寫入文件的信息緩沖區,count為要寫入信息的長度,ppos為當前的偏移位置,這個值通常是用來判斷寫文件是否越界*/

static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
  printk(“first_drv_write\n”);      //打印,在內核中打印只能用printk()
   return 0;
}

 
/*2定義file_operations結構體來封裝驅動函數first_drv_open first_drv_write */

 static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,     //被使用時阻止模塊被卸載
    .open   =   first_drv_open,      
    .write   =   first_drv_write,   
  };

 

/*4寫first_drv_init入口函數來調用這個register_chrdev()注冊函數*/
int first_drv_init(void)
{
   /*3 register_chrdev注冊字符設備,並設置major=111*/
  /*如果設置major為0,表示由內核動態分配主設備號,函數的返回值是主設備號*/
register_chrdev (111, “first_drv”, &first_drv_fops); //111:主設備號,”first_drv”:設備名
/*
register_chrdev作用:在VFS虛擬文件系統中找到字符設備,然后通過主設備號找到內核數組里對應的位置,最后將設備名字和fops結構體填進去
*/
   return 0;
}

/*5 module_init修飾入口函數*/
module_init(first_drv_init);

 
/*6 寫first_drv_exit出口函數*/
void first_drv_exit(void)
{
unregister_chrdev (111, “first_drv”);  //卸載驅動,只需要主設備號和設備名就行 
}

/*7 module_exit修飾出口函數*/
module_exit(first_drv_exit);

/*8許可證聲明, 描述內核模塊的許可權限,如果不聲明LICENSE,模塊被加載時,將收到內核被污染 (kernel tainted)的警告。*/
MODULE_LICENSE( "GPL v2" );

2然后寫Makefile編譯腳本:

KERN_DIR = /work/system/linux-2.6.22.6   //依賴的內核目錄,前提內核是編譯好的
 

all:                                
       make -C $(KERN_DIR) M=`pwd` modules   
// M=`pwd`:指定當前目錄 //make -C $(KERN_DIR) 表示將進入(KERN_DIR)目錄,執行該目錄下的Makefile
//等價於在linux-2.6.22.6目錄下執行: make M=(當前目錄) modules
// modules:要編譯的目標文件    clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += frist_drv.o //obj-m:內核模塊文件,指將myleds.o編譯成myleds.ko

3. make,編譯生成frist_drv.ko文件

 

4.然后開發板通過nfs網絡文件系統來加載frist_drv.ko

加載之前首先通過 cat /proc/devices來查看字符主設備號111是否被占用

 

然后通過 insmod first_drv.ko來掛載, 通過 cat /proc/devices就能看到first_drv已掛載好

 

5.通過測試程序測試frist_drv模塊

測試程序first_driver_text.c代碼如下

#include <sys/types.h>    //調用sys目錄下types.h文件
#include <sys/stat.h>      //stat.h獲取文件屬性
#include <fcntl.h>
#include <stdio.h>

/*輸入”./first_driver_text”,     agc就等於1, argv[0]= first_driver_text  */
/*輸入”./first_driver_text on”,   agc就等於2, argv[0]= first_driver_text,argv[1]=on;  */

int main(int argc,char **argv) 
{
int fd1, fd2;
int val=1;
fd1 = open("/dev/xxx",O_RDWR);  //打開/dev/xxx設備節點
if(fd1<0)                   //無法打開,返回-1
  printf("can't open%d!\n", fd1);
else
   printf("can open%d!\n", fd1);    //打開,返回文件描述符

write(fd1,
&val, 4);    //寫入數據1 return 0; }

6.然后arm-linux-gcc -o first_driver_text first_driver_text.c生成執行文件

回到板子串口上使用./first_driver_text來運行,發現如果open()打不開,會返回-1

 

是因為我們沒有創建dev/xxx這個設備節點,然后我們來創建,使它等於剛剛掛載好的first_drv模塊

mknod -m 660 /dev/xxx c 111 0            // first_drv模塊的主設備號=111

./first_driver_text

 

結果如上圖,發現測試程序里的open()函數調用了驅動中的first_drv_open()

write()函數調用了驅動中的first_drv_write(),

其中open()函數返回值為3,是因為描述符0,1,2都已經被控制台占用了,所以從3開始

7.改進底層驅動,使用動態裝載:

除了靜態裝載驅動外,還可以動態裝載,讓系統自動為我們驅動設備自動分配設備號

7.1 修改first_drv_init入口函數和first_drv_exit 出口函數:

代碼如下:

int major;              //定義一個全局變量,用來保存主設備號
int first_drv_init(void)
{
 /*設置major為0,由內核動態分配主設備號,函數的返回值是主設備號*/
  major =register_chrdev (0, “first_drv”, &first_drv_fops);  
  return 0;

}

void first_drv_exit(void)
{
unregister_chrdev (major, “first_drv”);  //卸載驅動, 將major填入即可
}

 

如下圖,通過動態分配得出它的主設備號是252,然后重創252的測試程序

 

rm dev/xxx

mknod -m 660 /dev/xxx c 252 0

./first_driver_text

 

7.2 每次都要手工創建設備節點,大家肯定也會覺得這樣做太麻煩了。

可以使用自動創建設備節點,Linux有udev、mdev的機制,而我們的ARM開發板上移植的busybox有mdev機制,然后mdev機制會通過class類來找到相應類的驅動設備來自動創建設備節點 (前提需要有mdev)

在哪里設置了mdev機制?

制作根文件系統之使用里有介紹

7.3 接下來使用insmod自動創建設備節點, rmmod自動注銷設備節點

(1)首先創建一個class設備類,class是一個設備的高級視圖,它抽象出低級的實現細節,然后在class類下,創建一個class_device,即類下面創建類的設備:(在C語言中class就是個結構體)

static struct class *firstdrv_class;               //創建一個class類
static struct class_device   *firstdrv_class_devs; //創建類的設備

 

(2)在first_drv_init入口函數中添加:

firstdrv_class= class_create(THIS_MODULE,"firstdrv");  

//創建類,它會在sys/class目錄下創建firstdrv_class這個類
 

firstdrv_class_devs=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"xyz");

//創建類設備,會在sys/class/firstdrv_class類下創建xyz設備,然后mdev通過這個自動創建/dev/xyz這個設備節點,            

 

(3)在first_drv_exit出口函數中添加:

 

 class_device_unregister(firstdrv_class_devs);      //注銷類設備,與class_device_create對應
  class_destroy(firstdrv_class);                    //注銷類,與class_create對應

 

 

 重新編譯insmod后,會發現在/dev下自動的創建了xyz設備節點

 

其中在sys/class里有各種類的設備, 比如sys/class/fristdev下就有xyz

 

然后mdv通過insmod xxx 就去class找到相應類的驅動設備來自動創建設備節點

為什么內容一更改,mdv就能自動運行創建設備節點呢?

是因為以前創建根文件系統時候,

在etc/init.d/rcS里添加了這么一段:

echo /sbin/mdev > /proc/sys/kernel/hotplug             //支持熱拔插

然后kernel每當設備出現變動時,調用/sbin/mdev來處理對應的信息,使mdev應用程序操作/dev目錄下的設備,進行添加或刪除

(4).再修改測試程序里open函數,將/dev/xxx改為/dev/xyz,這樣就測試模塊,就不需要再mknod了.

 驅動程序first_drv_open first_drv_write中只是打印數據,接下來下一節便開始來點亮LED.

 

 


免責聲明!

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



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