Linux进阶-字符设备

目录

Linux内核是怎样设计字符设备的?

文件系统调用系统IO的内核处理过程

硬件层原理

驱动层原理

文件系统层原理

设备号的组成与哈希表

hash table(哈希表、散列表,数组和链表的混合使用)

设备号管理

关键的数据结构:char_device_struct(存放在ebf-buster-linux/fs/char_dev.c)

关键的函数:__register_chrdev_region(存放在ebf-buster-linux/fs/char_dev.c)

保存file_operation结构体

关键数据结构:cdev(存放在ebf-buster-linux/include/linux/cdev.h)

关键数据结构:kobj_map(与哈希表有关,存放在ebf-buster-linux/drivers/base/map.c)

关键函数:cdev_init(存放在ebf-buster-linux/fs/char_dev.c)

关键函数:cdev_add(存放在ebf-buster-linux/fs/char_dev.c)


Linux内核是怎样设计字符设备的?

文件系统调用系统IO的内核处理过程

inode索引节点是文件系统中的一种数据结构,用于存储文件的元数据信息,包括文件的大小、访问权限、创建时间、修改时间等。每个文件在文件系统中都对应着一个唯一的inode节点,通过inode节点可以查找到文件的实际数据块的位置。inode节点通常存储在磁盘的inode表中,文件系统通过inode号来访问和管理文件。

file_operation结构体是函数指针表,用于定义文件的操作方法。当应用程序通过文件描述符打开文件时,内核会根据文件描述符找到对应的inode节点,并获取与inode节点关联的file_operation表。通过file_operation表中的函数指针,内核可以调用相应的函数来执行文件操作,如open、read、write、close等。不同内核可以有不同的file_operation表,因为不同的内核可能有不同的文件操作方法和特性。

task_struct结构体用于描述和管理进程,内容很多很复杂。里面有个成员变量是struct files_struct *files,用于存储与进程相关的文件描述符表的信息(文件描述符表记录了进程打开的文件以及相应的操作权限等信息)。要想获取进程的文件描述符相关信息,需要通过访问task_struct结构体的files指针来获取files_struct结构体,进而访问文件描述符表的信息。

files_struct结构体用于跟踪和管理进程打开的文件。fd_array[]为指针数组,用于存储进程打开的文件描述符的信息,即每个文件描述符都对应一个files_struct。通过fd_array数组可以快速访问和操作这些文件描述符,数组索引值对应着文件描述符的值。

硬件层原理

思路:把底层寄存器配置操作放在文件操作接口里,新建一个文件绑定该文件操作接口,应用程序通过操作指定文件来设置底层寄存器。

基本接口实现:查原理图,数据手册,确定底层需要配置的寄存器。类似于裸机开发。实现一个文件的底层操作接口,这是文件的基本特征。

struct file_operations存放在ebf-buster-linux/include/linux/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 *);
        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 *);
#endif
        ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, loff_t, size_t, unsigned int);
        int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t, u64);
        int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t, u64);
        int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;

驱动层原理

把file_operations文件操作接口注册到内核,内核通过主次设备号来记录它

构造驱动基本对象:struct cdev,里面记录具体的file_operations

cdev_init()  //把用户构建的file_operations结构体记录在内核驱动的基本对象

两个Hash表(帮助找到cdev结构体)

chrdevs:登记设备号。

__register_chrdev_region()

cdev_map->probe:保存驱动基本对象struct cdev。

cdev_add()

文件系统层原理

mknod + 主次设备号

构建一个新的设备文件,通过主次设备号在cdev_map中找到cdev->file_operations,把cdev->file_operations绑定到新的设备文件中。

到这一步,应用程序就可以使用open()、write()、read()等函数来控制设备文件了。

设备号的组成与哈希表

ebf-buster-linux/include/linux/kdev_t.h描述了设备号的具体构成。


/* 截取部分代码,关于设备号的描述 */
#define MINORBITS    20
#define MINORMASK    ((1U << MINORBITS) -1)
 
#define MAJOR(dev)   ((unsigned int)((dev) >> MINORBITS))
#define MINOR(dev)   ((unsigned int)((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

理论取值范围:

主设备号:2^12=4K

次设备号:2^20=1M

cat /proc/devices查看已注册的设备号

内核是希望一个设备驱动(file_operation)可以独自占有一个主设备号和多个次设备号,而通常一个设备文件绑定一个主设备号和一个次设备号,所以设备驱动与设备文件是一对一或者一对多的关系。

