Linux驱动复习——驱动

Linux驱动复习------驱动

一、Linux下驱动的分类

1. 字符设备驱动

  • 定义:以字符为单位,一个字节一个字节地读写操作设备

  • 特点:以字符流的形式传输数据,不带缓存

  • 常见设备:鼠标、键盘、串口

2. 块设备驱动

  • 定义:以固定块的大小为单位(通常是512字节)读写操作设备

  • 特点:带缓存,提高读写效率

  • 常见设备:SD卡、磁盘、硬盘

3. 网络设备驱动

  • 定义:将网络数据包从物理层传输到网络层,并将网络层的数据封装成物理层可以传输的形式

  • 特点:需要集成多种网络协议及接口标准

  • 常见设备:以太网卡

二、字符设备驱动实现流程

1. 驱动实现步骤

  • 构建cdev结构体,用来描述字符型设备

  • 构建file_operations结构体,用来描述驱动层操作函数集合

  • 通过module_init和module_exit指定驱动模块的入口函数和注销函数

  • 通过MODULE_LICENSE宏,指定GNU组织规定的GPL协定

  • 实现驱动模块的入口函数

  • 实现驱动模块的注销函数

  • 完成file_operations结构体中各函数的具体实现

2. 驱动模块入口函数实现

复制代码
static int __init driver_init(void)
{
    // 1. 注册字符型设备(两种方式)
    register_chrdev_region(dev, count, name);   // 静态注册
    // 或
    alloc_chrdev_region(&dev, baseminor, count, name); // 动态注册
    
    // 2. 初始化cdev结构体
    cdev_init(&cdev, &fops);
    
    // 3. 将cdev结构体添加到系统
    cdev_add(&cdev, dev, count);
    
    // 4. 创建类
    class_create(THIS_MODULE, "class_name");
    
    // 5. 在这个类下创建设备节点
    device_create(class, NULL, dev, NULL, "device_name");
    
    // 6. 完成对硬件的初始化
    return 0;
}

3. 驱动模块注销函数实现

复制代码
static void __exit driver_exit(void)
{
    // 1. 注销字符型设备
    unregister_chrdev_region(dev, count);
    
    // 2. 从系统中删除cdev结构体
    cdev_del(&cdev);
    
    // 3. 删除类
    class_destroy(class);
    
    // 4. 删除设备节点
    device_destroy(class, dev);
    
    // 5. 完成对硬件的注销
}

4. file_operations结构体实现

复制代码
static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = open_function,      // 打开设备
    .read = read_function,      // 读取数据
    .write = write_function,    // 写入数据
    .release = close_function,  // 关闭设备
    .unlocked_ioctl = ioctl_function, // 控制命令
};

三、驱动编译流程

1. 编译步骤

复制代码
 1. 在drivers/char下编写驱动程序(如led.c)

 2. 修改Makefile文件
 在drivers/char/Makefile中添加:
obj-$(CONFIG_LED) += led.o

 3. 修改Kconfig文件,增加驱动编译选项
 在drivers/char/Kconfig中添加:
config LED
    tristate "LED Driver"
    help
      This is a LED driver.

 4. 回到内核源码顶层目录,配置内核
make menuconfig
 将对应的驱动选项选择为:
# 'Y'(*) - 静态编译进内核
# 'M'   - 编译成模块
# 'N'   - 不编译

# 5. 编译驱动
make           # 编译整个内核(包含静态编译的驱动)
make modules   # 只编译模块(动态编译)

四、静态编译和动态编译的区别

比较项 静态编译 动态编译
编译方式 直接编译进内核 编译成.ko模块
修改难度 修改后需重新配置内核并编译 修改后只需重新编译.ko模块
开发调试 比较复杂 相对简单
使用场景 稳定、常用的驱动 开发调试阶段的驱动

五、设备号相关概念

1. 主设备号和次设备号

  • 主设备号:设备的类型

  • 次设备号:该类设备下具体的某一个设备

2. 设备号大小

  • Linux下用32位的unsigned int类型表示设备号

  • 高12位:代表主设备号

  • 低20位:代表次设备号

3. 设备号构建

复制代码
// 通过MKDEV宏,传入主次设备号来构建一个新的设备号
dev_t dev = MKDEV(major, minor);

4. 杂项设备的主设备号

  • 杂项设备主设备号固定为10

5. 静态注册和动态注册的区别

比较项 静态注册 动态注册
函数 register_chrdev_region alloc_chrdev_region
设备号来源 自己定义主次设备号 系统自动分配
优点 设备号固定 不会冲突
缺点 可能和系统中设备号冲突 设备号不固定

六、字符设备驱动和杂项设备驱动的区别

比较项 字符设备驱动 杂项设备驱动
设备号 需要自己定义或系统分配 主设备号固定为10
设备节点 需要手动创建 可以自动生成
复杂度 较高 较低

手动创建设备节点

复制代码
mknod /dev/xxx c 248 0
# /dev/xxx:设备节点
# c:设备类型,字符型设备
# 248:主设备号
# 0:次设备号

