zoukankan      html  css  js  c++  java
  • Linux内核驱动学习(三)字符型设备驱动之初体验

    Linux字符型设备驱动之初体验

    前言

    驱动总共分为字符型设备驱动,块设备驱动,网络设备驱动。对于字符型设备驱动的资料,网上比较多,《Linux Kernel Driver》这本书可以了解一下,对于学习Linux驱动有很大的帮助,当然还有很多优秀的书籍,暂不一一列举,本文简单总结了在学习字符型设备驱动的过程中遇到的问题,以及对该类驱动的理解。

    框架

    在这里插入图片描述

    字符型设备

    什么是字符型设备?字符型以字符(Byte/Char)为单位进行数据传输的设备,如键盘,串口等等设备,所以Linux环境编程中文件I/O进行操作的系统接口如openreadwriteclose等等,在字符型设备驱动中同样需要支持这些接口。这里会用到file_operations结构体,在后面会讲到。

    程序实现

    下面是一个简单字符型设备驱动程序,可以在系统注册一个字符型设备驱动,目前未实现openreadwriteclose等接口。

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/cdev.h>
    #include <linux/fs.h>
    #include <linux/slab.h>
    
    #define DRIVER_DATA_SIZE 	4096
    static int major_dev_index = 0;
    
    struct cnc_character_st{
    	struct cdev device;
    	u8	data[DRIVER_DATA_SIZE];
    };
    static struct cnc_character_st *character_dev;
    //TODO
    static ssize_t cnc_character_read (struct file * fd, char __user * data, size_t len, loff_t * offset){
    	ssize_t ret = 0;
        printk("%s call
    ",__func__);
    	return ret;
    }
    //TODO
    static ssize_t cnc_character_write (struct file * fd, const char __user * data, size_t len, loff_t * offset){
    	ssize_t ret = 0;
    	return ret;
    }
    //TODO
    static long cnc_character_unlocked_ioctl (struct file * fd, unsigned int data, unsigned long cmd){
    	long ret = 0;
    	return ret;
    }
    //TODO
    static int cnc_character_open (struct inode * node, struct file * fd){
    	int ret = 0;
    	return ret;
    }
    //TODO
    static int cnc_character_release (struct inode * node, struct file * fd){
    	int ret = 0;
    	return ret;
    }
    
    static const struct file_operations cnc_character_ops = {
    	.owner = THIS_MODULE,
    	.read = cnc_character_read,
    	.write = cnc_character_write,
    	.open = cnc_character_open,
    	.unlocked_ioctl = cnc_character_unlocked_ioctl,
    	.release = cnc_character_release,
    };
    
    static int register_device(struct cnc_character_st *mdev,int major_dev_index,int minor_dev_index){
    
    	int ret = 0;
    	int	dev_no = MKDEV(major_dev_index, minor_dev_index);
    	// 初始化dev 
    	cdev_init(&mdev->device, &cnc_character_ops);
    	mdev->device.owner = THIS_MODULE;
    
    	ret = cdev_add(&mdev->device,dev_no,1);
    
    	if(ret){
    		printk(KERN_ERR "cdev add device failed
    ");
    	}
    	return ret;
    }
    
    static int unregister_device(struct cnc_character_st *mdev){
    	int ret= 0;
    	kfree(character_dev);
    	return ret;
    }
    
    static int __init cnc_character_init(void){
    
    	int ret = 0;
    
    	dev_t devno = MKDEV(major_dev_index, 0);
    
    	if(major_dev_index){
    		ret = register_chrdev_region(devno, 1, "cnc_character");
    	}else{
    		ret = alloc_chrdev_region(&devno, 0, 1, "cnc_character");
    		major_dev_index = MAJOR(devno);
    	}
    
    	if(ret < 0){
    		return ret;
    	}
    	character_dev = kmalloc(sizeof(struct cnc_character_st),GFP_KERNEL);
    
    	if(!character_dev){
    		printk("%s failed malloc character_dev call
    ",__func__);
    		ret = -ENOMEM;
    		goto failed;
    	}else{
    		printk("%s success malloc character_dev call
    ",__func__);
    
    	}
    	register_device(character_dev,major_dev_index,0);
    	return 0;
    	
    failed:
    	unregister_chrdev_region(devno, 1);
    	return ret;
    }
    module_init(cnc_character_init);
    
    
    static void __exit cnc_character_exit(void){
    	printk("%s call
    ",__func__);
    	unregister_device(character_dev);
    }
    module_exit(cnc_character_exit);
    
    MODULE_AUTHOR("zhaojunhui@cncgroup.top");
    MODULE_VERSION("1.0");
    MODULE_LICENSE("GPL");
    

    cdev

    linux/cdev.h中可以阅读相关字符型设备驱动的信息,其中包括cdev结构体可以做一下分析,先定位到源码做一下分析

    #ifndef _LINUX_CDEV_H
    #define _LINUX_CDEV_H
    #include <linux/kobject.h>
    #include <linux/kdev_t.h>
    #include <linux/list.h>
    struct file_operations;
    struct inode;
    struct module;
    struct cdev {
            struct kobject kobj;
            struct module *owner;
            const struct file_operations *ops;
            struct list_head list;
            dev_t dev;
            unsigned int count;
    };
    void cdev_init(struct cdev *, const struct file_operations *);
    struct cdev *cdev_alloc(void);
    void cdev_put(struct cdev *p);
    int cdev_add(struct cdev *, dev_t, unsigned);
    void cdev_del(struct cdev *);
    void cd_forget(struct inode *);
    #endif
    

    其中包括结构体cdev和cdev的一系列函数接口cdev_initcdev_alloccdev_putcdev_addcdev_delcd_forget

    kobj

    kobject是所有设备驱动模型的基类,而cdev可以理解为是它的派生类,这里使用了面向对象的思想,通过访问cdev中的kobj成员,就能使用kobject中所有功能。关于kobject的详细内容可以参考内核文档Documentation/kobject.txt

    owner

    首先明确一点的是ownerstruct module的指针变量,owner=THIS_MODULE;,这里将指针指向当前的模块,关于THIS_MODULE以及struct module的知识可以参考这篇博客

    file_operations

    这个结构体位于/linux/include/fs.h,代码如下。

    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 (*iterate) (struct file *, struct dir_context *);
            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 **, 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 *);
    #endif
    };
    

    file_operations定义了很多I/O操作接口,这里同样使用了面向对象编程的思想,每个接口可以在重新定义file_operations结构体变量的时候,重新赋于自定义功能的函数,如下,可以理解readwriteopenunlocked_ioctlrelease是对抽象函数的实现。

    static const struct file_operations cnc_character_ops = {
    	.owner = THIS_MODULE,
    	.read = cnc_character_read,
    	.write = cnc_character_write,
    	.open = cnc_character_open,
    	.unlocked_ioctl = cnc_character_unlocked_ioctl,
    	.release = cnc_character_release,
    };
    

    dev_t

    设备注册过程

    设备的初始化在函数cnc_character_init中完成具体的功能实现,主要分为两个部分,设备号的申请和设备的注册。其中设备注册单独封装到register_device函数中。

    申请设备号

    dev_t devno = MKDEV(major_dev_index, 0);
    
    if(major_dev_index){
        ret = register_chrdev_region(devno, 1, "cnc_character");
    }else{
        ret = alloc_chrdev_region(&devno, 0, 1, "cnc_character");
        major_dev_index = MAJOR(devno);
    }
    

    注册设备

    character_dev = kmalloc(sizeof(struct cnc_character_st),GFP_KERNEL);
    
    if(!character_dev){
        printk("%s failed malloc character_dev call
    ",__func__);
        ret = -ENOMEM;
        goto failed;
    }else{
        printk("%s success malloc character_dev call
    ",__func__);
    
    }
    register_device(character_dev,major_dev_index,0);
    

    register_device

    register_device中,主要用到了cdev提供的函数接口。

    cdev_init初始化一个字符型设备并传入自定义的file_operations类型变量cnc_character_ops

    cdev_add将初始化的字符型设备添加到内核,并分配已经申请好的设备号。

    static int register_device(struct cnc_character_st *mdev,int major_dev_index,int minor_dev_index){
    
    	int ret = 0;
    	int	dev_no = MKDEV(major_dev_index, minor_dev_index);
    	// 初始化dev 
    	cdev_init(&mdev->device, &cnc_character_ops);
    	mdev->device.owner = THIS_MODULE;
    
    	ret = cdev_add(&mdev->device,dev_no,1);
    
    	if(ret){
    		printk(KERN_ERR "cdev add device failed
    ");
    	}
    	return ret;
    }
    

    如何构建

    模块编译

    使用这个Makefile

    KVERS = $(shell uname -r)
    # Kernel modules
    obj-m += demo_character.o
    # Specify flags for the module compilation.
    EXTRA_CFLAGS=-g -O0
    build: kernel_modules
    kernel_modules:
            make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
    clean:
            make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
    

    内核编译

    Makefile

    obj-$(CONFIG_DEMO_CHARACTER_DRIVER) +=demo_character.o
    

    Kconfig

    menuconfig DEMO_DRIVERS
        tristate "demo drivers"
    config DEMO_CHARACTER_DRIVER
        tristate "the most simplest character driver"
        help
            character driver
    endif
    

    总结

    总体上来说,字符型设备驱动框架还是相对简单的,通过这次学习加深了对cdev的认识和linux内核源码中面向对象的设计思想,但是这里还没有对devfssysfs做相应的介绍,后面继续学习这两者的区别以及总线驱动模型,总之,加油吧。

    参考

    https://blog.csdn.net/lucky_liuxiang/article/details/83413946
    https://www.cnblogs.com/helloahui/p/3677192.html
    https://blog.csdn.net/jk110333/article/details/8563647

  • 相关阅读:
    事件修饰符(.passive)
    vue中$nextTick函数(异步dom更新)
    使用ellipsis时的问题和控制文字n行显示(webkit-box方法)
    font-size 设为0 解决行内元素边距问题(空白字符带来的间距问题)
    Vue-eBookReader 学习笔记(阅读进度部分)
    Vue-eBookReader 学习笔记(阅读器解析和渲染部分)
    mysql 免密登录
    ansible 复制文件到本地 localhost
    对象存储测试工具 cosbench
    Mac 下安装 mongodb
  • 原文地址:https://www.cnblogs.com/unclemac/p/12783411.html
Copyright © 2011-2022 走看看