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

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

二、塊設備:是指能夠從設備的任意位置讀取必定長度數據的設備。塊設備包括硬盤、磁盤、U盤和SD卡等。linux

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

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

驅動程序原理圖:

網絡

那麼對於剛接觸驅動的咱們來講如何快速編寫一個驅動程序呢?app

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

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

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此時的驅動是有問題的,如今只是在測試學習驅動階段,可暫且無論,如今我屏幕沒有企鵝了,花屏~~~繼續日後學習!

相關文章
相關標籤/搜索