字符设备驱动_3:register_chrdev_region() 简单字符设备驱动的实现

news/2024/10/31 2:24:13/

概述:利用regist_chrdev_region() 函数接口注册同一类字符设备的多个子设备。

        上一节一起整理了一遍注册一个简单字符设备的流程,接下来就来实现一个同一类字符设备的多个子设备驱动程序。

1. Demo 程序

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/fs.h>#define CDE_NAME "Rivotek_cdev"struct my_char_dev 
{unsigned int maj;    //主设备号unsigned int mio;    //次设备号unsigned int count;struct cdev *cdev;};struct my_char_dev *lcdev;static int lcdev_open(struct inode *inode, struct file *file)
{printk(KERN_INFO"lcdev open\n");return 0;
}static int lcdev_release (struct inode *inode, struct file *file)
{printk(KERN_INFO"lcdev release\n");return 0;
}static const struct file_operations lcdev_fops = {.owner = THIS_MODULE,.open = lcdev_open,.release	= lcdev_release,};static int __init char_test_init(void)
{int ret;lcdev = kmalloc(sizeof(struct my_char_dev), GFP_KERNEL);if(!lcdev) {printk(KERN_ERR"No memory for lcdev");ret =  -ENOMEM;goto out;}printk(KERN_ALERT"kmalloc ok \n");lcdev->maj = 252;lcdev->mio = 0;lcdev->count = 3;ret = register_chrdev_region(MKDEV(lcdev->maj,lcdev->mio), lcdev->count, "chartest");if(0 > ret) {printk(KERN_ERR"register failed\n");goto register_err;}printk(KERN_ALERT"register char dev ok ,ret:%d\n", ret);lcdev->cdev = cdev_alloc();if (!lcdev->cdev)goto register_err;cdev_init(lcdev->cdev, &lcdev_fops);ret = cdev_add(lcdev->cdev,MKDEV(lcdev->maj,lcdev->mio), lcdev->count);if(ret < 0)goto add_fail;printk(KERN_ALERT"maj: %d ,mio:%d\n", lcdev->maj, lcdev->mio);return 0;add_fail:kobject_put(&lcdev->cdev->kobj);register_err:unregister_chrdev_region(MKDEV(lcdev->maj,lcdev->mio), lcdev->count);if(lcdev)kfree(lcdev);return -1;out:return ret;}static void __exit char_test_exit(void)
{unregister_chrdev(lcdev->maj, CDE_NAME);kfree(lcdev);printk(KERN_ALERT"char test exit\n");
}module_init(char_test_init);
module_exit(char_test_exit);MODULE_LICENSE("GPL");

2. 验证结果

(1)驱动安装

 安装成功。

(2)创建新设备节点并验证

 

 创建了4个相同的主设备号但是次设备号分别为0、1、2、3的次设备,进行测试,前三个是可以访问的,次设备号为3的访问出错了,这是为啥?

==》 因为驱动程序中只是注册了三个子设备

3.register_chrdev_region()函数梳理

//以主设备号 = 252, 次设备号 = 0 ,count=3 为例进行分析
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{struct char_device_struct *cd;dev_t to = from + count;   // from = fc0 0000, to = fc0 003dev_t n, next;//n = fc0 0000,第二次循环 n = fc0 0003 退出循环for (n = from; n < to; n = next) {  next = MKDEV(MAJOR(n)+1, 0); //next = fd0 0000if (next > to)next = to;      // next = fc0 0003// 参数 252, 0, 3cd = __register_chrdev_region(MAJOR(n), MINOR(n),          next - n, name);if (IS_ERR(cd))goto fail;}return 0;
fail:to = n;for (n = from; n < to; n = next) {next = MKDEV(MAJOR(n)+1, 0);kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));}return PTR_ERR(cd);

从以上代码和带入参数可以得如下结论:

  •  for循环只执行了一次
  • __register_chrdev_region()函数才是真正要干活的

(1)__register_chrdev_region() 函数

static struct char_device_struct *                //252                     0
__register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)
{                  // 3struct char_device_struct *cd, **cp;int ret = 0;int i = -1;int count = 0;/*分配内存*/cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);if (cd == NULL)return ERR_PTR(-ENOMEM);mutex_lock(&chrdevs_lock);/* temporary 如果传入的主设备号为0,则动态分配主设备号*//*动态分配的方法:从全局字符设备数组中查找成员为空下标,将其下标作为主设备号*/if (major == 0) {for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {if (chrdevs[i] == NULL) {if(!strcmp("chartest", name))printk("i1: %d\t", i);break;}}if (i == 0) {ret = -EBUSY;goto out;}major = i;}/*填充刚才分配内存的指针*/cd->major = major;cd->baseminor = baseminor;cd->minorct = minorct;strlcpy(cd->name, name, sizeof(cd->name));i = major_to_index(major);  //i = 252 /*此处应该是处理添加次设备号设备的情况,for执行完成,cp处于*/for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)if ((*cp)->major > major ||((*cp)->major == major &&(((*cp)->baseminor >= baseminor) ||    //当前次设备号大于等于即将要注册的次设备号((*cp)->baseminor + (*cp)->minorct > baseminor))))  //或者当前设备次设备号加上当前设备个数 大于即将要注册的次设备号break;/* 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;mutex_unlock(&chrdevs_lock);return cd;
out:mutex_unlock(&chrdevs_lock);kfree(cd);return ERR_PTR(ret);
}

所谓的字符设备注册,就是填充了一个字符设备(struct char_device_struct)数组中的一个成员。

这里我们一次性注册了相同主设备号但不同次设备号的三个字符设备,但是字符数组成员只占用了一个。

代码添加如下打印:

mutex_lock(&chrdevs_lock);if(!strcmp("chartest", name)) {for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {if (chrdevs[i] != NULL)count += 1;}printk("count_1: %d\n", count);}
....
....
mutex_unlock(&chrdevs_lock);if(!strcmp("chartest", name)) {count = 0;for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {if (chrdevs[i] != NULL)count += 1;}printk("count_2: %d\n", count);}return cd;

结果为:

19 --》20,增加了一个数组成员。

假如相同另外一个驱动添加相同主设备号,但是不同次设备号或者相同次设备号会是什么情况?

情景一: 相同主设备号,不同次设备号

复制Demo程序,更改设备号和count 如下:

lcdev->maj = 252;
lcdev->mio = 3;
lcdev->count = 1;ret = register_chrdev_region(MKDEV(lcdev->maj,lcdev->mio), lcdev->count, "chartest");

编译注册后,可以正常访问,但是字符设备数组个数并未改变。

数组成员并未改变。

情景二: 相同的次设备号(应该会报错)

代码更改

printk(KERN_ALERT"kmalloc ok \n");lcdev->maj = 252;lcdev->mio = 2;lcdev->count = 1;ret = register_chrdev_region(MKDEV(lcdev->maj,lcdev->mio), lcdev->count, "chartest");if(0 > ret) {

运行效果:

 果真报错。

4. 总结

(1)注册调用流程

register_chrdev_region()__register_chrdev_region()

(2) 遗留问题

相同主设备号的字符设备都占用同一个字符设备数组成员,系统访问的时候是怎么区分的?


http://www.ppmy.cn/news/5661.html

相关文章

[洛谷]P3613 【深基15.例2】寄包柜

[洛谷]P3613 【深基15.例2】寄包柜一、问题描述题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示二、思路分析1、算法标签2、算法思路三、代码实现一、问题描述 [洛谷]P3613 【深基15.例2】寄包柜 题目描述 超市里有 n(1≤n≤105)n(1\le n\le10^5)n(1≤n≤105) 个…

2023年元旦倒计时案例

文章目录基于HTML实现2023年倒计时代码实现基于HTML实现2023年倒计时 代码实现 <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>2023倒计时</title> <!-- <link rel"stylesheet" href"sty…

Linux进程概念

进程的概念 进程&#xff08;Process&#xff09;是计算机中的一个具有独立功能的程序关于某个数据集合的一次运行活动&#xff0c;是系统进行资源分配和调度的基本单位&#xff0c;是操作系统结构的基础。在早期面向进程设计的计算机结构中&#xff0c;进程是程序的基本执行实…

基于Canal+kafka监听数据库变化的最佳实践

1、前言 工作中&#xff0c;我们很多时候需要根据某些状态的变化更新另一个业务的逻辑&#xff0c;比如订单的生成&#xff0c;成交等&#xff0c;需要更新或者通知其他的业务。我们通常的操作通过业务埋点、接口的调用或者中间件完成。 但是状态变化的入口比较多的时候&#x…

FT2232作为JTAG烧录器的使用步骤详解

FT2232作为JTAG烧录器的使用步骤详解FT2232作为JTAG烧录器的使用步骤详解配置OpenOCD环境(已经配置好的可以跳过)【步骤 1】安装 FT2232HL 芯片的驱动&#xff0c;安装文件为 CDM21228_Setup.exe。【步骤 2】 安装 FT_Prog_v3.6.88.402 Installer.exe【步骤 3】 使用 FT Prog 软…

你易忽略的三极管电路问题1:下拉电阻

如下这个三极管共射极驱动电路中&#xff0c;B、E极之间的下拉电阻的作用&#xff1f;是否可以将其去除&#xff1f;该电阻有两个重要的作用&#xff1a;在驱动信号关闭时给三极管基极一个固定的电平。当驱动信号&#xff08;SIGNAL&#xff09;关闭时&#xff0c;若没有下拉电…

【Java基础知识复盘】String、StringBuffer、StringBuilder篇——持续更新中

本人知识复盘系列的博客并非全部原创&#xff0c;大部分摘自网络&#xff0c;只是为了记录在自己的博客方便查阅&#xff0c;往后也会陆续在本篇博客更新本人查阅到的新的知识点&#xff0c;望悉知&#xff01; String类 在 Java 中字符串属于对象&#xff0c;Java 提供了 Str…

矢量网络分析仪如何测量史密斯图及滤波器的带宽?

矢量网络分析仪是一种很神奇的测量仪器&#xff0c;它的功能很强大也值得人们去探索。今天&#xff0c;安泰测试工程师就针对矢量网络分析仪中的史密斯图及滤波器的带宽测量进行简单的介绍&#xff0c;希望能够让更多的人对此有所了解&#xff0c;并产生兴趣。 首先仪器了解一下…