1. 程式人生 > >字元裝置驅動(一)框架

字元裝置驅動(一)框架

目錄


title: 字元裝置驅動(一)框架
tags: linux
date: 2018-11-19 22:40:11
toc: true
---

字元裝置驅動(一)框架

命令速記

mknod  file c major cnt #建立字元裝置
rmmod  file             #解除安裝驅動
insmod file             #安裝驅動
lsmod                   #檢視安裝的驅動
mount -t nfs -o nolock 192.168.5.222:/home/book/stu /mnt #掛載nfs
mount -o nolock,rsize=1024,wsize=1024  192.168.137.222:/home/book/stu  /mnt


ifconfig eth0 192.168.5.200
showmount -e #檢視主機允許的掛載

框架結構

LinuxApp通過呼叫open/close等庫函式去控制硬體裝置.

  1. open,write,read這些是系統的介面函式,由C庫實現
  2. 呼叫C庫的這些函式時,會觸發swi x指令,引發異常,進入異常處理函式稱為 system call interface
  3. system call interface會去呼叫 system open/write/read----稱為virtual Filesystem 虛擬檔案系統
  4. system open/write/read根據不同的檔案呼叫不同的驅動程式

mark

資料結構

  1. 驅動程式建立了按照 字元裝置屬性+主裝置號+次裝置號
    操作的介面
  2. 需要有一個裝置檔案,他的屬性是字元裝置+驅動對應的主裝置號
  3. app操作這個裝置檔案,獲取其屬性,操作裝置檔案時,核心呼叫相應的驅動程式介面
  4. 注意app操作的裝置的屬性一定是要等同於驅動要求的裝置屬性,否則依然是無法找到裝置的

介面實現

App使用open等函式呼叫驅動,open這一類函式是定義在fs.h中的struct file_operations,這是一個統一的介面.所以一個驅動程式需要按照這個格式提供相應的介面即可

*
 * 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);
};
struct module *owner
第一個 file_operations 成員根本不是一個操作; 它是一個指向擁有這個結構的模組的指標. 這個成員用來在它的操作還在被使用時阻止模組被解除安裝. 幾乎所有時間中, 它被簡單初始化為 THIS_MODULE, 一個在 <linux/module.h> 中定義的巨集.

loff_t (*llseek) (struct file *, loff_t, int);
llseek 方法用作改變檔案中的當前讀/寫位置, 並且新位置作為(正的)返回值. loff_t 引數是一個"long offset", 並且就算在 32位平臺上也至少 64 位寬. 錯誤由一個負返回值指示. 如果這個函式指標是 NULL, seek 呼叫會以潛在地無法預知的方式修改 file 結構中的位置計數器( 在"file 結構" 一節中描述).

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
用來從裝置中獲取資料. 在這個位置的一個空指標導致 read 系統呼叫以 -EINVAL("Invalid argument") 失敗. 一個非負返回值代表了成功讀取的位元組數( 返回值是一個 "signed size" 型別, 常常是目標平臺本地的整數型別).

ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);
初始化一個非同步讀 -- 可能在函式返回前不結束的讀操作. 如果這個方法是 NULL, 所有的操作會由 read 代替進行(同步地).

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
傳送資料給裝置. 如果 NULL, -EINVAL 返回給呼叫 write 系統呼叫的程式. 如果非負, 返回值代表成功寫的位元組數.

ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);
初始化裝置上的一個非同步寫.

int (*readdir) (struct file *, void *, filldir_t);
對於裝置檔案這個成員應當為 NULL; 它用來讀取目錄, 並且僅對檔案系統有用.

unsigned int (*poll) (struct file *, struct poll_table_struct *);
poll 方法是 3 個系統呼叫的後端: poll, epoll, 和 select, 都用作查詢對一個或多個檔案描述符的讀或寫是否會阻塞. poll 方法應當返回一個位掩碼指示是否非阻塞的讀或寫是可能的, 並且, 可能地, 提供給核心資訊用來使呼叫程序睡眠直到 I/O 變為可能. 如果一個驅動的 poll 方法為 NULL, 裝置假定為不阻塞地可讀可寫.

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
ioctl 系統呼叫提供了發出裝置特定命令的方法(例如格式化軟盤的一個磁軌, 這不是讀也不是寫). 另外, 幾個 ioctl 命令被核心識別而不必引用 fops 表. 如果裝置不提供 ioctl 方法, 對於任何未事先定義的請求(-ENOTTY, "裝置無這樣的 ioctl"), 系統呼叫返回一個錯誤.

