当前位置: 首页 > news >正文

Linux驱动基本概念(内核态、用户态、模块、加载、卸载、设备注册、字符设备)

驱动开发基础知识

文章目录

    • 驱动开发基础知识
      • I 基本概念
        • 1 什么是驱动程序
        • 2 驱动的作用与功能
        • 3 内核空间与用户空间
      • II 一个简单的驱动程序
      • III 驱动程序的基本组成
        • 1 驱动程序的组成
        • 2 驱动加载流程
        • 3 驱动卸载流程
        • 4 设备操作实现
        • 5 关键注意事项
        • 6 完整生命周期示例
      • IV 附录:测试示例驱动的应用程序
        • 1 使用示例
        • 2 代码

I 基本概念

1 什么是驱动程序
  • 设备驱动是操作系统内核中用于管理和控制硬件的特殊软件,它是设备和操作系统之间的桥梁

  • 向上为用户空间提供操作设备的接口(如系统调用或设备文件),向下直接管理硬件,实现设备的读写、配置等功能

  • 驱动可以是内核模块(动态加载/卸载)或编译进内核,运行在特权模式下,负责抽象硬件细节,使应用程序无需关心底层差异。
2 驱动的作用与功能
  • 初始化硬件设备、提供设备访问接口、处理设备中断、管理数据传输、实现电源管理功能…

  • 核心功能

    • 初始化硬件设备(寄存器配置、资源分配)
    • 提供标准设备访问接口(实现file_operations结构体)
    • 处理设备中断和DMA传输
    • 管理设备电源状态(如休眠唤醒)
    • 维护设备状态和数据缓冲区
3 内核空间与用户空间
  • 内核空间

    • 驱动程序运行的特权模式,可直接访问硬件和所有内存
    • 使用内核专用函数(如 kmallocprintk),无法使用标准库(如 libc)。
    • 代码错误可能导致系统崩溃(如空指针访问)。
    • 通过 copy_to_user/copy_from_user 与用户空间交换数据
    • 遵循GPL协议,调试困难(可用printk/ftrace)
  • 用户空间

    • 应用程序运行的受限模式,通过系统调用(如 openread)间接访问硬件。
    • 使用标准库(如 glibc),内存访问受 MMU 保护。
    • 进程崩溃通常不影响系统稳定性。
    • 开发灵活,可使用各种调试工具
  • 两者区别

    • 内存隔离:用户空间进程有独立地址空间,内核空间共享同一地址。
    • 权限级别:内核代码可执行特权指令(如开关中断),用户代码受限。
    • 稳定性要求:内核代码需更严格的错误处理(如资源释放)。
    • 开发约束:内核模块需遵循 GPL 协议,调试工具受限(如无 gdb)。
    • 执行上下文:内核代码可能运行在中断上下文,不能睡眠

II 一个简单的驱动程序

    #include <linux/module.h>#include <linux/fs.h>#include <linux/errno.h>#include <linux/miscdevice.h>#include <linux/kernel.h>#include <linux/major.h>#include <linux/mutex.h>#include <linux/proc_fs.h>#include <linux/seq_file.h>#include <linux/stat.h>#include <linux/init.h>#include <linux/device.h>#include <linux/tty.h>#include <linux/kmod.h>#include <linux/gfp.h>#define DEVICE_NAME "hello_driver"#define SIZE(x,y) (x < y ? x : y)  // Return the smaller of two valuesstatic int major = 0;  // Major number for the devicestatic char hello_buf[1024];  // Buffer for storing datastatic struct class *hello_class;  // Device class structure// Called when device is openedstatic int hello_open(struct inode *node, struct file *file){printk(KERN_INFO "%s : open called !\n", __FUNCTION__);return 0;}// Called when device is closedstatic int hello_close(struct inode *node, struct file *file){printk(KERN_INFO "%s : close called !\n", __FUNCTION__);return 0;}// Called when reading from devicestatic ssize_t hello_read(struct file *file, char __user *buf, size_t size, loff_t *offset){int err ;printk(KERN_INFO "%s : read called !\n", __FUNCTION__);err = copy_to_user(buf, hello_buf, SIZE(1024, size));  // Copy data to user space{if(err != 0){printk(KERN_ERR "%s: copy_to_user error!\n", __FUNCTION__);return -EFAULT;  // Return bad address error}}return SIZE(1024, size);  // Return number of bytes read}// Called when writing to devicestatic ssize_t hello_write(struct file *file, const char __user *buf, size_t size, loff_t *offset){int err ;printk(KERN_INFO "%s : open called !\n", __FUNCTION__);err = copy_from_user(hello_buf, buf, SIZE(1024, size));  // Copy data from user space{if(err != 0){printk(KERN_ERR "%s: copy_from_user error!\n", __FUNCTION__);return -EFAULT;  // Return bad address error}}return SIZE(1024, size);  // Return number of bytes written}// File operations structurestatic struct file_operations hello_drv = {.owner		= THIS_MODULE,  // Owner module.read		= hello_read,   // Read operation.write		= hello_write,  // Write operation.open		= hello_open,   // Open operation.release    = hello_close,  // Close operation};// Module initialization functionstatic int __init hello_init(void){int err = 0;major = register_chrdev(major, DEVICE_NAME, &hello_drv);  // Register character deviceif(major < 0){printk(KERN_ERR "%s: register_chardev error!\n", __FUNCTION__);return major;}hello_class = class_create(THIS_MODULE, "hello_class");  // Create device classPTR_ERR(hello_class);if(IS_ERR(hello_class)){unregister_chrdev(major, DEVICE_NAME);  // Cleanup if errorreturn -1;}device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");  // Create device nodeprintk(KERN_INFO "registe char dev %s(%d) suucssfully\n",DEVICE_NAME, major);return 0;}// Module exit functionstatic void __exit hello_exit(void){device_destroy(hello_class,MKDEV(major,0));  // Remove device nodeunregister_chrdev(major, DEVICE_NAME);  // Unregister character deviceprintk(KERN_INFO "device %s(%d) unregisted\n", DEVICE_NAME, major);}module_init(hello_init);module_exit(hello_exit);MODULE_AUTHOR("fison");MODULE_DESCRIPTION("hello");MODULE_LICENSE("GPL");

