上一节的代码有两个使用不是很方便问题:
1.写驱动时,每个次设备号都要自己指定。(这样就不能作为移植性强的软件了)
2.引用层使用时不但要手动创建设备节点,还要知道其设备信息和设备号。
我们先来解决第一个问题:
其实很简单,主设备号为0,则系统就会为我们自动分配未使用的主设备号。
static unsigned int major;
static int __init first_drv_init(void)
{
printk("first_drv_init\n");
major = register_chrdev(0,"first_drv",&first_drv_file_operation);
/* 映射io寄存器 */
p_gpj0 = ioremap(GPJ0CON_PA, 0x08);
if(NULL == p_gpj0)
{
printk("ioremap fail\n");
return -1;
}
return 0;
}
之后编译安装,查看其自动分配的主设备号为254。至于为什么为254,可以查看我下面文章的分析。
https://blog.csdn.net/qq_16777851/article/details/80723572
接下来看如何自动创建设备节点
#include <linux/fs.h> /* 包含file_operation结构体 */
#include <linux/init.h> /* 包含module_init module_exit */
#include <linux/module.h> /* 包含LICENSE的宏 */
#include <asm/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
#define GPJ0CON_PA 0xe0200240
static unsigned int major;
static struct class *first_class;
static struct device *led_dev;
struct gpj0 {
unsigned int gpj0con;
unsigned int gpj0dat;
};
static struct gpj0* p_gpj0 = NULL;
static int first_drv_open(struct inode *inodep, struct file *filep)
{
printk("first_drv_open\n");
/* 初始化gpj0_3为输出 */
p_gpj0->gpj0con &= ~(0xf << 12);
p_gpj0->gpj0con |= (1 << 12);
/* 默认熄灭led灯 */
p_gpj0->gpj0dat |= (1<<3);
return 0;
}
static ssize_t first_drv_write(struct file *filep, const char __user * from, size_t len, loff_t *ppos)
{
char buf[10];
int ret;
printk("first_drv_write\n");
memset(buf, 0,10);
printk("from = 0x%p\n", from);
ret = copy_from_user(buf, from, 1);
if(ret)
{
printk("copy_from_user fail\n");
}
if('0' == buf[0])
{
p_gpj0->gpj0dat |= (1<<3);
}
else if('1' == buf[0])
{
p_gpj0->gpj0dat &= ~(1<<3);
}
else
{
printk("please input 1/0 ?");
}
return 0;
}
static const struct file_operations first_drv_file_operation = {
.owner = THIS_MODULE,
.open = first_drv_open,
.write = first_drv_write,
};
static int __init first_drv_init(void)
{
printk("first_drv_init\n");
major = register_chrdev(0,"first_drv",&first_drv_file_operation);
/* 映射io寄存器 */
p_gpj0 = ioremap(GPJ0CON_PA, 0x08);
if(NULL == p_gpj0)
{
printk("ioremap fail\n");
return -1;
}
/* 创建一个类 */
first_class = class_create(THIS_MODULE, "first_class");
if(!first_class)
{
if (IS_ERR(first_class))
return PTR_ERR(first_class);
}
/* 创建从属这个类的设备,主设备号使用自动分配的major,次设备号我们自己指定为0 */
led_dev = device_create(first_class,NULL,MKDEV(major, 0), NULL, "led");
if (IS_ERR(led_dev))
return PTR_ERR(led_dev);
return 0;
}
static void __exit first_drv_exit(void)
{
unregister_chrdev(major,"first_drv");
printk("first_drv_exit\n");
/* 使用完取消映射 */
iounmap(p_gpj0);
/* 注销类里面的设备 */
device_unregister(led_dev);
/* 注销类 */
class_destroy(first_class);
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");
其中class的知识可以参考我的设备模型相关文章
https://blog.csdn.net/qq_16777851/article/details/81488020
device的知识可以参考我的设备模型相关文章
https://blog.csdn.net/qq_16777851/article/details/81437352
如果有分析,device的注册过程,就可以发现,它最终其实只是调用来uevent来发送一个action.
真正的创建设备节点在在应用程序中创建。
通常是一个叫做udev的应用程序来创建,在嵌入式系统中我们是用busybox的mdev。
我们在创建根文件系统时,曾经在/etc/init.d/rcS文件中曾经加入了这么两句命令。
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
查看mdev的帮助信息可以知道。
其中 mdev -s的作用是 在引导期间运行以扫描/ sys下设备的并根据扫描到的设备信息在 /dev创建设备节点。
mdev是一个应用程序,所以肯定是在内核启动,内核中的驱动程序注册后,再运行的,所以上面的引导是指在系统启动完,执行rsS文件时,扫描一遍/sys文件系统,将扫描到的每个设备都加入到/dev目录下。
echo /sbin/mdev > /proc/sys/kernel/hotplug
上面这条命令则是,使用/sbin/mdev应用程序来激活内核的热插拔功能(即指明热插拔功能是用这个应用程序来处理)。
在pc机上默认是使用netlink机制, /proc/sys/kernel/hotplug文件默认是空。我们在里面写入mdev应用程序的命令,即配置为用mdev来创建设备节点。
即 mdev -s是在系统启动阶段使用来在/dev下创建设备节点。
而echo /sbin/mdev > /proc/sys/kernel/hotplug 是在系统的运行起来后通过insmod方式安装的设备在/dev下创建设备节点。
从/sys/kernel/下可以看到,uevent的一些信息,包括从开机开始到目前的事件数量。
而启动节点的扫描主要是扫描/sys/dev/目录下
其中每一个都是一个主从设备号的组合。
而它里面的uevent里面就包含着创建设备节点的信息,包括设备号,名字,(char,block进入文件夹时就可以知道)
关于uevent的相关知识,可以查看我的驱动模型章节中对uevent的分许,链接如下
https://blog.csdn.net/qq_16777851/article/details/81395281
真正的运行时安装卸载程序,则就靠前面的uevnet函数中,最后的call_usermodehelper函数来来执行用户空间应用程序
其作用是fork一个进程,以env为参数,执行uevent_helper(我们这里的就是/dev/mdev应用程序)
可见,mdev其实是一个很简单的应用程序,它主要实现mdev -s扫描 /sys/dev/下的 char和block目录下的每一个设备中的uevent.
利用uevnet中得到的信息创建设备节点。
而动态插拔的就更简单了,在驱动注册时通过时通过内核的call_usermodehelper函数调用应用程序mdev,并直接传给它新设备在/sys下的目录,进而mdev直接得到设备的uevent里面的信息,直接创建设备节点。
注释掉rcS中的mdev -s后,启动阶段将不再扫描注册设备。
自己手动执行这条命令后,当然也可以通过mdev应用程序,扫描/sys下来创建设备节点。
启动后的激活热插拔,注释那条命令,自己对比insmod实践一下就可以看出区别了。