之前的实验,都是 mknod 手动创建节点,确实比较麻烦。
如何自动创建节点呢?
如果是pc机,比较高级的用的如果是udev 那么比较简单,如果是嵌入式系统被才裁减了,用mdev 相对复杂一些。
之前,如果做过,mknod 创建过的,需要删除掉,rm -rf /dev/hello
1. 保证根文件系统支持mdev可执行程序
which is mdev
如果没有,那就不支持了,然后 which is udevadm,如果有机制udev,那就不需要往下走了。
2. 保证文件系统的etc目录下有fstab文件,文件内容必须有:
proc /proc proc defaults 0 0
说明:将procfs虚拟文件系统挂接到/proc目录
sysfs /sys sysfs defaults 0 0
说明:将sysfs虚拟文件系统挂接到/sys目录
tmpfs /dev tmpfs defaults 0 0
说明:将tmpfs虚拟文件系统挂接到/dev目录
注意:sysfs,tmpfs,procfs虚拟文件系统的内容都是存在于内存中!
3. 保证根文件系统的etc/init.d/rcS脚本中,必须有:
/bin/mount -a #为了解析fstab文件
echo /sbin/mdev > /proc/sys/kernel/hotplug #将来内核解析hotplug文件,执行mdev可执行程序,创建设备文件
4. 字符设备驱动只需调用以下四个函数,即可完成设备文件的最终创建
struct class *cls; //定义设备类指针
入口函数调用:
// 定义设备类
cls = class_create(THIS_MODULE, "dev_name");
// 创建设备文件(长苹果),dev表示设备号,myled表示设备文件
device_create(cls, NULL, dev, NULL, "dev_file_name");
出口函数调用:
// 删除设备文件
device_destroy(cls, dev);
//删除设备类
class_destroy(cls);
案例:基于之前的代码,添加自动创建设备文件的功能
#include
#include
#include // struct file_operations
#include // struct cdev
#include
#include
MODULE_DESCRIPTION("Frocheng: Driver for DEMO!");
MODULE_AUTHOR("Frodo Cheng");
MODULE_LICENSE("GPL");
MODULE_VERSION("V0.0.1");
static char* name = "hello";
static struct class *cls;
static int state;
// 应用程序调用关系:open->软中断->sys_open->hello_open
static int hello_open(struct inode *inode,
struct file *file)
{
printk("===[frocheng]===[%s]===[%s]===[%d]===\n",__FILE__, __func__, __LINE__);
return 0; // 执行成功返回0,失败返回负值
}
// 应用程序调用关系:close->软中断->sys_close->hello_close
static int hello_close(struct inode *inode,
struct file *file)
{
printk("===[frocheng]===[%s]===[%s]===[%d]===\n",__FILE__, __func__, __LINE__);
return 0; // 执行成功返回0,失败返回负值
}
// 应用read->软中断->sys_read->hello_read
static ssize_t hello_read(struct file *file,
char __user *buf,
size_t count,
loff_t *ppos)
{
// 将内核缓冲区的数据拷贝到用户缓冲区
copy_to_user(buf, &state, sizeof(state));
return sizeof(state); // 返回实际读取的字节数
}
// 应用write->软中断->sys_write->hello_write
static ssize_t hello_write(struct file *file,
const char __user *buf,
size_t count,
loff_t *ppos)
{
int m = 0; // 内核区域的 对象
copy_from_user(&m, buf, sizeof(m));
// 模拟操作一下:
// 如果写进来的是偶数,那么,内部状态 更新为 m * 2;
// 如果写进来的是计数,那么,内部状态 更新为 m * 3;
if ((m & 0x01) == 0x00)
{
state = m * 2;
}
else
{
state = m * 3;
}
return sizeof(m);
}
// 定义初始化LED的硬件操作对象
// open,release一旦加载内存中,静静等待着应用程序来调用
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open, // 打开设备
.release = hello_close, // 关闭设备
.write = hello_write,
.read = hello_read
};
// 定义字符设备对象
static struct cdev hello_cdev;
// 定义设备号对象
static dev_t dev;
static int __init hello_init(void)
{
int rc = -1;
printk("===[frocheng]===[%s]===[%s]===[%d]===[Hello !]===\n",__FILE__, __func__, __LINE__);
// 申请设备号
rc = alloc_chrdev_region(&dev, 0, 1, name);
if (rc != 0)
{
printk("===[frocheng]===[%s]===[%s]===[alloc_chrdev_region error with %d]===\n",__FILE__, __func__, rc);
// hello 驱动模块加载失败,因为并没有申请到字符神资源,驱动加载失败。
return -1;
}
// 自动创建 device
cls = class_create(THIS_MODULE, name);
device_create(cls, NULL, dev, NULL, name);
printk("===[frocheng]===[%s]===[%s]===[name=%s, major=%u, minor=%u]===\n",__FILE__, __func__, name, MAJOR(dev), MINOR(dev));
// 初始化字符设备对象
cdev_init(&hello_cdev, &hello_fops);
// 注册字符设备对象
cdev_add(&hello_cdev, dev, 1);
return 0;
}
static void __exit hello_exit(void)
{
// 删除设备文件
device_destroy(cls, dev);
// 删除设备类
class_destroy(cls);
// 卸载字符设备对象
cdev_del(&hello_cdev);
// 释放设备号
unregister_chrdev_region(dev, 1);
printk("===[frocheng]===[%s]===[%s]===[%d]===[Bye bye...]===\n",__FILE__, __func__, __LINE__);
}
module_init(hello_init);
module_exit(hello_exit);
注意: PC机器上,就不用走上面的流程了,一般PC即用的都是udev。
加载了模块之后,ls /dev/hello,就有文件了,卸载了模块之后,ls /dev/hello ,就不存在了。