五月份嵌入式面试总结
目录
1、札记
1.1、芯片的bring up 主要做哪些工作:
2、Linux驱动八股文
中断与同步互斥
2.1.1 内核同步互斥的几种方式
2.1.2 互斥锁和自旋锁的区别
2.1.3 spin_lock 和 spin_lock_irqsave 的区别
2.1.4 进程上下文和中断上下文有什么区别
2.1.5 进行上下文用什么锁
2.1.6 中断上下文用什么锁
2.1.8 中断下半部的三种方式 以及有什么区别
2.1.9 tasklet 和工作队列能否休眠?运行在中断上下文还是进程上下文
Linux驱动基础问题
2.2.1 驱动分类
2.2.2 驱动模块基本结构
2.2.3 驱动的加载方式
2.2.4 字符驱动设备
2.2.5 文件操作结构体
2.2.6 常见面试问题
1、札记
1.1、芯片的bring up 主要做哪些工作:
1、sdk 编译 烧录 启动 调试串口
2、屏幕驱动正常工作 demo正常启动
2、Linux驱动八股文
中断与同步互斥
2.1.1 内核同步互斥的几种方式
互斥锁、自旋锁、原子操作、禁止抢占、内存屏障
信号量、读写锁、顺序锁
2.1.2 互斥锁和自旋锁的区别
自旋锁:忙等、不可休眠、持有时间短、适合中断上下文
互斥锁:睡眠等,持有时间长
2.1.3 spin_lock 和 spin_lock_irqsave 的区别
区别在于中断开关,通常在中断上下文,需要 对寄存器进行操作,寄存器操作需要用 spin_lock_irqsave ,而 spin_lock 只是禁止内核抢占,适用于没有中断处理的场景,确保临界区资源不被中断程序访问
2.1.4 进程上下文和中断上下文有什么区别
进程上下文:用户态进程的执行环境,例如系统调用,内核线程,可休眠(允许调用可休眠函数,如果kmalloc msleep)
中断上下文: 硬中断、软中断触发的执行条件,不可休眠
2.1.5 进行上下文用什么锁
看进程能否休眠,可以休眠的话用互斥锁,比如系统调用,内核线程等场景都是可以休眠的
不可休眠:自旋锁,比如中断处理程序的上半部,持有自旋锁、原子操作的领域
2.1.6 中断上下文用什么锁
自旋锁
2.1.8 中断下半部的三种方式 以及有什么区别
软中断 tasklet 工作队列
tasklet 基于软中断,动态注册,而软中断是静态注册的
工作队列运行在进程上下文,可休眠 ;tasklet 和软中断是在中断上下文,不可休眠
2.1.9 tasklet 和工作队列能否休眠?运行在中断上下文还是进程上下文
tasklet : 中断上下文,禁止休眠
工作队列: 进程上下文,允许休眠
Linux驱动基础问题
2.2.1 驱动分类
- 字符设备驱动:按字节访问 如串口 按键
- 块设备驱动:按块访问 如硬盘 SD卡
- 网络设别驱动:网络接口设备
2.2.2 驱动模块基本结构
#include <linux/module.h> #include <linux/init.h>static int __init my_driver_init(void) {printk(KERN_INFO "Driver initialized\n");return 0; }static void __exit my_driver_exit(void) {printk(KERN_INFO "Driver exited\n"); }module_init(my_driver_init); module_exit(my_driver_exit);MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Sample Driver");
2.2.3 驱动的加载方式
- 静态加载: 编译进内核镜像
- 动态加载:编译为模块 (.ko)文件,使用 insmod/ modprobe 加载
- 对应模块的静态加载和动态加载可以通过menuconfig 界面进行选择
config EXAMPLE_DRIVER
tristate "Example Driver Support"
depends on NETDEVICES
help
This is an example driver for Linux.
tristate
是支持动态加载(<M>
)的关键字。通过
menuconfig
界面按Y/M/N
切换编译方式。依赖项(
depends on
)和默认值(default
)会影响最终行为。
2.2.4 字符驱动设备
// 分配设备号 dev_t dev; alloc_chrdev_region(&dev, 0, 1, "my_device");// 初始化cdev结构 struct cdev *my_cdev = cdev_alloc(); cdev_init(my_cdev, &fops); my_cdev->owner = THIS_MODULE;// 添加字符设备 cdev_add(my_cdev, dev, 1);// 创建设备节点 struct class *my_class = class_create(THIS_MODULE, "my_class"); device_create(my_class, NULL, dev, NULL, "my_device");
2.2.5 文件操作结构体
static struct file_operations my_fops = {.owner = THIS_MODULE,.open = my_open,.release = my_release,.read = my_read,.write = my_write,.unlocked_ioctl = my_ioctl, };
2.2.6 常见面试问题
1. 字符设备驱动的主设备号和次设备号有什么作用
- 主设备号 标识设备驱动程序
- 此设备号 标识使用同一驱动的不同设备通过 MAJOR() 和 MINOR ()宏获取
2.如何实现设备的并发访问控制
- 使用自旋锁、互斥锁等同步机制
3.copy_to_user 和 copy_from_user 的作用是什么
- 安全在内核空间和用户空间之间复制数据
2.3 中断处理
2.3.1 中断注册流程
// 注册中断处理函数 int ret = request_irq(irq_num, my_interrupt_handler, IRQF_SHARED, "my_device", dev_id);// 中断处理函数 static irqreturn_t my_interrupt_handler(int irq, void *dev_id) {// 处理中断// ...return IRQ_HANDLED; }// 释放中断 free_irq(irq_num, dev_id);
- 先 请求中断 -> 在写中断函数 -> 释放中断
2.3.2 中断注册流程
- 上半部 中断处理函数,快速响应
- 下半部 延迟处理 可调度
// 工作队列实现下半部 static struct work_struct my_work;static void my_work_handler(struct work_struct *work) {// 耗时操作 }static irqreturn_t my_interrupt_handler(int irq, void *dev_id) {// 快速处理schedule_work(&my_work);return IRQ_HANDLED; }// 初始化 INIT_WORK(&my_work, my_work_handler);
2.3.4 常见面试问题
1、Linux 中断下半部有哪几种机制
- 软中断 : 静态分配,优先级高
- tasklet : 基于软中断,动态创建
- 工作队列:在进程上下文中执行,可睡眠
2、中断上下文有什么限制
- 不能睡眠
- 不能使用可能睡眠的函数 (互斥锁)
- 尽量减少处理时间
3、如何处理共享中断
共享中断是指多个设备共享一个硬件中断线,当中断触发,内核需要调用所有注册到这个irq 的设备处理函数处,处理函数中回去 检查中断源 和 返回处理结果 、
2.4 设备树与平台驱动
2.4.1 设备树基础
/* 设备树节点示例 */ my_device: my_device@50000000 {compatible = "vendor,my-device";reg = <0x50000000 0x1000>;interrupts = <0 29 4>;clocks = <&clk 1>;status = "okay"; };
2.4.2 平台驱动模型
// 平台驱动结构体 static struct platform_driver my_platform_driver = {.probe = my_platform_probe,.remove = my_platform_remove,.driver = {.name = "my-device",.of_match_table = my_of_match,.pm = &my_pm_ops,}, };// 设备树匹配表 static const struct of_device_id my_of_match[] = {{ .compatible = "vendor,my-device" },{ /* sentinel */ } }; MODULE_DEVICE_TABLE(of, my_of_match);// 注册平台驱动 module_platform_driver(my_platform_driver);
2.4.3 常见面试问题
1.设备树的作用是什么
- 描述硬件设备,实现硬件与驱动分离支持运行适合
2、如何在驱动中获得设备树属性
- 通过设备树匹配节点(compatible)
- 提取常用属性(of函数)
#include <linux/of.h>
#include <linux/platform_device.h>static int my_probe(struct platform_device *pdev)
{struct device_node *node = pdev->dev.of_node;struct resource *res;void __iomem *regs;int irq, ret;u32 freq;/* 1. 获取寄存器地址(通过 reg 属性) */res = platform_get_resource(pdev, IORESOURCE_MEM, 0);regs = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(regs))return PTR_ERR(regs);/* 2. 获取中断号 */irq = platform_get_irq(pdev, 0);if (irq < 0)return irq;/* 3. 读取自定义整数属性 */ret = of_property_read_u32(node, "clock-frequency", &freq);if (ret) {dev_warn(&pdev->dev, "clock-frequency not specified, using default\n");freq = 25000000; // 默认值}/* 4. 检查布尔属性 */if (of_property_read_bool(node, "dma-capable")) {setup_dma();}/* 注册中断处理函数 */ret = devm_request_irq(&pdev->dev, irq, my_irq_handler, 0, "my-device", NULL);if (ret)return ret;dev_info(&pdev->dev, "Device probed, freq=%d Hz\n", freq);return 0;
}static const struct of_device_id my_device_ids[] = {{ .compatible = "vendor,my-device" },{ }
};
MODULE_DEVICE_TABLE(of, my_device_ids);static struct platform_driver my_driver = {.driver = {.name = "my-device",.of_match_table = my_device_ids,},.probe = my_probe,
};
module_platform_driver(my_driver);
3、platform_device 和 platform_driver 关系
- platform_device 描述设备资源
- platform_driver 实现设别驱动通过总线和模型绑定
2.5 同步与互斥
2.5.1 常用的同步机制
- 自旋锁(spin_lock): 忙等待,适合短时间持锁,中断可用
- 互斥锁(mutex): 可能睡眠,适合长时间持锁
- 读写锁(rwlock): 读共享写独占
// 自旋锁示例 spinlock_t my_lock; spin_lock_init(&my_lock);spin_lock(&my_lock); // 临界区 spin_unlock(&my_lock);// 互斥锁示例 struct mutex my_mutex; mutex_init(&my_mutex);mutex_lock(&my_mutex); // 临界区 mutex_unlock(&my_mutex);
2.5.2 常见的面试问题
1. 自旋锁和互斥锁的区别
- 自旋锁:忙等待,不释放 cpu 适合短时间持有
- 互斥锁:可能睡眠,释放 cpu ,适合长时间持有
- 自旋锁可以用于中断上下文,互斥锁不可以
2.死锁概念
多并发进程因争夺资源而产生的相互等待的现象
本质:(1、资源有限;2、进程推进不合理 )
3.死锁的四个条件
- 互斥:涉及的资源非共享
- 占有且等待:进程每次申请他所需要的一部分资源,在等待新资源的同时继续占用已分配到的资源
- 不可剥夺:进程所获得的资源在未使用完毕之前不会被其他进程抢走
- 循环等待:某一个进程已获得的资源会被下一个进程请求
4.死锁的处理方式:防止死锁,避免死锁,检测死锁,解除死锁
预防死锁:
- 资源一次性分配(破坏请求和保持条件)
- 可剥夺资源(当进程的资源满足是,释放已占有资源,破坏不可剥夺条件)
- 资源有序分配(给每个资源赋予一个标号,按照编号顺序请求资源)
- 当某个进程一个请求得不到满足时,则剥夺他的所有条件
避免死锁:
- 系统在进行资源分配时,先计算资源分配的安全性,若分配会导致系统进入不安全状态,则取消此次资源分配(银行家算法)
检测死锁:
- 为每个进程和资源分配一个唯一的号码,然后建立资源分配表和进程等待表
解除死锁:在检测到死锁后,可以采用以下两个方面解除死锁:
- 剥夺资源:从其他进程剥夺足够多的资源分配给死锁进程,以接触死锁状态
- 撤销进程:撤销死锁进程,直到有足够的资源可用
5、如何避免死锁
一般来说,有三种避免死锁的技术
- 加锁顺序:(线程按照一定的顺序加锁)
- 加锁时间限制:(超过时限就放弃该锁的请求,并释放自己占用的资源)
- 死锁检测
6、在单核mcu上写多线程程序是否要加锁,
依旧要加锁的,线程锁适用于实现线程的同步和通信的,多线程之间依旧是要线程同步的,不使用线程锁的话,会导致共享数据的修改引起的冲突。
2.6 gpio与设备 io
// 获取GPIO int gpio = of_get_named_gpio(node, "reset-gpio", 0); if (gpio_is_valid(gpio)) {gpio_request(gpio, "reset");// 设置方向gpio_direction_output(gpio, 1);// 设置值gpio_set_value(gpio, 0);msleep(10);gpio_set_value(gpio, 1);// 释放GPIOgpio_free(gpio); }
2.6.1 内存映射 io
// 映射IO内存 void __iomem *base; struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(base))return PTR_ERR(base);// 读写寄存器 writel(value, base + OFFSET); value = readl(base + OFFSET);
2.6.3 常见面试问题
1、如何处理Linux下的 gpio 中断
在Linux下 处理 gpio 中断通常涉及 内核驱动 或 用户空间轮询/事件监听 两种方式
- 确认 gpio 编号
# 查看 GPIO 编号(假设物理引脚为 GPIO17) echo 17 > /sys/class/gpio/export ls /sys/class/gpio/gpio17 # 确认 GPIO 已导出
- 配置 gpio 为输入并启用中断
# 设置为输入模式 echo "in" > /sys/class/gpio/gpio17/direction# 设置中断触发方式(可选:rising, falling, both, none) echo "rising" > /sys/class/gpio/gpio17/edge
- 在用户空间监听中断
#include <stdio.h> #include <fcntl.h> #include <poll.h>int main() {int fd = open("/sys/class/gpio/gpio17/value", O_RDONLY);struct pollfd pfd = { fd, POLLPRI | POLLERR, 0 };while (1) {int ret = poll(&pfd, 1, -1); // 阻塞等待中断if (ret > 0) {lseek(fd, 0, SEEK_SET); // 重置文件指针char buf[10];read(fd, buf, sizeof(buf));printf("Interrupt! Value: %s", buf);}}close(fd);return 0; }
2、readl、writel 与 ioread32、iowrite32的联系与区别
功能基本相同,都是用于32位 IO 访问 readl、writel
3、如何处理设备的字节序问题
使用 cpu_to_le32 \ le32_to_cpu 等转换函数明确区分大小端字节序使用位域
Linux 驱动框架系列
3.1 Linux 设备驱动模型
3.1.1 设备驱动模型基础
- 设备模型三要素:总线(bus)、设备(device)、驱动(driver)
- kobject:设备模型的基础对象,实现引用计数和 sysfs 导出
- 设备树:描述硬件信息的数据结构,减少硬编码
3.1.2 驱动匹配机制
// 平台驱动匹配示例
static const struct of_device_id my_of_match[] = {{ .compatible = "vendor,my-device" },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);static struct platform_driver my_platform_driver = {.probe = my_probe,.remove = my_remove,.driver = {.name = "my-device",.of_match_table = my_of_match,},
};
3.1.3 常见面试问题
1、Linux 设备驱动模型的核心组件有哪些
- kobject : 基础对象,提供引用技术和 sysfs 接口
- kset : kobject 的集合,管理相关对象
- device : 表示物理或者逻辑设备
- driver : 实现设备功能的代码
- bus : 连接设备和驱动的媒介
2、驱动和设备是如何匹配
- 基于总线的匹配机制设备树中的 compatible 属性与 驱动中的 of_match_table 匹配平台设备的 name 与 平台驱动的 name 匹配成功后调用 probe 函数
3、设备树在驱动开发中的作用是什么
- 描述硬件设备信息,减少硬编码实现硬件与驱动分离支持 运行时配置修改简化
3.2 GPIO 子系统
3.2.1 gpio子系统框架
- gpio 控制器 : 管理一组 gpio 引脚
- gpiochip :表示其中一个 gpio 控制器
- gpio_desc : 描述单个 gpio 引脚
- gpiolib : 提供同一的 gpio 操作接口
3.2.2 gpio 驱动实现
// GPIO控制器驱动示例
static const struct gpio_chip my_gpio_chip = {.label = "my-gpio",.owner = THIS_MODULE,.base = -1, // 动态分配.ngpio = 32, // 32个GPIO.request = my_gpio_request,.free = my_gpio_free,.direction_input = my_gpio_direction_input,.direction_output = my_gpio_direction_output,.get = my_gpio_get,.set = my_gpio_set,
};static int my_gpio_probe(struct platform_device *pdev)
{struct my_gpio_priv *priv;int ret;priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);if (!priv)return -ENOMEM;priv->base = devm_platform_ioremap_resource(pdev, 0);if (IS_ERR(priv->base))return PTR_ERR(priv->base);priv->chip = my_gpio_chip;priv->chip.parent = &pdev->dev;ret = devm_gpiochip_add_data(&pdev->dev, &priv->chip, priv);if (ret)return ret;platform_set_drvdata(pdev, priv);return 0;
}
3.2.3常见面试问题
1、如何在驱动中使用 gpio
在 Linux 内核驱动中使用 GPIO 主要涉及 申请 GPIO、配置方向(输入/输出)、读写 GPIO 值、处理中断 等操作
2、gpio中断是如何实现的
首先需要在设备树中定义
my_device {compatible = "my,gpio-device";interrupt-parent = <&gpio>;interrupts = <17 IRQ_TYPE_EDGE_RISING>; // GPIO17,上升沿触发
};
然后在驱动中注册中断处理函数
static irqreturn_t gpio_irq_handler(int irq, void *dev_id) {printk(KERN_INFO "GPIO Interrupt triggered!\n");return IRQ_HANDLED;
}// 在 probe 函数中注册中断
int irq = gpiod_to_irq(gpio); // 新版 API
// int irq = gpio_to_irq(gpio_num); // 旧版 APIint ret = request_irq(irq, gpio_irq_handler, IRQF_TRIGGER_RISING, "my_gpio_irq", NULL);
if (ret) {dev_err(&pdev->dev, "Failed to request IRQ\n");return ret;
}// 在 remove 函数中释放中断
free_irq(irq, NULL);
完整驱动
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>struct my_device_data {struct gpio_desc *gpio;int irq;
};static irqreturn_t my_gpio_irq(int irq, void *dev_id) {printk(KERN_INFO "Interrupt occurred!\n");return IRQ_HANDLED;
}static int my_probe(struct platform_device *pdev) {struct my_device_data *data;data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);if (!data)return -ENOMEM;// 获取 GPIOdata->gpio = gpiod_get(&pdev->dev, "my-gpios", GPIOD_IN);if (IS_ERR(data->gpio))return PTR_ERR(data->gpio);// 注册中断data->irq = gpiod_to_irq(data->gpio);if (request_irq(data->irq, my_gpio_irq, IRQF_TRIGGER_RISING, "my_gpio_irq", NULL)) {gpiod_put(data->gpio);return -EINVAL;}platform_set_drvdata(pdev, data);return 0;
}static int my_remove(struct platform_device *pdev) {struct my_device_data *data = platform_get_drvdata(pdev);free_irq(data->irq, NULL);gpiod_put(data->gpio);return 0;
}static const struct of_device_id my_of_match[] = {{ .compatible = "my,gpio-device" },{},
};
MODULE_DEVICE_TABLE(of, my_of_match);static struct platform_driver my_driver = {.driver = {.name = "my_gpio_driver",.of_match_table = my_of_match,},.probe = my_probe,.remove = my_remove,
};
module_platform_driver(my_driver);MODULE_LICENSE("GPL");
3.3 Pinctrl 子系统