Linux驱动学习笔记(2)----字符型设备驱动基本框架

本文介绍字符型设备驱动的基本框架。

一个字符型设备驱动基本框架主要包括:

    1. 设备结构体、全局变量的定义

    2. 实现设备模块加载、卸载函数

    3. 实现 fops 中操作设备相关的函数

 

1、结构体、全局变量定义:

    a. 通常会定义一个设备结构体:

struct xxx_dev{

    struct cdev cdev;                      //字符设备结构体,必须要有

    /* 以下均为驱动中可能需要用到的变量,如缓存数组、信号量、等待队列头等 */

    unsignedint current_len;              //当前fifo中的数据长度

    unsignedchar mem[GLOBALFIFO_SIZE];    //全局内存

    struct semaphore sem;                 //信号量,并发访问用到

    wait_queue_head_t r_wait;             //用于阻塞读

    wait_queue_head_t w_wait;              //用于阻塞写

    struct fasync_struct *async_queue;     //异步结构体指针,用于读

};

   

b. 然后实例化一个设备结构体指针:

struct xxx_dev *xxx_devp;

    通常该结构体指针会在 xxx_open() 函数中赋给 filp->private_data,这样,在read、write、ioctl等函数中就可以直接通过filp->private_data获取设备结构体。

   

c. 实例化文件操作结果体:

static const struct file_operations xxx_fops={

    .owner = THIS_MODULE,

    .read = xxx_read,

    .write = xxx_write,

    .open = xxx_open,

    .release = xxx_release,

    .poll = xxx_poll,

    .fasync = xxx_fasync,

};

    该结构体中的成员很多,但通常需要实现的只有read、write、open、release、ioctl,而poll用于非阻塞访问的查询操作,fasync用于异步通知。

    这些函数在应用程序调用响应的系统调用时被调用,系统调用相当于应用程序与内核的接口,而驱动中的这些函数即是内核与硬件之间的接口。 xxx_fops 是在驱动模块装载时通过 cdev_init() 函数与 xxx_dev->cdev 建立起联系的。

   

d. 主设备号:

static int xxx_major = XXX_MAJOR;

    一般来说,XXX_MAJOR定义为0,而变量 xxx_major 会作为模块参数:

module_param(xxx_major,int, S_IRUGO);

    这样做是可以让用户指定主设备号:

 insmod xxx.ko xxx_major=249

    而如果用户没有指定,那么默认为0,在初始化时,xxx_init()函数中会让系统分配一个还没有被使用的主设备号,以避免主设备号冲突的情况。

 

2、驱动模块装载、卸载函数

    a. 模块装载函数 xxx_init()

    模块加载函数主要要做以下几件事情:

    1) 注册设备号:

        32位的设备号由12位的主设备号和20位的次设备号构成。

        主设备号可以指定,也可以由系统分配,为了防止主设备号冲突,由系统分配是个不错的办法。

        此设备号通常从0开始。

    2) 为设备结构体申请内存空间:

        xxx_devp= kmalloc(sizeof(struct xxx_dev), GFP_KERNEL);

    3) 初始化设备结构体中的字符设备结构体cdev :

        首先,调用 cdev_init() 将 xxx_fops 与 xxx_devp->cdev 绑定。(cdev结构体中有个ops成员, 实际上就是将该成员指向xxx_fops)

        其次,xxx_devp->cdev.owner= THIS_MODULE;

        最后,调用 cdev_add()向系统添加一个cdev,完成字符型设备的注册

    4) 初始化一些需要用到的其他变量,如信号量、等待队列头等

    5) 增加udev支持,linux2.6之后引入了udev 设备文件系统,如果不加该语句,insmod之后,还需要通过mknod创建设备文件节点,而在驱动程序添加了udev支持后,有udev的linux系统会自动创建设备文件节点。

static int __init xxx_init(void)

