自动创建设备节点是Linux设备驱动开发中的一个重要环节,它允许设备驱动程序在内核中注册后,自动在/dev
目录下创建对应的设备文件,从而使得用户空间程序可以通过标准的文件操作接口(如open、read、write等)与硬件设备进行交互。 ❤
一、自动创建设备节点的概念
在Linux中,一切皆文件,设备驱动程序也不例外。设备驱动程序通过设备文件的形式向上层程序提供接口。设备文件通常位于/dev
目录下,包括字符设备文件、块设备文件、网络设备文件等。这些特殊类型的文件使用统一的文件操作函数(如open、read、write等)进行访问。
二、相关函数和宏定义
(1)class_create
宏定义
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
实际上,class_create
是一个宏定义,它内部调用了__class_create
函数。
参数:
owner
:指向模块所有者的指针,通常为THIS_MODULE
宏,表示当前模块。name
:设备类的名称,该名称将用于在/sys/class/
目录下创建对应的目录。
返回值:返回指向新创建的struct class
结构体的指针,如果创建失败则返回ERR_PTR
错误码。
(2)class_destroy
/**
* class_destroy - destroys a struct class structure
* @cls: pointer to the struct class that is to be destroyed
*
* Note, the pointer to be destroyed must have been created with a call
* to class_create().
*/
void class_destroy(struct class *cls)
{
if ((cls == NULL) || (IS_ERR(cls)))
return;
class_unregister(cls);
}
功能:class_destroy
函数用于销毁之前通过class_create
创建的设备类。它会删除/sys/class/
目录下对应的目录,并清理相关的资源。
参数:cls
是指向要销毁的设备类的指针。
返回值:无返回值
(3)device_create
功能:device_create
函数用于在指定的设备类下创建一个新的设备,并自动在/dev
目录下创建对应的设备文件(如果udev或mdev配置正确的话)。此外,它还会在/sys/devices/
和/sys/class/<类名>/
目录下创建相应的设备目录。
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...);
参数:
class
:设备所属的类。parent
:设备的父设备,如果设备没有父设备,则传递NULL
。devt
:设备的设备号,由主设备号和次设备号组成。drvdata
:传递给设备的私有数据,通常传递NULL
。fmt
:设备文件的名称格式字符串,后续可变参数将根据这个格式字符串生成设备文件的名称。
返回值:返回指向新创建的struct device
结构体的指针,如果创建失败则返回ERR_PTR
错误码
(4)device_destroy
功能:device_destroy
函数用于销毁之前通过device_create
创建的设备。它会删除/sys/devices/
、/sys/class/<类名>/
目录下对应的设备目录,并尝试删除/dev
目录下的设备文件(这通常依赖于udev或mdev的配置)
void device_destroy(struct class *class, dev_t devt);
参数:
class
:设备所属的类。devt
:设备的设备号。
返回值:无返回值。
三、实现方式
(1)设置class—create()
(2)出错处理
(3)实现步骤
①. 使用udev
udev通过读取sysfs中的设备信息,结合udev规则文件(通常位于/etc/udev/rules.d/
目录下),来决定如何创建设备文件。但是,在设备驱动代码中,通常需要执行以下步骤来配合udev自动创建设备节点:
-
使用
class_create
函数创建一个设备类(class),这个类会在/sys/class/
目录下创建一个对应的目录。 -
使用
device_create
函数在创建的设备类下创建一个设备,这个函数会在/sys/class/<类名>/
目录下创建一个设备目录,并且udev会根据规则文件在/dev
目录下创建对应的设备文件。
②. 使用mdev(嵌入式系统)
在嵌入式Linux系统中,由于资源限制,可能会使用mdev作为udev的简化版。mdev同样能够监听内核的设备事件,并自动创建或删除设备文件。但是,与udev不同,mdev的配置可能更加简单直接,通常通过配置文件或命令行参数来指定行为。
(4)具体代码
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <asm/io.h>
#include <asm/string.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#define MAJOR_NUM 253
#define MINOR_NUM 0
#define DEV_NAME "led"
#define DEV_NUM 1
#define GPBCON 0x56000010
#define GPBDAT 0x56000014
static volatile unsigned long * gpbcon;
static volatile unsigned long * gpbdat;
static void init_led(void)
{
// 配置GPB5引脚功能为输出
*gpbcon &= ~(0xff << 10);
*gpbcon |= (0x55 << 10);
// 将GPB5引脚电平置高
*gpbdat |= (0xf << 5);
}
static void led_on(void)
{
// 将GPB5引脚电平置低
*gpbdat &= ~(0xf << 5);
}
static void led_off(void)
{
// 将GPB5引脚电平置高
*gpbdat |= (0xf << 5);
}
static int open (struct inode * inode, struct file * file)
{
init_led();
printk("led open ...\n");
return 0;
}
static ssize_t read (struct file * file, char __user * buf, size_t len, loff_t * offset)
{
//copy_to_user(buf, data, len);
printk("led read ...\n");
return 0;
}
static ssize_t write (struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
unsigned char data[12] = {0};
size_t len_cp = sizeof(data) < len ? sizeof(data) : len;
copy_from_user(data, buf, len_cp);
if(!strcmp(data, "ledon"))
led_on();
else if(!strcmp(data, "ledoff"))
led_off();
else
return -1;
printk("led write ...\n");
return len_cp;
}
static int close (struct inode * inode, struct file * file)
{
printk("led close ...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.release = close
};
static struct cdev cdev;
static dev_t dev;
struct class * pclass;
struct device * pdev;
static int __init led_init(void)
{
int ret = 0;
dev = MKDEV(MAJOR_NUM, MINOR_NUM);
cdev_init(&cdev, &fops);
ret = cdev_add(&cdev, dev, DEV_NUM);
if(ret < 0)
goto err_cdev_add;
ret = register_chrdev_region(dev, DEV_NUM, DEV_NAME);
if(ret < 0)
goto err_register_chrdev_region;
pclass = class_create(THIS_MODULE, "led_class");
if(pclass == NULL)
goto err_class_create;
pdev = device_create(pclass, NULL, dev, NULL, DEV_NAME);
if(pdev == NULL)
goto err_device_create;
gpbcon = ioremap(GPBCON, sizeof(*gpbcon));
gpbdat = ioremap(GPBDAT, sizeof(*gpbdat));
printk("led_init ...\n");
return ret;
err_cdev_add:
cdev_del(&cdev);
printk("led cdev_add failed\n");
return ret;
err_register_chrdev_region:
unregister_chrdev_region(dev, DEV_NUM);
cdev_del(&cdev);
printk("led register_chrdev_region failed\n");
return ret;
err_class_create:
class_destroy(pclass);
unregister_chrdev_region(dev, DEV_NUM);
cdev_del(&cdev);
printk("led class_create failed\n");
return -1;
err_device_create:
device_destroy(pclass, dev);
class_destroy(pclass);
unregister_chrdev_region(dev, DEV_NUM);
cdev_del(&cdev);
printk("led device_create failed\n");
return -1;
}
static void __exit led_exit(void)
{
iounmap(gpbcon);
iounmap(gpbdat);
device_destroy(pclass, dev);
class_destroy(pclass);
unregister_chrdev_region(dev, DEV_NUM);
cdev_del(&cdev);
printk("led_exit ###############################\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");