• 第三章——字符驱动设备


    3.1、字符设备驱动基础

     设备文件通常位于/dev目录下:

    crw-rw-rw-  1 root      root           5,   0 2020-04-17 18:51 tty
    crw-rw----  1 radio     radio        237,   3 2020-04-17 18:51 ttyC0
    crw-rw----  1 radio     radio        237,   7 2020-04-17 18:51 ttyC1
    

     其中c表示字符设备。在现在的Linux系统中,设备文件通常是自动创建的,但我们还是可以通过mknod命令手动创建一个设备文件:

    # mknod /dev/vser0 c 256 0
    # ls -li /dev/vser0
    46347 crw-rw---- 1 root root 256,   0 2020-04-19 17:36 /dev/vser0
    

     mknod命令创建了一个节点,在Linux中一个节点代表一个文件,创建一个文件的最主要的工作就是分配一个新的节点(node),包含节点号(46347唯一)的分配。以ext2文件系统为例:

    /*fs/ext2/ext2.h*/
    /*
     * Structure of an inode on the disk
     */
    struct ext2_inode {
        __le16  i_mode;	/* File mode */
        __le16  i_uid;	/* Low 16 bits of Owner Uid */
        __le32  i_size;	/* Size in bytes */
        __le32  i_atime;	/* Access time */
        __le32  i_ctime;	/* Creation time */
        __le32  i_mtime;	/* Modification time */
    ......
        __le32  i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
    ......
    };
    
    

     inode成员结构在上面的结构里做了表述,在/fs/ext2/inode.c中:

    static int __ext2_write_inode(struct inode *inode, int do_sync) {
        struct ext2_inode *raw_inode = ext2_get_inode(sb, ino, &bh); //获得一个要写入磁盘的ext2_inode结构,并初始化了部分成员。
    }
    

     同理,目录本身也是一个文件,在fs/ext2/ext2.h也有struct ext2_dir_entry结构来描述目录。
    mknod的执行,将文件名、文件类型和主、次设备号等信息保存在了磁盘上。

    ext2

    3.2、如何打开一个文件?

    1. 一个进程(task_struct)的成员(task_struct->file_struct)该结构中有一个指针数组fd_array,用于维护打开文件的信息。数组fd_array的每一个元素是指向file结构的一个指针。
    2. open系统调用在内核中对应函数为sys_open,sys_open调用do_sys_open。
    3. do_sys_open调用getname函数将文件名从用户空间复制到内核空间,再调用get_unused_fd_flags获取一个没用过的文件描述符fd。
    4. 调用do_filp_open来构造一个file结构并初始化里面的成员,最重要的是将f_op成员指向驱动操作方法file_operations。通过file_operations的open函数指针可以调用驱动的打开操作。
    5. do_filp_open调用成功后,调用fd_install函数,该函数将得到的文件描述符作为访问fd_array数组的下标,让下标对应的元素指向新构造的file结构。
    6. 最后系统调用返回到应用层,将刚才的数组下标作为打开文件的文件描述符返回。

    对字符设备来说,设备号、cdev和file_operations至关重要,内核找到路径名对应的inode后,要和驱动建立连接,首先要做的就是根据inode中的设备号找到cdev,然后根据cdev找到file_operations集合。

    3.3、字符驱动设备框架

    3.3.1、注册设备号

    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/fs.h>
    
    #define VSER_MAJOR 256
    #define VSER_MINOR 0
    #define VSER_DEV_CNT 1
    #define VSER_DEV_NAME "vser"
    
    //加载模块
    static int __init vser_init(void) {
        int ret;
        dev_t dev;
        dev = MKDEV(VSER_MAJOR, VSER_MINOR); //将主次设备号合并,主设备号占12位,次设备号占20位
        ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
        if(ret)
    	goto reg_err;
        return 0;
    	
    reg_err:
        return ret;
    }
    
    //卸载模块
    static void __exit vser_exit(void) {
        printk("vser_exit
    ");
        dev_t dev;
        dev = MKDEV(VSER_MAJOR, VSER_MINOR);
        unregister_chrdev_region(dev, VSER_DEV_CNT);
    }
    
    module_init(vser_init);
    module_exit(vser_exit);
    MODULE_LICENSE("GPL");
    

     框架可在ubuntu下测试

    # make
    # make modules_install
    # depmod
    # modprobe vser
    # cat proc/devices
    

     上面描述的方法为静态注册设备号,还有一种动态注册设备号的函数:

    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
    

    3.3.2、构造并添加cdev结构体

    @@ -8,5 +8,5 @@
    #define VSER_DEV_NAME "vser"
    
    +   static struct cdev vsdev;
    +   static struct file_operations vser_ops = {
    +       .owner = THIS_MODULE,
    +   
    +   }
    
    @@ -11,5 +11,5 @@
    static int __init vser_init(void) {
        if(ret)
    	goto reg_err;
    +   cdev_init(&vsdev, &vser_ops); //cdev初始化
    +   vsdev.owner = THIS_MODULE;
    +   ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);
    +   if(ret)
    +	goto add_err;
        return 0;
    
    +add err:
    +   unregister_chrdev_region(dev, VSER_DEV_CNT);
    reg_err:
        return ret;
    }
    
    @@ -27,5 +27,5 @@
    static void __exit vser_exit(void) {
    
        dev = MKDEV(VSER_MAJOR, VSER_MINOR);
    +   cdev_del(&vsdev);
        unregister_chrdev_region(dev, VSER_DEV_CNT);
    
    }
    

     同理,cdev对象是静态定义的,也可以进行动态分配,对应的函数如下:

    struct cdev *cdev_alloc(void);
    //成功返回动态分配的cdev对象地址,失败返回NULL
    
  • 相关阅读:
    Java 中的JOption函数
    01背包与完全背包(对比)
    AC注定不平坦(大神回忆录)
    背包精讲之——01背包
    动规问题概述(待整理)
    背包九讲
    Tautology(递推)||(栈(stack))(待整理)
    深度优先和广度优先区别
    Linux下JDK、Tomcat的安装及配置
    同IP不同端口Session冲突问题
  • 原文地址:https://www.cnblogs.com/hansenn/p/12738162.html
Copyright © 2020-2023  润新知