int (*mmap) (struct file *, struct vm_area_struct *);
mmap 用來請求將裝置記憶體對映到程序的地址空間. 如果這個方法是 NULL, mmap 系統呼叫返回 -ENODEV.

int (*open) (struct inode *, struct file *);
儘管這常常是對裝置檔案進行的第一個操作, 不要求驅動宣告一個對應的方法. 如果這個項是 NULL, 裝置開啟一直成功, 但是你的驅動不會得到通知.

int (*flush) (struct file *);
flush 操作在程序關閉它的裝置檔案描述符的拷貝時呼叫; 它應當執行(並且等待)裝置的任何未完成的操作. 這個必須不要和使用者查詢請求的 fsync 操作混淆了. 當前, flush 在很少驅動中使用; SCSI 磁帶驅動使用它, 例如, 為確保所有寫的資料在裝置關閉前寫到磁帶上. 如果 flush 為 NULL, 核心簡單地忽略使用者應用程式的請求.

int (*release) (struct inode *, struct file *);
在檔案結構被釋放時引用這個操作. 如同 open, release 可以為 NULL.

int (*fsync) (struct file *, struct dentry *, int);
這個方法是 fsync 系統呼叫的後端, 使用者呼叫來重新整理任何掛著的資料. 如果這個指標是 NULL, 系統呼叫返回 -EINVAL.

int (*aio_fsync)(struct kiocb *, int);
這是 fsync 方法的非同步版本.

int (*fasync) (int, struct file *, int);
這個操作用來通知裝置它的 FASYNC 標誌的改變. 非同步通知是一個高階的主題, 在第 6 章中描述. 這個成員可以是NULL 如果驅動不支援非同步通知.

int (*lock) (struct file *, int, struct file_lock *);
lock 方法用來實現檔案加鎖; 加鎖對常規檔案是必不可少的特性, 但是裝置驅動幾乎從不實現它.

ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
這些方法實現發散/匯聚讀和寫操作. 應用程式偶爾需要做一個包含多個記憶體區的單個讀或寫操作; 這些系統呼叫允許它們這樣做而不必對資料進行額外拷貝. 如果這些函式指標為 NULL, read 和 write 方法被呼叫( 可能多於一次 ).

ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
這個方法實現 sendfile 系統呼叫的讀, 使用最少的拷貝從一個檔案描述符搬移資料到另一個. 例如, 它被一個需要傳送檔案內容到一個網路連線的 web 伺服器使用. 裝置驅動常常使 sendfile 為 NULL.

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
sendpage 是 sendfile 的另一半; 它由核心呼叫來發送資料, 一次一頁, 到對應的檔案. 裝置驅動實際上不實現 sendpage.

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
這個方法的目的是在程序的地址空間找一個合適的位置來對映在底層裝置上的記憶體段中. 這個任務通常由記憶體管理程式碼進行; 這個方法存在為了使驅動能強制特殊裝置可能有的任何的對齊請求. 大部分驅動可以置這個方法為 NULL.[10]

int (*check_flags)(int)
這個方法允許模組檢查傳遞給 fnctl(F_SETFL...) 呼叫的標誌.

int (*dir_notify)(struct file *, unsigned long);
這個方法在應用程式使用 fcntl 來請求目錄改變通知時呼叫. 只對檔案系統有用; 驅動不需要實現 dir_notify.

其中用到了struct file表示開啟的檔案,具體的點這裡,Struct inode表示一個磁碟上的具體檔案.

