没志青年
发布于 2025-06-14 / 14 阅读
0

6 Linux 字符设备驱动

设备文件位于 /dev 下。

设备文件和普通文件的区别:

设备号

设备号共 32 位:

  • 主设备号:占高12位,标识一个类型的设备,全局唯一。

  • 次设备号:占低20位,标识一个类型下的不同设备,每个主设备号下的次设备号互不干扰,不会冲突。



// 拼接成设备号
dev_t devno = MKDEV(major,minor);

// 获取主设备号
int major = MAJOR(devno);

// 获取次设备号
int minor = MINOR(devno);

设备号相同的驱动无法加载。

设备文件位于 /dev 目录下。

相关函数:

(1)静态申请设备号

int register_chrdev_region(dev_t first, unsigned count, const char *name);

(2)释放设备号

void unregister_chrdev_region(dev_t first, unsigned count);

(2)动态分配设备号,主设备号由内核分配

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
  • dev:输出参数,返回分配到的起始设备号(dev_t)

  • baseminor:起始次设备号(通常为 0)

  • count:要申请的连续次设备号数量

  • name:设备名称(出现在 /proc/devices 中)

返回值:

  • 成功:0

  • 失败:负数错误码(如 -EINVAL, -ENOMEM)

字符设备驱动

  • 字符设备驱动(driver)= 管理一个或多个 cdev的代码逻辑

  • cdev = 一个字符设备实例(device)的内核表示,字符设备最终表现为 /dev/ 目录下的一个节点。

  • cdev.file_operations = 定义“如何操作这个设备”

字符设备驱动模板:

#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#define DEVICE_NAME "mychardev"
#define DEVICE_MAJOR 200
#define DEVICE_MINOR 0
#define DEVICE_COUNT 4 // 最大支持设备数量

static int __init chardev_init(void) {
  int ret;
  dev_t dev = MKDEV(DEVICE_MAJOR, DEVICE_MINOR);
  ret = register_chrdev_region(dev, DEVICE_COUNT, DEVICE_NAME);
  if (ret)
    goto fail;
  return 0;

fail:
  return ret;
}

static void __exit chardev_exit(void) {
  dev_t dev = MKDEV(DEVICE_MAJOR, DEVICE_MINOR);
  unregister_chrdev_region(dev, DEVICE_COUNT);
}

module_init(chardev_init);
module_exit(chardev_exit);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("HLTJ");
MODULE_DESCRIPTION("simple char driver");
MODULE_VERSION("1.0");

(1)cdev 相关操作函数

1、动态分配 dev

1、初始化 cdev 结构体,关联文件操作集

void cdev_init(struct cdev *cdev, const struct file_operations *fops);

2、添加 cdev 到内核中

int cdev_add(struct cdev *cdev, dev_t dev, unsigned count);
  • dev:设备号

  • count:

    • 通常为 1

    • 从设备号 dev 开始,接下来的 count 个次设备号都属于这个 cdev

    • 比如设为 3,那就是 /dev/mydev0、/dev/mydev1、/dev/mydev2

返回值:

  • 0:成功

  • 负数:失败,错误码

3、删除 cdev

void cdev_del(struct cdev *cdev);

(2)设备节点相关操作函数

1、创建设备节点:

struct device *device_create(struct class *class, struct device *parent,
                              dev_t devt, void *drvdata, const char *fmt, ...);
  • class:所属的类,不能为NULL

  • parent:父设备,通常为 NULL

  • devt:设备号

  • drvdata:私有数据(可通过 dev_get_drvdata()获取)

  • fmt:设备节点名称格式字符串(如 "mydev%d", i)

返回值:

  • 成功:指向 struct device 的指针

  • 失败:ERR_PTR(err)

2、删除设备节点:

void device_destroy(struct class *class, dev_t devt);

3、创建设备类:

struct class *class_create(struct module *owner, const char *name);

设备类用于自动创建设备节点

参数:

  • owner:指向拥有者的模块(通常是 THIS_MODULE)

  • name:类名(如 "mychar")

返回值:

  • 成功:指向 struct class的指针

  • 失败:ERR_PTR(err),需用 IS_ERR()检查

4、销毁设备类:

void class_destroy(struct class *cls);

使用 mknod 命令手动创建设备文件:

