复习——Linux设备驱动开发笔记

Linux设备驱动开发笔记

一、硬件基础

1. 存储器类型

RAM(随机存储器)
  • 特点:访问速度快、容量小、运行程序使用、掉电数据丢失、可线性访问

  • 种类

    • SRAM:静态RAM,速度快,成本高

    • DRAM:动态RAM,需要刷新

    • SDRAM:同步DRAM

    • DDRn:双倍数据速率SDRAM

ROM(只读存储器)
  • 特点:访问速度慢、容量大、存储文件、掉电数据不丢失、大部分不可线性访问

  • 种类

    • PROM:可编程ROM

    • EPROM:可擦除可编程ROM

    • EEPROM:电可擦除可编程ROM

Flash/emmc
  • 访问速率快

  • 掉电数据不丢失

  • 常用作存储设备

二、Linux启动流程

1. 总体流程对比

复制代码
Windows: BIOS -> Windows -> 桌面
Linux: bootloader -> linux kernel -> rootfs

2. Linux启动三阶段

(1) Bootloader阶段

主要任务

  • 初始化CPU(设置工作模式)

  • 初始化异常向量表

  • 初始化堆栈

  • 关中断

  • 关看门狗(防止自动重启)

  • 关Cache

  • 关闭MMU(将虚拟地址转为物理地址)

  • 初始化内存

  • 初始化相关设备(串口、网卡)

  • 集成相关协议

  • 搬移内核到内存,向内核传参(控制台、根文件系统类型/位置)

  • 启动内核

  • 移交CPU控制权给内核

(2) Linux内核阶段

五大管理功能

  1. 文件管理

  2. 进程管理

  3. 网络管理

  4. 内存管理

  5. 设备管理

启动过程

  • 内核启动到最后阶段加载(挂载)根文件系统

  • 内核init进程退化为用户init进程

  • 流程:init(用户) -> services -> shell -> userapp

(3) 根文件系统

包含内容

  • 系统命令

  • 启动脚本(系统服务、安装的应用程序)

  • 配置文件

  • 应用程序(工具、用户程序)

  • 普通文件(文本、mp3、mp4)

3. imx6开发板启动方式

SD卡启动
  1. 系统上电,执行imx6内部ROM中的启动程序

  2. 根据boot mode选择对应外设

  3. 拷贝SD卡中bootloader前半部分到imx6内部RAM

  4. bootloader初始化内存,并将自己后半部分搬移到内存执行

  5. bootloader搬移SD卡中的内核(zImage)到内存0x80800000

  6. PC指向0x80800000启动内核

  7. 内核挂载SD卡上的根文件系统

网络启动(内核和根文件系统在ubuntu上)
  1. bootloader通过tftp下载ubuntu中的zImage到内存0x80800000

  2. bootloader引导内核启动

  3. 内核通过nfs挂载ubuntu中的rootfs

内存地址布局

复制代码
0地址        <- 起始地址
0x80000000   <- RAM起始
0x80800000   <- 内核加载地址

三、Ubuntu环境准备

1. TFTP服务配置

用途 :传输内核镜像zImage和设备树文件dtb
步骤

  1. 安装tftp服务

  2. 配置服务目录

  3. 拷贝zImage和xxx.dtb到tftp服务目录

2. NFS服务配置

用途 :通过网络挂载根文件系统
步骤

  1. 安装nfs服务

  2. 配置服务目录

  3. 解压根文件系统到nfs目录

复制代码
sudo tar -xvf rootfs.tar.bz2

3. 开发板挂载命令

复制代码
# 在开发板上执行
mount -o nolock,nfsvers=3 192.168.1.3:/home/linux/nfs /mnt

四、U-Boot常用命令

基本命令

  • help/?:查看uboot支持的命令

  • reset:uboot阶段重启命令

  • ping:测试网络

  • printenv:打印环境变量

  • setenv name value:设置环境变量(字符串类型)

  • setenv name:删除环境变量(值设为空)

  • saveenv:保存环境变量(通常保存到MMC)

网络相关环境变量

复制代码
# 设置开发板IP
setenv ipaddr 192.168.1.100

# 设置MAC地址
setenv ethaddr xx:xx:xx:xx:xx:xx

# 设置TFTP服务器IP
setenv serverip 192.168.1.3

TFTP下载命令

复制代码
# 下载内核到内存
tftp 0x80800000 zImage

# 下载设备树到内存
tftp 0x83000000 imx6.dtb

启动内核命令