III 驱动程序的基本组成

1 驱动程序的组成

Linux 驱动程序通常包含以下核心部分:

  • 头文件:包含必要的内核头文件(如 linux/module.hlinux/fs.h 等)。
  • 设备名称与参数:定义设备名称、主设备号、缓冲区等(如 DEVICE_NAME、major、hello_buf)。
  • 文件操作结构体file_operations):
    • 定义设备支持的操作(如 open、read、write、release)。
    • 每个操作对应一个回调函数(如 hello_openhello_read)。
    • 必须指定 .owner = THIS_MODULE 以防止模块被意外卸载。
  • 初始化与退出函数
    • module_init(hello_init):注册驱动加载时的初始化函数。
    • module_exit(hello_exit):注册驱动卸载时的清理函数。
    • 必须正确处理错误路径的资源释放。
  • 模块信息:通过宏声明作者、描述、许可证(如 MODULE_LICENSE("GPL"))。
  • 同步机制:必要时添加互斥锁(mutex)或自旋锁(spinlock)保护共享资源。
2 驱动加载流程
  • 注册字符设备
    • 调用 register_chrdev(major, DEVICE_NAME, &hello_drv) 注册设备。
    • 内核动态分配主设备号(若 major=0),或使用指定的主设备号。
    • 必须检查返回值,失败时返回错误码。
  • 创建设备类
    • 使用 class_create(THIS_MODULE, "hello_class") 创建设备类。
    • 检查返回值是否有效(IS_ERR),失败时需注销已注册的设备。
  • 创建设备节点
    • 调用 device_create()/dev 下生成设备文件(如 /dev/hello)。
    • 设备节点权限默认为 0666,可通过 udev 规则修改。
  • 资源初始化
    • 初始化缓冲区、锁等资源。
  • 日志输出
    • 使用 printk(KERN_INFO ...) 打印加载成功信息。
    • 错误路径需使用 KERN_ERR 级别日志。
3 驱动卸载流程
  • 销毁设备节点
    • 调用 device_destroy(hello_class, MKDEV(major, 0)) 移除设备文件。
  • 注销字符设备
    • 调用 unregister_chrdev(major, DEVICE_NAME) 释放主设备号。
  • 销毁设备类
    • 调用 class_destroy(hello_class) 清理设备类。
  • 日志输出
    • 使用 printk(KERN_INFO ...) 打印卸载成功信息。
4 设备操作实现
  • open
    • 设备打开时调用,通常进行初始化或资源分配。
    • 示例中仅打印日志(printk(KERN_INFO "%s : open called !\n", __FUNCTION__))。
  • read
    • 使用 copy_to_user(buf, hello_buf, SIZE(1024, size)) 将内核缓冲区数据复制到用户空间。
    • 检查返回值(if(err != 0)),错误时返回 -EFAULT
    • 返回实际读取的字节数(SIZE(1024, size))。
  • write
    • 使用 copy_from_user(hello_buf, buf, SIZE(1024, size)) 将用户数据复制到内核缓冲区。
    • 类似地处理错误(-EFAULT)。
    • 返回实际写入的字节数。
  • release
    • 设备关闭时调用,通常释放资源。
    • 示例中仅返回0。
