zoukankan      html  css  js  c++  java
  • Linux 设备驱动之字符设备

    参考转载博客:http://blog.chinaunix.net/uid-26833883-id-4369060.html

                            https://www.cnblogs.com/xiaojiang1025/p/6181833.html

                            http://blog.csdn.net/yueqian_scut/article/details/45938557

                           http://www.cnblogs.com/geneil/archive/2011/12/03/2272869.html

                           http://blog.csdn.net/z781567363r/article/details/23422743

                          http://blog.csdn.net/dcx1205/article/details/45877345

                         http://blog.chinaunix.net/uid-21556133-id-3408488.html

                        https://www.cnblogs.com/chen-farsight/p/6177870.html

                     http://blog.csdn.net/zqixiao_09/article/details/50850004

                     http://blog.csdn.net/maopig/article/details/7195048

                  http://blog.csdn.net/yueqian_scut/article/details/46771595

                 http://www.linuxidc.com/Linux/2017-02/140227.htm

      https://blog.csdn.net/coding__madman/article/details/51347290

      https://blog.csdn.net/wuheshi/article/details/52913970

         驱动程序就是向下控制硬件,向上提供接口,这里的向上提供的接口最终对应到应用层有三种方式:设备文件,/proc,/sys,其中最常用的就是使用设备文件,而Linux设备中用的最多的就是字符设备,本文就以字符设备为例来分析创建并打开一个字符设备的文件内部机制。每一个字符设备或块设备都在/dev目录下对应一个设备文件。linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备                   

     一)字符设备概述

    1、linux系统将设备分为3类:字符设备、块设备、网络设备。使用驱动程序:

    1、字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。
    2、块设备:是指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。

    二、字符设备流程(从上层应用到底层驱动)

    (1)创建设备文件

    插入的设备模块,我们就可以使用cat /proc/devices命令查看当前系统注册的设备,但是我们还没有创建相应的设备文件,用户也就不能通过文件访问这个设备。设备文件的inode应该是包含了这个设备的设备号,操作方法集指针等信息,这样我们就可以通过设备文件找到相应的inode进而访问设备。创建设备文件的方法有两种,手动创建自动创建手动创建设备文件就是使用mknod /dev/xxx 设备类型 主设备号 次设备号的命令创建,所以首先需要使用cat /proc/devices查看设备的主设备号并通过源码找到设备的次设备号,需要注意的是,理论上设备文件可以放置在任何文件加夹,但是放到"/dev"才符合Linux的设备管理机制,这里面的devtmpfs是专门设计用来管理设备文件的文件系统。设备文件创建好之后就会和创建时指定的设备绑定,即使设备已经被卸载了,如要删除设备文件,只需要像删除普通文件一样rm即可。理论上模块名(lsmod),设备名(/proc/devices),设备文件名(/dev)并没有什么关系,完全可以不一样,但是原则上还是建议将三者进行统一,便于管理。

    除了使用蹩脚的手动创建设备节点的方式,我们还可以在设备源码中使用相应的措施使设备一旦被加载就自动创建设备文件,自动创建设备文件需要我们在编译内核的时候或制作根文件系统的时候就好相应的配置:

    Device Drivers --->
            Generic Driver Options --->
                [*]Maintain a devtmpfs filesystem to mount at /dev
                [*] Automount devtmpfs at /dev,after the kernel mounted the rootfs

    OR
    制作根文件系统的启动脚本写入

    mount -t sysfs none sysfs /sys
    mdev -s //udev也行

    有了这些准备,只需要导出相应的设备信息到"/sys"就可以按照我们的要求自动创建设备文件。内核给我们提供了相关的API

    class_create(owner,name);
    struct device *device_create_vargs(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, va_list vargs);
    
    void class_destroy(struct class *cls);   
    void device_destroy(struct class *cls, dev_t devt);

    有了这几个函数,我们就可以在设备的xxx_init()xxx_exit()中分别填写以下的代码就可以实现自动的创建删除设备文件

        /* 在/sys中导出设备类信息 */
        cls = class_create(THIS_MODULE,DEV_NAME);
    
        /* 在cls指向的类中创建一组(个)设备文件 */
        for(i= minor;i<(minor+cnt);i++){
            devp = device_create(cls,NULL,MKDEV(major,i),NULL,"%s%d",DEV_NAME,i);
        }  
    
        /* 在cls指向的类中删除一组(个)设备文件 */
        for(i= minor;i<(minor+cnt);i++){
            device_destroy(cls,MKDEV(major,i));
        }
    
        /* 在/sys中删除设备类信息 */
        class_destroy(cls);             //一定要先卸载device再卸载class

    Linux的世界里一切都是文件,所有硬件设备操作到应用层都会被抽象成文件的操作。我们知道如果应用层要访问硬件设备,它必定要调用硬件对应的驱动程序。当我们在Linux中创建一个文件时,就会在相应的文件系统创建一个inode与之对应,文件实体和文件的inode是一一对应的,创建好一个inode会存在存储器中,第一次open就会将inode在内存中有一个备份,同一个文件被多次打开并不会产生多个inode,当所有被打开的文件都被close之后,inode在内存中的实例才会被释放。既然如此,当我们使用mknod(或其他方法)创建一个设备文件时,也会在文件系统中创建一个inode,这个inode和其他的inode一样,用来存储关于这个文件的静态信息(不变的信息),包括这个设备文件对应的设备号,文件的路径以及对应的驱动对象etc。inode作为VFS四大对象之一,在驱动开发中很少需要自己进行填充,更多的是在open()方法中进行查看并根据需要填充我们的file结构。
    对于不同的文件类型,inode被填充的成员内容也会有所不同,以创建字符设备为例,我们知道,add_chrdev_region其实是把一个驱动对象和一个(一组)设备号联系到一起。而创建设备文件,其实是把设备文件设备号联系到一起。至此,这三者就被绑定在一起了。这样,内核就有能力创建一个struct inode实例了,下面是4.8.5内核中的inode。这个inode是VFS的inode,是最具体文件系统的inode的进一步封装,也是驱动开发中关心的inode,针对具体的文件系统,还有struct ext2_inode_info 等结构。

    //include/linux/fs.h
     596 /*
     597  * Keep mostly read-only and often accessed (especially for
     598  * the RCU path lookup and 'stat' data) fields at the beginning
     599  * of the 'struct inode'
     600  */
     601 struct inode {
     602         umode_t                 i_mode;
     603         unsigned short          i_opflags;
     604         kuid_t                  i_uid;
     605         kgid_t                  i_gid;
     606         unsigned int            i_flags;
     607 
     608 #ifdef CONFIG_FS_POSIX_ACL
     609         struct posix_acl        *i_acl;
     610         struct posix_acl        *i_default_acl;
     611 #endif
     612 
     613         const struct inode_operations   *i_op;
     614         struct super_block      *i_sb;
     615         struct address_space    *i_mapping;
     616 
     617 #ifdef CONFIG_SECURITY
     618         void                    *i_security;
     619 #endif
     620 
     621         /* Stat data, not accessed from path walking */
     622         unsigned long           i_ino;
     623         /*
     624          * Filesystems may only read i_nlink directly.  They shall use the
     625          * following functions for modification:
     626          *
     627          *    (set|clear|inc|drop)_nlink
     628          *    inode_(inc|dec)_link_count
     629          */
     630         union {
     631                 const unsigned int i_nlink;
     632                 unsigned int __i_nlink;
     633         };
     634         dev_t                   i_rdev;
     635         loff_t                  i_size;
     636         struct timespec         i_atime;
     637         struct timespec         i_mtime;
     638         struct timespec         i_ctime;
     639         spinlock_t              i_lock; /* i_blocks, i_bytes, maybe i_size */
     640         unsigned short          i_bytes;
     641         unsigned int            i_blkbits;
     642         blkcnt_t                i_blocks;
     643                                 
     644 #ifdef __NEED_I_SIZE_ORDERED
     645         seqcount_t              i_size_seqcount;
     646 #endif
     647 
     648         /* Misc */
     649         unsigned long           i_state;
     650         struct rw_semaphore     i_rwsem;
     651 
     652         unsigned long           dirtied_when;   /* jiffies of first dirtying */
     653         unsigned long           dirtied_time_when;
     654 
     655         struct hlist_node       i_hash;
     656         struct list_head        i_io_list;      /* backing dev IO list */
     657 #ifdef CONFIG_CGROUP_WRITEBACK
     658         struct bdi_writeback    *i_wb;          /* the associated cgroup wb */
     659 
     660         /* foreign inode detection, see wbc_detach_inode() */
     661         int                     i_wb_frn_winner;
     662         u16                     i_wb_frn_avg_time;
     663         u16                     i_wb_frn_history;
     664 #endif
     665         struct list_head        i_lru;          /* inode LRU list */
     666         struct list_head        i_sb_list;
     667         struct list_head        i_wb_list;      /* backing dev writeback list */
     668         union {
     669                 struct hlist_head       i_dentry;
     670                 struct rcu_head         i_rcu;
     671         };
     672         u64                     i_version;
     673         atomic_t                i_count;
     674         atomic_t                i_dio_count;
     675         atomic_t                i_writecount;
     676 #ifdef CONFIG_IMA
     677         atomic_t                i_readcount; /* struct files open RO */
     678 #endif
     679         const struct file_operations    *i_fop; /* former ->i_op->default_file_ops */
     680         struct file_lock_context        *i_flctx;
     681         struct address_space    i_data;
     682         struct list_head        i_devices;
     683         union {
     684                 struct pipe_inode_info  *i_pipe;
     685                 struct block_device     *i_bdev;
     686                 struct cdev             *i_cdev;
     687                 char                    *i_link;
     688                 unsigned                i_dir_seq;
     689         };
     690 
     691         __u32                   i_generation;
     692 
     693 #ifdef CONFIG_FSNOTIFY
     694         __u32                   i_fsnotify_mask; /* all events this inode cares about */
     695         struct hlist_head       i_fsnotify_marks;
     696 #endif
     697 
     698 #if IS_ENABLED(CONFIG_FS_ENCRYPTION)
     699         struct fscrypt_info     *i_crypt_info;
     700 #endif
     701 
     702         void                    *i_private; /* fs or device private pointer */
     703 };    

    这里面与本文相关的成员主要有:

    struct inode
    --602-->i_mode表示访问权限控制
    --604-->UID
    --605-->GID
    --606-->i_flags文件系统标志
    --630-->硬链接数计数
    --635-->i_size以字节为单位的文件大小
    --636-->最后access时间
    --637-->最后modify时间
    --638-->最后change时间
    --669-->i_dentry; //目录项链表
    --673-->i_count引用计数,当引用计数变为0时,会释放inode实例
    --675-->i_writecount写者计数
    --679-->创建设备文件的时候i_fops填充的是def_chr_fops,blk_blk_fops,def_fifo_fops,bad_sock_fops之一,参见创建过程中调用的init_special_inode()
    --683-->特殊文件类型的union,pipe,cdev,blk.link etc,i_cdev表示这个inode属于一个字符设备文件,本文中创建设备文件的时候会把与之相关的设备号的驱动对象cdev拿来填充
    --702-->inode的私有数据

    上面的几个成员只有struct def_chr_fops 值得一追,后面有大用:

    //fs/char_dev.c
    429 const struct file_operations def_chr_fops = { 
    430         .open = chrdev_open,
    431         .llseek = noop_llseek,
    432 };

    struct file

    Linux内核会为每一个进程维护一个文件描述符表,这个表其实就是struct file[]的索引。open()的过程其实就是根据传入的路径填充好一个file结构并将其赋值到数组中并返回其索引。下面是file的主要内容

    //include/linux/fs.h
     877 struct file {
     878         union {
     879                 struct llist_node       fu_llist;
     880                 struct rcu_head         fu_rcuhead;
     881         } f_u;
     882         struct path             f_path;
     883         struct inode            *f_inode;       /* cached value */
     884         const struct file_operations    *f_op;
     885 
     886         /*                                            
     887          * Protects f_ep_links, f_flags.
     888          * Must not be taken from IRQ context.
     889          */
     890         spinlock_t              f_lock;
     891         atomic_long_t           f_count;
     892         unsigned int            f_flags;
     893         fmode_t                 f_mode;
     894         struct mutex            f_pos_lock;
     895         loff_t                  f_pos;
     896         struct fown_struct      f_owner;
     897         const struct cred       *f_cred;
     898         struct file_ra_state    f_ra;f
     904         /* needed for tty driver, and maybe others */
     905         void                    *private_data;
     912         struct address_space    *f_mapping;
     913 } __attribute__((aligned(4)));  /* lest something weird decides that 2 is OK */

    struct file
    --882-->f_path里存储的是open传入的路径,VFS就是根据这个路径逐层找到相应的inode
    --883-->f_inode里存储的是找到的inode
    --884-->f_op里存储的就是驱动提供的file_operations对象,这个对象在open的时候被填充,具体地,应用层的open通过层层搜索会调用inode.i_fops->open,即chrdev_open()
    --891-->f_count的作用是记录对文件对象的引用计数,也即当前有多少个使用CLONE_FILES标志克隆的进程在使用该文件。典型的应用是在POSIX线程中。就像在内核中普通的引用计数模块一样,最后一个进程调用put_files_struct()来释放文件描述符。
    --892-->f_flags当打开文件时指定的标志,对应系统调用open的int flags,比如驱动程序为了支持非阻塞型操作需要检查这个标志是否有O_NONBLOCK。
    --893-->f_mode;对文件的读写模式,对应系统调用open的mod_t mode参数,比如O_RDWR。如果驱动程序需要这个值,可以直接读取这个字段。
    --905-->private_data表示file结构的私有数据

    我在Linux设备管理(二)_从cdev_add说起一文中已经分析过chrdev_open(),这里仅作概述。

    //fs/chr_dev.c
    348 /*
    349  * Called every time a character special file is opened
    350  */
    351 static int chrdev_open(struct inode *inode, struct file *filp)
    352 {
                /* 搜索cdev */
                ...
    390         replace_fops(filp, fops);
    391         if (filp->f_op->open) {
    392                 ret = filp->f_op->open(inode, filp);
    393                 if (ret)
    394                         goto out_cdev_put;
    395         } 
                ...
    402 }

    可以看出,这个函数有三个任务(划重点!!!):

    chrdev_open()
    --352-389-->利用container_of等根据inode中的成员找到相应的cdev
    --390-->用cdev.fops替换filp->f_op,即填充了一个空的struct file的f_op成员。
    --392-->回调替换之后的filp->f_op->open,由于替换,这个其实就是cdev.fops

    至此,我们知道了我们写的驱动中的open()在何时会被回调,这样我们就可以实现很多有意思的功能,比如,
    我们可以在open中通过inode->cdev来识别具体的设备,并将其私有数据隐藏到file结构的private_data中,进而识别同一个驱动操作一类设备;
    我们也可以在回调cdev.fops->open()阶段重新填充file结构的fop,进而实现同一个驱动操作不同的设备,这种思想就是内核驱动中常用的分层!
    最后总结一下这些结构之间的关系:

  • 相关阅读:
    命令拷屏之网络工具
    PHP 设计模式 笔记与总结(1)命名空间 与 类的自动载入
    Java实现 计蒜客 1251 仙岛求药
    Java实现 计蒜客 1251 仙岛求药
    Java实现 计蒜客 1251 仙岛求药
    Java实现 蓝桥杯 算法训练 字符串合并
    Java实现 蓝桥杯 算法训练 字符串合并
    Java实现 蓝桥杯 算法训练 字符串合并
    Java实现 LeetCode 143 重排链表
    Java实现 LeetCode 143 重排链表
  • 原文地址:https://www.cnblogs.com/linux-xin/p/8086073.html
Copyright © 2011-2022 走看看