Linux设备驱动-内核管理设备号机制_linux 内核自动获取设备号

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前在阿里

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Linux运维全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上运维知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以点击这里获取!

设备号的构成

一个设备号由主设备号和次设备号构成。

主设备号对应设备驱动程序,同一类设备一般使用相同的主设备号。

次设备号由驱动程序使用,驱动程序用来描述使用该驱动的设备的序号,序号一般从 0 开始。

Linux 设备号用 dev_t 类型的变量进行标识,这是一个 32位 无符号整数,内核源码定义为:

/* <include/linux/types.h> */

typedef u32 __kernel_dev_t;
typedef __kernel_dev_t		dev_t;

主设备号用 dev_t 的高 12 位表示,次设备号用 dev_t 低 20 位表示。

内核提供了几个宏定义,供驱动程序操作设备号时使用:

/* <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))

宏 MAJOR 从设备号 dev 中提取主设备号。宏 MINOR 用来从设备号 dev 中提取次设备号。宏 MKDEV 用来将主设备号 ma 和 次设备号 mi 组合成 dev_t 类型的设备号。

另外,内核也提供了从设备文件 i-节点结构(inode 结构体)中获取主次设备号的函数,如下:

/* <include/linux/fs.h> */

/* 获取次设备号 */
static inline unsigned iminor(const struct inode *inode)
{
	return MINOR(inode->i_rdev);
}

/* 获取主设备号 */
static inline unsigned imajor(const struct inode *inode)
{
	return MAJOR(inode->i_rdev);
}

通过函数源码可知,获取主设备号和次设备号最终是通过宏定义完成的。

内核管理设备号

以字符设备为例,向内核中注册设备号,内核是如何分配和管理设备号的呢?

在编写字符设备驱动时,可以通过如下两个系统调用向内核注册设备号:

  • register_chrdev_region()

注册一系列连续的字符设备号,主设备号需要函数调用者指定。此函数的原型为:

int register_chrdev_region(dev_t from, unsigned count, const char *name)

参数 from 为设备编号,包含主设备号和次设备号。参数 count 用于指定连续设备号的个数,即当前驱动程序所管理的同类设备的个数。参数 name 为设备或驱动的名字。

执行成功,返回 0。失败,则返回一个负值的错误码。

  • alloc_chrdev_region()

注册一系列连续的字符设备号,主设备号是由内核动态分配得到的。此函数的原型为:

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

参数 dev 为函数的传出参数,用于记录动态分配的设备号,如果申请多个设备号,则此参数记录这些连续设备号的起始值。

参数 baseminor 指定首个次设备号。参数 count 用于指定连续设备号的个数。参数 name 为设备或驱动的名字。

执行成功,返回 0。失败,则返回一个负值的错误码。

接下来,看看这两个函数的内部实现流程。

register_chrdev_region()

该函数的内核源码为,关键部分已加注释:

/* <fs.char_dev.c> */

int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
	struct char_device_struct *cd;
	dev_t to = from + count;
	dev_t n, next;

	/* 循环,注册多个连续的设备号 */
	for (n = from; n < to; n = next) 
	{
		/* 计算得到下一个设备号 */
		next = MKDEV(MAJOR(n)+1, 0);
		/* 判断是否超限 */
		if (next > to)
			next = to;
		/* 向内核注册指定的设备号 */
		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);
}

由代码内容可知,这个函数的核心处理流程是通过内部调用 __register_chrdev_region()实现的。

这个函数的主要功能是,将要使用的设备号注册到内核的设备号管理体系中,避免多个驱动程序使用相同的设备号,而引起的混乱。

如果注册设备号已经被使用,则会返回错误码告知调用者,即调用失败。如果成功,则函数返回 0。

struct char_device_struct

在调用过程中,会涉及到一个关键的数据结构 struct char_device_struct,其定义如下:

#define CHRDEV_MAJOR_HASH_SIZE 255

static struct char_device_struct {
	struct char_device_struct *next;  /* 链表指针 */
	unsigned int major;  /* 主设备号 */
	unsigned int baseminor;  /* 次设备号 */
	int minorct;   /* 此设备号个数 */
	char name[64];  /* 设备名称 */
	struct cdev *cdev;		/* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

定义结构体的同时,还定义了一个全局性的指针数组 chrdevs,是内核用来分配和管理设备号的。数组中的每一个元素都是指向 struct char_device_struct 类型的指针。

函数 register_chrdev_region() 的主要功能是将驱动程序要使用的设备号记录到 chrdevs 数组中。

__register_chrdev_region()

核心处理函数 __register_chrdev_region() 内部,首先会分配一个 struct char_device_struct 类型的指针 cd,然后对其进行初始化(已经去除无关代码):

static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
			   int minorct, const char *name)
{
	...
	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
	if (cd == NULL)
		return ERR_PTR(-ENOMEM);
	
	/* 根据主设备号计算索引,搜索 chrdevs 数组,判断主设备号是否可用 */
	i = major_to_index(major);
	for (curr = chrdevs[i]; curr; prev = curr, curr = curr->next) 
	{
		if (curr->major < major)
			continue;
		if (curr->major > major)
			break;
		if (curr->baseminor + curr->minorct <= baseminor)
			continue;
		if (curr->baseminor >= baseminor + minorct)
			break;
		goto out;
	}
	/* 初始化信息 */
	cd->major = major;
	cd->baseminor = baseminor;
	cd->minorct = minorct;
	strlcpy(cd->name, name, sizeof(cd->name));
	