struct file {
    /*
     * fu_list becomes invalid after file_free is called and queued via
     * fu_rcuhead for RCU freeing
     */
    union {
        struct list_head    fu_list;
        struct rcu_head     fu_rcuhead;
    } f_u;
    struct path     f_path;
#define f_dentry    f_path.dentry
#define f_vfsmnt    f_path.mnt
    const struct file_operations    *f_op;
    atomic_t        f_count;
    unsigned int        f_flags;
    mode_t          f_mode;
    loff_t          f_pos;
    struct fown_struct  f_owner;
    unsigned int        f_uid, f_gid;
    struct file_ra_state    f_ra;

    unsigned long       f_version;
#ifdef CONFIG_SECURITY
    void            *f_security;
#endif
    /* needed for tty driver, and maybe others */
    void            *private_data;

#ifdef CONFIG_EPOLL
    /* Used by fs/eventpoll.c to link all the hooks to this file */
    struct list_head    f_ep_links;
    spinlock_t      f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
    struct address_space    *f_mapping;
};

驅動註冊

實現介面後需要告知核心,也就是註冊介面,註冊到核心中的函式原型如下:

/**
 * register_chrdev() - Register a major number for character devices.
 * @major: major device number or 0 for dynamic allocation
 * @name: name of this range of devices
 * @fops: file operations associated with this devices
 *
 * If @major == 0 this functions will dynamically allocate a major and return
 * its number.
 *
 * If @major > 0 this function will attempt to reserve a device with the given
 * major number and will return zero on success.
 *
 * Returns a -ve errno on failure.
 *
 * The name of this device has nothing to do with the name of the device in
 * /dev. It only helps to keep track of the different owners of devices. If
 * your module name has only one type of devices it's ok to use e.g. the name
 * of the module here.
 *
 * This function registers a range of 256 minor numbers. The first minor number
 * is 0.
 */
int register_chrdev(unsigned int major, const char *name,
            const struct file_operations *fops)

major 為主裝置號,name為字串標識,fops就是包含所有介面函式的結構體file_operations這裡補充下裝置號的知識.在之前的busybox 之 最小根檔案系統中我們使用mknod來建立字元裝置或者塊裝置(/dev/console).

# ls -l /dev/console
crw-rw----    1 0        0          5,   1 Jan  1 01:05 /dev/console

mark

如何使系統去呼叫這個註冊函式?

這裡使用巨集module_init來實現,module_init就是定義一個結構體,這個結構體中有一個函式指標,指向“入口函式”。 當安裝一個驅動程式時,核心會自動找到這樣一個結構體,呼叫裡面的函式指標,入口函式.也就是當我們執行命令去載入驅動的時候,會去遍歷這些指標應該.

先了解自上而下的呼叫過程

  1. app自主去open("dev/xxx"),這裡的xxx裝置檔案為字元裝置,它有標識為字元裝置,以及主裝置號和次裝置號
  2. VFS 系統根據xxx的屬性,即字元裝置型別+主裝置號找到註冊到的file_operations結構,呼叫函式

函式原理

綜上,register_chrdev函式最簡單的實現方式也就是存在一個chardev的陣列,按照主裝置號為索引,內容為file_operations指標即可.所謂註冊也就是填充這個陣列即可

小結

register_chrdev是實現註冊的方式,module_init是系統在裝載驅動的時候去尋找到這個註冊的方式.為什麼不直接在register_chrdev中直接實現module_init的通知功能.可以這麼理解,裝載驅動的時候可能還需要其他一些動作,系統提供的register_chrdev只是在這個chardev陣列增加,並沒有辦法做其他事情,所以這裡一般是以下這種形式

void my_regist_fun()
{
    dosomething();
    register_chrdev(major,name,&my_file_operations);
}
module_init(my_regist_fun);

驅動解除安裝

同驅動載入註冊一樣,使用unregister_chrdev實現,使用module_exit來使得核心能夠主動呼叫

int unregister_chrdev(unsigned int major, const char *name)

同樣的一般實現形式如下

void my_unregist_fun()
{
    dosomething();
    unregister_chrdev(major,name);
}
module_init(my_regist_fun);

程式設計

