字符设备驱动编程②(cdev结构)

简介: 字符设备驱动编程②(cdev结构)

前言

我们接着讲,我们使用正点原子提供的字符设备驱动的模板来进行讲解。相信大家应该都已经知道代码的意思,我们来分析函数之间的调用关系。模板如下:

/* 注册字符设备驱动 */
  /* 1、创建设备号 */
  if (newchrled.major) {    /*  定义了设备号 */
    newchrled.devid = MKDEV(newchrled.major, 0);
    register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
  } else {            /* 没有定义设备号 */
    alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);  /* 申请设备号 */
    newchrled.major = MAJOR(newchrled.devid); /* 获取分配号的主设备号 */
    newchrled.minor = MINOR(newchrled.devid); /* 获取分配号的次设备号 */
  }
  printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor); 
  /* 2、初始化cdev */
  newchrled.cdev.owner = THIS_MODULE;
  cdev_init(&newchrled.cdev, &newchrled_fops);
  /* 3、添加一个cdev */
  cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
  /* 4、创建类 */
  newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
  if (IS_ERR(newchrled.class)) {
    return PTR_ERR(newchrled.class);
  }
  /* 5、创建设备 */
  newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
  if (IS_ERR(newchrled.device)) {
    return PTR_ERR(newchrled.device);
  }
  return 0;
}
/*
 * @description : 驱动出口函数
 * @param     : 无
 * @return    : 无
 */
static void __exit led_exit(void)
{
  /* 注销字符设备驱动 */
  cdev_del(&newchrled.cdev);/*  删除cdev */
  unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */
  device_destroy(newchrled.class, newchrled.devid);
  class_destroy(newchrled.class);
}

在这里,关于函数的参数什么的就不讲了,如果不知道,可以找我前面写的博文。


注册字符设备编号

内核提供了三个函数来注册一组字符设备编号,这三个函数分别是

int register_chrdev(unsigned int, const char *,struct file_operations *)//int为0时候动态注册
int alloc_chrdev_region(dev_t, unsigned, const char *);     //动态的申请注册一个设备号
int register_chrdev_region(dev_t, unsigned, const char *); //静态的申请和注册设备号

这三个函数都会调用一个共用的 __register_chrdev_region() 函数来注册一组设备编号范围(即一个 char_device_struct 结构)。

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 i;
    int ret = 0;
    cd = kmalloc(sizeof(struct char_device_struct), GFP_KERNEL);/*slab分配一个char_device_struct变量*/
    if (cd == NULL)
        return ERR_PTR(-ENOMEM);
    memset(cd, 0, sizeof(struct char_device_struct));/*将刚刚分配的变量的内存区清零*/
    write_lock_irq(&chrdevs_lock);/*关中断,禁止内核抢占+读写锁*/
    /* temporary */
    if (major == 0) {/*分配一个主设备号!*/
        for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
            if (chrdevs[i] == NULL)
                break;
        }
        if (i == 0) {
            ret = -EBUSY;
            goto out;
        }
        major = i;
        ret = major;
    }
    cd->major = major;
    cd->baseminor = baseminor;
    cd->minorct = minorct;/*申请设备号的个数*/
    cd->name = name;
    /****************处理char_device_struct变量的分配和初始化************/
    /****************将char_device_struct变量注册到内核*****************/
    i = major_to_index(major);/*将major对256取余数,得到可以存放char_device_struct在chrdevs中的索引*/
    /*
     *退出循环:
         (1)chrdevs[i]为空
         (2)chrdevs[i]的主设备号大于major
         (3)chrdevs[i]的主设备号等于major,但是次设备号大于等于baseminor
     */
  for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
    if ((*cp)->major > major ||
        ((*cp)->major == major &&
         (((*cp)->baseminor >= baseminor) ||
          ((*cp)->baseminor + (*cp)->minorct > baseminor))))
      break;
    /*
     *如果*cp不空,并且*cp的major与要申请的major相同,
     *此时,如果(*cp)->baseminor < baseminor + minorct,就会发生冲突
     *因为和已经分配了的设备号冲突了。
     *出错!
     */
  /* Check for overlapping minor ranges.  */
  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;
    /* New driver overlaps from the left.  */
    if (new_max >= old_min && new_max <= old_max) {
      ret = -EBUSY;
      goto out;
    }
    /* New driver overlaps from the right.  */
    if (new_min <= old_max && new_min >= old_min) {
      ret = -EBUSY;
      goto out;
    }
  }
    /*
     *所要申请的设备号可以满足
     */
    cd->next = *cp;/*按照主设备号从小到达的顺序排列*/
    *cp = cd;
    write_unlock_irq(&chrdevs_lock);
    return cd;
