当前位置: 首页 > 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.dtcms.com/a/274359.html

相关文章:

  • linux文件系统目录结构以及交互界面
  • 稳定币将成为新时代的重要金融工具
  • Pandas:数据类型转换
  • c99-柔性数组
  • NVME在ubuntu上总是导致死机
  • Android simpleperf生成火焰图
  • 深度解析 DApp 开发:从技术架构到商业落地的全链路解决
  • Linux 进程管理核心机制
  • 掌握Spring声明式事务传播机制:AOP与ThreadLocal的协同工作
  • 破解异构日志清洗五大难题,全面提升运维数据可观测性
  • 用FunctionCall实现文件解析(一):环境准备与基础知识
  • uniapp语音播报天气预报微信小程序
  • 秒杀系统该怎么设计?
  • uniapp-在windows上IOS真机运行(含开发证书申请流程)
  • 在Spring Boot 开发中 Bean 的声明和依赖注入最佳的组合方式是什么?
  • Spring Boot集成Redis:从配置到实战的完整指南
  • Adobe Acrobat DC JavaScript 基础到应用
  • python的卷烟营销数据统计分析系统
  • 重学前端003 --- CSS 颜色
  • 汽车级MCU选型新方向:eVTOL垂桨控制监控芯片的替代选型技术分析
  • 实现在线预览pdf功能,后台下载PDF
  • PDF 转图助手 PDF2JPG 绿色版:免安装直接用,急处理文件的救急小天使
  • 电力分析仪的“双语对话”:CCLinkIE与Modbus TCP的无缝连接
  • 【Unity游戏存档系统】
  • 爬虫练习1
  • 【环境配置】KAG - Windows 安装部署
  • 7.11文件和异常
  • kafka kraft模式升级metadata.version
  • JVM--监控和故障处理工具
  • Oracle 高可用性与安全性