字符设备驱动
一、什么是字符设备
- 按字节流访问的设备
- 与块设备的区别:能否随机访问(块设备能)
- 内核用
cdev表示 - 用户态通过
/dev/<name>访问
二、设备号
#include <linux/kdev_t.h>
#include <linux/fs.h>
// 主设备号区分驱动,次设备号区分设备实例
// 例:ttySAC0(major=204, minor=0)、ttySAC1(major=204, minor=1)
MAJOR(dev_t dev);
MINOR(dev_t dev);
MKDEV(int major, int minor);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
int register_chrdev_region(dev_t dev, unsigned count, const char *name);
void unregister_chrdev_region(dev_t dev, unsigned count);
三、核心结构 file_operations
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.llseek = my_llseek,
.read = my_read,
.write = my_write,
.unlocked_ioctl = my_ioctl,
.mmap = my_mmap,
.open = my_open,
.release = my_release,
.fsync = my_fsync,
.poll = my_poll,
};
四、初始化与注册
static dev_t devno;
static struct cdev my_cdev;
static struct class *my_class;
static struct device *my_device;
static int __init my_init(void)
{
int ret;
// 1. 分配设备号
ret = alloc_chrdev_region(&devno, 0, 1, "mydev");
if (ret) return ret;
// 2. 初始化 cdev
cdev_init(&my_cdev, &my_fops);
my_cdev.owner = THIS_MODULE;
// 3. 添加到内核
ret = cdev_add(&my_cdev, devno, 1);
if (ret) goto err_region;
// 4. 创建 class 和 device(自动创建设备节点)
my_class = class_create(THIS_MODULE, "mydev");
if (IS_ERR(my_class)) { ret = PTR_ERR(my_class); goto err_cdev; }
my_device = device_create(my_class, NULL, devno, NULL, "mydev");
if (IS_ERR(my_device)) { ret = PTR_ERR(my_device); goto err_class; }
pr_info("loaded: major=%d minor=%d\n", MAJOR(devno), MINOR(devno));
return 0;
err_class:
class_destroy(my_class);
err_cdev:
cdev_del(&my_cdev);
err_region:
unregister_chrdev_region(devno, 1);
return ret;
}
static void __exit my_exit(void)
{
device_destroy(my_class, devno);
class_destroy(my_class);
cdev_del(&my_cdev);
unregister_chrdev_region(devno, 1);
}
五、关键回调
1. open / release
static int my_open(struct inode *inode, struct file *filp)
{
// 从 inode 获取 cdev 数据
// filp->private_data = ...
return 0;
}
static int my_release(struct inode *inode, struct file *filp)
{
return 0;
}
2. read / write
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
// 1. 检查数据是否就绪
// 2. 从设备或缓冲区读取
// 3. copy_to_user(buf, kbuf, n)
return 实际读取字节数; // 0=EOF, -EFAULT, -EINVAL 等
}
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
// 1. copy_from_user(kbuf, buf, n)
// 2. 写入设备或缓冲区
return 实际写入字节数;
}
注意:
- 一次 read 不一定读完(特别是管道/网络),但字符设备一般可以一次读满
- 写入缓冲要考虑并发
3. ioctl(推荐 unlocked_ioctl)
static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
// 必须检查 cmd 合法性、arg 指针
if (_IOC_TYPE(cmd) != MY_IOC_MAGIC) return -ENOTTY;
if (_IOC_NR(cmd) > MY_IOC_MAXNR) return -ENOTTY;
switch (cmd) {
case MY_GET_VAL:
return put_user(val, (int __user *)arg);
case MY_SET_VAL:
if (get_user(val, (int __user *)arg)) return -EFAULT;
return 0;
}
return -ENOTTY;
}
4. poll
static __poll_t my_poll(struct file *filp, struct poll_table_struct *pt)
{
__poll_t mask = 0;
poll_wait(filp, &my_waitq, pt);
if (data_available)
mask |= EPOLLIN | EPOLLRDNORM;
if (space_available)
mask |= EPOLLOUT | EPOLLWRNORM;
return mask;
}
// 用户态
struct pollfd pfd = { .fd = fd, .events = POLLIN };
poll(&pfd, 1, -1);
5. mmap
static int my_mmap(struct file *filp, struct vm_area_struct *vma)
{
// 把设备内存映射到用户态
return remap_pfn_range(vma, vma->vm_start,
vmalloc_to_pfn(buf),
vma->vm_end - vma->vm_start,
vma->vm_page_prot);
}
6. llseek
static loff_t my_llseek(struct file *filp, loff_t off, int whence)
{
loff_t newpos;
switch (whence) {
case SEEK_SET: newpos = off; break;
case SEEK_CUR: newpos = filp->f_pos + off; break;
case SEEK_END: newpos = size + off; break;
default: return -EINVAL;
}
if (newpos < 0) return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
六、阻塞与非阻塞
// 等待队列
static DECLARE_WAIT_QUEUE_HEAD(my_waitq);
// 读
static ssize_t my_read(...)
{
if (wait_event_interruptible(my_waitq, data_available))
return -ERESTARTSYS;
// ...
}
// 中断中唤醒
wake_up_interruptible(&my_waitq);
// 非阻塞
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
七、设备私有数据
struct my_dev {
void __iomem *base;
int irq;
wait_queue_head_t waitq;
struct mutex lock;
// ...
};
static int my_open(struct inode *inode, struct file *filp)
{
struct my_dev *dev = container_of(inode->i_cdev, struct my_dev, cdev);
filp->private_data = dev;
return 0;
}
八、多个设备实例
alloc_chrdev_region(&devno, 0, NUM_DEVS, "mydev");
for (i = 0; i < NUM_DEVS; i++) {
cdev_init(&devs[i].cdev, &my_fops);
cdev_add(&devs[i].cdev, MKDEV(MAJOR(devno), MINOR(devno)+i), 1);
device_create(cls, NULL, MKDEV(MAJOR(devno), MINOR(devno)+i), NULL, "mydev%d", i);
}
九、devtmpfs / mdev
- 内核内置
CONFIG_DEVTMPFS_MOUNT=y即可自动创建 /dev - busybox mdev:
mdev -s - udev:systemd 自带
十、测试命令
cat /proc/devices | grep mydev
ls -l /dev/mydev
cat /dev/mydev
echo "test" > /dev/mydev
strace -e read,write,open,ioctl cat /dev/mydev