(一)手動建立主裝置號

  1. 標頭檔案包含

    #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>              //ioremap等 io訪問
    #include <asm/arch/regs-gpio.h>
    #include <asm/hardware.h>
  2. Makefile 中需要提前編譯好核心,並且加入其中.-C是指使用KERN_DIR中的Makefile來編譯,M表示當前目錄,moudle也就是目標了

    KERN_DIR = /work/system/linux-2.6.22.6
    all:
     make -C $(KERN_DIR) M=`pwd` modules 
    clean:
     make -C $(KERN_DIR) M=`pwd` modules clean
     rm -rf modules.order
    obj-m    += first_drv.o
  3. 新增 GPL 的licence,否則在安裝的時候會有警告

    MODULE_LICENSE("GPL");
    
    # insmod ./dri.ko
    dri: module license 'unspecified' taints kernel.
  4. 使用cat /proc/devices檢視掛載的驅動裝置

    # cat /proc/devices
    Character devices:
      1 mem
      ...

程式碼一覽

// 驅動程式 dri.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>             //ioremap等 io訪問
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

int first_drv_open(struct inode *inode, struct file *file)
{
    printk("first_drv_open\n");
    return 0;
}
ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    printk("first_drv_write\n");
    return 0;
}
struct file_operations first_drv_fops = {
    .open   =   first_drv_open,     
    .write  =   first_drv_write,       
};
int first_drv_init(void)
{
    register_chrdev(111, "first_drv", &first_drv_fops); 
    return 0;
}
void first_drv_exit(void)
{
    unregister_chrdev(111, "first_drv");  
}

module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

測試程式test.c,編譯命令是arm-linux-gcc -o test test.c

#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("xxx", O_RDWR);
    if (fd < 0)
    {
        printf("can't open!\n");
    }
    write(fd, &val, 4);
    return 0;
}

測試

  1. 安裝驅動insmod dri.ko,如果之前有安裝過,使用rmmod dri.ko解除安裝驅動

  2. 檢視驅動是否安裝cat /proc/devices ,這裡能夠看到主裝置號是111,裝置名是first_drv

    Character devices:
    ...
    111 first_drv
    ...
  3. 建立測試所需的裝置檔案xxx,這裡的xxx的主裝置號必須與驅動的一致,否則開啟錯誤,核心是根據主裝置號查詢的,而非名字

    mknod xyz c 111 5 #111 是主裝置號,5是次裝置號
  4. 執行測試程式./test,如果沒有步驟3建立裝置檔案,會提示沒有裝置檔案的

    # ./test
    first_drv_open
    first_drv_write

(二)自動分配主裝置號

註冊函式中使用major=0引數傳遞給註冊函式register_chrdev時,系統就能自動尋找空閒的一個主裝置號返回.

int major;
int first_drv_init(void)
{
    major=register_chrdev(0, "first_drv", &first_drv_fops); 
    return 0;
}
void first_drv_exit(void)
{
    unregister_chrdev(major, "first_drv");  
}

這個時候可以手動cat /proc/devices去檢視裝置號然後去建立裝置去控制,使用cat /proc/devices檢視裝置號然後去操作

(三)自動建立裝置檔案

busybox 完善(四)中提到了mdev機制,也就是系統自動掛載,mdev會根據sys下的資訊自動建立裝置的,自動在/proc/sys/kernel/hotplug完成載入與解除安裝

# cat init.d/rcS
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug      #這裡實現熱拔插自動載入資訊
mdev -s

我們這裡的是會在sys下建立一個class的資料夾的

程式碼一覽

設計流程

static struct class *firstdrv_class;
static struct class_device  *firstdrv_class_dev;

static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,    /* 這是一個巨集,推向編譯模組時自動建立的__this_module變數 */
    .open=.....
    .write=...
}

//建立一個類
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
//在類下面去建立一個裝置 /dev/xyz 
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* 

//解除安裝裝置需要刪除這個類
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);

注意 如果沒有加入MODULE_LICENSE("GPL");,會提示

# insmod dri.ko
dri: Unknown symbol class_device_create
dri: Unknown symbol class_device_unregister
dri: Unknown symbol class_create
dri: Unknown symbol class_destroy
insmod: cannot insert 'dri.ko': Unknown symbol in module (-1): No such file or directory

