字符设备的注册和卸载函数
介绍
我们已经知道什么是驱动程序,以及为什么需要它们。 角色驱动程序有何特别之处? 如果我们为面向字节的操作编写驱动程序,则将它们称为字符驱动程序。 由于大多数设备是面向字节的,因此大多数设备驱动程序都是字符设备驱动程序。 以串行驱动程序,音频驱动程序,视频驱动程序,摄像机驱动程序和基本I / O驱动程序为例。 实际上,所有既不是存储设备也不是网络设备驱动程序的设备驱动程序都是某种类型的字符驱动程序。
应用程序将如何通信硬件设备?
下图将显示完整的通信路径。
- 最初,Application将打开设备文件。 该设备文件是由设备驱动程序创建的。
- 然后,该设备文件将使用大号和小号找到相应的设备驱动程序。
- 然后,该设备驱动程序将与硬件设备对话。
字符设备驱动程序的主号和副号
Linux内核的基本功能之一是它抽象化设备的处理。 所有的硬件设备看起来像常规文件。 可以使用用于处理文件的相同,标准的系统调用来打开,关闭,读取和写入它们。 对于Linux,一切都是文件。 要写入硬盘,您要写入文件。 从键盘读取就是从文件读取。 将备份存储在磁带设备上就是写入文件。 甚至从内存读取也就是从文件读取。 如果要读取的文件或要写入的文件是“普通”文件,则此过程非常容易理解:打开文件并读取或写入数据。 因此,设备驱动程序也喜欢该文件。 驱动程序将为每个硬件设备创建一个特殊文件。 我们可以使用那些特殊文件(设备文件)与硬件进行通信。
如果要创建一个特殊文件,我们应该知道设备驱动程序中的主号和副号。 在本教程中,我们将学习主要和次要数字。
主号和副号
Linux内核将字符和块设备表示为数字对 <major>:<minor>
。
专业号码
传统上,主号码标识与设备关联的驱动程序。 主号码也可以由多个设备驱动程序共享。 请参阅 /proc/devices
以了解如何在正在运行的Linux实例上分配主号。
1
2
3
4
5
6
7
8
9
|
linux@embetronicx-VirtualBox:~/project/devicedriver/devicefile$ cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
Block devices:
1 ramdisk
259 blkext
|
这些数字是主要数字。
副号码
主号码是标识相应的驱动程序。 许多设备可能使用相同的主号码。 因此,我们需要为使用相同主号码的每个设备分配编号。 所以这是次要号码。 换句话说,设备驱动程序使用次设备号 <minor>
来区分各个物理或逻辑设备。
分配主要和次要号码
我们可以通过两种方式分配主要和次要数字。
- 静态分配
- 动态分配
静态分配
如果要为驱动程序设置特定的主号码,则可以使用此方法。 如果可用,此方法将分配该主号码。 否则,不会。
int register_chrdev_region(dev_t first, unsigned int count, char *name); |
这 first
是您要分配的范围的起始设备号。
count
是您请求的连续设备号的总数。 请注意,如果计数很大,则您请求的范围可能会溢出到下一个主数字; 但是只要您请求的号码范围可用,一切都将正常运行。
name
是应该与此编号范围关联的设备的名称; 它会出现在/ proc / devices和sysfs中。
from的返回值 register_chrdev_region
如果分配成功完成,则 将为0。 如果出现错误,将返回一个错误代码,您将无权访问所请求的区域。
的 dev_t
类型(在定义的 <linux/types.h>
)用于保持装置的数字-两者的主要和次要部分。 dev_t
是一个32位数量,其中将12个位留给主要编号,将20个留给次要编号。
如果要 创建 dev_t
为您的主号码和副号码 结构变量,请使用以下功能。
MKDEV(int major, int minor); |
如果要从获取您的主电话号码和次电话号码 dev_t
,请使用以下方法。
MAJOR(dev_t dev); |
MINOR(dev_t dev); |
如果将 传递 dev_t
结构 给此MAJOR或MONOR函数,它将返回该驱动程序的主/次编号。
例,
1
2
3
|
dev_t dev = MKDEV(235, 0);
register_chrdev_region(dev, 1, "Embetronicx_Dev");
|
动态分配
如果我们不希望固定主,副号码,请使用此方法。 此方法会将主号码动态分配给可用的驱动程序。
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); |
dev
是仅用于输出的参数,成功完成后将保留分配范围内的第一个数字。
firstminor
应该是要求使用的第一个次要号码; 通常为0。
count
是您请求的连续设备号的总数。
name
是应该与此编号范围关联的设备的名称; 它会出现在 /proc/devices
和中 sysfs
。
静态和动态方法之间的区别
静态方法仅在事先知道要从哪个大数开始的情况下才真正有用。 使用“静态”方法,您可以告诉内核所需的设备号(主要/次要号和起始数字),它是否提供给您(取决于可用性)。
使用动态方法时,您告诉内核您需要多少个设备号(起始的次要号和数量),如果有可用的话,它将为您找到一个起始的主号。
在某种程度上避免与其他设备驱动程序发生冲突,建议使用Dynamic方法功能,该功能将为您动态分配设备编号。
动态分配的缺点是您无法提前创建设备节点,因为分配给模块的主要编号会有所不同。 对于驱动程序的正常使用,这几乎没有问题,因为一旦分配了编号,便可以从中读取它 /proc/devices
。
注销主要和次要号码
无论您如何分配设备号,都应在不再使用它们时释放它们。 设备编号可通过以下方式释放:
void unregister_chrdev_region(dev_t first, unsigned int count); |
通常的调用位置 unregister_chrdev_region
是在模块的清除函数(退出函数)中。
静态分配主号码程序
[从 获取源代码 GitHub ]
在此程序中,我将235分配为主号码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
/***************************************************************************//**
* \file driver.c
*
* \details Simple linux driver (Statically allocating the Major and Minor number)
*
* \author EmbeTronicX
*
* *******************************************************************************/
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/module.h>
#include <linux/fs.h>
//creating the dev with our custom major and minor number
dev_t dev = MKDEV(235, 0);
/*
** Module Init function
*/
static int __init hello_world_init(void)
{
register_chrdev_region(dev, 1, "Embetronicx_Dev");
printk(KERN_INFO "Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
printk(KERN_INFO "Kernel Module Inserted Successfully...\n");
return 0;
}
/*
** Module exit function
*/
static void __exit hello_world_exit(void)
{
unregister_chrdev_region(dev, 1);
printk(KERN_INFO "Kernel Module Removed Successfully...\n");
}
module_init(hello_world_init);
module_exit(hello_world_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeTronicX <embetronicx@gmail.com>");
MODULE_DESCRIPTION("Simple linux driver (Statically allocating the Major and Minor number)");
MODULE_VERSION("1.0");
|
- 使用Makefile( 构建驱动程序
sudo make
) - 使用以下方式加载驱动程序
sudo insmod
- 使用查询大号
cat /proc/devices
1
2
3
|
linux@embetronicx-VirtualBox::/home/driver/driver$ cat /proc/devices | grep "Embetronicx_Dev"
235 Embetronicx_Dev
|
- 使用以下方法卸载驱动程序
sudo rmmod
动态分配主号码程序
[从 获取源代码 GitHub ]
该程序将动态分配主号码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
/***************************************************************************//**
* \file driver.c
*
* \details Simple linux driver (Dynamically allocating the Major and Minor number)
*
* \author EmbeTronicX
*
* *******************************************************************************/
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kdev_t.h>
#include<linux/fs.h>
dev_t dev = 0;
/*
** Module Init function
*/
static int __init hello_world_init(void)
{
/*Allocating Major number*/
if((alloc_chrdev_region(&dev, 0, 1, "Embetronicx_Dev")) <0){
printk(KERN_INFO "Cannot allocate major number for device 1\n");
return -1;
}
printk(KERN_INFO "Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
printk(KERN_INFO "Kernel Module Inserted Successfully...\n");
return 0;
}
/*
** Module exit function
*/
static void __exit hello_world_exit(void)
{
unregister_chrdev_region(dev, 1);
printk(KERN_INFO "Kernel Module Removed Successfully...\n");
}
module_init(hello_world_init);
module_exit(hello_world_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeTronicX <embetronicx@gmail.com>");
MODULE_DESCRIPTION("Simple linux driver (Dynamically allocating the Major and Minor number)");
MODULE_VERSION("1.1");
|
- 使用Makefile( 构建驱动程序
sudo make
) - 使用以下方式加载驱动程序
sudo insmod
- 使用查询大号
cat /proc/devices
1
2
3
|
linux@embetronicx-VirtualBox::/home/driver/driver$ cat /proc/devices | grep "Embetronicx_Dev"
243 Embetronicx_Dev
|
- 使用以下方法卸载驱动程序
sudo rmmod
此函数为此驱动程序分配主要数字243。
卸载驱动程序之前,请 检查 的文件 /dev
使用来 目录中 ls /dev/
。
参考资料:
https://embetronicx.com/tutorials/linux/device-drivers/character-device-driver-major-number-and-minor-number/