out:
    write_unlock_irq(&chrdevs_lock);
    kfree(cd);
    return ERR_PTR(ret);
}

函数 __register_chrdev_region() 主要执行以下步骤:

  1. 分配一个新的 char_device_struct 结构,并用 0 填充。
  2. 如果申请的设备编号范围的主设备号为 0,那么表示设备驱动程序请求动态分配一个主设备号。动态分配主设备号的原则是从散列表的最后一个桶向前寻找,那个桶是空的,主设备号就是相应散列桶的序号。所以动态分配的主设备号总是小于 256,如果每个桶都有字符设备编号了,那动态分配就会失败。
  3. 根据参数设置 char_device_struct 结构中的初始设备号,范围大小及设备驱动名称。
  4. 计算出主设备号所对应的散列桶,为新的 char_device_struct 结构寻找正确的位置。同时,如果设备编号范围有重复的话,则出错返回。
  5. 将新的 char_device_struct 结构插入散列表中,并返回 char_device_struct 结构的地址。

初始化cdev

函数cdev_init()用于初始化一个静态分配的cdev结构体变量,函数cdev_init会自动初始化cdev->ops对象,将函数的第二个输入参数赋值给cdev->ops对象,不会初始化cdev->owner对象,因此在经过函数cdev_alloc()和函数cdev_init()处理之后的cdev结构体变量,在应用程序中只需要给cdev->owner对象赋值,此结构变量就可以被插入Linux内核系统了,作为一个可用的字符设备使用。

struct cdev {
  struct kobject kobj;
  /*字段 kobj用来描述设备的引用计数,在终端中,可以通过lsmod命令显示模块相关的信息,其中包括引用计数。*/
  struct module *owner;
  /*字段owner描述了模块的从属关系,指向拥有这个结构的模块的指针,这个描述符只有对编译为模块方式的驱动才是有意义的,一般赋值为“THIS_MODULE”。*/
  const struct file_operations *ops;
  /*字段ops描述了字符设备的操作函数指针,*/
  struct list_head list;
  /*字段list描述了与cdev对应的字符设备文件的inode->i_devices的链表的表头。*/
  dev_t dev;
  /*字段dev描述了字符设备的设备号,包括主设备号和次设备号。*/
  unsigned int count;
  /*字段count指定设备编号范围的大小。*/
};

添加一个cdev

函数cdev_add()用于向Linux内核系统中添加一个新的cdev结构体变量所描述的字符设备,并且使这个设备立即可用。

创建类和创建设备就不多说了。

总结

  1. struct file_operations是一个字符设备把驱动的操作和设备号联系在一起的纽带,是一系列指针的集合,每个被打开的文件都对应于一系列的操作,这就是file_operations,用来执行一系列的系统调用。
    struct file代表一个打开的文件,在执行file_operation中的open操作时被创建,这里需要注意的是与用户空间inode指针的区别,一个在内核,而file指针在用户空间,由c库来定义。
    struct inode被内核用来代表一个文件,注意和struct file的区别,struct inode一个是代表文件,struct file一个是代表打开的文件。
  2. 在Linux文件系统中,每个文件都用一个struct inode结构体来描述,这个结构体里面记录了这个文件的所有信息,例如:文件类型,访问权限等。
struct inode {
  umode_t     i_mode; 
  ...
  unsigned int    i_flags;
    .....
  dev_t     i_rdev;
  loff_t      i_size;
  ...
  struct list_head  i_devices;
  union {
    struct pipe_inode_info  *i_pipe;
    struct block_device *i_bdev;
    struct cdev   *i_cdev;
  };
  ...
};

struct inode描述的是文件的静态信息,即这些信息很少会改变。而struct file描述的是动态信息,即在对文件的操作的时候,struct file里面的信息经常会发生变化。典型的是struct file结构体里面的f_pos(记录当前文件的位移量),每次读写一个普通文件时f_ops的值都会发生改变

2. 在Linux操作系统中,每个驱动程序在应用层的/dev目录下都会有一个设备文件和它对应,并且该文件会有对应的主设备号和次设备号。