复制代码
# 设置启动参数
setenv bootargs console=ttymxc0,115200 root=/dev/nfs \
nfsroot=192.168.1.3:/home/linux/nfs/imx6/rootfs,nfsvers=3 \
ip=192.168.1.100 init=/linuxrc

# 启动内核
bootz 0x80800000 - 0x83000000

启动参数解释

  • console:指定Linux控制台

  • root:根文件系统类型为nfs

  • nfsroot:nfs文件系统路径

  • ip:Linux内核启动阶段使用的IP地址

  • init:指定init进程(1号进程)

五、内核编译

1. 配置系统

  • Kconfig:定义make menuconfig中的配置选项

  • .config:存储配置结果

  • Makefile:根据配置编译对应模块

Makefile示例

复制代码
OBJ-$(CONFIG_MAIN) += main.o
OBJ-$(CONFIG_FUN1_MEMORY) += fun1.o
OBJ-$(CONFIG_FUN2_NET) += fun2.o

$(TARGET):$(OBJ)
    gcc $^ -o $@

.config示例

复制代码
CONFIG_MAIN=y
CONFIG_FUN1_MEMORY=y
CONFIG_FUN2_NET=y

2. 内核编译步骤

复制代码
# 1. 解压内核源码
sudo tar -xvf linuxxxxxx.tar.gz

# 2. 修改权限
sudo chmod 0777 linuxxxxxx -R

# 3. 导入默认配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_alientek_emmc_defconfig

# 4. 图形化配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

# 5. 编译内核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16

3. 内核镜像类型

  • Image:可以直接执行的内核镜像

  • zImage:解压程序 + Image的压缩包

  • uImage:64字节头信息 + zImage

4. 向内核添加新文件

以向drivers/char/添加demo.c为例:

步骤1 :创建并编辑drivers/char/demo.c

步骤2 :修改drivers/char/Makefile

复制代码
obj-$(CONFIG_DEMO) += demo.o

步骤3 :修改drivers/char/Kconfig

复制代码
config DEMO
    tristate "Demo driver"
    help
      This is a demo driver.

步骤4:配置和编译

复制代码
make menuconfig    # 配置DEMO选项
make zImage        # 编译内核

六、设备驱动分类

1. 字符设备驱动

  • 数据按字节流访问(顺序性访问)

  • 90%以上的设备都是字符设备

  • 示例:LED、按键、串口

2. 块设备驱动

  • 数据访问可以是随机的

  • 一般是存储设备

  • 示例:硬盘、SD卡

3. 网络设备驱动

  • 集成复杂的协议栈

  • 没有设备号(按名字维护)

  • 示例:网卡

七、字符设备驱动开发

1. 驱动程序实现要素

  1. 硬件操作方法(open、read、write、close)

  2. 申请驱动模块对应的设备号

  3. 向系统注册驱动模块

2. 设备号

  • 无符号32位整数

  • 高12位:主设备号,代表设备类型(功能)

  • 低20位:次设备号,区分同类不同设备

3. 开发工具ctags

复制代码
# 在内核源码目录生成索引
ctags -R

# 使用快捷键
Ctrl + ]  # 跳转到符号定义
Ctrl + o  # 返回

4. 手动创建设备节点

复制代码
# 格式:mknod 设备名 类型 主设备号 次设备号
mknod /dev/demo1 c 255 0

# 查看已加载驱动的主设备号
cat /proc/devices

八、字符设备驱动模板

1. 标准字符设备驱动

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

static struct file_operations fops;
static dev_t dev;
static struct cdev cdev;
static struct class *cls;

static int __init demo_init(void)
{
    // 1. 分配设备号
    dev = MKDEV(255, 0);
    register_chrdev_region(dev, 1, "demo");
    
    // 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, "demo1");
    
    return 0;
}

static void __exit demo_exit(void)
{
    // 清理顺序与初始化相反
    device_destroy(cls, dev);
    class_destroy(cls);
    cdev_del(&cdev);
    unregister_chrdev_region(dev, 1);
}

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

2. 杂项设备驱动(简化版)

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

static struct file_operations fops;

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);
}

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

九、内核模块编译

1. 配置为模块

  1. 修改Kconfig,将模块类型设为tristate

  2. make menuconfig,将对应模块配置为M

  3. make modules编译模块

2. 模块操作命令

复制代码
# 加载模块
insmod xxx.ko

# 卸载模块
rmmod xxx

# 查看已加载模块
lsmod

十、ioctl命令结构

cmd参数是32位整数,包含以下信息:

位段 名称 大小 说明
31-24 type 8 bits 设备类型
23-16 nr 8 bits 设备内的命令编号
15-14 dir 2 bits 数据流向(读/写)
13-0 size 14 bits 参数的大小

