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

news2024/11/16 6:02:26

概述:利用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 003
	dev_t n, next;

    //n = fc0 0000,第二次循环 n = fc0 0003 退出循环
	for (n = from; n < to; n = next) {  
		next = MKDEV(MAJOR(n)+1, 0); //next = fd0 0000
		if (next > to)
			next = to;      // next = fc0 0003
        // 参数 252, 0, 3
		cd = __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)
{                  // 3
	struct 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.coloradmin.cn/o/108074.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux篇 三、香橙派Zero2搭建Qt环境

香橙派Zero2系列文章目录 一、香橙派Zero2设置开机连接wifi 二、香橙派Zero2获取Linux SDK源码 三、香橙派Zero2搭建Qt环境 文章目录香橙派Zero2系列文章目录前言一、下载交叉编译工具二、编译QT库1.先去网站下载Qt的资源包2.解压3.开始移植&#xff1a;4.编译&#xff1a;5.安…

jQuery 查找方法

文章目录jQuery 查找方法查找祖先元素parent()parents()parentsUntil()查找后代元素children()find()contents()向前查找兄弟元素prev()prevAll()prevUnitl()向后查找兄弟元素next()nextAll()nextUntil()查找所有兄弟元素siblings()jQuery 查找方法 查找祖先元素查找后代元素向…

年度创新力十强,热点领域重要力量,典型案例报告入选!美创再获ISC安全百强多项殊荣

12月21日&#xff0c;数字安全界“奥斯卡”—ISC 2022数字安全创新能力百强&#xff08;简称“创新百强”&#xff09;重磅揭晓&#xff0c;本届评选由ISC平台发起&#xff0c;联合赛迪顾问、数世咨询、数说安全、看雪、安在等网络安全行业权威机构、媒体共同开启评选&#xff…

web开发前基础知识补充

什么是URL&#xff1f; URL是统一资源定位符&#xff0c;对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示&#xff0c;是互联网上标准资源的地址&#xff1b; 互联网上的每个文件都有一个唯一的URL&#xff1b; 基本URL包含模式&#xff08;或称协议&#xff0…

Kafka使用MirrorMaker同步数据的两种方式

1.前言 MirrorMaker 是 Kafka官方提供的跨数据中心的流数据同步方案。原理是通过从 原始kafka集群消费消息&#xff0c;然后把消息发送到 目标kafka集群。操作简单&#xff0c;只要通过简单的 consumer配置和 producer配置&#xff0c;然后启动 Mirror&#xff0c;就可以实现准…

不喜欢现在的工作,如何成功转行?

对现有行业职业公司不满、不喜欢&#xff0c;感觉此路不通&#xff0c;想换个跑道再战&#xff01; 我想说&#xff0c;请先冷静一下。 我有两个认识的人&#xff0c;一个成功转行&#xff0c;另一个失败。后面会有我的分析~~ 我的一位女性朋友A&#xff0c;小公司里工作近1…

00后的他为何能年薪30w,转行真的很难吗?

网上有很多人经常在讨论转行&#xff0c;有的人说转行很难&#xff0c;有的却说不难&#xff0c;到底是怎样呢&#xff1f;我来说一个我身边发生的这么一个事实吧&#xff01; 我之前接触过一个00后&#xff0c;他不同于别人&#xff0c;网上大多说00后是看老板不爽就直接不干…

unidbg入门级案例-某航空app_hnairSign分析

今天要分析的是某航空app&#xff0c;版本号是8.19.0&#xff0c;分析的样本在文章底部会提供&#xff0c;这次我们要借用unidbg 来辅助进行算法还原。 有关unidbg的介绍笔者就不做过多的描述&#xff0c;大家可自行百度查询。 该样本的so比较简单&#xff0c;但重点是记录分析…

多标签分类怎么做?(Python)

一、基本介绍 首先简单介绍下&#xff0c;多标签分类与多分类、多任务学习的关系&#xff1a; 多分类学习&#xff08;Multi-class&#xff09;&#xff1a;分类器去划分的类别是多个的&#xff0c;但对于每一个样本只能有一个类别&#xff0c;类别间是互斥的。例如&#xff1…

electron:获取MAC地址

一、背景 当我们需要用户“使用指定设备”访问程序的时候&#xff0c;我们需要获取用户设备的固定的id&#xff0c;设备id用户id实现业务需求&#xff0c;这个所谓的id就是MAC地址。 对于其他方法&#xff1a; uuid&#xff1a;uuid是一个唯一的字符串&#xff0c;可以存放到…

深度融合钉钉PaaS,授客学堂助力企业实现培训数字化

方案简介 授客学堂将企业培训领域的经验与钉钉开放能力深度融合&#xff0c;通过集成钉钉人事一体、酷应用、IM底座、待办等多种开放能力&#xff0c;实现学员培训数据实时互通&#xff0c;为客户提供更新更酷的能力&#xff0c;高效解决企业培训的数字化服务。 方案场景 在…

tensorflow feature_columns

总结来说&#xff1a; feature_column定义了一种数据预处理的方式&#xff0c;可以看作是一种格式&#xff0c;指定了key&#xff0c;用于后续读取输入流中对应列的数据feature_column不是tensor&#xff0c;所以如果在下一步应用到模型中是需要tensor&#xff0c;还需要通过f…

非互联网人士如何转行互联网?

结论是&#xff0c;具备互联网式的做事思维积累互联网项目经验。我靠着这个方法从一名传统销售顺利转行&#xff0c;&#xff08;之前没有任何互联网工作经验&#xff09;入职了一家互联网公司做用户运营&#xff0c;半年前跳槽成为一个4人运营小团队的leader。 在分享我自身的…

我国丁辛醇行业现状:上游丙烯供给充足 下游需求下滑 市场出现高差价现象

根据观研报告网发布的《中国丁辛醇行业发展深度分析与投资前景研究报告&#xff08;2022-2029年&#xff09;》显示&#xff0c;丁辛醇是一种丁醇和辛醇合成的有机物&#xff0c;无色透明、易燃的油状液体&#xff0c;具有特殊的气味&#xff0c;能与水及多种化合物形成共沸物&…

服务器IPMI(BMC)装机

将网线连接服务器的控制口与PC&#xff0c;服务器的控制口默认IP为192.168.100.100&#xff0c;网关默认为192.168.100.1&#xff0c;将PC的IP修改为与服务器控制口相同网段。打开浏览器&#xff0c;输入https://19168.100.100&#xff0c;进入IPMI登录界面。账号密码需要找运维…

Apache Airflow Hive Provider <5.0.0 存在操作系统命令注入漏洞

漏洞描述 Apache Airflow 是一个用于以编程方式创作、安排和监控工作流平台。Apache Airflow Hive Provider 是一个使用 SQL 读取、写入和管理分布式存储中的大型数据集的工具包。 Apache Airflow Hive Provider 在 5.0.0 之前的版本中由于对airflow/providers/apache/hive/h…

Stm32标准库函数6——f103 PWM 电调(50Hz)

#include "stm32f10x.h" #include "delay.h" TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; u16 Ppm; /************************************************* 函数: void RCC_Configuration(void) 功能: 配…

D. Friends and Subsequences Codeforces Round #361 (Div. 2)RMQ+二分 单调队列

题目传送门 题意为 给定两个长度为n的数组&#xff0c;设为a数组和b数组&#xff0c;需要找到所有可能的区间中&#xff0c;a数组的最大值等于b数组的最小值的个数。 1&#xff1a;RMQ 二分 RMQ 能找到一个数组在任意区间的最大值或者最小值&#xff0c;只需要在O(n)的时间…

【Java基础知识复盘】HashMap篇——持续更新中

本人知识复盘系列的博客并非全部原创&#xff0c;大部分摘自网络&#xff0c;只是为了记录在自己的博客方便查阅&#xff0c;往后也会陆续在本篇博客更新本人查阅到的新的知识点&#xff0c;望悉知&#xff01; HashMap 概述 HashMap 是一个散列表&#xff0c;它存储的内容是…

hashMap相关

文章目录HashMapHashMap介绍HashMap在 JDK1.7和 JDK1.8中的区别JDK1.7中HashMap头插法死循环的原因HashMap的底层原理HashMap的扩容机制解决Hash冲突的方法为什么在解决hash冲突的时候选择先用链表&#xff0c;再转红黑树?HashMap为什么线程不安全一般用什么作为HashMap的key?…