七、驱动模块加载

1. 加载命令

复制代码
insmod xxx.ko      # 加载驱动模块
modprobe xxx.ko    # 加载驱动模块
rmmod xxx          # 卸载驱动模块
lsmod              # 查看已加载的模块

2. insmod和modprobe区别

命令 特点
insmod 不会分析驱动模块之间的依赖关系
modprobe 会分析驱动模块之间的依赖关系,自动加载依赖模块

八、__init 和 __exit 作用

1. __init 作用

  • 执行insmod加载驱动模块时,会自动执行被__init修饰的函数

  • 执行完该函数后,会自动回收内存空间

2. __exit 作用

  • 执行rmmod卸载驱动模块时,会执行被__exit修饰的函数

  • 执行完后会自动释放相关内存空间

九、platform平台驱动

1. platform思想

  • 目的:实现设备和驱动分离,方便更好地对驱动资源进行管理

  • 原理:将device设备和driver驱动分离,注册到platform总线bus上

  • 匹配方式:通过name、设备树compatible、ID table进行匹配

  • 匹配成功后:执行driver中的probe函数

  • 注销时:执行remove函数

2. platform匹配方式

  1. name匹配

  2. 设备树compatible匹配

  3. ID table匹配

3. platform驱动实现流程

(1) 设备树描述device信息
复制代码
// 在设备树中增加设备节点
ptled {
    compatible = "pt-led";        // 用于匹配
    ptdht11-gpio = <&gpio1 2 GPIO_ACTIVE_HIGH>;
    status = "okay";
};
(2) 驱动实现
复制代码
// 1. 定义匹配表
static struct of_device_id led_table[] = {
    {.compatible = "pt-led"},
    {}
};

// 2. 定义platform_driver结构体
static struct platform_driver pdrv = {
    .probe = probe,      // 匹配成功执行
    .remove = remove,    // 注销时执行
    .driver = {
        .name = "led",
        .of_match_table = led_table  // 匹配表
    }
};

// 3. 注册platform驱动
static int __init led_driver_init(void)
{
    return platform_driver_register(&pdrv);
}

// 4. 注销platform驱动
static void __exit led_driver_exit(void)
{
    platform_driver_unregister(&pdrv);
}

// 5. probe函数实现
static int probe(struct platform_device * pdev)
{
    // 获取设备树中的GPIO信息
    led_gpio = of_get_named_gpio(pdev->dev.of_node, "ptdht11-gpio", 0);
    
    // 注册杂项设备/字符设备
    misc_register(&misc_dev);
    
    // 初始化硬件
    gpio_request(led_gpio, "led");
    gpio_direction_output(led_gpio, 1);
    
    return 0;
}

十、I2C子系统驱动框架

1. I2C子系统层次结构

复制代码
用户层:调用驱动层接口,操作I2C设备
    ↓
I2C设备驱动层:自己实现的I2C设备和驱动
    ↓
I2C核心层(core):提供I2C设备和驱动的注册、匹配、通信的方法
    ↓
I2C适配器驱动层:I2C控制器驱动,一般由芯片厂家实现
    ↓
I2C物理硬件层:具体I2C硬件设备(如LM75传感器)

2. 各层功能

层次 功能
物理硬件层 具体的I2C硬件设备
适配器驱动层 I2C控制器驱动,由芯片厂家实现
核心层 管理设备和驱动的注册、匹配、通信方法
设备驱动层 自己实现的I2C设备驱动
用户层 调用驱动接口操作I2C设备

3. 工作流程

  1. I2C传感器挂载在I2C总线上

  2. I2C适配器驱动层由芯片厂家实现

  3. I2C核心层管理设备和驱动的注册、匹配、通信

  4. 设备驱动层将I2C总线上设备和驱动匹配

  5. 匹配成功后执行probe函数

  6. 注销时执行remove函数

  7. 应用层调用驱动接口实现I2C数据读写

十一、GPIO子系统相关操作函数

函数 功能
gpio_request() 申请一个GPIO引脚
gpio_free() 释放GPIO引脚
gpio_direction_output() 设置GPIO为输出模式
gpio_direction_input() 设置GPIO为输入模式
gpio_set_value() 设置GPIO引脚电平
gpio_get_value() 获取GPIO引脚电平

使用示例

复制代码
// 1. 申请GPIO
ret = gpio_request(led_gpio, "led");

// 2. 设置方向
gpio_direction_output(led_gpio, 1);  // 输出高电平

// 3. 控制电平
gpio_set_value(led_gpio, 0);  // 输出低电平

// 4. 读取电平
int val = gpio_get_value(key_gpio);

// 5. 释放GPIO
gpio_free(led_gpio);

十二、中断实现

1. 中断处理的两部分

中断顶半部(上半部)
  • 中断服务函数

  • 特点:处理速度要快,不能加延时

  • 任务:接收中断,调度底半部处理

中断底半部(下半部)
  • 处理费时的任务

  • 三种实现方式