完整程式碼

#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>             //ioremap等 io訪問
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static struct class *firstdrv_class;
static struct class_device  *firstdrv_class_dev;

static int first_drv_open(struct inode *inode, struct file *file)
{
    printk("first_drv_open\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\n");
    return 0;
}
static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,    /* 這是一個巨集,推向編譯模組時自動建立的__this_module變數 */
    .open   =   first_drv_open,     
    .write  =   first_drv_write,       
};
static int major;
static int first_drv_init(void)
{
    major=register_chrdev(0, "first_drv", &first_drv_fops); // 註冊, 告訴核心

    firstdrv_class = class_create(THIS_MODULE, "first_drv");
    firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
    return 0;
}
static void first_drv_exit(void)
{
    unregister_chrdev(major, "first_drv"); // 解除安裝
    class_device_unregister(firstdrv_class_dev);
    class_destroy(firstdrv_class);
}

module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

測試

  1. 安裝insmod dri.ko

  2. 檢視下是否載入,這裡被自動分配了252這個主裝置號

    # cat /proc/devices
    ...
    252 first_drv
    ...
    # lsmod
    Module                  Size  Used by    Tainted: P
    dri                     2124  0
  3. 執行./test

    # ./test
    first_drv_open
    first_drv_write

分析

系統會自動在/sys/class下建立class下的檔案資訊first_drv,可以看到在/sys/class/first_drv/xyz/dev中的檔案內容就是其主裝置號+次裝置號

# cd /sys/
# ls
block     class     firmware  kernel    power
bus       devices   fs        module

# ls class
first_drv     mem           ppdev         scsi_host     usb_hostt
# ls /sys/class/first_drv/
xyz
# ls /sys/class/first_drv/xyz
dev        subsystem  uevent
# cat  /sys/class/first_drv/xyz/dev
252:0

(四)使用次裝置號

在(三)自動分配主裝置號中,是在class結構下掛接具體的class_device,這裡更改為在同一個class下掛接多個class_device.

//定義一個類下面有四個物件,也就是4個子裝置了--------------------------------------
static struct class *firstdrv_class;
static struct class_device  *firstdrv_class_dev[4];
//註冊生成次裝置號-------------------------------------------------------------
major=register_chrdev(0, "first_drv", &first_drv_fops); // 註冊, 告訴核心
firstdrv_class = class_create(THIS_MODULE, "first_drv");
for (minor = 0; minor < 4; minor++)
{
    firstdrv_class_dev[minor] = class_device_create(firstdrv_class, NULL, MKDEV(major, minor), NULL, "xyz%d", minor);
}
//解除安裝裝置----------------------------------------------------------------------
unregister_chrdev(major, "first_drv"); // 解除安裝
for ( minor = 0; minor < 4; minor++)
{
    class_device_unregister(firstdrv_class_dev[minor]);
}
class_destroy(firstdrv_class);

那麼如何區分出次裝置號呢?

//在讀寫函式中,使用file結構指標中的引數
int minor =  MINOR(file->f_dentry->d_inode->i_rdev);
//在open函式中,使用node節點
int minor = MINOR(inode->i_rdev);

程式碼一覽

完整的驅動函式程式碼

#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>             //ioremap等 io訪問
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static struct class *firstdrv_class;
static struct class_device  *firstdrv_class_dev[4];

