资讯动态

从字符设备到块设备:手把手教你读懂Linux的/dev目录和设备号(以i2c和loop为例)

发布时间:2026/9/24 18:39:57 来源:尧图企业网站定制
从字符设备到块设备深入解析Linux设备模型与/dev目录实战指南当你第一次在Linux终端输入ls /dev时屏幕上滚动显示的数百个设备文件可能会让你感到困惑。这些看似普通的文件背后隐藏着Linux设备管理的核心机制。本文将带你从零开始通过动手实验和原理分析彻底理解字符设备、块设备、设备号与驱动模块之间的精妙关系。1. Linux设备模型基础文件即设备的哲学Linux继承Unix的核心理念之一就是一切皆文件。这种设计哲学使得设备操作变得异常简单——读写设备就像读写普通文件一样。但在这简单的表象之下是一套精巧的设备管理机制。设备文件的两种类型字符设备(Character Device)以字节流形式进行数据传输没有固定大小和结构。典型例子包括键盘、鼠标、串口等。块设备(Block Device)以固定大小的数据块为单位进行读写支持随机访问。硬盘、SSD、USB存储设备都属于这一类。在/dev目录下你可以通过颜色区分这两类设备取决于你的ls配置ls -l /dev | grep -E ^b|^c输出示例crw-rw-rw- 1 root root 1, 3 Aug 10 09:23 null brw-rw---- 1 root disk 8, 0 Aug 10 09:23 sda设备号的秘密 每个设备文件都有两个关键数字主设备号(Major number)和次设备号(Minor number)。它们通常显示在ls -l输出的第5和第6列如上面的1,3和8,0。主设备号标识设备类型和对应的驱动程序而次设备号用于区分同一驱动程序管理的不同设备实例。例如多个硬盘分区可能共享同一个主设备号但有不同的次设备号。2. 深入设备号内核如何关联设备与驱动2.1 查看已注册设备号Linux内核维护着一个设备号分配表。要查看当前系统中已注册的设备号cat /proc/devices输出分为两部分——字符设备和块设备例如Character devices: 1 mem 4 /dev/vc/0 7 vcs 89 i2c Block devices: 7 loop 8 sd 11 sr2.2 设备号与驱动模块的关联设备号与驱动模块的关联是通过内核的数据结构实现的。我们可以通过以下步骤验证这种关联查找设备对应的主设备号ls -l /dev/i2c-0 /dev/loop0检查哪些模块注册了这些设备号grep -l 89 /sys/module/*/holders/* 2/dev/null # 查找i2c主设备号89 grep -l 7 /sys/module/*/holders/* 2/dev/null # 查找loop主设备号7确认模块是否加载lsmod | grep -E i2c|loop2.3 设备号的动态分配现代Linux内核支持设备号的动态分配。驱动开发者可以静态声明主设备号不推荐可能冲突动态申请设备号推荐做法查看动态分配的设备号范围cat /proc/sys/kernel/devices.txt3. 实战从零创建虚拟字符设备理解理论最好的方式就是动手实践。让我们创建一个简单的虚拟字符设备3.1 编写基础驱动代码创建simple_char.c#include linux/module.h #include linux/fs.h #include linux/uaccess.h #define DEVICE_NAME simple_char static int major_num; static int device_open(struct inode *inode, struct file *file) { printk(KERN_INFO Simple char device opened\n); return 0; } static ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t *offset) { char message[] Hello from the kernel!\n; int msg_len strlen(message); if (*offset msg_len) return 0; if (length msg_len - *offset) msg_len length; if (copy_to_user(buffer, message *offset, msg_len)) return -EFAULT; *offset msg_len; return msg_len; } static struct file_operations fops { .open device_open, .read device_read, }; static int __init simple_char_init(void) { major_num register_chrdev(0, DEVICE_NAME, fops); if (major_num 0) { printk(KERN_ALERT Failed to register device: %d\n, major_num); return major_num; } printk(KERN_INFO Registered device with major number %d\n, major_num); return 0; } static void __exit simple_char_exit(void) { unregister_chrdev(major_num, DEVICE_NAME); printk(KERN_INFO Unregistered simple char device\n); } module_init(simple_char_init); module_exit(simple_char_exit); MODULE_LICENSE(GPL);3.2 编译并加载模块创建Makefileobj-m : simple_char.o KDIR : /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: make -C $(KDIR) M$(PWD) modules clean: make -C $(KDIR) M$(PWD) clean编译并加载模块make sudo insmod simple_char.ko dmesg | tail # 查看分配的主设备号3.3 创建设备文件并测试假设分配的主设备号为250sudo mknod /dev/simple_char c 250 0 sudo chmod 666 /dev/simple_char cat /dev/simple_char # 应显示Hello from the kernel!4. 块设备深度解析以loop设备为例loop设备是Linux中一种特殊的块设备它允许将普通文件作为块设备挂载。这在创建磁盘映像、测试文件系统等场景非常有用。4.1 loop设备工作原理loop设备的核心机制包括文件映射将普通文件映射为块设备缓存机制提高读写性能加密支持可选的文件系统加密查看系统中的loop设备ls -l /dev/loop*典型输出brw-rw---- 1 root disk 7, 0 Aug 10 09:23 /dev/loop0 brw-rw---- 1 root disk 7, 1 Aug 10 09:23 /dev/loop1 ...4.2 手动创建和使用loop设备创建测试文件dd if/dev/zero oftest.img bs1M count100关联loop设备sudo losetup -f --show test.img命令会输出分配的loop设备路径如/dev/loop5创建文件系统并挂载sudo mkfs.ext4 /dev/loop5 sudo mount /dev/loop5 /mnt使用后卸载sudo umount /mnt sudo losetup -d /dev/loop54.3 loop设备的内核实现loop设备的驱动代码主要在内核的drivers/block/loop.c中实现。关键数据结构包括struct loop_device { int lo_number; // 次设备号 struct file *lo_backing_file; // 关联的文件 struct bio_list lo_bio_list; // 待处理的I/O请求队列 // ...其他字段 };当用户空间程序读写loop设备时内核会将请求转发到关联的文件整个过程对上层应用透明。5. I2C设备模型解析I2C总线在嵌入式系统中广泛使用Linux内核提供了完整的I2C子系统支持。让我们深入分析/dev/i2c-*设备。5.1 I2C设备文件特性查看典型的I2C设备文件ls -l /dev/i2c-*输出示例crw-rw---- 1 root i2c 89, 0 Aug 10 09:23 /dev/i2c-0 crw-rw---- 1 root i2c 89, 1 Aug 10 09:23 /dev/i2c-1所有I2C设备共享主设备号89次设备号对应不同的I2C总线控制器。5.2 I2C用户空间接口用户空间程序可以通过/dev/i2c-*设备文件直接与I2C设备通信。常用操作包括打开I2C设备int fd open(/dev/i2c-1, O_RDWR);设置从设备地址ioctl(fd, I2C_SLAVE, 0x50); // 设置地址为0x50读写数据uint8_t buf[10]; write(fd, buf, sizeof(buf)); read(fd, buf, sizeof(buf));5.3 I2C内核子系统架构Linux I2C子系统采用分层设计I2C核心层提供注册总线、设备、驱动的接口I2C总线驱动实现特定控制器的硬件操作I2C设备驱动实现具体设备的业务逻辑这种设计使得添加新的I2C控制器或设备变得非常模块化。6. 设备模型高级话题6.1 udev与设备文件自动化管理现代Linux系统使用udev动态管理/dev目录。udev的主要功能包括自动创建设备节点设置设备权限执行设备插入时的自定义脚本查看udev规则ls /etc/udev/rules.d/6.2 sysfs与设备信息导出Linux通过/sys文件系统向用户空间导出设备信息。例如查看I2C设备信息ls /sys/bus/i2c/devices/6.3 设备树(DTS)与硬件描述在嵌入式系统中设备树用于描述硬件配置。查看已加载的设备树ls /proc/device-tree/设备树中的I2C控制器描述示例i2c400A0000 { compatible vendor,i2c-controller; reg 0x400A0000 0x1000; interrupts 32; #address-cells 1; #size-cells 0; eeprom50 { compatible atmel,24c256; reg 0x50; }; };

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价