方式 上下文 能否延时 说明
软中断 中断上下文 不能加延时 基于中断上下文
tasklet 中断上下文 不能加延时 基于软中断实现
工作队列 进程上下文 可以加延时 基于进程上下文

2. 中断实现示例

(1) tasklet方式
复制代码
// 定义tasklet结构体
static struct tasklet_struct tsk;

// tasklet处理函数
static void key_tasklet_handler(unsigned long arg)
{
    // 处理费时任务,但不能延时
    condition = 1;
    wake_up_interruptible(&wq);
}

// 中断顶半部
static irqreturn_t key_irq_handler(int irq, void * dev)
{
    // 调度tasklet执行底半部
    tasklet_schedule(&tsk);
    return IRQ_HANDLED;
}

// 初始化tasklet
tasklet_init(&tsk, key_tasklet_handler, 100);
(2) 工作队列方式
复制代码
// 定义工作结构体
static struct work_struct work;

// 工作处理函数
static void key_work_func(struct work_struct *work)
{
    // 处理费时任务,可以延时
    ssleep(1);  // 可以休眠
    condition = 1;
    wake_up_interruptible(&wq);
}

// 中断顶半部
static irqreturn_t key_irq_handler(int irq, void * dev)
{
    // 调度工作队列执行底半部
    schedule_work(&work);
    return IRQ_HANDLED;
}

// 初始化工作队列
INIT_WORK(&work, key_work_func);

十三、同步机制:互斥锁、自旋锁、读写锁

1. 互斥锁

  • 特点:加锁解锁过程中,CPU可以切换调度任务

  • 适用场景:长时间资源占用时使用

2. 自旋锁

  • 特点:加锁过程中,CPU处于忙等待,不会进行任务调度和切换

  • 适用场景:短时间资源占用时使用

3. 读写锁

特点
  • 一把锁分为两部分:读锁和写锁
操作 效果
加读锁 多个线程同时读不会阻塞(读锁共享)
加写锁 效果和互斥锁相同,只允许一个线程写(写锁独占)
应用场景
  • 多个线程操作同一个临界资源时

  • 读操作多于写操作时,读写锁效率高于互斥锁

十四、驱动整体框架总结

1. 字符设备驱动完整示例

复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>

#define DEV_NAME "demo"

static int major;
static struct cdev cdev;
static struct class *cls;

// 文件操作函数
static int open(struct inode *inode, struct file *file)
{
    printk("device open\n");
    return 0;
}

static ssize_t read(struct file *file, char __user *buf, 
                    size_t size, loff_t *off)
{
    return 0;
}

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

// 模块入口
static int __init demo_init(void)
{
    dev_t dev;
    
    // 1. 分配设备号
    alloc_chrdev_region(&dev, 0, 1, DEV_NAME);
    major = MAJOR(dev);
    
    // 2. 初始化cdev
    cdev_init(&cdev, &fops);
    
    // 3. 添加cdev到系统
    cdev_add(&cdev, dev, 1);
    
    // 4. 创建类
    cls = class_create(THIS_MODULE, "demo_class");
    
    // 5. 创建设备节点
    device_create(cls, NULL, dev, NULL, DEV_NAME);
    
    printk("demo init success, major=%d\n", major);
    return 0;
}

// 模块出口
static void __exit demo_exit(void)
{
    dev_t dev = MKDEV(major, 0);
    
    device_destroy(cls, dev);
    class_destroy(cls);
    cdev_del(&cdev);
    unregister_chrdev_region(dev, 1);
    
    printk("demo exit\n");
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

2. 杂项设备驱动简化版

复制代码
#include <linux/miscdevice.h>

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

static struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,  // 动态分配次设备号
    .name = "demo",                // 设备名
    .fops = &fops                  // 文件操作
};

static int __init demo_init(void)
{
    return misc_register(&misc_dev);
}

static void __exit demo_exit(void)
{
    misc_deregister(&misc_dev);
}
相关推荐
xmlhcxr1 小时前
Nginx(一)
运维·nginx
LuDvei2 小时前
LINUX文件操作函数
java·linux·算法
枷锁—sha2 小时前
【CTFshow-pwn系列】03_栈溢出【pwn 053】详解:逐字节爆破!手写 Canary 的终极破解
网络·笔记·安全·网络安全
大连好光景2 小时前
PyTorch深度学习----优化器
pytorch·深度学习·学习
浅念-3 小时前
C++ 继承
开发语言·c++·经验分享·笔记·学习·算法·继承
czxyvX3 小时前
017-Linux-网络基础概念
linux·网络
一个人旅程~3 小时前
win10LTSB2016与win10LTSC2019对于老机型哪个更合适?
linux·windows·经验分享·电脑
峰顶听歌的鲸鱼3 小时前
Zabbix监控系统
linux·运维·笔记·安全·云计算·zabbix·学习方法
物联网软硬件开发-轨物科技3 小时前
【技术白皮书】光伏电站数智化技改技术白皮书:从老旧场站到高收益智能资产的演进路径
大数据·运维·服务器