具体掌握字符设备驱动

一、字符设备驱动的特性

先搞懂它 "是什么样的驱动":

  • 数据访问方式 :按字节流顺序读写(像水管流水,从头到尾依次读 / 写),典型设备:串口、键盘、LED、串口屏。
  • 无缓存 / 低缓存:数据 "来了就处理",实时性强(比如键盘按一下就立刻响应),和 "块设备(硬盘)" 的 "先存缓存再批量处理" 区别明显。
  • 设备文件映射 :用户空间通过设备文件(比如 /dev/ttyS0 访问,内核通过 struct file_operations 把文件操作映射到硬件操作。

二、字符设备的 "创建 + 识别 + 注册" 步骤

内核要管理字符设备,得完成这 4 步(相当于给设备 "上户口"):

1. 准备 "设备号"(内核识别设备的身份证)

设备号分两部分:

  • 主设备号:标识 "设备类型"(比如所有串口的主设备号可能是 4);
  • 次设备号 :标识 "同类型下的具体设备"(比如 /dev/ttyS0 次设备号是 0,/dev/ttyS1 是 1)。

获取设备号有 2 种方式:

复制代码
// 方式1:静态分配(需提前查系统未用的号,容易冲突)
dev_t dev_num = MKDEV(主设备号, 次设备号); // 比如 MKDEV(200, 0)
register_chrdev_region(dev_num, 1, "my_dev"); // 注册1个设备

// 方式2:动态分配(推荐,内核自动分配未用的号)
alloc_chrdev_region(&dev_num, 0, 1, "my_dev"); // 次设备号从0开始,分配1个

2. 初始化 "字符设备结构体(cdev)"

struct cdev 是内核管理字符设备的核心结构体,需要把它和你的驱动逻辑绑定:

复制代码
struct cdev my_cdev;
cdev_init(&my_cdev, &my_fops); // 绑定"操作函数集(my_fops就是file_operations)"
my_cdev.owner = THIS_MODULE;   // 标记驱动所属模块,防止模块被意外卸载

3. 把设备 "注册到内核"

让内核知道这个设备存在:

复制代码
cdev_add(&my_cdev, dev_num, 1); // 把cdev添加到内核的字符设备链表

4. 创建设备文件(用户空间访问的入口)

内核注册完设备后,用户空间还需要一个 "访问入口"------ 设备文件,有 2 种方式:

  • 手动创建 :终端执行 mknod /dev/my_dev c 主设备号 次设备号c 表示字符设备);

  • 自动创建 :用 class_create + device_create(驱动加载时自动在 /dev 生成文件):

    // 1. 创建设备类(会在/sys/class下生成目录)
    struct class *my_class = class_create(THIS_MODULE, "my_class");
    // 2. 创建设备文件(自动在/dev下生成my_dev)
    device_create(my_class, NULL, dev_num, NULL, "my_dev");

三、设备文件方法 + struct file_operations 详解

用户空间用open/read/write等系统调用访问设备文件时,内核会转发到 struct file_operations 里的对应函数 ------ 这是 "用户空间和硬件交互的桥梁"。

1. struct file_operations 核心成员(对应系统调用)

用户空间系统调用 file_operations 成员函数 作用说明
open("/dev/my_dev") int (*open)(struct inode *inode, struct file *filp) 打开设备时执行(比如初始化硬件、记录设备状态)
read(fd, buf, size) ssize_t (*read)(struct file *filp, char __user *buf, size_t count, loff_t *pos) 从设备读数据到用户空间(必须用copy_to_user传数据,不能直接写__user 指针)
write(fd, buf, size) ssize_t (*write)(struct file *filp, const char __user *buf, size_t count, loff_t *pos) 从用户空间写数据到设备(必须用copy_from_user读数据)
close(fd) int (*release)(struct inode *inode, struct file *filp) 关闭设备时执行(比如释放硬件资源、重置状态)
lseek(fd, off, SEEK_SET) loff_t (*llseek)(struct file *filp, loff_t off, int whence) 调整文件读写指针(字符设备可选实现)

2. 写一个最小的 file_operations 示例

复制代码
// 定义read函数:用户读时返回"hello char dev"
ssize_t my_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
    char kernel_buf[] = "hello char dev";
    int len = strlen(kernel_buf);
    // 把内核数据复制到用户空间(必须用这个函数,保证安全)
    if (copy_to_user(buf, kernel_buf, len)) {
        return -EFAULT; // 复制失败返回错误
    }
    return len; // 返回实际读取的字节数
}

// 定义open函数:打开时打印日志
int my_dev_open(struct inode *inode, struct file *filp)
{
    printk(KERN_INFO "my_dev opened!\n");
    return 0;
}

// 组装file_operations结构体
struct file_operations my_fops = {
    .owner = THIS_MODULE,
    .open = my_dev_open,
    .read = my_dev_read,
};

四、完整流程串起来(驱动加载 + 用户测试)

  1. 驱动加载 :编译驱动为.ko模块,执行insmod my_dev.ko,此时/dev/my_dev会自动创建;

  2. 用户测试:写个 C 程序访问设备文件:

    #include <stdio.h>
    #include <fcntl.h>
    #include <unistd.h>

    int main() {
    char buf[100];
    int fd = open("/dev/my_dev", O_RDONLY); // 打开设备
    read(fd, buf, 100); // 读设备数据
    printf("read from dev: %s\n", buf); // 输出"hello char dev"
    close(fd);
    return 0;
    }

相关推荐
HIT_Weston2 小时前
68、【Ubuntu】【Hugo】搭建私人博客:方案分析(二)
linux·运维·ubuntu
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [fs]ext4
linux·笔记·学习
程序员老舅2 小时前
C++ STL 算法:从原理到工程实践
linux·c++·stl·c/c++·数据结构与算法
chenyuhao20243 小时前
Linux系统编程:线程概念与控制
linux·服务器·开发语言·c++·后端
Pyeako3 小时前
MySQL基础知识&Linux导入导出数据
linux·数据库·mysql·sql查询·sql分类
prettyxian3 小时前
【linux】进程概念(1)PCB、系统调用与 proc 目录全解析
linux·运维·服务器
霜雪i3 小时前
Linux MD5
linux·服务器
取加若则_3 小时前
Vim基本操作
linux·编辑器·vim
小尧嵌入式3 小时前
Linux进程线程与进程间通信
linux·运维·服务器·c语言·开发语言·数据结构·microsoft