在操作系统这座城市中,设备文件系统就像城市里的设备登记处。每一个硬件设备(硬盘、串口、鼠标等)都需要在登记处有一个唯一的身份标识,这样应用程序才能找到并使用它们。
🏙️ 城市类比:
设备文件系统的重要性体现在:
Linux 设备文件系统经历了三代演化:
让我们将设备文件系统比作城市的设备管理局:
Linux 使用 主设备号(major) 和 次设备号(minor) 标识设备:
// include/linux/types.htypedef u32 dev_t;#define MAJOR(dev) ((unsigned int) ((dev) >> 20))#define MINOR(dev) ((unsigned int) ((dev) & 0xfffff))#define MKDEV(ma,mi) (((ma) << 20) | (mi))城市类比:主设备号像公交车线路号,次设备号像线路上的具体站点。
设备文件系统本质是 VFS(虚拟文件系统)的一种实现,关键协议包括:

struct kobject | ||
struct kset | ||
struct kobj_type | ||
struct kernfs_node | ||
struct device |
// include/linux/kobject.hstructkobject {constchar *name;structlist_headentry;structkobject *parent;structkset *kset;structkobj_type *ktype;structkernfs_node *sd;/* sysfs directory entry */structkrefkref;#ifdef CONFIG_DEBUG_KOBJECT_RELEASEstructdelayed_workrelease;#endifunsignedint state_initialized:1;unsignedint state_in_sysfs:1;unsignedint state_add_uevent_sent:1;unsignedint state_remove_uevent_sent:1;unsignedint uevent_suppress:1;};城市类比:这就像城市里每个居民都有的身份证,包含名字、所属社区、身份类型等信息。

3.1.2 struct device
// include/linux/device.hstructdevice {structdevice *parent;structdevice_private *p;structkobjectkobj;constchar *init_name;conststructdevice_type *type;structmutexmutex;structbus_type *bus;structdevice_driver *driver;void *platform_data;void *driver_data;structdev_links_infolinks;structdev_pm_infopower;dev_t devt; u32 id;structclass *class;conststructattribute_group **groups;void (*release)(struct device *dev);};城市类比:这是设备的完整档案,包含所属公交公司(bus)、司机(driver)、档案编号(devt)等。
// fs/sysfs/file.cintsysfs_create_file(struct kobject *kobj, const struct attribute *attr){return sysfs_create_file_ns(kobj, attr, NULL);}intsysfs_create_file_ns(struct kobject *kobj, const struct attribute *attr,constvoid *ns){structkernfs_node *parent;int error;if (!kobj || !kobj->sd || !attr)return -EINVAL; parent = kobj->sd; error = kernfs_create_file_ns(parent, attr->name, attr->mode, attr->size, sysfs_file_operations, attr, ns);return error;}城市类比:这就像在信息公告栏(sysfs)上张贴一张新公告(属性文件)。

🏙️ 城市类比:

#include<linux/module.h>#include<linux/fs.h>#include<linux/device.h>#include<linux/cdev.h>#include<linux/uaccess.h>#define DEVICE_NAME "simple_dev"#define CLASS_NAME "simple_class"staticint major;staticstructclass* simple_class = NULL;staticstructdevice* simple_device = NULL;staticstructcdevsimple_cdev;staticintsimple_open(struct inode *inode, struct file *file){ pr_info("simple_dev: open() called\n");return0;}staticssize_tsimple_read(struct file *file, char __user *buffer,size_t len, loff_t *offset){char msg[] = "Hello from simple device!\n";int msg_len = sizeof(msg);if (*offset >= msg_len)return0;if (*offset + len > msg_len) len = msg_len - *offset;if (copy_to_user(buffer, msg + *offset, len))return -EFAULT; *offset += len;return len;}staticconststructfile_operationssimple_fops = { .owner = THIS_MODULE, .open = simple_open, .read = simple_read,};staticint __init simple_init(void){int err;dev_t dev; err = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);if (err < 0)return err; major = MAJOR(dev); simple_class = class_create(CLASS_NAME);if (IS_ERR(simple_class))goto unregister_chrdev; cdev_init(&simple_cdev, &simple_fops); simple_cdev.owner = THIS_MODULE; err = cdev_add(&simple_cdev, dev, 1);if (err)goto destroy_class; simple_device = device_create(simple_class, NULL, dev, NULL, DEVICE_NAME);if (IS_ERR(simple_device))goto del_cdev;return0;del_cdev: cdev_del(&simple_cdev);destroy_class: class_destroy(simple_class);unregister_chrdev: unregister_chrdev_region(MKDEV(major, 0), 1);return err;}staticvoid __exit simple_exit(void){dev_t dev = MKDEV(major, 0); device_destroy(simple_class, dev); cdev_del(&simple_cdev); class_destroy(simple_class); unregister_chrdev_region(dev, 1);}module_init(simple_init);module_exit(simple_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Example");MODULE_DESCRIPTION("Simple character device driver");#include<linux/device.h>staticssize_tmy_attr_show(struct device *dev, struct device_attribute *attr,char *buf){return sysfs_emit(buf, "Hello from sysfs\n");}staticssize_tmy_attr_store(struct device *dev, struct device_attribute *attr,constchar *buf, size_t count){ pr_info("Received: %.*s\n", (int)count, buf);return count;}staticDEVICE_ATTR_RW(my_attr);staticintmy_probe(struct platform_device *pdev){int ret; ret = device_create_file(&pdev->dev, &dev_attr_my_attr);if (ret)return ret;return0;}staticintmy_remove(struct platform_device *pdev){ device_remove_file(&pdev->dev, &dev_attr_my_attr);return0;}// simple_char_dev.c#include<linux/module.h>#include<linux/fs.h>#include<linux/device.h>#include<linux/cdev.h>#include<linux/uaccess.h>#include<linux/slab.h>#define DEVICE_NAME "simple_char_dev"#define CLASS_NAME "simple_char_class"#define BUF_SIZE 1024structsimple_dev_data {char buffer[BUF_SIZE];int size;structmutexlock;};staticint major;staticstructclass* simple_class = NULL;staticstructdevice* simple_device = NULL;staticstructcdevsimple_cdev;staticstructsimple_dev_data *dev_data;staticintsimple_open(struct inode *inode, struct file *file){ file->private_data = dev_data; pr_info("simple_char_dev: open() called\n");return0;}staticintsimple_release(struct inode *inode, struct file *file){ pr_info("simple_char_dev: release() called\n");return0;}staticssize_tsimple_read(struct file *file, char __user *buffer,size_t len, loff_t *offset){structsimple_dev_data *data = file->private_data; mutex_lock(&data->lock);if (*offset >= data->size) { mutex_unlock(&data->lock);return0; }if (*offset + len > data->size) len = data->size - *offset;if (copy_to_user(buffer, data->buffer + *offset, len)) { mutex_unlock(&data->lock);return -EFAULT; } *offset += len; mutex_unlock(&data->lock);return len;}staticssize_tsimple_write(struct file *file, constchar __user *buffer,size_t len, loff_t *offset){structsimple_dev_data *data = file->private_data; mutex_lock(&data->lock);if (*offset >= BUF_SIZE) { mutex_unlock(&data->lock);return -ENOSPC; }if (*offset + len > BUF_SIZE) len = BUF_SIZE - *offset;if (copy_from_user(data->buffer + *offset, buffer, len)) { mutex_unlock(&data->lock);return -EFAULT; } *offset += len;if (*offset > data->size) data->size = *offset; mutex_unlock(&data->lock);return len;}staticconststructfile_operationssimple_fops = { .owner = THIS_MODULE, .open = simple_open, .release = simple_release, .read = simple_read, .write = simple_write,};staticssize_tbuffer_show(struct device *dev, struct device_attribute *attr,char *buf){return sysfs_emit(buf, "%.*s\n", dev_data->size, dev_data->buffer);}staticDEVICE_ATTR_RO(buffer);staticssize_tsize_show(struct device *dev, struct device_attribute *attr,char *buf){return sysfs_emit(buf, "%d\n", dev_data->size);}staticDEVICE_ATTR_RO(size);staticint __init simple_init(void){int err;dev_t dev; dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);if (!dev_data)return -ENOMEM; mutex_init(&dev_data->lock); err = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);if (err < 0) { kfree(dev_data);return err; } major = MAJOR(dev); simple_class = class_create(CLASS_NAME);if (IS_ERR(simple_class)) { err = PTR_ERR(simple_class);goto unregister_chrdev; } cdev_init(&simple_cdev, &simple_fops); simple_cdev.owner = THIS_MODULE; err = cdev_add(&simple_cdev, dev, 1);if (err < 0)goto destroy_class; simple_device = device_create(simple_class, NULL, dev, NULL, DEVICE_NAME);if (IS_ERR(simple_device)) { err = PTR_ERR(simple_device);goto del_cdev; } err = device_create_file(simple_device, &dev_attr_buffer);if (err < 0)goto destroy_device; err = device_create_file(simple_device, &dev_attr_size);if (err < 0)goto remove_buffer_attr;return0;remove_buffer_attr: device_remove_file(simple_device, &dev_attr_buffer);destroy_device: device_destroy(simple_class, dev);del_cdev: cdev_del(&simple_cdev);destroy_class: class_destroy(simple_class);unregister_chrdev: unregister_chrdev_region(MKDEV(major, 0), 1); kfree(dev_data);return err;}staticvoid __exit simple_exit(void){dev_t dev = MKDEV(major, 0); device_remove_file(simple_device, &dev_attr_size); device_remove_file(simple_device, &dev_attr_buffer); device_destroy(simple_class, dev); cdev_del(&simple_cdev); class_destroy(simple_class); unregister_chrdev_region(dev, 1); kfree(dev_data);}module_init(simple_init);module_exit(simple_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Example");MODULE_DESCRIPTION("Simple character device driver with sysfs attributes");# Makefileobj-m += simple_char_dev.oKERNELDIR ?= /lib/modules/$(shell uname -r)/buildPWD := $(shell pwd)all: make -C $(KERNELDIR) M=$(PWD) modulesclean: make -C $(KERNELDIR) M=$(PWD) clean# 编译驱动make# 加载驱动sudo insmod simple_char_dev.ko# 查看设备文件ls -l /dev/simple_char_dev# 查看 sysfs 属性ls -l /sys/class/simple_char_class/simple_char_dev/# 测试设备echo"Hello World" | sudo tee /dev/simple_char_devcat /dev/simple_char_devcat /sys/class/simple_char_class/simple_char_dev/buffercat /sys/class/simple_char_class/simple_char_dev/size# 卸载驱动sudo rmmod simple_char_devimplicit declaration | #include <linux/...> | |
struct device has no member | ||
module license GPL | MODULE_LICENSE("GPL") |
🏙️ 城市类比:编译错误就像城市建设许可证问题,需要补齐手续。
# 检查步骤:dmesg | tail -20 # 查看内核日志ls -l /sys/class/ # 检查 class 是否创建systemctl status udev # 检查 udev 状态解决方案:
device_create 返回值城市类比:这就像安装了设备但设备登记处还没发身份证。
# 解决方案:创建 udev 规则cat > /etc/udev/rules.d/99-simple.rules << 'EOF'KERNEL=="simple_char_dev", MODE="0666"EOF# 重新加载 udev 规则sudo udevadm control --reload-rulessudo udevadm trigger# 监听 ueventudevadm monitor# 或者使用 netlink 监听cat /proc/kmsg# 查看设备的 sysfs 目录tree /sys/class/simple_char_class/simple_char_dev/# 查看 uevent 内容cat /sys/class/simple_char_class/simple_char_dev/uevent
🏙️ 城市类比:进阶学习就像城市向「智慧城市」升级
环境信息: