第一個驅動之字符設備驅動(一)


1、字符設備:是指只能一個字節一個字節讀寫的設備,不能隨機讀取設備內存中的某一數據,讀取數據需要按照先后順序。字符設備是面向流的設備,常見的字符設備有鼠標、鍵盤、串口、控制台和LED設備等。

2、塊設備:是指可以從設備的任意位置讀取一定長度數據的設備。塊設備包括硬盤、磁盤、U盤和SD卡等。

  每一個字符設備或塊設備都在/dev目錄下對應一個設備文件。linux用戶程序通過設備文件(或稱設備節點)來使用驅動程序操作字符設備和塊設備。

主設備號和次設備號(二者一起為設備號):
  一個字符設備或塊設備都有一個主設備號和一個次設備號。主設備號用來標識與設備文件相連的驅動程序,用來反映設備類型。次設備號被驅動程序用來辨別操作的是哪個設備,用來區分同類型的設備。

驅動程序原理圖:

那么對於剛接觸驅動的我們來說如何快速編寫一個驅動程序呢?

最好也是最快的方法是參考內核源代碼中的demo。例如現在,我想編寫我們的第一個字符驅動程序,那么我們可以看看別人是怎么實現的,在內核driver目錄下找到led的驅動程序,參考別人是如何實現。還有就是廠家的參考demo。這是我們最快的學習方式。和STM32學習固件庫函數一樣的道理。

先寫出兩個函數模型,打開(open)和寫(write)函數:

static int first_drv_open(struct inode *inode, struct file *file)
{
    
    return 0;
}

static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    return 0;
}

然后是要告訴內核有這兩個函數,怎樣告訴內核呢?通過定義下面這樣結構:

/*
 * NOTE:
 * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl
 * can be called without the big kernel lock held in all filesystems.
 */
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, struct dentry *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*dir_notify)(struct file *filp, unsigned long arg);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
}

然后通過一個函數告訴內核:

 

 那么誰來調用上面這個函數呢?調用上面這個函數的函數就叫做驅動入口函數(這里是first_drv_init):

 

入口函數需要區分是哪個驅動,所以需要修飾一下,怎么修飾呢?就是調用一個函數:

 完整的myled.c函數如下:

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/init.h>

#include <linux/delay.h>

#include <asm/uaccess.h>

#include <asm/irq.h>

#include <asm/io.h>

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

#include <asm/hardware.h>



static int first_drv_open(struct inode *inode, struct file *file)

{

    printk("first_drv_open...\r\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_write...\r\n");

    return 0;

}



/* 這個結構是字符設備驅動程序的核心

 * 當應用程序操作設備文件時所調用的open、read、write等函數,

 * 最終會調用這個結構中指定的對應函數

 */

static struct file_operations first_drv_fops = {

    .owner  =   THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */

    .open   =   first_drv_open,            

    .write    =    first_drv_write,       

};

int fisrt_drv_init(void)

{

    register_chrdev(111, "first_drv", &first_drv_fops);

    return 0;

}



void fisrt_drv_exit(void)

{

    unregister_chrdev(111, "first_drv");

}



module_init(fisrt_drv_init);

module_exit(fisrt_drv_init);

MODULE_AUTHOR("http://www.100ask.net");

MODULE_VERSION("0.1.0");

MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");

MODULE_LICENSE("GPL");
View Code

/*

更正上面一個錯誤,由於粗心導致之后的程序出現bug:

 

*/

Makefile如下:

  1 KERN_DIR =/home/book/Documents/linux-2.6.22.6
  2 PWD       := $(shell pwd)
  3 all:
  4         make -C $(KERN_DIR) M=$(PWD) modules 
  5 
  6 clean:
  7         make -C $(KERN_DIR) M=$(PWD) modules clean
  8         rm -rf modules.order
  9 
 10 obj-m   += myled.o

上面的Makefile經過了一次更改,之前韋老師的Makefile如下:

關鍵在於使用韋老師的`pwd`這個方式,我在ubuntu 16.04上make會失敗,查詢網上資料,改成$(PWD)之后,終於make成功了。特別注意一點,在make驅動函數之前,需要先構建內核樹,其實就是保證在make驅動函數之前,先make一下內核。還有一點需要注意,想要加載驅動,在第一次make內核之后,把此次生成的uImage下載進入flash(如果無法生成uImage,執行sudo apt-get install u-boot-tools即可),然后才可以看到驅動被加載。還有就是,在使用不更改uboot參數的網絡文件系統,即通過手動mount的方式,這種情況下insmod驅動的.ko文件,會比較耗時,甚至容易出現失敗或者長時間卡死狀態,所以建議選用set uboot參數的方式,這樣insmod的時候可以快速響應:

現在寫個main函數測試這個驅動:

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>



int main(int argc, char **argv)

{

    int fd;

    int val=1;

    fd=open("/dev/xxx",O_RDWR);

    if(fd<0)

        printf("can't open!\r\n");

    write(fd,&val,4);

    return 0;

}

在nfs共享目錄下編譯一下這個源文件:

生成可執行文件之后,在開發板上運行:

首先執行的時候,顯示不能打開,因為我們還沒有創建這樣的設備,使用mknod創建設備節點之后,可以看到應用程序的open和write會觸發我們驅動函數的open和write,證明我們的入門測試成功了。創建設備采用/dev/xxx是為了展示這個設備的名字,其實無關緊要,但是最好能有意義。

當然,這里只是我們第一個測試程序,存在不足,我們在驅動函數中是寫死了主設備號為111,而且還需要手動創建節點,在之后的隨筆中,將對其進行改進。

(現在我是使用的經過uboot更改了參數的nfs網絡文件系統,這樣的方式insmod更快)

但是,現在有個問題,LCD此時的驅動是有問題的,現在只是在測試學習驅動階段,可暫且不管,現在我屏幕沒有企鵝了,花屏~~~繼續往后學習!


免責聲明!

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



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