Linux 驱动开发学习计划与实战项目(求职导向版)
·
Linux 驱动开发学习计划与实战项目(求职导向版)
求职竞争力分析
市场需求与技能权重
| 技能领域 | 面试频率 | 重要程度 | 简历亮点权重 |
|---|---|---|---|
| 字符设备驱动 | ★★★★★ | 高 | ★★★★★ |
| 设备树(Device Tree) | ★★★★★ | 高 | ★★★★☆ |
| 平台驱动模型 | ★★★★☆ | 高 | ★★★★☆ |
| I2C/SPI 总线 | ★★★★☆ | 高 | ★★★★☆ |
| 中断处理 | ★★★★☆ | 高 | ★★★★☆ |
| 并发控制(锁机制) | ★★★★★ | 极高 | ★★★★★ |
| GPIO/Pinctrl | ★★★☆☆ | 中 | ★★★☆☆ |
| 内存管理 | ★★★★☆ | 高 | ★★★★☆ |
| 电源管理 | ★★☆☆☆ | 中 | ★★★☆☆ |
| DMA/定时器 | ★★★☆☆ | 中 | ★★★☆☆ |
面试高频问题清单
必背核心问题:
- 用户空间与内核空间的区别?如何进行数据交互?
- spinlock 和 mutex 的区别及适用场景?
- 中断上下半部的处理方式有哪些?各有什么特点?
- 设备树的作用是什么?compatible 属性如何匹配驱动?
- platform_driver 的 probe 函数什么时候被调用?
- copy_to_user/copy_from_user 的作用和必要性?
- kmalloc 和 vmalloc 的区别?
- request_irq 的参数含义及标志位作用?
- cdev 注册的完整流程是什么?
- I2C 协议的基本时序是怎样的?
第一部分:学习计划(求职导向版)
总体规划(共12周)
| 阶段 | 周期 | 核心内容 | 求职目标 | 面试重点 |
|---|---|---|---|---|
| 基础筑基 | 第1-2周 | 内核基础 + 字符设备 | 掌握驱动开发框架 | 模块机制、file_operations |
| 核心机制 | 第3-5周 | 并发控制 + 设备树 + 中断 | 独立编写平台驱动 | 锁机制、设备树解析、中断处理 |
| 总线驱动 | 第6-8周 | I2C + SPI + GPIO/Pinctrl | 驱动常见外设 | I2C/SPI协议、设备树匹配 |
| 综合实战 | 第9-12周 | 完整项目开发 + 面试准备 | 简历项目亮点 | 架构设计、问题排查 |
每周学习计划与面试准备
第1周:内核基础与开发环境
理论学习:
- Linux 内核架构(用户空间 vs 内核空间、系统调用)
- 内核模块机制(insmod、rmmod、modprobe、depmod)
- 内核编译与交叉编译(.config、make menuconfig)
- 内核源码目录结构
重点文件:
include/linux/module.h # 模块API
include/linux/kernel.h # printk级别
include/linux/init.h # __init __exit
scripts/Makefile.lib # 内核编译系统
练习:
- 编写 hello world 内核模块
- 掌握 dmesg 日志查看技巧
- 理解 obj-m 和 obj-y 的区别
面试准备:
- 背诵:用户空间与内核空间的区别
- 背诵:内核模块的加载过程
- 背诵:printk 与 printf 的区别
第2周:字符设备驱动
理论学习:
- 主设备号与次设备号(静态分配 vs 动态分配)
- struct file_operations 结构体详解
- cdev 注册流程(register_chrdev_region → cdev_init → cdev_add)
- 用户态/内核态数据交互(copy_to_user / copy_from_user)
- ioctl 机制与命令定义
重点文件:
include/linux/fs.h # file_operations
include/linux/cdev.h # cdev 结构体
include/linux/uaccess.h # copy_to_user
drivers/char/mem.c # 经典参考
练习:
- 实现完整字符设备(open/read/write/ioctl/close)
- 通过 ioctl 实现用户空间控制内核数据
面试准备:
- 能画出 cdev 注册流程图
- 背诵:为什么需要 copy_to_user?
- 背诵:file_operations 各成员的作用
第3周:并发控制与内存管理
理论学习:
- 自旋锁(spinlock):适用中断上下文
- 互斥锁(mutex):适用进程上下文
- 原子变量(atomic_t)
- 等待队列(wait_queue)实现阻塞操作
- 内存分配:kmalloc、kzalloc、vmalloc、kfree
- IO 内存映射:ioremap、iounmap、devm_ioremap
- 寄存器读写:readl、writel、readb、writeb
重点文件:
include/linux/spinlock.h # 自旋锁
include/linux/mutex.h # 互斥锁
include/linux/wait.h # 等待队列
include/linux/slab.h # 内存分配
include/linux/io.h # IO操作
练习:
- 在字符设备中加入互斥锁保护共享数据
- 使用等待队列实现阻塞读
- 练习 ioremap 映射物理地址
面试准备:
- 背诵:spinlock vs mutex 的区别
- 背诵:为什么中断上下文不能使用 mutex?
- 背诵:kmalloc 和 vmalloc 的区别及适用场景
第4周:设备树(Device Tree)
理论学习:
- 设备树的起源与作用(替代板级文件)
- DTS/DTB/DTSI 文件关系
- 设备树节点、属性语法
- compatible 匹配机制
- 常用属性:reg、interrupts、clocks、pinctrl、gpio
- 设备树编译(dtc)与反编译
重点文件:
arch/arm64/boot/dts/rockchip/rk3568.dtsi
arch/arm64/boot/dts/rockchip/rk3568-evb*.dts
include/dt-bindings/
Documentation/devicetree/bindings/
练习:
- 阅读 RK3568 设备树
- 编写自定义设备树节点
- 使用 of_property_read_xxx 系列函数读取属性
面试准备:
- 背诵:设备树的作用是什么?
- 背诵:compatible 属性如何匹配驱动?
- 能解释 reg 属性的含义
第5周:平台驱动模型与中断
理论学习:
- 总线-设备-驱动模型(bus-device-driver)
- platform_device 与 platform_driver
- 设备树匹配机制(of_device_id)
- probe 和 remove 函数调用时机
- 资源获取:platform_get_resource、platform_get_irq
- 中断注册:request_irq / devm_request_irq
- 中断上下半部:tasklet、workqueue、threaded_irq
重点文件:
drivers/base/platform.c
include/linux/platform_device.h
include/linux/interrupt.h
drivers/gpio/gpio-rockchip.c
练习:
- 基于设备树 + 平台驱动重写 LED 驱动
- 实现按键中断驱动(按键触发中断,LED翻转)
- 使用 workqueue 处理中断耗时操作
面试准备:
- 背诵:platform_driver 的 probe 何时被调用?
- 背诵:中断上下半部的区别及适用场景
- 背诵:tasklet 和 workqueue 的区别
第6周:I2C 子系统
理论学习:
- I2C 协议基础(SDA/SCL、主从、地址、ACK/NACK)
- I2C 总线驱动 vs 设备驱动
- struct i2c_driver、struct i2c_client
- i2c_transfer / i2c_smbus_read/write 系列函数
- 设备树中 I2C 设备描述
重点文件:
drivers/i2c/i2c-core.c
include/linux/i2c.h
drivers/i2c/busses/i2c-rk3x.c
drivers/i2c/i2c-dev.c
练习:
- 编写 I2C 设备驱动(如 SHT30 温湿度传感器)
- 使用 i2c-tools 调试(i2cdetect、i2cget、i2cset)
面试准备:
- 背诵:I2C 协议的基本时序
- 背诵:i2c_transfer 的工作原理
- 能解释 I2C 设备地址的概念
第7周:SPI 子系统
理论学习:
- SPI 协议基础(MOSI/MISO/CLK/CS、四种模式)
- struct spi_driver、struct spi_device
- spi_sync / spi_async 传输函数
- SPI 设备树描述
重点文件:
drivers/spi/spi.c
include/linux/spi/spi.h
drivers/spi/spi-rockchip.c
练习:
- 编写 SPI 设备驱动(如 OLED 显示屏)
- 使用 spidev 在用户空间测试
面试准备:
- 背诵:SPI 与 I2C 的区别
- 背诵:SPI 的四种模式
- 理解 spi_sync 和 spi_async 的区别
第8周:GPIO 子系统与 Pinctrl
理论学习:
- GPIO 子系统 API(gpio_request、devm_gpio_request)
- gpio_direction_input/output、gpio_get/set_value
- Pinctrl 子系统(引脚复用、电气属性配置)
- 设备树中 GPIO 和 Pinctrl 的描述
重点文件:
drivers/gpio/gpiolib.c
include/linux/gpio/consumer.h
drivers/pinctrl/pinctrl-rockchip.c
练习:
- 使用 GPIO 子系统控制 LED 和按键
- 配置设备树中的 pinctrl 节点
面试准备:
- 背诵:devm_xxx 系列函数的作用
- 理解 Pinctrl 的作用
第9-12周:综合实战项目
详见第二部分"实战项目"。
第二部分:实战项目 —— 智能环境监测系统
项目概述
基于 RK3568 开发板,开发完整的智能环境监测系统。此项目可作为简历亮点,展示以下能力:
| 能力维度 | 项目体现 |
|---|---|
| 驱动架构设计 | 字符设备框架 + 多子系统整合 |
| 设备树配置 | 完整的设备树节点定义 |
| I2C 总线驱动 | SHT30 温湿度传感器 |
| GPIO 控制 | LED 指示灯驱动 |
| 中断处理 | 按键中断驱动 |
| 并发控制 | mutex 保护共享资源 |
| 用户态交互 | read/ioctl 接口 |
项目架构
+--------------------------------------------------+
| 用户空间应用程序 |
| cat /dev/env_monitor |
| ioctl(fd, GET_TEMPERATURE, &temp) |
+--------------------------------------------------+
|
系统调用
|
+--------------------------------------------------+
| 字符设备驱动框架层 |
| file_operations: open/read/write/ioctl/release |
+--------------------------------------------------+
|
+---------------+---------------+
| | |
+-------+------+ +------+------+ +------+------+
| 温湿度传感器 | | LED指示灯 | | 按键中断 |
| (I2C驱动) | | (GPIO驱动) | | (GPIO+IRQ) |
+--------------+ +------------+ +-------------+
| | |
+---------------+---------------+
|
RK3568 GPIO/I2C 控制器
硬件接线说明
I2C 温湿度传感器(SHT30):
- SDA → RK3568 I2C3_SDA (GPIO1_A4)
- SCL → RK3568 I2C3_SCL (GPIO1_A3)
- VCC → 3.3V
- GND → GND
LED 指示灯:
- LED1 (绿色/正常) → GPIO3_B5
- LED2 (红色/告警) → GPIO3_B6
按键:
- KEY1 (模式切换) → GPIO3_A4
- KEY2 (数据清除) → GPIO3_A5
项目文件结构
env_monitor/
├── dts/
│ └── rk3568-env-monitor.dtsi # 设备树片段
├── driver/
│ ├── Makefile
│ ├── env_monitor_main.c # 主驱动(字符设备框架)
│ ├── sht30_i2c.c # SHT30 I2C 驱动
│ ├── sht30_i2c.h
│ ├── led_gpio.c # LED GPIO 驱动
│ ├── led_gpio.h
│ ├── key_irq.c # 按键中断驱动
│ └── key_irq.h
└── app/
├── Makefile
└── env_monitor_app.c # 用户空间测试程序
模块一:设备树配置
文件:rk3568-env-monitor.dtsi
/ {
env_monitor {
compatible = "mycompany,env-monitor";
status = "okay";
led-green {
label = "status-green";
gpios = <&gpio3 RK_PB5 GPIO_ACTIVE_HIGH>;
default-state = "off";
};
led-red {
label = "alarm-red";
gpios = <&gpio3 RK_PB6 GPIO_ACTIVE_HIGH>;
default-state = "off";
};
key-mode {
label = "key-mode";
gpios = <&gpio3 RK_PA4 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio3>;
interrupts = <RK_PA4 IRQ_TYPE_EDGE_FALLING>;
linux,code = <1>;
};
key-clear {
label = "key-clear";
gpios = <&gpio3 RK_PA5 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio3>;
interrupts = <RK_PA5 IRQ_TYPE_EDGE_FALLING>;
linux,code = <2>;
};
};
};
&i2c3 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c3m1_xfer>;
sht30@44 {
compatible = "sensirion,sht30";
reg = <0x44>;
measurement-frequency = <1>;
};
};
模块二:SHT30 I2C 驱动
文件:sht30_i2c.h
#ifndef _SHT30_I2C_H_
#define _SHT30_I2C_H_
#include <linux/i2c.h>
#define SHT30_ADDR 0x44
#define SHT30_CMD_MEASURE_H 0x2400
#define SHT30_CMD_MEASURE_L 0x240B
#define SHT30_CMD_SOFT_RST 0x30A2
struct sht30_data {
struct i2c_client *client;
struct mutex lock;
int temperature;
int humidity;
};
int sht30_init(struct i2c_client *client, struct sht30_data *data);
void sht30_deinit(struct sht30_data *data);
int sht30_read_temp_humi(struct sht30_data *data);
#endif
文件:sht30_i2c.c
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include "sht30_i2c.h"
static u8 sht30_crc8(const u8 *data, int len)
{
u8 crc = 0xFF;
int i, j;
for (i = 0; i < len; i++) {
crc ^= data[i];
for (j = 0; j < 8; j++) {
crc = (crc & 0x80) ? ((crc << 1) ^ 0x31) : (crc << 1);
}
}
return crc;
}
static int sht30_send_cmd(struct i2c_client *client, u16 cmd)
{
u8 buf[2] = { (cmd >> 8) & 0xFF, cmd & 0xFF };
return i2c_master_send(client, buf, 2);
}
int sht30_read_temp_humi(struct sht30_data *data)
{
u8 buf[6];
u16 raw_temp, raw_humi;
int ret;
mutex_lock(&data->lock);
ret = sht30_send_cmd(data->client, SHT30_CMD_MEASURE_H);
if (ret < 0) {
dev_err(&data->client->dev, "send measure cmd failed\n");
goto out;
}
msleep(20);
ret = i2c_master_recv(data->client, buf, 6);
if (ret < 0) {
dev_err(&data->client->dev, "read data failed\n");
goto out;
}
if (sht30_crc8(buf, 2) != buf[2] || sht30_crc8(buf + 3, 2) != buf[5]) {
dev_err(&data->client->dev, "CRC error\n");
ret = -EIO;
goto out;
}
raw_temp = (buf[0] << 8) | buf[1];
raw_humi = (buf[3] << 8) | buf[4];
data->temperature = ((int)raw_temp * 1750) / 65535 - 450;
data->humidity = ((int)raw_humi * 1000) / 65535;
ret = 0;
out:
mutex_unlock(&data->lock);
return ret;
}
int sht30_init(struct i2c_client *client, struct sht30_data *data)
{
int ret;
data->client = client;
mutex_init(&data->lock);
ret = sht30_send_cmd(client, SHT30_CMD_SOFT_RST);
if (ret < 0) {
dev_err(&client->dev, "soft reset failed\n");
return ret;
}
msleep(10);
dev_info(&client->dev, "SHT30 initialized, addr=0x%02x\n", client->addr);
return 0;
}
void sht30_deinit(struct sht30_data *data)
{
mutex_destroy(&data->lock);
}
模块三:LED GPIO 驱动
文件:led_gpio.h
#ifndef _LED_GPIO_H_
#define _LED_GPIO_H_
#include <linux/gpio.h>
#define LED_GREEN 0
#define LED_RED 1
#define LED_MAX 2
struct led_dev {
int gpio;
const char *label;
bool state;
};
int led_init(struct led_dev *leds, int count);
void led_deinit(struct led_dev *leds, int count);
void led_set(struct led_dev *led, bool on);
void led_toggle(struct led_dev *led);
#endif
文件:led_gpio.c
#include <linux/module.h>
#include <linux/gpio.h>
#include "led_gpio.h"
int led_init(struct led_dev *leds, int count)
{
int i, ret;
for (i = 0; i < count; i++) {
ret = devm_gpio_request(NULL, leds[i].gpio, leds[i].label);
if (ret) {
pr_err("led: request gpio %d failed: %d\n", leds[i].gpio, ret);
return ret;
}
gpio_direction_output(leds[i].gpio, 0);
leds[i].state = false;
}
return 0;
}
void led_deinit(struct led_dev *leds, int count)
{
int i;
for (i = 0; i < count; i++) {
gpio_set_value(leds[i].gpio, 0);
}
}
void led_set(struct led_dev *led, bool on)
{
gpio_set_value(led->gpio, on);
led->state = on;
}
void led_toggle(struct led_dev *led)
{
led->state = !led->state;
gpio_set_value(led->gpio, led->state);
}
模块四:按键中断驱动
文件:key_irq.h
#ifndef _KEY_IRQ_H_
#define _KEY_IRQ_H_
#include <linux/interrupt.h>
typedef void (*key_callback_t)(int key_code, void *priv);
struct key_dev {
int gpio;
int irq;
int code;
const char *label;
key_callback_t callback;
void *priv;
};
int key_init(struct key_dev *keys, int count);
void key_deinit(struct key_dev *keys, int count);
#endif
文件:key_irq.c
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include "key_irq.h"
static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
struct key_dev *key = dev_id;
if (key->callback)
key->callback(key->code, key->priv);
return IRQ_HANDLED;
}
int key_init(struct key_dev *keys, int count)
{
int i, ret;
for (i = 0; i < count; i++) {
ret = devm_gpio_request(NULL, keys[i].gpio, keys[i].label);
if (ret) {
pr_err("key: request gpio %d failed: %d\n", keys[i].gpio, ret);
return ret;
}
gpio_direction_input(keys[i].gpio);
ret = devm_request_irq(NULL, keys[i].irq, key_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
keys[i].label, &keys[i]);
if (ret) {
pr_err("key: request irq %d failed: %d\n", keys[i].irq, ret);
return ret;
}
}
return 0;
}
void key_deinit(struct key_dev *keys, int count)
{
/* devm_* 资源自动释放 */
}
模块五:主驱动(字符设备框架)
文件:env_monitor_main.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include "sht30_i2c.h"
#include "led_gpio.h"
#include "key_irq.h"
#define DEVICE_NAME "env_monitor"
#define CLASS_NAME "env_monitor_class"
#define ENV_IOC_MAGIC 'E'
#define ENV_IOC_GET_TEMP _IOR(ENV_IOC_MAGIC, 1, int)
#define ENV_IOC_GET_HUMI _IOR(ENV_IOC_MAGIC, 2, int)
#define ENV_IOC_GET_ALARM _IOR(ENV_IOC_MAGIC, 3, int)
#define ENV_IOC_CLR_ALARM _IO(ENV_IOC_MAGIC, 4)
#define TEMP_HIGH_THRESHOLD 400
#define HUMI_HIGH_THRESHOLD 800
struct env_monitor_dev {
struct cdev cdev;
dev_t devno;
struct class *cls;
struct sht30_data sht30;
struct led_dev leds[LED_MAX];
struct key_dev keys[2];
bool alarm;
int mode;
};
static struct env_monitor_dev *g_env_dev;
static void key_event_callback(int key_code, void *priv)
{
struct env_monitor_dev *env = priv;
if (key_code == 1) {
env->mode = (env->mode + 1) % 3;
pr_info("env_monitor: mode switch to %d\n", env->mode);
} else if (key_code == 2) {
env->alarm = false;
led_set(&env->leds[LED_RED], false);
pr_info("env_monitor: alarm cleared\n");
}
}
static int env_monitor_open(struct inode *inode, struct file *filp)
{
filp->private_data = g_env_dev;
return 0;
}
static ssize_t env_monitor_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
struct env_monitor_dev *env = filp->private_data;
char kbuf[128];
int len, ret;
ret = sht30_read_temp_humi(&env->sht30);
if (ret)
return ret;
if (env->sht30.temperature > TEMP_HIGH_THRESHOLD ||
env->sht30.humidity > HUMI_HIGH_THRESHOLD) {
env->alarm = true;
led_set(&env->leds[LED_RED], true);
}
len = snprintf(kbuf, sizeof(kbuf),
"Temperature: %d.%d C\nHumidity: %d.%d %%\nAlarm: %s\n",
env->sht30.temperature / 10, env->sht30.temperature % 10,
env->sht30.humidity / 10, env->sht30.humidity % 10,
env->alarm ? "YES" : "NO");
if (count < len)
return -EINVAL;
if (copy_to_user(buf, kbuf, len))
return -EFAULT;
return len;
}
static long env_monitor_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct env_monitor_dev *env = filp->private_data;
int val;
switch (cmd) {
case ENV_IOC_GET_TEMP:
sht30_read_temp_humi(&env->sht30);
val = env->sht30.temperature;
return copy_to_user((int __user *)arg, &val, sizeof(val)) ? -EFAULT : 0;
case ENV_IOC_GET_HUMI:
sht30_read_temp_humi(&env->sht30);
val = env->sht30.humidity;
return copy_to_user((int __user *)arg, &val, sizeof(val)) ? -EFAULT : 0;
case ENV_IOC_GET_ALARM:
val = env->alarm;
return copy_to_user((int __user *)arg, &val, sizeof(val)) ? -EFAULT : 0;
case ENV_IOC_CLR_ALARM:
env->alarm = false;
led_set(&env->leds[LED_RED], false);
return 0;
default:
return -ENOTTY;
}
}
static const struct file_operations env_monitor_fops = {
.owner = THIS_MODULE,
.open = env_monitor_open,
.read = env_monitor_read,
.unlocked_ioctl = env_monitor_ioctl,
};
static int env_monitor_probe(struct platform_device *pdev)
{
struct env_monitor_dev *env;
struct device_node *node = pdev->dev.of_node;
struct device_node *child;
int ret;
env = devm_kzalloc(&pdev->dev, sizeof(*env), GFP_KERNEL);
if (!env)
return -ENOMEM;
g_env_dev = env;
platform_set_drvdata(pdev, env);
child = of_get_child_by_name(node, "led-green");
if (child) {
env->leds[LED_GREEN].gpio = of_get_gpio(child, 0);
env->leds[LED_GREEN].label = "status-green";
}
child = of_get_child_by_name(node, "led-red");
if (child) {
env->leds[LED_RED].gpio = of_get_gpio(child, 0);
env->leds[LED_RED].label = "alarm-red";
}
ret = led_init(env->leds, LED_MAX);
if (ret) {
dev_err(&pdev->dev, "led init failed\n");
return ret;
}
led_set(&env->leds[LED_GREEN], true);
child = of_get_child_by_name(node, "key-mode");
if (child) {
env->keys[0].gpio = of_get_gpio(child, 0);
env->keys[0].irq = gpio_to_irq(env->keys[0].gpio);
env->keys[0].code = 1;
env->keys[0].label = "key-mode";
env->keys[0].callback = key_event_callback;
env->keys[0].priv = env;
}
child = of_get_child_by_name(node, "key-clear");
if (child) {
env->keys[1].gpio = of_get_gpio(child, 0);
env->keys[1].irq = gpio_to_irq(env->keys[1].gpio);
env->keys[1].code = 2;
env->keys[1].label = "key-clear";
env->keys[1].callback = key_event_callback;
env->keys[1].priv = env;
}
ret = key_init(env->keys, 2);
if (ret) {
dev_err(&pdev->dev, "key init failed\n");
return ret;
}
ret = alloc_chrdev_region(&env->devno, 0, 1, DEVICE_NAME);
if (ret) {
dev_err(&pdev->dev, "alloc chrdev region failed\n");
return ret;
}
cdev_init(&env->cdev, &env_monitor_fops);
env->cdev.owner = THIS_MODULE;
ret = cdev_add(&env->cdev, env->devno, 1);
if (ret) {
dev_err(&pdev->dev, "cdev add failed\n");
unregister_chrdev_region(env->devno, 1);
return ret;
}
env->cls = devm_class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(env->cls)) {
dev_err(&pdev->dev, "class create failed\n");
cdev_del(&env->cdev);
unregister_chrdev_region(env->devno, 1);
return PTR_ERR(env->cls);
}
devm_device_create(env->cls, &pdev->dev, env->devno, NULL, DEVICE_NAME);
dev_info(&pdev->dev, "env_monitor probe success\n");
return 0;
}
static int env_monitor_remove(struct platform_device *pdev)
{
struct env_monitor_dev *env = platform_get_drvdata(pdev);
cdev_del(&env->cdev);
unregister_chrdev_region(env->devno, 1);
led_deinit(env->leds, LED_MAX);
sht30_deinit(&env->sht30);
dev_info(&pdev->dev, "env_monitor removed\n");
return 0;
}
static int sht30_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
if (!g_env_dev)
return -EPROBE_DEFER;
return sht30_init(client, &g_env_dev->sht30);
}
static int sht30_i2c_remove(struct i2c_client *client)
{
if (g_env_dev)
sht30_deinit(&g_env_dev->sht30);
return 0;
}
static const struct of_device_id sht30_of_match[] = {
{ .compatible = "sensirion,sht30" },
{ }
};
static const struct of_device_id env_monitor_of_match[] = {
{ .compatible = "mycompany,env-monitor" },
{ }
};
static struct i2c_driver sht30_i2c_driver = {
.driver = { .name = "sht30", .of_match_table = sht30_of_match },
.probe = sht30_i2c_probe,
.remove = sht30_i2c_remove,
};
static struct platform_driver env_monitor_platform_driver = {
.driver = { .name = "env_monitor", .of_match_table = env_monitor_of_match },
.probe = env_monitor_probe,
.remove = env_monitor_remove,
};
static int __init env_monitor_init(void)
{
int ret;
ret = platform_driver_register(&env_monitor_platform_driver);
if (ret)
return ret;
ret = i2c_add_driver(&sht30_i2c_driver);
if (ret)
platform_driver_unregister(&env_monitor_platform_driver);
return ret;
}
static void __exit env_monitor_exit(void)
{
i2c_del_driver(&sht30_i2c_driver);
platform_driver_unregister(&env_monitor_platform_driver);
}
module_init(env_monitor_init);
module_exit(env_monitor_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dufu");
MODULE_DESCRIPTION("RK3568 Environment Monitor Driver");
MODULE_VERSION("1.0");
模块六:用户空间测试程序
文件:env_monitor_app.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <signal.h>
#define DEVICE_PATH "/dev/env_monitor"
#define ENV_IOC_MAGIC 'E'
#define ENV_IOC_GET_TEMP _IOR(ENV_IOC_MAGIC, 1, int)
#define ENV_IOC_GET_HUMI _IOR(ENV_IOC_MAGIC, 2, int)
#define ENV_IOC_GET_ALARM _IOR(ENV_IOC_MAGIC, 3, int)
#define ENV_IOC_CLR_ALARM _IO(ENV_IOC_MAGIC, 4)
static volatile int running = 1;
void sig_handler(int sig) { running = 0; }
int main(int argc, char *argv[])
{
int fd, temp, humi, alarm;
char buf[256];
signal(SIGINT, sig_handler);
fd = open(DEVICE_PATH, O_RDONLY);
if (fd < 0) { perror("open device failed"); return -1; }
printf("Environment Monitor started. Press Ctrl+C to exit.\n\n");
while (running) {
memset(buf, 0, sizeof(buf));
if (read(fd, buf, sizeof(buf)) > 0)
printf("=== Read Interface ===\n%s\n", buf);
if (ioctl(fd, ENV_IOC_GET_TEMP, &temp) == 0)
printf("[ioctl] Temperature: %d.%d C\n", temp / 10, abs(temp % 10));
if (ioctl(fd, ENV_IOC_GET_HUMI, &humi) == 0)
printf("[ioctl] Humidity: %d.%d %%\n", humi / 10, humi % 10);
if (ioctl(fd, ENV_IOC_GET_ALARM, &alarm) == 0)
printf("[ioctl] Alarm: %s\n", alarm ? "YES" : "NO");
if (alarm) {
printf("High threshold detected! Clearing alarm...\n");
ioctl(fd, ENV_IOC_CLR_ALARM);
}
printf("\n");
sleep(2);
}
close(fd);
printf("Monitor stopped.\n");
return 0;
}
Makefile
驱动 Makefile:
KERN_DIR := /path/to/rk356x_linux/kernel
PWD := $(shell pwd)
obj-m := env_monitor.o
env_monitor-objs := env_monitor_main.o sht30_i2c.o led_gpio.o key_irq.o
all:
$(MAKE) -C $(KERN_DIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERN_DIR) M=$(PWD) clean
应用程序 Makefile:
CC := aarch64-linux-gnu-gcc
all:
$(CC) -o env_monitor_app env_monitor_app.c -static
clean:
rm -f env_monitor_app
编译与运行步骤
# 1. 编译驱动
cd env_monitor/driver && make
# 2. 编译应用程序
cd ../app && make
# 3. 拷贝到开发板
scp env_monitor.ko env_monitor_app root@<板卡IP>:/data/local/tmp/
# 4. 加载驱动
insmod env_monitor.ko
# 5. 运行测试
./env_monitor_app
项目知识点覆盖与面试价值
| 模块 | 覆盖知识点 | 面试价值 |
|---|---|---|
| env_monitor_main.c | 字符设备框架、ioctl、platform驱动 | ★★★★★ |
| sht30_i2c.c | I2C协议、CRC校验、mutex保护 | ★★★★★ |
| led_gpio.c | GPIO子系统、devm资源管理 | ★★★☆☆ |
| key_irq.c | 中断处理、tasklet/workqueue | ★★★★☆ |
| 设备树 | DTS语法、属性读取、compatible匹配 | ★★★★★ |
简历项目描述模板
智能环境监测系统驱动 (RK3568)
- 基于 Linux 4.19 内核,开发完整的环境监测系统驱动
- 实现 I2C 温湿度传感器(SHT30)驱动,包含 CRC 校验机制
- 设计字符设备框架,提供 read/ioctl 接口供用户空间访问
- 实现按键中断驱动,使用 workqueue 处理中断下半部
- 使用设备树描述硬件资源,支持 platform_driver 自动匹配
- 技术栈:Linux 驱动、I2C子系统、GPIO子系统、中断处理、设备树
第三部分:面试冲刺指南
简历建议
- 突出项目经验:将实战项目放在显眼位置,描述清楚职责和成果
- 强调技术栈:Linux 内核版本、开发板型号、使用的子系统
- 量化成果:如"独立完成XX驱动开发"、“优化XX性能XX%”
面试准备清单
必背知识点:
- 用户态/内核态区别与数据交互
- 锁机制(spinlock、mutex、semaphore)
- 中断处理流程(上下半部)
- 设备树机制
- 平台驱动模型
- I2C/SPI 协议基础
必写代码:
- hello world 内核模块
- 字符设备驱动框架
- platform_driver 模板
- 中断注册与处理
常见面试题:
- 解释 Linux 内核模块的加载过程
- spinlock 和 mutex 的区别
- 为什么不能在中断上下文使用 mutex?
- 设备树的作用是什么?
- platform_driver 的 probe 何时被调用?
- copy_to_user 和 __copy_to_user 的区别?
- kmalloc 和 vmalloc 的区别?
- 中断上下半部的处理方式有哪些?
- I2C 协议的基本时序?
- cdev 注册的完整流程?
扩展学习(进阶)
完成基础项目后,可尝试以下扩展以提升竞争力:
- 定时器驱动:使用 hrtimer 实现定时采集
- proc/sysfs 接口:通过文件系统暴露设备状态
- PWM 驱动:控制蜂鸣器实现声音告警
- 电源管理:实现设备低功耗模式
- DMA 传输:优化大数据量传输性能
- 网络上报:通过 socket 上报数据到云端
更多推荐




所有评论(0)