五月份嵌入式面试总结
目录
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 实现设别驱动通过总线和模型绑定