hash table(哈希表、散列表,数组和链表的混合使用)

数组的优缺点:查找快,增删元素效率低,容量固定。

链表的优缺点:查找慢,增删元素效率高,容量不限。

以主设备号为编号,使用哈希函数f(major)= major % 255 来计算主设备号的对应数组下标。

主设备号冲突(如0、255,都挂载在数组0下标),则以次设备号为比较值来排序链表节点。

哈希函数的设计目标:链表节点尽量平均分布在各个数组元素中,提高查询效率。

设备号管理

关键的数据结构:char_device_struct(存放在ebf-buster-linux/fs/char_dev.c)

static struct char_device_struct {
        struct char_device_struct *next;    //指向下一个链表节点
        unsigned int major;                      //主设备号  
        unsigned int baseminor;               //次设备号 
        int minorct;                                 //次设备号的数量
        char name[64];                           //设备名称
        struct cdev *cdev;                       //内核字符对象(已丢弃)
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

关键的函数:__register_chrdev_region(存放在ebf-buster-linux/fs/char_dev.c)

static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name)
{
        struct char_device_struct *cd, **cp;
        int ret = 0;
        int i;
     /* 动态申请内存 */
        cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
        if (cd == NULL)  return ERR_PTR(-ENOMEM);
     /* 加互斥锁保护资源 */
        mutex_lock(&chrdevs_lock);

        if (major == 0) {            /* 主设备号为0,从chadevs哈希表中查找一个空闲位置 */
                ret = find_dynamic_major();
                if (ret < 0) {
                        pr_err("CHRDEV \"%s\" dynamic allocation region is full\n", name);
                        goto out;
                }          /* 返回主设备号 */
                major = ret;  
     }

        if (major >= CHRDEV_MAJOR_MAX) {
                pr_err("CHRDEV \"%s\" major requested (%u) is greater than the maximum (%u)\n", name, major, CHRDEV_MAJOR_MAX-1);
                ret = -EINVAL;
                goto out;
        }
     /* 保存参数 */
        cd->major = major;
        cd->baseminor = baseminor;
        cd->minorct = minorct;
        strlcpy(cd->name, name, sizeof(cd->name));
     /* 哈希函数,计算哈希表的位置 */
        i = major_to_index(major);     /* 链表排序,按主设备号从小到大排序。如果主设备号相等,按次设备号从小到大排序,要考虑次设备号的最大值 */
        for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
                if ( (*cp)->major > major || ( (*cp)->major == major && ( ( (*cp)->baseminor >= baseminor ) || ( (*cp)->baseminor + (*cp)->minorct > baseminor) ) ) )  break;

        /* 如果主设备号相等,检查次设备号是否存在冲突  */
        if (*cp && (*cp)->major == major) {          /* 获取链表节点的次设备号范围 */
                int old_min = (*cp)->baseminor;
                int old_max = (*cp)->baseminor + (*cp)->minorct - 1;          /* 获取新设备的次设备号范围 */
                int new_min = baseminor;
                int new_max = baseminor + minorct - 1;

                /* 判断新设备的次设备号最大值是否位于链表节点的次设备号范围  */
                if (new_max >= old_min && new_max <= old_max) {               /* 确定冲突,返回错误 */
                        ret = -EBUSY;
                        goto out;
                }

                /* 判断新设备的次设备号最小值是否位于链表节点的次设备号范围  */
                if (new_min <= old_max && new_min >= old_min) {               /* 确定冲突,返回错误 */
                        ret = -EBUSY;
                        goto out;
                }
          /* 判断新设备的次设备号是否跨越链表节点的次设备号范围 */
                if (new_min < old_min && new_max > old_max) {               /* 确定冲突,返回错误 */
                        ret = -EBUSY;
                        goto out;
                }
        }
     /* 插入新设备的链表节点 */
        cd->next = *cp;
        *cp = cd;
        mutex_unlock(&chrdevs_lock);
        return cd;
out:
        mutex_unlock(&chrdevs_lock);
        kfree(cd);
        return ERR_PTR(ret);
}

上诉函数主设备号相等,判断新旧次设备号三种错误图如下。

保存新注册的设备号到chrdevs哈希表中,防止设备号冲突。

主设备为0时,动态分配设备号(优先使用255~234,其次使用511~384)。主设备号最大为512。

保存file_operation结构体

关键数据结构:cdev(存放在ebf-buster-linux/include/linux/cdev.h)