定义宏

复制代码
#define _IO(type,nr)            // 无参数命令
#define _IOR(type,nr,size)      // 读命令
#define _IOW(type,nr,size)      // 写命令
#define _IOWR(type,nr,size)     // 读写命令

十一、设备驱动与设备资源

1. 总线类型

  • bus_type:总线类型基类

  • 具体总线:i2c、spi、platform等

2. 设备资源匹配流程

复制代码
设备树节点(compatible属性)
        ↓
驱动匹配表(compatible属性)
        ↓
匹配成功 → 执行probe函数
        ↓
在probe中注册字符设备驱动
        ↓
设备可用

3. Platform设备结构

复制代码
struct platform_device {
    const char *name;          // 设备名,可用于匹配
    int id;
    struct device dev;
    u32 num_resources;
    struct resource *resource; // 设备资源
    const struct platform_device_id *id_entry;
};

4. 资源结构体

复制代码
struct resource {
    resource_size_t start;    // 资源起始地址
    resource_size_t end;      // 资源结束地址
    const char *name;         // 资源名称
    unsigned long flags;      // 资源标志
};

5. Platform驱动结构

复制代码
struct platform_driver {
    int (*probe)(struct platform_device *);    // 设备探测
    int (*remove)(struct platform_device *);   // 设备移除
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;               // 包含name属性
    const struct platform_device_id *id_table; // 匹配表
};

6. 平台驱动注册

复制代码
// 注册宏
#define platform_driver_register(drv) \
    __platform_driver_register(drv, THIS_MODULE)

// 注册函数
int __platform_driver_register(struct platform_driver *, struct module *);

// 注销函数
void platform_driver_unregister(struct platform_driver *);

十二、设备树(Device Tree)

1. 设备树文件类型

  • .dts:设备树源文件(可编辑)

  • .dtb:设备树编译后的二进制文件(内核解析)

2. 编译命令

复制代码
make dtbs           # 编译所有设备树
make pt.dtb         # 编译pt.dts为pt.dtb

3. 设备树匹配机制

匹配条件

  1. 设备树节点中有compatible属性

  2. 驱动程序中有相同的compatible属性

  3. 两者匹配成功则执行驱动的probe函数

备用匹配 :如果没有compatible,可以用name匹配

十三、GPIO子系统

Linux内核提供的统一GPIO操作接口:

1. 常用函数

复制代码
// 申请GPIO
int gpio_request(unsigned gpio, const char *label);

// 配置为输出
int gpio_direction_output(unsigned gpio, int value);

// 配置为输入
int gpio_direction_input(unsigned gpio);

// 设置输出值
void gpio_set_value(unsigned gpio, int value);

// 获取输入值
int gpio_get_value(unsigned gpio);

// 释放GPIO
void gpio_free(unsigned gpio);

2. 使用流程

复制代码
// 1. 获取GPIO编号
int gpio_num = of_get_named_gpio(dev->of_node, "led-gpio", 0);

// 2. 申请GPIO
ret = gpio_request(gpio_num, "led");

// 3. 配置为输出
gpio_direction_output(gpio_num, 0);

// 4. 控制GPIO
gpio_set_value(gpio_num, 1);  // 高电平
gpio_set_value(gpio_num, 0);  // 低电平

// 5. 释放GPIO(在remove函数中)
gpio_free(gpio_num);
相关推荐
stars-he7 小时前
AI工具配置学习笔记
人工智能·笔记·学习
The森7 小时前
Linux IO 模型纵深解析 03:同步 IO 与异步 IO
linux·服务器
袁气满满~_~7 小时前
深度学习笔记三
人工智能·笔记·深度学习
草莓熊Lotso8 小时前
Linux 文件描述符与重定向实战:从原理到 minishell 实现
android·linux·运维·服务器·数据库·c++·人工智能
历程里程碑8 小时前
Linux22 文件系统
linux·运维·c语言·开发语言·数据结构·c++·算法
wdfk_prog16 小时前
[Linux]学习笔记系列 -- [drivers][input]input
linux·笔记·学习
ouliten16 小时前
cuda编程笔记(36)-- 应用Tensor Core加速矩阵乘法
笔记·cuda
盟接之桥17 小时前
盟接之桥说制造:引流品 × 利润品,全球电商平台高效产品组合策略(供讨论)
大数据·linux·服务器·网络·人工智能·制造
忆~遂愿17 小时前
ops-cv 算子库深度解析:面向视觉任务的硬件优化与数据布局(NCHW/NHWC)策略
java·大数据·linux·人工智能