mknod /dev/设备文件名 c 256 0

查询:

  • ls /dev

  • cat /proc/devices

一个驱动支持多个设备

两种方案:

方式

说明

优缺点

多个 cdev

每个设备对应一个cdev,cdev_add( , , 1); 第 3 个参数为 1

每个设备的行为独立

一个 cdev

所有设备共用一个cdev,cdev_add( , , N); 第 3 个参数为设备数量

需要在读写函数中判断是哪个设备

static ssize_t device_read_function(struct file *filp, char __user *buf,
                                    size_t count, loff_t *f_pos) {
  return 0;
}

static int device_open_function(struct inode *inode, struct file *filp) {
  return 0;
}

static const struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = device_read_function,
    .open = device_open_function,
};

操作函数

打开

关闭

读操作

写操作

ioctl 操作

用于处理设备的非数据操作(数据操作就是read、write)。

Linux 应用开发使用的 ioctl :

#include <sys/ioctl.h>

int ioctl(int fd, unsigned long request, ...);

request:操作码,告诉驱动要干什么。

后面的可变参数根据前面的操作码可有可无,通常是一个指向数据缓冲区的指针。有时也可以是一个整数值。

操作码的构成:

方向:数据传输的方向:

  • _IOC_NONE:无数据传输

  • _IOC_READ:读数据到用户空间

  • _IOC_WRITE:写数据到内核空间

  • _IOC_READ | _IOC_WRITE:双向传输

大小:传输数据的字节数(sizeof)

类型:魔数,驱动程序中检查这个操作码是不是该驱动对应的。

编号:自定义命令码

用户空间和内核空间数据传输函数:

int copy_from_user(void *to, const void __user *from, unsigned long n)
int copy_to_user(void __user *to, const void *from, unsigned long n)

__user:没啥特殊功能,只是标记这个数据是用户空间的。

使用:

头文件,应用程序和驱动程序共享:

// 幻数 'M'
#define MYDEVICE_MAGIC 'M'

// 命令编号
#define MYDEVICE_GET_STATUS _IOR(MYDEVICE_MAGIC, 0, int)
#define MYDEVICE_SET_STATUS _IOW(MYDEVICE_MAGIC, 1, int)
#define MYDEVICE_RESET      _IO(MYDEVICE_MAGIC, 2)

应用程序:

驱动程序:

#include <linux/uaccess.h> // for copy_to/from_user
#include "mydevice_ioctl.h" // 共享的头文件

static long mydevice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    int ret = 0;
    int temp_status;

    // 1. 检查魔数
    if (_IOC_TYPE(cmd) != MYDEVICE_MAGIC) {
        pr_err("Invalid magic number 0x%x\n", _IOC_TYPE(cmd));
        return -ENOTTY; // 通常用于“不支持的ioctl”错误
    }

    // 2. 根据命令号分发
    switch (cmd) {
        case MYDEVICE_GET_STATUS:
            // 检查方向和大小是否匹配
            if (_IOC_DIR(cmd) != _IOC_READ) {
                return -EINVAL;
            }
            // 从内核空间复制数据到用户空间
            temp_status = device_status;
            ret = copy_to_user((int __user *)arg, &temp_status, sizeof(temp_status));
            if (ret != 0) {
                pr_err("copy_to_user failed\n");
                return -EFAULT;
            }
            break;

        case MYDEVICE_SET_STATUS:
            if (_IOC_DIR(cmd) != _IOC_WRITE) {
                return -EINVAL;
            }
            // 从用户空间复制数据到内核空间
            ret = copy_from_user(&temp_status, (int __user *)arg, sizeof(temp_status));
            if (ret != 0) {
                pr_err("copy_from_user failed\n");
                return -EFAULT;
            }
            device_status = temp_status;
            pr_info("Status set to %d\n", device_status);
            break;

        case MYDEVICE_RESET:
            if (_IOC_DIR(cmd) != _IOC_NONE) {
                return -EINVAL;
            }
            device_status = 0;
            pr_info("Device reset\n");
            break;

        default:
            pr_err("Unknown command 0x%x\n", cmd);
            return -ENOTTY;
    }

    return 0; // 成功
}

netlink、memfd+ seals​ 或 io_uring

虚拟串口设备