	/* 将分配的 cd 加入到 chrdevs[i] 中 */
	if (!prev) {
		cd->next = curr;
		chrdevs[i] = cd;
	} else {
		cd->next = prev->next;
		prev->next = cd;
	}
	...
}

函数申请完内存资源后,开始扫描 chrdevs 数组,确保当前注册的设备号可用。如果设备号占用,函数返回错误码,即调用失败。

如果设备号可用,则用设备号和名字信息初始化。初始化完成后,将 struct char_device_struct 加入到内核管理设备号的链表中。

alloc_chrdev_region()

此函数由内核动态分配设备号,该函数的内核源码如下,关键部分已加注释:

/* <fs.char_dev.c> */

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
			const char *name)
{
	struct char_device_struct *cd;
	
	/* 向内核注册设备号 */
	cd = __register_chrdev_region(0, baseminor, count, name);
	if (IS_ERR(cd))
		return PTR_ERR(cd);
	
	/* 得到动态获取的首个设备号 */
	*dev = MKDEV(cd->major, cd->baseminor);
	return 0;
}


![](https://img-blog.csdnimg.cn/img_convert/9a8cb5f8c0ec69e6499adead0da6e95b.png)


最全的Linux教程,Linux从入门到精通

======================

1.  **linux从入门到精通(第2版)**

2.  **Linux系统移植**

3.  **Linux驱动开发入门与实战**

4.  **LINUX 系统移植 第2版**

5.  **Linux开源网络全栈详解 从DPDK到OpenFlow**



![华为18级工程师呕心沥血撰写3000页Linux学习笔记教程](https://img-blog.csdnimg.cn/img_convert/59742364bb1338737fe2d315a9e2ec54.png)



第一份《Linux从入门到精通》466页

====================

内容简介

====

本书是获得了很多读者好评的Linux经典畅销书**《Linux从入门到精通》的第2版**。本书第1版出版后曾经多次印刷,并被51CTO读书频道评为“最受读者喜爱的原创IT技术图书奖”。本书第﹖版以最新的Ubuntu 12.04为版本,循序渐进地向读者介绍了Linux 的基础应用、系统管理、网络应用、娱乐和办公、程序开发、服务器配置、系统安全等。本书附带1张光盘,内容为本书配套多媒体教学视频。另外,本书还为读者提供了大量的Linux学习资料和Ubuntu安装镜像文件,供读者免费下载。



![华为18级工程师呕心沥血撰写3000页Linux学习笔记教程](https://img-blog.csdnimg.cn/img_convert/9d4aefb6a92edea27b825e59aa1f2c54.png)



**本书适合广大Linux初中级用户、开源软件爱好者和大专院校的学生阅读,同时也非常适合准备从事Linux平台开发的各类人员。**

> 需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/topics/618635766)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

从事Linux平台开发的各类人员。**

> 需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/topics/618635766)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《深入Linux设备驱动程序内核机制》是一本经典的Linux设备驱动程序开发指南。该书讲解了Linux内核设备驱动程序开发原理与机制,对于想要深入理解Linux内核设备驱动开发的开发者来说是一本不可多得的参考书籍。 这本书首先介绍了Linux设备驱动的基本概念和工作原理,包括字符设备驱动、块设备驱动、网络设备驱动等。然后深入讲解了Linux内核设备驱动程序的注册、初始化、读写、中断处理等核心内容。通过详细的代码示例和实践经验,读者可以了解Linux设备驱动程序的编写和调试方法,提高自己的设备驱动开发能力。 除了基本的设备驱动编写方法,该书还介绍了Linux内核中其他相关的概念和机制,如中断处理、内存管理、并发控制等。这些内容为读者理解和掌握Linux设备驱动开发提供了更全面的视角和工具。 《深入Linux设备驱动程序内核机制》还强调了设备驱动的性能优化和调试技巧。通过优化驱动程序的设计和实现,读者可以提高设备的响应速度和并发处理能力。同时,书中还介绍了一些常见的设备驱动程序问题和调试方法,帮助开发者快速定位和解决设备驱动开发中的各种问题。 总之,《深入Linux设备驱动程序内核机制》是一本非常有价值的书籍,对于想要深入理解和掌握Linux设备驱动程序开发的读者来说是一本必备的参考书。无论是初学者还是经验丰富的Linux开发者,都能从中获得实用的知识和经验,提高自己的设备驱动开发水平。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值