字符设备驱动:原理与架构
什么是字符设备
字符设备以字节流方式访问,没有缓冲区概念,读写操作直接与硬件交互。典型例子:串口、GPIO、传感器、自定义硬件接口。
用户空间
open("/dev/mydev", O_RDWR)
read() / write() / ioctl()
│
▼ VFS 层
struct file_operations
│
▼ 字符设备驱动
硬件寄存器操作完整字符设备驱动
c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#define DEVICE_NAME "mydev"
#define CLASS_NAME "myclass"
#define BUF_SIZE 4096
struct mydev_priv {
struct cdev cdev;
struct device *device;
char *buf;
size_t buf_len;
struct mutex lock;
};
static struct class *mydev_class;
static dev_t mydev_devt; /* 主设备号 + 次设备号 */
static struct mydev_priv mydev_priv;
/* ===== file_operations 实现 ===== */
static int mydev_open(struct inode *inode, struct file *filp)
{
struct mydev_priv *priv = container_of(inode->i_cdev,
struct mydev_priv, cdev);
filp->private_data = priv;
return 0;
}
static int mydev_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t mydev_read(struct file *filp, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct mydev_priv *priv = filp->private_data;
ssize_t ret;
if (mutex_lock_interruptible(&priv->lock))
return -ERESTARTSYS;
if (*ppos >= priv->buf_len) {
ret = 0; /* EOF */
goto out;
}
count = min(count, priv->buf_len - (size_t)*ppos);
if (copy_to_user(ubuf, priv->buf + *ppos, count)) {
ret = -EFAULT;
goto out;
}
*ppos += count;
ret = count;
out:
mutex_unlock(&priv->lock);
return ret;
}
static ssize_t mydev_write(struct file *filp, const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct mydev_priv *priv = filp->private_data;
ssize_t ret;
if (count > BUF_SIZE)
return -EINVAL;
if (mutex_lock_interruptible(&priv->lock))
return -ERESTARTSYS;
if (copy_from_user(priv->buf, ubuf, count)) {
ret = -EFAULT;
goto out;
}
priv->buf_len = count;
*ppos += count;
ret = count;
out:
mutex_unlock(&priv->lock);
return ret;
}
/* ioctl 命令定义(通常放在头文件中供用户空间使用) */
#define MYDEV_IOC_MAGIC 'M'
#define MYDEV_RESET _IO(MYDEV_IOC_MAGIC, 0)
#define MYDEV_GET_LEN _IOR(MYDEV_IOC_MAGIC, 1, int)
#define MYDEV_SET_MODE _IOW(MYDEV_IOC_MAGIC, 2, int)
static long mydev_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct mydev_priv *priv = filp->private_data;
int val;
switch (cmd) {
case MYDEV_RESET:
mutex_lock(&priv->lock);
memset(priv->buf, 0, BUF_SIZE);
priv->buf_len = 0;
mutex_unlock(&priv->lock);
return 0;
case MYDEV_GET_LEN:
val = priv->buf_len;
return put_user(val, (int __user *)arg);
case MYDEV_SET_MODE:
if (get_user(val, (int __user *)arg))
return -EFAULT;
/* 设置硬件模式 */
return 0;
default:
return -ENOTTY; /* 未知命令 */
}
}
/* poll/select 支持 */
static __poll_t mydev_poll(struct file *filp, poll_table *wait)
{
struct mydev_priv *priv = filp->private_data;
__poll_t mask = 0;
poll_wait(filp, &priv->read_wq, wait); /* 注册等待队列 */
if (priv->buf_len > 0)
mask |= EPOLLIN | EPOLLRDNORM; /* 可读 */
mask |= EPOLLOUT | EPOLLWRNORM; /* 始终可写 */
return mask;
}
static const struct file_operations mydev_fops = {
.owner = THIS_MODULE,
.open = mydev_open,
.release = mydev_release,
.read = mydev_read,
.write = mydev_write,
.unlocked_ioctl = mydev_ioctl,
.poll = mydev_poll,
.llseek = default_llseek,
};
/* ===== 模块初始化 ===== */
static int __init mydev_init(void)
{
int ret;
/* 1. 动态分配设备号 */
ret = alloc_chrdev_region(&mydev_devt, 0, 1, DEVICE_NAME);
if (ret) {
pr_err("alloc_chrdev_region failed: %d\n", ret);
return ret;
}
/* 2. 初始化 cdev */
cdev_init(&mydev_priv.cdev, &mydev_fops);
mydev_priv.cdev.owner = THIS_MODULE;
ret = cdev_add(&mydev_priv.cdev, mydev_devt, 1);
if (ret) {
pr_err("cdev_add failed: %d\n", ret);
goto err_cdev;
}
/* 3. 创建设备类(/sys/class/myclass/) */
mydev_class = class_create(CLASS_NAME);
if (IS_ERR(mydev_class)) {
ret = PTR_ERR(mydev_class);
goto err_class;
}
/* 4. 创建设备节点(/dev/mydev) */
mydev_priv.device = device_create(mydev_class, NULL,
mydev_devt, NULL, DEVICE_NAME);
if (IS_ERR(mydev_priv.device)) {
ret = PTR_ERR(mydev_priv.device);
goto err_device;
}
/* 5. 分配缓冲区 */
mydev_priv.buf = kzalloc(BUF_SIZE, GFP_KERNEL);
if (!mydev_priv.buf) {
ret = -ENOMEM;
goto err_buf;
}
mutex_init(&mydev_priv.lock);
pr_info("mydev: registered with major=%d minor=%d\n",
MAJOR(mydev_devt), MINOR(mydev_devt));
return 0;
err_buf:
device_destroy(mydev_class, mydev_devt);
err_device:
class_destroy(mydev_class);
err_class:
cdev_del(&mydev_priv.cdev);
err_cdev:
unregister_chrdev_region(mydev_devt, 1);
return ret;
}
static void __exit mydev_exit(void)
{
kfree(mydev_priv.buf);
device_destroy(mydev_class, mydev_devt);
class_destroy(mydev_class);
cdev_del(&mydev_priv.cdev);
unregister_chrdev_region(mydev_devt, 1);
pr_info("mydev: unregistered\n");
}
module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");设备号
dev_t = 主设备号(12位) + 次设备号(20位)
主设备号:标识驱动类型
次设备号:区分同类型的多个设备实例bash
# 查看已分配的字符设备号
cat /proc/devices
# 手动创建设备节点(通常由 udev 自动完成)
mknod /dev/mydev c 240 0mmap 零拷贝
c
static int mydev_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct mydev_priv *priv = filp->private_data;
unsigned long size = vma->vm_end - vma->vm_start;
if (size > BUF_SIZE)
return -EINVAL;
/* 将内核缓冲区映射到用户空间 */
return remap_pfn_range(vma,
vma->vm_start,
virt_to_phys(priv->buf) >> PAGE_SHIFT,
size,
vma->vm_page_prot);
}用户空间使用:
c
int fd = open("/dev/mydev", O_RDWR);
void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
/* 直接读写 addr,无需 read/write 系统调用 */
munmap(addr, 4096);