5 关键注意事项
  • 错误处理
    • 所有内核函数调用必须检查返回值。
    • 初始化失败时需反向释放已申请的资源。
  • 并发控制
    • 若多进程访问设备,需添加互斥锁(mutex)保护共享资源(如 hello_buf)。
  • 缓冲区安全
    • 确保 copy_to/from_usersize 不超过缓冲区大小(示例中通过 SIZE(1024, size) 限制)。
  • 日志级别
    • 使用 KERN_INFO 记录正常操作,KERN_ERR 记录错误。
6 完整生命周期示例
    加载: insmod hello_driver.ko → hello_init() → register_chrdev() → class_create() → device_create()使用: 用户程序通过 /dev/hello 调用 open/read/write/release卸载: rmmod hello_driver → hello_exit() → device_destroy() → unregister_chrdev() → class_destroy()

IV 附录:测试示例驱动的应用程序

1 使用示例

(1)写入数据到设备hello

./main -w "the message you want to write to the device hello"

(2)从设备hello中读取数据

./main -r
2 代码
    /** Simple program to interact with a character device "/dev/hello"* Supports reading from and writing to the device*/#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <string.h>int main(int argc, char *argv[]){// Check for minimum required argumentsif(argc < 2){printf("Usage:%s <-r(read)/-w(write)> <string>\n", argv[0]);return -1;}// Open the device file with read/write permissionsint fd = open("/dev/hello", O_RDWR);if(fd == -1){perror("open");return -1;}char buf[1024];  // Buffer for read operationsint len;        // Length variable for read/write operations// Handle read operationif(strcmp(argv[1], "-r") == 0){len = read(fd, buf, 1024);if(len < 0){perror("read");close(fd);return -1;}buf[1023] = '\0';  // Ensure null terminationprintf("read %d bytes from device hello:%s\n", len, buf);}// Handle write operationif(strcmp(argv[1], "-w") == 0){if(argv[2] == NULL){printf("nothing to write\n");close(fd);return -1;}// Calculate write length (including null terminator)len = strlen(argv[2]) + 1;len = len < 1024 ?  len : 1024;  // Cap at buffer sizelen = write(fd, argv[2], len);if(len < 0 ){perror("write");close(fd);return -1;} printf("write %d bytes to device hello\n", len);}close(fd);  // Clean up file descriptorreturn 0;}len = write(fd, argv[2], len);if(len < 0 ){perror("write");close(fd);return -1;} printf("write %d bytes to device hello\n", len);}close(fd);  // Clean up file descriptorreturn 0;}
http://www.lryc.cn/news/585782.html

相关文章:

  • Allegro 17.4操作记录
  • 【理念●体系】从零打造 Windows + WSL + Docker + Anaconda + PyCharm 的 AI 全链路开发体系
  • 数据库系统的基础知识(三)
  • uniapp---入门、基本配置了解
  • spring-ai RAG(Retrieval-Augmented Generation)
  • ESP32_启动日志分析
  • 力扣 hot100 Day41
  • RLHF:人类反馈强化学习 | 对齐AI与人类价值观的核心引擎
  • Linux711 Mysql
  • openpilot:为您的汽车插上智能驾驶的翅膀
  • 创意总监的动态视觉秘诀:用AE动态遮罩AI,轻松实现“人景分离”
  • 【每日刷题】加一
  • Java 中的锁分类
  • 【牛客刷题】吃糖果----糖果甜度问题(贪心策略详解)
  • 小车循迹功能的实现(第六天)
  • UML 与 SysML 图表对比全解析:软件工程 vs 系统工程建模语言
  • 持有对象-泛型和类型安全的容器
  • 线程通信V
  • 【Linux】系统引导修复
  • InnoDB 存储引擎的 架构
  • 渗透测试之木马后门实验
  • 世界现存燃油汽车品牌起源国别梳理
  • k8s新增jupyter服务
  • 中国国际会议会展中心模块化解决方案的技术经济分析报告
  • 【机器学习应用】基于集成学习的电力负荷预测系统实战案例
  • Linux设备树(dts/dtsi/dtb、设备树概念,设备树解析,驱动匹配)
  • kubernetes单机部署踩坑笔记
  • 【linux网络】深入理解 TCP/UDP:从基础端口号到可靠传输机制全解析
  • 【理念●体系】Windows AI 开发环境搭建实录:六层架构的逐步实现与路径治理指南
  • ATAM与效用树:架构评估的核心方法论