声明:内容搬自阿三哥网站,只是翻译了一下。侵删。https://embetronicx.com/tutorials/linux/device-drivers/
正文如下:
这是“linux设备驱动系列”教程的续集,并且接着讨论字符驱动程序及其实现。本系列的目的是提供简单实用的示例,使每个人都能以简单的方式理解这些概念。现在让我们即将学习“linux设备驱动第四部分:字符设备驱动major号 & minor号”。
在之前的教程中,我们讨论了如何在加载时向Linux设备驱动程序传递参数。所以现在让我们来学习有关major号和minor号的知识。
内容速览
·1 简介
·2 应用程序如何与硬件设备通信?
·4.1 major号
·5.1 静态分配
·1 简介
我们已经知道了什么是驱动程序,以及为什么我们需要驱动程序。字符驱动程序有什么特殊的吗?如果我们编写面向字节操作的驱动程序,我们就将其视为字符驱动程序。由于大多数设备都是面向字节的,大多数设备驱动程序都是字符设备驱动程序。例如,串行驱动程序,音频驱动程序,视频驱动程序,摄像头驱动程序,以及基本I/O驱动程序。事实上,
设备驱动程序中,既不是存储设备驱动程序,也不是网络设备驱动程序的话,就属于字符驱动程序啦。
·2 应用程序如何与硬件设备通信?
下图展示了通信的完整路径。
·首先,应用程序会打开设备文件。这个设备文件是设备驱动程序创建的。
·然后这个设备文件会根据major号和minor号来找到对应的设备驱动程序。
·然后找到的这个设备驱动程序就可以跟硬件设备交流。
Linux内核的基本特性之一就是抽象对设备的处理。所有的硬件设备看起来都像是普通文件;可以使用相同的,标准的,用来操作文件的系统调用(system call)来打开(open),关闭(close),读(read),写(write)他们。对于Linux来说,一切皆文件。往硬盘写入数据时,其实是往文件写入。从键盘读取数据时,其实是向文件读取。保存磁带设备的备份时,其实是往文件里写入。甚至读取内存数据时也是从文件中读取。假如你想读或写的文件是一个普通文件,过程非常好理解:文件被打开,然后你读取或者写入数据。设备驱动程序与普通文件也差不多。驱动程序会为每个硬件设备创建一个特殊文件(与普通文件相对)。我们通过这些特殊文件(设备文件)与硬件沟通。
如果你想创建一个特殊文件,我们得了解设备驱动程序的major号与minor号相关的知识。在此节教程中我们会了解major和minor号。
·4 major号与minor号
Linux内核用一对数字来表示字符和块设备 <major> : <minor>。
·4.1 major号
传统来说,major号用来标识与设备相关联的驱动程序。一个major可以被许多设备驱动程序共享。在 /proc/devices 中我们可以看到,在一个运行中的Linux实例是怎么分配major号的。
这些数字是major号。
·4.2 minor号
major号用来标识相应的驱动程序。许多设备使用相同的major号。所以我们需要为使用相同major号的设备单独再分配一个数字。这就是minor号啦。换句话说,设备驱动程序使用minor号<minor>来辨别单个物理或逻辑设备。
·5 分配major号与minor号
我们有两种方法分配major号和minor号。
·静态分配
·动态分配
·5.1 静态分配
假如你想为你的驱动程序设置一个特别的major号,你可以使用这种方法。如果major号可用的话,这种方法会将major号分配给你。假如不可用,就分配失败。
int register_chardev_refion(dev_t first, unsigned int count, char *name);
first 是你想要分配设备号范围的起始数字。
count 是你请求的连续设备号的总数。请注意,如果count过大,你请求的范围会溢出至下一个major号;但是只要你请求的数字范围可用,一切都可以正常工作。
name 是与请求的设备号范围([first, first+count])相关联的设备名称。它将会在 /proc/devices 和 sysfs 目录中出现。
申请成功的话,函数 register_chrdev_region 的返回值为零。出错的话,会返回一个负的错误码,并且不能访问所申请的设备号范围。
dev_t 数据类型(在<linux/types.h>中定义)用来存储设备号的major和minor部分。dev_t 是32位的数据,12bits用于表示major号,20bits用来表示minor号。
假如你想创建dev_t结构体变量表示你的major号和minor号,请使用下面这个函数。
MKDEV(int major, int minor);
假如你想从dev_t变量中获取major号和minor号,请使用以下方法。
MAJOR(dev_t dev);
MINOR(dev_t dev);
如果你将dev_t变量传入MAJOR 或者MINOR函数,他会返回驱动程序的major号/minor号。
举个栗子,
dev_t dev = MKDEV(235, 0); register_chrdev_region(dev, 1, "Embetronicx_Dev");
·5.2 动态分配
假如我们不想要固定的major号和minor号请使用这种方法。这种方法会为驱动程序动态分配可用的major号。
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
dev 是一个输出参数,成功执行时会返回分配范围的第一个值。
firstminor 应为请求使用的第一个minor号,通常为0。
count 为您请求的连续设备号总数。
name 时与分配的设备号相关联的设备名称。他会在 /proc/devices 和 sysfs 中出现。
·5.3 静态和动态方法的区别
静态方法只有在你提前知道你想从哪一个major号开始时真的有用。使用静态方法时,你告诉内核你想要的设备号(起始major/minor号以及count)然后内核分配给你或不分配(取决于这些设备号是否可用)。
使用动态方法时,你告诉内核你需要多少设备号(起始minor号以及count)然后他会为你找到一个起始major号,如果他是可用的。
有些人为了避免与其他设备驱动程序产生冲突,会优先选择使用动态方法函数,它会为你动态分配设备号。
动态分配的缺点就是你不能提前创建设备节点,因为分配给你的major号可能会变化。对于驱动程序的正常使用来说,这不算是个问题。因为一旦分配了设备号,你可以从 /proc/devices 中读取。
·6 注销major号与minor号
不论你是怎样分配你的设备号的,当你不再使用他们的时候应该释放掉。设备号使用下面函数释放:
void unregister_chrdev_region(dev_t first, unsigned int count);
通常将unregister_chrdev_region放在模块的退出函数中。
·7 静态分配major号程序示例
从GitHub中获取源代码
在此程序中,我指定235作为major号。
/***************************************************************************//** * 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 ",MAJOR(dev), MINOR(dev)); printk(KERN_INFO "Kernel Module Inserted Successfully... "); return 0; } /* ** Module exit function */ static void __exit hello_world_exit(void) { unregister_chrdev_region(dev, 1); printk(KERN_INFO "Kernel Module Removed Successfully... "); } 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 insmode 加载驱动程序
·使用 cat /proc/devices 检查major号
linux@embetronicx-VirtualBox::/home/driver/driver$ cat /proc/devices | grep "Embetronicx_Dev" 235 Embetronicx_Dev
·8 动态分配major号程序示例
从GitHub中获取源代码
此程序会动态分配一个major号。
/***************************************************************************//** * 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 "); return -1; } printk(KERN_INFO "Major = %d Minor = %d ",MAJOR(dev), MINOR(dev)); printk(KERN_INFO "Kernel Module Inserted Successfully... "); return 0; } /* ** Module exit function */ static void __exit hello_world_exit(void) { unregister_chrdev_region(dev, 1); printk(KERN_INFO "Kernel Module Removed Successfully... "); } 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 insmode 加载驱动程序
·使用 cat /proc/devices 检查major号
linux@embetronicx-VirtualBox::/home/driver/driver$ cat /proc/devices | grep "Embetronicx_Dev" 243 Embetronicx_Dev
函数分配的major号243给这个驱动程序。
在卸载驱动程序之前使用 ls /dev/ 检查下/dev下的文件。你找不到我们的驱动程序文件。因为我们现在还没有创建它呢。在下一小节的教程里,我们会看到设备文件。
---------------------分割线-------------------