3. 在Linux操作系统中,每个驱动程序都要分配一个主设备号,字符设备的设备号保存在struct cdev结构体中。

  1. 在Linux操作系统中,每打开一次文件,Linux操作系统在VFS层都会分配一个struct file结构体来描述打开的这个文件。该结构体用于维护文件打开权限、文件指针偏移值、私有内存地址等信息。

    结构体之间的关系:

    如果想访问底层设备,就必须打开对应的设备文件。也就是在这个打开的过程中,Linux内核将应用层和对应的驱动程序关联起来。
  2. 当open函数打开设备文件时,可以根据设备文件对应的struct inode结构体描述的信息,可以知道接下来要操作的设备类型(字符设备还是块设备)。还会分配一个struct file结构体。
  3. 根据struct inode结构体里面记录的设备号,可以找到对应的驱动程序。这里以字符设备为例。在Linux操作系统中每个字符设备有一个struct cdev结构体。此结构体描述了字符设备所有的信息,其中最重要一项的就是字符设备的操作函数接口。
  4. 找到struct cdev结构体后,Linux内核就会将struct cdev结构体所在的内存空间首地记录在struct inode结构体的i_cdev成员中。将struct cdev结构体的中记录的函数操作接口地址记录在struct file结构体的f_op成员中。
  5. 任务完成,VFS层会给应用层返回一个文件描述符(fd)。这个fd是和struct file结构体对应的。接下来上层的应用程序就可以通过fd来找到strut file,然后在由struct file找到操作字符设备的函数接口了。



目录
相关文章
|
4月前
|
人工智能 JSON 自然语言处理
构建AI智能体:三十一、AI医疗场景实践:医学知识精准问答+临床智能辅助决策CDSS
本文探讨了医疗AI从传统规则系统向大模型的演进,重点介绍了基于RAG技术的医学知识精准问答系统与临床智能辅助决策系统(CDSS)的构建原理与应用。二者协同工作,前者提供权威知识检索,后者结合患者数据生成个性化诊疗建议,共同提升医疗效率、安全与质量,助力医生实现更精准的临床决策。
607 2
|
11月前
|
弹性计算 关系型数据库 数据库
快速体验Cloudberry和APCC
通过Docker快速体验Cloudberry和APCC
624 6
|
Ubuntu Linux 虚拟化
LinuxUbuntu安装VMware tools Segmentation fault (core dumped)怎么解决
更新操作系统和内核:使用apt-get或apt命令更新你的Ubuntu操作系统和内核。运行以下命令更新软件包:
2420 0
|
数据可视化 数据挖掘
R中单细胞RNA-seq分析教程 (9)
R中单细胞RNA-seq分析教程 (9)
R中单细胞RNA-seq分析教程 (9)
|
文字识别 Serverless 开发工具
【全自动改PDF名】批量OCR识别提取PDF自定义指定区域内容保存到 Excel 以及根据PDF文件内容的标题来批量重命名
学校和教育机构常需处理成绩单、报名表等PDF文件。通过OCR技术,可自动提取学生信息并录入Excel,便于统计分析和存档管理。本文介绍使用阿里云服务实现批量OCR识别、内容提取、重命名及导出表格的完整步骤,包括开通相关服务、编写代码、部署函数计算和设置自动化触发器等。提供Python示例代码和详细操作指南,帮助用户高效处理PDF文件。 链接: - 百度网盘:[链接](https://pan.baidu.com/s/1mWsg7mDZq2pZ8xdKzdn5Hg?pwd=8866) - 腾讯网盘:[链接](https://share.weiyun.com/a77jklXK)
2072 5
|
安全 Linux 数据安全/隐私保护
python知识点100篇系列(15)-加密python源代码为pyd文件
【10月更文挑战第5天】为了保护Python源码不被查看,可将其编译成二进制文件(Windows下为.pyd,Linux下为.so)。以Python3.8为例,通过Cython工具,先写好Python代码并加入`# cython: language_level=3`指令,安装easycython库后,使用`easycython *.py`命令编译源文件,最终生成.pyd文件供直接导入使用。
647 3
python知识点100篇系列(15)-加密python源代码为pyd文件
|
Linux 虚拟化
此虚拟机的处理器所支持的功能不同于保存虚拟机状态的虚拟机的处理器所支持的功能
此虚拟机的处理器所支持的功能不同于保存虚拟机状态的虚拟机的处理器所支持的功能
3693 0
此虚拟机的处理器所支持的功能不同于保存虚拟机状态的虚拟机的处理器所支持的功能
|
算法 安全 Linux
Linux 下共享内存方式 :System V共享内存、共享文件映射(mmap)、POSIX共享内存对比...
Linux 下共享内存方式 :System V共享内存、共享文件映射(mmap)、POSIX共享内存对比...
523 2
|
存储 开发框架 JavaScript
程序与技术分享:CKEditor与CKFinder使用
程序与技术分享:CKEditor与CKFinder使用
661 0
|
Web App开发 编解码 测试技术
HandBrake 开源视频转码器、编码转换器、格式转换器
HandBrake 开源视频转码器、编码转换器、格式转换器 点击下图进入官网下载页面:https://handbrake.fr/downloads.php macOS 下可能会阻止安装! 其实也不是安装,是运行! 需要打开系统偏好设置》安全性与隐私》窗口下方会看到被阻止的程序,点击左下角解锁就可以了。
2835 0