{

    int ret;

    /* 由主设备号和次设备号生成设备号 */

    dev_t devno = MKDEV(xxx_major,0);

  

    /* 注册设备号 */

    if(xxx_major>0)

        /* 用户设置好了主设备号,1表示注册1个设备,

           "xxx" 是设备名称,在cat /proc/devices时可以看到 */

        ret = register_chrdev_region(devno,1,"xxx");

    else {

        /* 用户没有设置主设备号,由系统自动分配主设备号,

            0表示次设备号从0开始, 1表示注册1个设备, 返回设备号 */

        ret = alloc_chrdev_region(&devno,0,1,"xxx");

        /* 由返回的设备号计算主设备号 */

        xxx_major = MAJOR(devno);

    } 

    /* 注册失败,返回错误代码*/

if (ret<0)   

return ret;

  

    /* 为设备结构体申请内存空间 */

    xxx_devp = kmalloc(sizeof(struct xxx_dev), GFP_KERNEL);

    /* 申请失败,返回错误代码,并且需要注销之前注册成功的设备号 */

    if (xxx_devp==NULL){

        ret = -ENOMEM;

        goto fail_malloc;

    }

    memset(xxx_devp,0,sizeof(struct xxx_dev));

  

    /* 初始化信号量和等待队列头 */

    sema_init(&xxx_devp->sem,1);//init_MUTEX(&xxx_devp->sem);

    init_waitqueue_head(&xxx_devp->r_wait);

    init_waitqueue_head(&xxx_devp->w_wait);



    /* 初始化字符型设备结构体cdev */

    ret = xxx_setup_cdev(xxx_devp,0);

if (ret<0)

    gotofail_add_cdev;

    return 0;

   

fail_add_cdev:

    kfree(xxx_devp);

fail_malloc:

    unregister_chrdev_region(devno,1);

    return ret;

}

 

static int xxx_setup_cdev(struct xxx_dev*dev,int index)

{ 

    int err, devno= MKDEV(xxx_major, index);

  

    /* 初始化cdev */

    cdev_init(&dev->cdev,&xxx_fops);

    dev->cdev.owner= THIS_MODULE;

  

    /* 添加字符型设备驱动 */

    err = cdev_add(&dev->cdev, devno,1);

    if(err)

        printk(KERN_ALERT"Error %d adding xxx", err);

    return err;

}

   

    b.模块卸载函数 xxx_exit()

static void __exit xxx_exit(void)

{

    /* udev支持 */

    device_destroy(xxx_devp->myclass, MKDEV(xxx_major,0));

    class_destroy(xxx_devp->myclass);

    /* 删除cdev结构 */

    cdev_del(&xxx_devp->cdev);                         

    /* 释放设备结构体内存 */

    kfree(xxx_devp);          

    /* 注销设备区域 */  

    unregister_chrdev_region(MKDEV(xxx_major,0),1);  

}

 

3. 实现结构体 xxx_fops 中的函数

a. 打开和关闭

首先来看文件打开和关闭所对应的 xxx_open() 和xxx_release()函数。

函数原型如下:

static int xxx_open(struct inode*inode,struct file *filp);

 

static int xxx_release(struct inode*inode,struct file *file);

通常在xxx_open()中要做的事情是一些硬件上的初始化,例如配置一些寄存器,申请中断等。通常还会把 xxx_devp放到 filp->private_data中:

file->private_data = key_devp;

以便在读写函数中获取设备结构体指针。

在xxx_release()中则是做和 xxx_open()相反的事情,例如:释放中断等。

 

b. 读写函数

与文件读写相关的 xxx_read()和 xxx_write()函数原型如下:

static ssize_txxx_read(struct file*filp,char __user *buf, size_t size, loff_t* ppos);

 

static ssize_t xxx_write(struct file*filp,constchar __user*buf, size_t size, loff_t*ppos)

其中buf是用户空间用于读写的指针,size是用户需要读写的字节数,ppos是文件读写指针的位置,返回值是实际读写的字节数。

在 xxx_read()中可以调用 copy_to_user()函数将数据拷贝到buf处,而在xxx_write中可以调用copy_from_user()函数从buf中读取数据。

 

c. 其他函数

ioctl用于io控制命令;

poll用于非阻塞访问的查询操作;

fasync用于异步通知等,

以后用到再细说。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值