字符驱动实例

字符设备驱动是linux驱动比较基础的一类设备,它比较典型的特征是按字节流的形式进行操作。下面分两种方式来介绍。

1、cdev实现的字符驱动

linux内核提供了比较成熟的cdev操作步骤,方便驱动开发者进行编写

  • 申请设备号

设备号包含主设备号和次设备号,主设备号一般标识这某一类的设备,次设备号标识这类设备具体是第几个设备,例如串口设备

[root@jingdomain ~]# ls /dev/ttyS* -l
crw--w----. 1 root tty     4, 64 Mar 15 22:19 /dev/ttyS0
crw-rw----. 1 root dialout 4, 65 Mar 15 22:19 /dev/ttyS1
crw-rw----. 1 root dialout 4, 66 Mar 15 22:19 /dev/ttyS2
crw-rw----. 1 root dialout 4, 67 Mar 15 22:19 /dev/ttyS3

4个串口设备节点的主设备号相同,都为4,但每一个串口设备的次设备号各不相同,分别标识各自的设备。

申请接口如下:

/**    
   * alloc_chrdev_region() - register a range of char device numbers    
   * @dev: output parameter for first assigned number    
   * @baseminor: first of the requested range of minor numbers    
   * @count: the number of minor numbers required    
   * @name: the name of the associated device or driver    
   *    
   * Allocates a range of char device numbers.  The major number will be    
   * chosen dynamically, and returned (along with the first minor number)    
   * in @dev.  Returns zero or a negative error code.    
   */    
  int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,    
              const char *name)    

使用者需要传入dev_t 指针类型的变量,指定次设备号的起始号(一般为0),次设备号的个数,最后传入此设备号标识的设备名称。例如如下代码:

ret = alloc_chrdev_region(&devt, 0, count, "cdev_drv");    
    if (ret) {    
         printk("failed to alloc chrdev dev_t\n");    
         return ret;    
     }    
  • 初始化cdev结构

首先,定义一个全局的strcut cdev指针变量,通过cdev_alloc申请空间,并通过cdev_init将single_fops结构关联起来,形如下面的代码:

static struct cdev *single_cdev;
 //alloc cdev
single_cdev = cdev_alloc();
if (!single_cdev) {
    printk("cdev alloc failed\n");
    goto unregister_chrdev;
}
  
//init cdev struct
cdev_init(single_cdev, &single_fops);

single_fops变量是struct  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 (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iopoll)(struct kiocb *kiocb, struct io_comp_batch *,
            unsigned int flags);
    int (*iterate) (struct file *, struct dir_context *);
    int (*iterate_shared) (struct file *, struct dir_context *);
    __poll_t (*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 *);
    unsigned long mmap_supported_flags;
    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 (*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 **, void **);
    long (*fallocate)(struct file *file, int mode, loff_t offset,
              loff_t len);
    void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
    unsigned (*mmap_capabilities)(struct file *);
#ifndef CONFIG_MMU
    unsigned (*mmap_capabilities)(struct file *);
#endif
    ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
            loff_t, size_t, unsigned int);
    loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
                   struct file *file_out, loff_t pos_out,
                   loff_t len, unsigned int remap_flags);
    int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;    

可以看到这个结构体中定义了很多函数指针,用于实现各个操作功能,如open、read、write、ioctl、poll等等,而这些接口,与用户态的open read write ioctl poll是相互对应的,从而实现了用户态与驱动的交互。

对于本文,简单实现如下:

static int    
single_open(struct inode *inode, struct file *file)
{                                                                                                                   
    return 0;
}
static const struct  file_operations single_fops = {
    .owner = THIS_MODULE,          
    .open = single_open,                                        
};
  • 为cdev设备填充设备号信息

通过cdev_add接口,将上面刚刚申请的设备号关连到single_cdev中

ret = cdev_add(single_cdev, devt, count);
if (ret) {
    printk("cdev_add failed\n");
	goto free_cdev;
}
  • 创建设备类

通过class_create接口为此驱动创建一个设备类,如cdev_class,此时在系统/sys/class目录下就会出现cdev_class目录,里面有此驱动的详细的设备信息。

cdev_class = class_create(THIS_MODULE, "cdev_class");
  if (IS_ERR(cdev_class)) {
      ret = PTR_ERR(cdev_class);
      goto free_cdev;
 }
  • 创建设备节点

通过device_create对各个设备进行创建,如果申请了多个次设备号,此时在/dev/目录下就会看到几个设备,比如count为3,就会生成3个设备,如/dev/cdev0 /dev/cdev1 /dev/cdev2

代码如下:

for (i = 0; i < count; i++) {
    dev[i] = device_create(cdev_class, NULL, MKDEV(MAJOR(devt), i), NULL, "cdev%d", i);
    if (IS_ERR(dev)) {
        ret = PTR_ERR(dev);
        goto free_class;
    }
}

