字符设备驱动核心是注册cdev并实现file_operations(open/release/read/write/ioctl/mmap);设备号用alloc_chrdev_region动态分配或register_chrdev_region静态指定;用cdev_init+ cdev_add注册;用户空间通过/dev/xxx节点访问,ioctl用于自定义控制命令;现代驱动推荐用miscdevice简化注册,或用设备树+platform_driver框架。
本文概述
字符设备是Linux最基础的设备类型,GPIO、LED、按键、ADC、传感器等都常用字符设备接口。驱动开发的核心是实现file_operations结构体中的回调函数,通过cdev注册到内核,用户空间通过标准文件操作(open/read/write/ioctl)访问。
核心要点
设备号 = 主设备号(标识驱动)+ 次设备号(标识具体设备),用MKDEV(major,minor)宏
动态分配设备号:alloc_chrdev_region(&dev, 0, 1, "mydev")
静态指定:register_chrdev_region(dev, 1, "mydev"),需要主设备号不冲突
cdev_init(&cdev, &fops) 绑定操作函数,cdev_add(&cdev, dev, 1) 注册到内核
file_operations包含:owner, open, release, read, write, unlocked_ioctl, mmap, poll等
ioctl命令用_IOC(dir,type,nr,size)宏构造,_IOW/_IOR/_IOWR分别表示写/读/读写
miscdevice是封装好的字符设备,主设备号固定10,自动创建设备节点,简化开发

面试官考察点
考察Linux驱动开发基础、字符设备注册流程、file_operations实现、ioctl命令构造、用户空间交互。回答思路
先讲字符设备的作用和设备号概念,再讲注册流程(分配设备号→cdev_init→cdev_add),然后讲file_operations各回调,最后讲ioctl和用户空间测试。参考回答
字符设备驱动开发我按这个流程:
1. 分配设备号。优先用动态分配,避免主设备号冲突:
dev_t dev; alloc_chrdev_region(&dev, 0, 1, "mydev"); // 0=次设备号起始, 1=数量 int major = MAJOR(dev); // 保存主设备号
卸载时用 unregister_chrdev_region(dev, 1) 释放。
2. 实现file_operations。这是驱动的核心,定义用户空间能做什么操作:
| 回调函数 | 对应系统调用 | 作用 |
|---|---|---|
| open | open() | 打开设备,初始化硬件 |
| release | close() | 关闭设备,释放资源 |
| read | read() | 从设备读数据到用户空间 |
| write | write() | 从用户空间写数据到设备 |
| unlocked_ioctl | ioctl() | 自定义控制命令 |
| mmap | mmap() | 映射设备内存到用户空间 |
| poll | select/poll | 非阻塞IO和事件通知 |
static int my_open(struct inode *inode, struct file *filp) { ... }
static int my_release(struct inode *inode, struct file *filp) { ... }
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { ... }
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { ... }
static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { ... }
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
.unlocked_ioctl = my_ioctl,
};3. 注册cdev。
struct cdev *my_cdev = cdev_alloc(); cdev_init(my_cdev, &my_fops); cdev_add(my_cdev, dev, 1);
4. read/write中处理用户空间数据。不能直接访问用户空间指针,必须用copy_to_user/copy_from_user:
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {
int val = read_hardware();
if (copy_to_user(buf, &val, sizeof(val))) return -EFAULT;
return sizeof(val);
}5. ioctl实现自定义命令。用宏构造命令字,区分读/写方向:
#define MY_IOC_MAGIC 'm'
#define MY_IOC_SET_VAL _IOW(MY_IOC_MAGIC, 1, int) // 用户写(驱动收)
#define MY_IOC_GET_VAL _IOR(MY_IOC_MAGIC, 2, int) // 用户读(驱动发)
static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
int val;
switch(cmd) {
case MY_IOC_SET_VAL:
if (copy_from_user(&val, (int __user *)arg, sizeof(val))) return -EFAULT;
set_hardware(val); break;
case MY_IOC_GET_VAL:
val = read_hardware();
if (copy_to_user((int __user *)arg, &val, sizeof(val))) return -EFAULT;
break;
default: return -ENOTTY;
}
return 0;
}6. 创建设备节点。用class_create+device_create自动在/dev下创建设备节点,或手动mknod。现代驱动都用udev自动创建。
7. 简化方式:miscdevice。简单字符设备可以用miscdevice,主设备号固定10,自动创建设备节点:
static struct miscdevice my_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mydev",
.fops = &my_fops,
};
misc_register(&my_misc);
扫码关注



























