《路由器开发-OpenWrt系统开发》第16章 驱动开发之字符设备驱动程序框架

16.1 字符设备驱动程序框架简介

我们在学习 C 语言的时候,知道每个应用程序的入口函数,即第一个被执行的函数是 main函数,那么,我们自己的驱动程序,哪个函数是入口函数呢?

在写驱动程序的时候,如果函数的名字可以任意取,常常为 xxxx_init(),当实现好这个 xxxx_init()函数以后,内核其实并不知道这个就是我们驱动的入口函数,因此我们要想办法告诉内核,我们的入口函数是哪个?我们通过 module_init()函数来告诉内核,具体如下。

module_init(MT7628_drv_init);

通过上面的修饰以后,MT7628_drv_init()这个函数就变成了我们的驱动程序的入口函数了。当然,有入口函数,自然还需要一个出口函数,我们通过 module_exit()函数来告诉内核,具体如下。

module_exit(MT7628_drv_exit);

从上一章中,我们知道,应用程序是通过 open、read、write …函数来和我们的驱动程序进行交互的,那么我们的驱动程序是怎么给应用程序提供这些接口的呢?我们在写驱动程序的时候,我们首先需要定义出一个 file_operations 结构体,该结构体便是驱动和应用程序交互的接口。具体定义如下。

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 *);
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 *, loff_t, loff_t, 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 (*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 (*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);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};

我们的驱动程序要给应用程序提供哪些接口,就只需要填充相应的成员即可。比如我们想提供 open、read、write 这三个接口,就应该如下定义。

static struct file_operations MT7628_drv_fops = {
.owner = THIS_MODULE, /*这是一个宏,推向编译模块时自动创建的__this_module 变量 */
.open = MT7628_drv_open,
.read = MT7628_drv_read,
.write = MT7628_drv_write,
};

当 file_operations 结构体定义、设置好以后,我们只需要通过 register_chrdev()函数将该机构图注册进内核即可。

16.2 字符设备驱动程序框架实现

经过前面部分的讲解,相信大家一定对如何写一个自己的驱动程序,有所感悟了。接下来,给大家一个简单的驱动程序的例子,可以用于作为框架模板,以后的驱动都可以基于该驱动进行修改。

#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
#include <linux/random.h>
#include <linux/init.h>
#include <linux/raw.h>
#include <linux/tty.h>
#include <linux/capability.h>
#include <linux/ptrace.h>
#include <linux/device.h>
#include <linux/highmem.h>
#include <linux/crash_dump.h>
#include <linux/backing-dev.h>
#include <linux/bootmem.h>
#include <linux/splice.h>
#include <linux/pfn.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/aio.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <asm/uaccess.h>

#define DEVICE_NAME     "MT7628"  /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
#define MT7628_MAJOR       0       /* 主设备号 */

static struct class *MT7628_drv_class;

static int MT7628_drv_open(struct inode *inode, struct file *file)
{
    printk("%s:Hello MT7628\n", __FUNCTION__);    // printk用于驱动中添加打印,用法和应用程序中的printf一样

    return 0;
}

static ssize_t MT7628_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    printk("%s:Hello MT7628\n", __FUNCTION__);    // printk用于驱动中添加打印,用法和应用程序中的printf一样

    return 0;
}

static ssize_t MT7628_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos)
{
    printk("%s:Hello MT7628\n", __FUNCTION__);    // printk用于驱动中添加打印,用法和应用程序中的printf一样

    return 0;
}

/* 这个结构是字符设备驱动程序的核心
** 当应用程序操作设备文件时所调用的open、read、write等函数,
** 最终会调用这个结构中指定的对应函数
**/
static struct file_operations MT7628_drv_fops = {
    .owner      = THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open       = MT7628_drv_open,     
    .read   = MT7628_drv_read,     
    .write  = MT7628_drv_write,    
};

int major;
/*
** 执行insmod命令时就会调用这个函数 
*/
static int __init MT7628_drv_init(void)
{
    /* 注册字符设备
    ** 这步是写字符设备驱动程序所必须的
    ** 参数为主设备号、设备名字、file_operations结构;
    ** 这样,主设备号就和具体的file_operations结构联系起来了,
    ** 操作主设备为MT7628_MAJOR的设备文件时,就会调用MT7628_drv_fops中的相关成员函数
    ** MT7628_MAJOR可以设为0,表示由内核自动分配主设备号
    */
    major = register_chrdev(MT7628_MAJOR, DEVICE_NAME, &MT7628_drv_fops);
    if (major < 0)
    {
        printk(DEVICE_NAME " can't register major number\n");
        return major;
    }

    /*
    ** 以下两行代码用于创建设备节点,是必须的。
    ** 当然,如果不写这两行,那么就必须在linux系统命令行中通过mknod这个命令来创建设备节点
    */
    /* 创建类 */
    MT7628_drv_class = class_create(THIS_MODULE, "MT7628");
    /* 类下面创建设备节点 */
    device_create(MT7628_drv_class, NULL, MKDEV(major, 0), NULL, "MT7628");       // /dev/MT7628

    /*
    ** 打印一个调试信息
    */
    printk("%s:Hello MT7628\n", __FUNCTION__);    // printk用于驱动中添加打印,用法和应用程序中的printf一样

    return 0;
}

/*
 * 执行rmmod命令时就会调用这个函数 
 */
static void __exit MT7628_drv_exit(void)
{
    unregister_chrdev(major, "MT7628");       // 与入口函数的register_chrdev函数配对使用
    device_destroy(RT5350_drv_class, MKDEV(major, 0));  // 与入口函数的device_create函数配对使用
    class_destroy(RT5350_drv_class);    // 与入口函数的class_create函数配对使用

    printk("%s:Hello MT7628\n", __FUNCTION__);    // printk用于驱动中添加打印,用法和应用程序中的printf一样
}

/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(MT7628_drv_init);
module_exit(MT7628_drv_exit);

/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("BruceOu");
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("MT7628 FIRST Driver");
MODULE_LICENSE("GPL");

Related posts

Leave a Comment