至此,cdev驱动的编写骨架就完成了,完成代码如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>

static dev_t devt;
static struct cdev *single_cdev;
struct class *cdev_class;
struct device *dev[100];
static int count = 1;
module_param(count, int, 0644);
MODULE_PARM_DESC(count, "cdev minor count (default: 1)");

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

static const struct  file_operations single_fops = {
        .owner = THIS_MODULE,
        .open = single_open,
};

static int __init
cdev_drv_init(void)
{
        int ret;
        int i;
        // alloc dev_t
        ret = alloc_chrdev_region(&devt, 0, count, "cdev_drv");
        if (ret) {
                printk("failed to alloc chrdev dev_t\n");
                return ret;
        }

        //alloc cdev
        single_cdev = cdev_alloc();
        if (!single_cdev) {
                printk("cdev alloc failed\n");
                goto unregister_chrdev;
        }

        //init cdev struct
        cdev_init(single_cdev, &single_fops);

        ret = cdev_add(single_cdev, devt, count);
        if (ret) {
                printk("cdev_add failed\n");
                goto free_cdev;
        }

        cdev_class = class_create(THIS_MODULE, "cdev_class");
        if (IS_ERR(cdev_class)) {
                ret = PTR_ERR(cdev_class);
                goto free_cdev;
        }

        for (i = 0; i < count; i++) {
                dev[i] = device_create(cdev_class, NULL, MKDEV(MAJOR(devt), i), NULL, "cdev%d", i);
                if (IS_ERR(dev)) {
                        ret = PTR_ERR(dev);
                        goto free_class;
                }
        }

        printk("cdev drv init success!!!\n");

        return 0;

free_class:
        class_destroy(cdev_class);
free_cdev:
        cdev_del(single_cdev);
unregister_chrdev:
        unregister_chrdev_region(devt, 1);
        return -1;
}

static void __exit
cdev_drv_exit(void)
{
        int i;
        for (i = 0; i < count; i++) {
                device_destroy(cdev_class, MKDEV(MAJOR(devt), i));
        }

        class_destroy(cdev_class);

        if (single_cdev)
                cdev_del(single_cdev);

        unregister_chrdev_region(devt, count);
        printk("cdev drv exit success\n");
}

module_init(cdev_drv_init);
module_exit(cdev_drv_exit);

MODULE_LICENSE("GPL v2");

通过编译,insmod命令加载到内核, 即可出现/dev/cdev0

2、misc实现的字符驱动

misc顾名思义为杂项,内核中主要针对一些无法进行分类的字符设备,而且实现方式比cdev简单的多,通过一个misc_register即可完成驱动的注册。下面我们简单介绍一下步骤:

1、定义misc_device结构体

static struct miscdevice single_dev = {

        .minor = MISC_DYNAMIC_MINOR,
        .fops = &single_ops,
        .name = "single_misc",

};

miscdevice结构体中主要对如上三个成员进行赋值,

①、指定minor次设备号,本文使用MISC_DYNAMIC_MINOR,代表让系统自动分配次设备号,用户也可以自行指定系统还未使用的次设备号。

②、填充fops指针,填充用户自定义的fops结构体指针即可,在本文中使用single_ops。

③、为这个misc 设备定义驱动名称,如single_misc,此名字与/dev/"devname"对应。

2、定义fops结构体

static const struct  file_operations single_fops = {
        .owner = THIS_MODULE,
        .open = single_open,
};

本文作为一个demo例子,简单地填充了两个成员,owner及open。

3、注册misc

misc设备通过misc_register函数进行注册到内核中,与之对应的卸载通过misc_degister进行实现

 ret = misc_register(&single_dev);
 misc_deregister(&single_dev);

misc驱动的实现的大致步骤就介绍完了,现在贴出完整的demo例子如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>


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

        return 0;
}

static const struct  file_operations single_fops = {
        .owner = THIS_MODULE,
        .open = single_open,
};

static struct miscdevice single_dev = {

        .minor = MISC_DYNAMIC_MINOR,
        .fops = &single_fops,
        .name = "single_misc",

};

static int __init
misc_drv_init(void)
{
        int ret;

        ret = misc_register(&single_dev);
        if (ret)
                return ret;

        printk("misc drv init success\n");

        return 0;
}

static void __exit
misc_drv_exit(void)
{
        misc_deregister(&single_dev);

        printk("misc drv exit success\n");
}

module_init(misc_drv_init);
module_exit(misc_drv_exit);

MODULE_LICENSE("GPL v2");

3、总结

        在内核中,实现字符驱动主要是上面介绍的两种方式,cdev及misc。cdev使用步骤比较复杂,但一般用于抽象某类字符设备,比如uio(后面会详细介绍),代表一类的字符驱动,主要特征是主设备号相同,此设备号不同的一类驱动。而misc字符驱动应用于实现简单、不太好分类的驱动。用户可以根据这两种特征,选择性使用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值