static int first_drv_open(struct inode *inode, struct file *file)
{
    int minor = MINOR(inode->i_rdev);
    printk("first_drv_open=%d\n",minor);

    return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    int minor =  MINOR(file->f_dentry->d_inode->i_rdev);
    printk("first_drv_write=%d\n",minor);
    return 0;
}
static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,    /* 這是一個巨集,推向編譯模組時自動建立的__this_module變數 */
    .open   =   first_drv_open,     
    .write  =   first_drv_write,       
};
static int major;
static int first_drv_init(void)
{
    int minor=0;
    major=register_chrdev(0, "first_drv", &first_drv_fops); // 註冊, 告訴核心

    firstdrv_class = class_create(THIS_MODULE, "first_drv");

    for (minor = 0; minor < 4; minor++)
    {
        firstdrv_class_dev[minor] = class_device_create(firstdrv_class, NULL, MKDEV(major, minor), NULL, "xyz%d", minor);
    }


    return 0;
}
static void first_drv_exit(void)
{
    int minor = 0;
    unregister_chrdev(major, "first_drv"); // 解除安裝
    for ( minor = 0; minor < 4; minor++)
    {
        class_device_unregister(firstdrv_class_dev[minor]);
    }
    class_destroy(firstdrv_class);
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

更改下測試程式碼,使用引數傳遞所需開啟的檔案

#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;
    if (argc <= 1)  {printf("nofile select !\n"); return ;}
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        printf("can't open!\n");
    }
    write(fd, &val, 4);
    return 0;
}

測試

  1. 首先安裝驅動insmod dri.ko

  2. 查詢驅動

    # lsmod
    Module                  Size  Used by    Tainted: P
    dri                     2232  0
    # cat /proc/devices
    Character devices:
    252 first_drv
  3. 查詢掛載裝置

    # ls /dev/xyz*
    /dev/xyz0  /dev/xyz1  /dev/xyz2  /dev/xyz3
  4. 測試

    # ./test /dev/xyz0
    first_drv_open=0
    first_drv_write=0
    # ./test /dev/xyz1
    first_drv_open=1
    first_drv_write=1
    # ./test /dev/xyz2
    first_drv_open=2
    first_drv_write=2

分析

檢視下sys/class下是否自動建立了xyz的相關資訊

# ls /sys/class/
first_drv 

# ls /sys/class/first_drv/xyz*
/sys/class/first_drv/xyz0:
dev        subsystem  uevent
/sys/class/first_drv/xyz1:
dev        subsystem  uevent
/sys/class/first_drv/xyz2:
dev        subsystem  uevent
/sys/class/first_drv/xyz3:
dev        subsystem  uevent

# cat  /sys/class/first_drv/xyz*/dev
252:0
252:1
252:2
252:3

(五)異常捕獲處理

註冊時的異常捕獲

ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
if (ret < 0) {
    printk(DEVICE_NAME " can't register major number\n");
    return ret;
}

使用類來呼叫mdev機制建立類

leds_class = class_create(THIS_MODULE, "leds");
if (IS_ERR(leds_class))
    return PTR_ERR(leds_class);

類下掛載裝置

leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor);
if (unlikely(IS_ERR(leds_class_devs[minor])))
{
    return PTR_ERR(leds_class_devs[minor]);
}           

(六)驅動資訊

/* 描述驅動程式的一些資訊,不是必須的 */
MODULE_AUTHOR("http://www.100ask.net");
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");
MODULE_LICENSE("GPL");

小結

  • 應用程式通過open/write/close呼叫,open等通過傳遞的檔案的主裝置號去尋找驅動,執行驅動函式.
  • 驅動程式中使用register_chrdev來註冊驅動,使用class_deviceclass相關函式呼叫mdev機制達到自動建立裝置檔案的目的
  • 主裝置號是提供給系統用的,次裝置號是提供給使用者用的,可以當作一個引數使用
  1. 定義file_operations結構體,填充開啟,讀寫函式

  2. 定義註冊函式與解除安裝函式

  3. 修飾註冊函式與解除安裝函式module_init,module_exit

  4. 使用mdev需要建立class類,並在class類下掛載對應的class_device裝置節點

  5. 建立驅動資訊MODULE_LICENSE("GPL");

  6. 標頭檔案包含

    #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>              //ioremap等 io訪問
    #include <asm/arch/regs-gpio.h>
    #include <asm/hardware.h>
  7. Makefile

    KERN_DIR = /work/system/linux-2.6.22.6
    all:
     make -C $(KERN_DIR) M=`pwd` modules 
    clean:
     make -C $(KERN_DIR) M=`pwd` modules clean
     rm -rf modules.order
    obj-m    += first_drv.o

注意:

視訊順序是LED驅動程式_操作LED 然後是LED驅動程式_測試改進