目录
[1.1、芯片的bring up 主要做哪些工作:](#1.1、芯片的bring up 主要做哪些工作:)
[2.1.1 内核同步互斥的几种方式](#2.1.1 内核同步互斥的几种方式)
[2.1.2 互斥锁和自旋锁的区别](#2.1.2 互斥锁和自旋锁的区别)
[2.1.3 spin_lock 和 spin_lock_irqsave 的区别](#2.1.3 spin_lock 和 spin_lock_irqsave 的区别)
[2.1.4 进程上下文和中断上下文有什么区别](#2.1.4 进程上下文和中断上下文有什么区别)
[2.1.5 进行上下文用什么锁](#2.1.5 进行上下文用什么锁)
[2.1.6 中断上下文用什么锁](#2.1.6 中断上下文用什么锁)
[2.1.8 中断下半部的三种方式 以及有什么区别](#2.1.8 中断下半部的三种方式 以及有什么区别)
[2.1.9 tasklet 和工作队列能否休眠?运行在中断上下文还是进程上下文](#2.1.9 tasklet 和工作队列能否休眠?运行在中断上下文还是进程上下文)
[2.2.1 驱动分类](#2.2.1 驱动分类)
[2.2.2 驱动模块基本结构](#2.2.2 驱动模块基本结构)
[2.2.3 驱动的加载方式](#2.2.3 驱动的加载方式)
[2.2.4 字符驱动设备](#2.2.4 字符驱动设备)
[2.2.5 文件操作结构体](#2.2.5 文件操作结构体)
[2.2.6 常见面试问题](#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_DRIVERtristate "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 常见面试问题
- 字符设备驱动的主设备号和次设备号有什么作用
- 主设备号 标识设备驱动程序
- 此设备号 标识使用同一驱动的不同设备通过 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 实现设别驱动通过总线和模型绑定