struct cdev {
        struct kobject kobj;
        struct module *owner;
        const struct file_operations *ops;
        struct list_head list;
        dev_t dev;
        unsigned int count;
} __randomize_layout;

关键数据结构:kobj_map(与哈希表有关,存放在ebf-buster-linux/drivers/base/map.c)

struct kobj_map {
        struct probe {
                struct probe *next;
                dev_t dev;
                unsigned long range;
                struct module *owner;
                kobj_probe_t *get;
                int (*lock)(dev_t, void *);
                void *data;
        } *probes[255];
        struct mutex *lock;
};

关键函数:cdev_init(存放在ebf-buster-linux/fs/char_dev.c)

作用:保存file_operation到cdev中。

关键函数:cdev_add(存放在ebf-buster-linux/fs/char_dev.c)

作用:根据哈希函数保存cdev到probes哈希表中,方便内核查找file_operation使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Linux设备驱动程序 第三版》是一本针对Linux设备驱动程序开发的专业书籍。该书详细介绍了Linux下常见的设备驱动程序开发技术,包括字符设备、块设备和网络设备等方面。这本书主要面向Linux系统内核开发领域的工程师、软件开发人员、嵌入式系统开发者和设备驱动程序编写者等,也适合对Linux设备驱动程序开发感兴趣的读者使用。 《Linux设备驱动程序 第三版》共分为14章,涵盖了设备驱动的基本知识、字符设备驱动程序、块设备驱动程序、内存映射、中断、高级字符驱动程序、USB驱动程序、串行通讯、网络接口卡驱动程序、媒体网关控制协议、显示屏驱动程序、电源管理、高级块驱动程序等方面。每一章的内容非常详细,涵盖了理论知识和例子,并且还提供了配套的示例代码,方便读者进行实践操作。 这本书的重点在于教授读者如何使用Linux内核框架编写驱动程序,如何开发Linux设备驱动程序,以及如何与设备进行交互。同时,还详细介绍了内核数据结构和编程规范、编译驱动程序等技术,使读者更加深入了解Linux设备驱动程序的开发过程。 总之,《Linux设备驱动程序 第三版》是一本非常优秀的Linux设备驱动程序开发教程,书中内容涵盖面广,基本知识详细介绍,适合初学者和进阶者使用。读者通过学习本书,可以更好地掌握Linux设备驱动程序的开发技术,提高自身的技术水平。 ### 回答2: 《Linux设备驱动程序》第三版PDF,是一本深入介绍Linux驱动程序开发的经典著作之一。本书主要介绍了Linux设备驱动程序的编写和调试方法,以及Linux内核的一些基本特性。 本书首先介绍了Linux内核框架和设备驱动模型,包括字符设备、块设备和网络接口设备等。接着,本书深入分析了设备驱动程序的结构和实现,详细介绍了内核的I/O系统、中断处理、定时器、内存管理、锁等基本特性,以及与硬件相关的总线、中断控制器、DMA等。此外,本书还介绍了如何使用ioctl和procfs接口,以及注册和使用内核模块等典型应用场景。 总体来说,这本书深入浅出,详细介绍了Linux设备驱动程序的开发与调试方法,为读者提供了全面了解内核和驱动开发的实战指南,是Linux驱动程序开发者必不可少的参考读物。 ### 回答3: 《Linux设备驱动程序 第三版 pdf》是一本介绍Linux设备驱动程序方面的经典教材,其内容包括了Linux设备驱动程序的开发、编写以及调试等相关知识。这本教材适合那些想要理解和开发嵌入式操作系统和设备驱动程序的程序员和工程师们。 该教材主要分为三部分,第一部分介绍了驱动程序的基础知识,包括设备文件、设备驱动程序机制、字符设备以及模块加载和卸载等内容;第二部分则介绍了高级设备驱动程序的开发,如块设备、网络设备以及USB设备驱动程序等;最后一部分则介绍了调试和测试驱动程序的方法和工具。 该教材有以下几个亮点:一是该教材的内容严谨,通俗易懂;二是该教材使用的代码清晰简单,易于理解和实践;三是该教材不仅仅局限于硬件设备的驱动,还涉及了Linux内核模块的编写、网络驱动的开发等内容。同时,该教材也提供了大量的实例代码和案例,可以帮助读者更好地理解和掌握Linux设备驱动程序的开发和调试。 总之,《Linux设备驱动程序 第三版 pdf》是一本非常优秀的Linux设备驱动程序教材,不仅适合于嵌入式开发工程师,也适合于Linux内核爱好者学习和参考。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值