Linux 驱动与应用开发核心自测题库(面试官问答完整版)
本题库基于你提供的全部文档(驱动开发、I2C/SPI、设备树、Platform、Qt、JsonRPC、GCC、Makefile、Framebuffer、汉字编码、网络编程等)提炼,覆盖驱动开发、应用编程、系统构建、前后台分离四大领域。每题标注难度⭐~⭐⭐⭐,部分题目含代码填空或排错。建议先口头回答,再对照解析。
文章目录
- [Linux 驱动与应用开发核心自测题库(面试官问答完整版)](#Linux 驱动与应用开发核心自测题库(面试官问答完整版))
-
- 第一部分:字符设备驱动与文件IO(基础但高频)
-
- [Q1:`register_chrdev` 和 `cdev` 接口的区别?为什么推荐 `cdev`?](#Q1:
register_chrdev和cdev接口的区别?为什么推荐cdev?) - [Q2:`copy_to_user` 和 `copy_from_user` 能直接用 `memcpy` 替换吗?为什么?](#Q2:
copy_to_user和copy_from_user能直接用memcpy替换吗?为什么?) - [Q3:系统调用 `open`/`read`/`write` 与标准 C 库 `fopen`/`fread`/`fwrite` 的根本区别?什么时候必须用系统调用?](#Q3:系统调用
open/read/write与标准 C 库fopen/fread/fwrite的根本区别?什么时候必须用系统调用?) - [Q4:文件描述符(fd)在内核中是如何与 `struct file` 关联的?](#Q4:文件描述符(fd)在内核中是如何与
struct file关联的?) - [Q5:写出 `open` 一个普通文件并读取 64 字节的标准代码片段,要求包含错误处理。](#Q5:写出
open一个普通文件并读取 64 字节的标准代码片段,要求包含错误处理。)
- [Q1:`register_chrdev` 和 `cdev` 接口的区别?为什么推荐 `cdev`?](#Q1:
- [第二部分:GPIO 子系统与中断(高频考点)](#第二部分:GPIO 子系统与中断(高频考点))
-
- [Q6:如何查看一个 GPIO 在内核中的编号(以 `GPIO4_19` 为例)?](#Q6:如何查看一个 GPIO 在内核中的编号(以
GPIO4_19为例)?) - [Q7:`request_irq` 的参数 `dev_id` 有什么作用?为什么 `free_irq` 需要相同的指针?](#Q7:
request_irq的参数dev_id有什么作用?为什么free_irq需要相同的指针?) - [Q8:中断处理函数(ISR)中能调用 `copy_to_user` 吗?为什么?](#Q8:中断处理函数(ISR)中能调用
copy_to_user吗?为什么?) - Q9:按键驱动中防抖的原理是什么?用定时器如何实现?
- [Q6:如何查看一个 GPIO 在内核中的编号(以 `GPIO4_19` 为例)?](#Q6:如何查看一个 GPIO 在内核中的编号(以
- 第三部分:等待队列、阻塞/非阻塞、Poll、异步通知
-
- [Q10:`wait_event_interruptible` 和 `wake_up_interruptible` 配合原理是什么?](#Q10:
wait_event_interruptible和wake_up_interruptible配合原理是什么?) - [Q11:驱动中如何支持非阻塞 I/O(`O_NONBLOCK`)?](#Q11:驱动中如何支持非阻塞 I/O(
O_NONBLOCK)?) - [Q12:驱动中的 `.poll` 函数做了哪两件关键事情?`poll_wait` 会让进程立即睡眠吗?](#Q12:驱动中的
.poll函数做了哪两件关键事情?poll_wait会让进程立即睡眠吗?) - [Q13:实现异步通知(`SIGIO`)驱动需要提供哪个 file_operation?ISR 中调用哪个函数发送信号?](#Q13:实现异步通知(
SIGIO)驱动需要提供哪个 file_operation?ISR 中调用哪个函数发送信号?)
- [Q10:`wait_event_interruptible` 和 `wake_up_interruptible` 配合原理是什么?](#Q10:
- [第四部分:设备树与 Platform 驱动(企业级开发核心)](#第四部分:设备树与 Platform 驱动(企业级开发核心))
-
- [Q14:设备树中 `compatible` 属性的作用是什么?驱动如何匹配?](#Q14:设备树中
compatible属性的作用是什么?驱动如何匹配?) - [Q15:`probe` 函数一般需要完成哪些初始化工作?](#Q15:
probe函数一般需要完成哪些初始化工作?) - [Q16:`platform_driver` 的匹配机制有哪几种?优先级顺序如何?](#Q16:
platform_driver的匹配机制有哪几种?优先级顺序如何?) - [Q17:在设备树中描述一个 GPIO 引脚的标准格式是什么?驱动中如何获取?](#Q17:在设备树中描述一个 GPIO 引脚的标准格式是什么?驱动中如何获取?)
- [Q14:设备树中 `compatible` 属性的作用是什么?驱动如何匹配?](#Q14:设备树中
- [第五部分:I2C 驱动与用户空间编程](#第五部分:I2C 驱动与用户空间编程)
-
- [Q18:I2C 驱动的 `i2c_drv_read` 为什么需要两条 `i2c_msg`?一条读消息行不行?](#Q18:I2C 驱动的
i2c_drv_read为什么需要两条i2c_msg?一条读消息行不行?) - [Q19:AT24C02 的页写限制是多少?驱动中如何实现分页写入?](#Q19:AT24C02 的页写限制是多少?驱动中如何实现分页写入?)
- [Q20:如何在不编写内核驱动的情况下,直接在用户空间访问 I2C 设备(如 AT24C02)?](#Q20:如何在不编写内核驱动的情况下,直接在用户空间访问 I2C 设备(如 AT24C02)?)
- [Q21:解释 `i2ctransfer -y 0 w1@0x50 0x10 r8` 这条命令的含义。](#Q21:解释
i2ctransfer -y 0 w1@0x50 0x10 r8这条命令的含义。)
- [Q18:I2C 驱动的 `i2c_drv_read` 为什么需要两条 `i2c_msg`?一条读消息行不行?](#Q18:I2C 驱动的
- [第六部分:SPI 驱动](#第六部分:SPI 驱动)
-
- [Q22:SPI 的四种模式由哪两个参数决定?设备树中如何指定模式 1?](#Q22:SPI 的四种模式由哪两个参数决定?设备树中如何指定模式 1?)
- [Q23:`spi_transfer` 和 `spi_message` 的关系是什么?为什么需要 `spi_message`?](#Q23:
spi_transfer和spi_message的关系是什么?为什么需要spi_message?) - [Q24:SPI 驱动中,CS(片选)信号需要驱动手动控制吗?](#Q24:SPI 驱动中,CS(片选)信号需要驱动手动控制吗?)
- 第七部分:并发控制与竞态条件
-
- Q25:自旋锁(spinlock)和互斥锁(mutex)的区别?分别在什么场景使用?
- [Q26:DS18B20 驱动中为什么要用 `spin_lock_irqsave` 而不是普通 `spin_lock`?](#Q26:DS18B20 驱动中为什么要用
spin_lock_irqsave而不是普通spin_lock?) - Q27:中断处理函数中能使用互斥锁吗?为什么?
- [第八部分:Linux 应用编程基础(GCC、Makefile、文件 IO)](#第八部分:Linux 应用编程基础(GCC、Makefile、文件 IO))
-
- [Q28:GCC 编译一个 C 程序需要哪四个步骤?分别用什么选项?](#Q28:GCC 编译一个 C 程序需要哪四个步骤?分别用什么选项?)
- Q29:如何制作一个动态库(`.so`)?使用时如何指定路径?
- [Q30:Makefile 中 `@\`、\`<`、`\^\` 分别代表什么?写出一个模式规则 \`%.o: %.c\` 的典型命令。](#Q30:Makefile 中 `@`、`<`、`^` 分别代表什么?写出一个模式规则 `%.o: %.c` 的典型命令。)
- [Q31:如何在 Makefile 中自动生成头文件依赖关系?(GCC 选项)](#Q31:如何在 Makefile 中自动生成头文件依赖关系?(GCC 选项))
- [第九部分:Framebuffer 与 LCD 显示](#第九部分:Framebuffer 与 LCD 显示)
-
- [Q32:Framebuffer 是什么?应用程序如何将像素写入 LCD?](#Q32:Framebuffer 是什么?应用程序如何将像素写入 LCD?)
- [Q33:已知 LCD 分辨率为 800×480,bpp=32,计算一行占用的字节数和屏幕总字节数。](#Q33:已知 LCD 分辨率为 800×480,bpp=32,计算一行占用的字节数和屏幕总字节数。)
- [Q34:在 16bpp(RGB565)的 Framebuffer 上如何将一个 RGB888 颜色(0xRRGGBB)转换为 RGB565?](#Q34:在 16bpp(RGB565)的 Framebuffer 上如何将一个 RGB888 颜色(0xRRGGBB)转换为 RGB565?)
- 第十部分:字符编码与汉字显示
-
- [Q35:ASCII、GB2312、UNICODE、UTF-8 之间的关系是什么?](#Q35:ASCII、GB2312、UNICODE、UTF-8 之间的关系是什么?)
- [Q36:如何在 C 程序中将一个 UTF-8 编码的字符串"中"转换为 GB2312 编码以便用 HZK16 字库显示?](#Q36:如何在 C 程序中将一个 UTF-8 编码的字符串“中”转换为 GB2312 编码以便用 HZK16 字库显示?)
- [Q37:HZK16 字库中一个汉字的点阵占多少字节?如何根据 GB2312 编码计算在文件中的偏移?](#Q37:HZK16 字库中一个汉字的点阵占多少字节?如何根据 GB2312 编码计算在文件中的偏移?)
- [第十一部分:Qt 开发与交叉编译](#第十一部分:Qt 开发与交叉编译)
-
- [Q38:在 Ubuntu 中为 ARM 开发板交叉编译 Qt 程序需要配置哪些工具链组件?](#Q38:在 Ubuntu 中为 ARM 开发板交叉编译 Qt 程序需要配置哪些工具链组件?)
- [Q39:在开发板上运行 Qt 程序需要设置哪些环境变量?各有什么作用?](#Q39:在开发板上运行 Qt 程序需要设置哪些环境变量?各有什么作用?)
- [Q40:Qt 程序如何通过 RPC 调用后台服务的函数?简述步骤。](#Q40:Qt 程序如何通过 RPC 调用后台服务的函数?简述步骤。)
- [第十二部分:前后台分离与 JsonRPC](#第十二部分:前后台分离与 JsonRPC)
-
- [Q41:前后台分离(前台 Qt + 后台 C 服务)有什么好处?](#Q41:前后台分离(前台 Qt + 后台 C 服务)有什么好处?)
- [Q42:TCP 和 UDP 有什么区别?JsonRPC 为什么选择 TCP?](#Q42:TCP 和 UDP 有什么区别?JsonRPC 为什么选择 TCP?)
- [Q43:服务器端使用 `socket` → `bind` → `listen` → `accept` 分别做什么?客户端使用 `socket` → `connect` 做什么?](#Q43:服务器端使用
socket→bind→listen→accept分别做什么?客户端使用socket→connect做什么?) - [Q44:JSON-RPC 请求的典型格式是什么?给出一个调用 `add` 方法并传参 [3,5] 的例子。](#Q44:JSON-RPC 请求的典型格式是什么?给出一个调用
add方法并传参 [3,5] 的例子。)
- [第十三部分:硬件操作(LED、DHT11)文件 IO](#第十三部分:硬件操作(LED、DHT11)文件 IO)
-
- [Q45:如何通过 sysfs 控制一个 GPIO 输出高低电平?写出命令序列。](#Q45:如何通过 sysfs 控制一个 GPIO 输出高低电平?写出命令序列。)
- [Q46:DHT11 驱动在应用层提供了什么接口?如何读取温湿度?](#Q46:DHT11 驱动在应用层提供了什么接口?如何读取温湿度?)
- [Q47:在 Qt 中读取 DHT11 数据时为什么要用单独的线程?如果直接在 UI 线程中 `read` 会有什么问题?](#Q47:在 Qt 中读取 DHT11 数据时为什么要用单独的线程?如果直接在 UI 线程中
read会有什么问题?)
- 第十四部分:调试与常见错误排查
-
- [Q48:`insmod` 报 `File exists` 错误,可能原因及排查步骤?](#Q48:
insmod报File exists错误,可能原因及排查步骤?) - [Q49:`request_irq` 失败,内核打印 `tried to flag a GPIO set as output for IRQ`,原因及修复?](#Q49:
request_irq失败,内核打印tried to flag a GPIO set as output for IRQ,原因及修复?) - [Q50:交叉编译时报 `gnu/stubs-soft.h: No such file or directory` 怎么解决?](#Q50:交叉编译时报
gnu/stubs-soft.h: No such file or directory怎么解决?)
- [Q48:`insmod` 报 `File exists` 错误,可能原因及排查步骤?](#Q48:
- 第十五部分:综合与扩展(考察融会贯通)
-
- [Q51:设计一个同时支持 LED 控制和 DHT11 读取的 RPC 后台服务,需要包含哪些注册的 RPC 方法?给出方法名和参数。](#Q51:设计一个同时支持 LED 控制和 DHT11 读取的 RPC 后台服务,需要包含哪些注册的 RPC 方法?给出方法名和参数。)
- [Q52:如果要把开发板的 Qt 界面通过 VNC 远程显示到 PC 上,应该修改哪个环境变量?](#Q52:如果要把开发板的 Qt 界面通过 VNC 远程显示到 PC 上,应该修改哪个环境变量?)
- [Q53:写一个简单的 Makefile,要求:编译 `main.c` 和 `sub.c`,生成 `test`,并自动处理头文件依赖。](#Q53:写一个简单的 Makefile,要求:编译
main.c和sub.c,生成test,并自动处理头文件依赖。) - [Q54:如何查看一个可执行文件是 x86 架构还是 ARM 架构?](#Q54:如何查看一个可执行文件是 x86 架构还是 ARM 架构?)
- [Q55:在 Framebuffer 中画一个红色矩形,请写出核心代码片段。](#Q55:在 Framebuffer 中画一个红色矩形,请写出核心代码片段。)
- Q56:解释什么是"野指针"和"内存泄漏"?在驱动开发中如何避免?
- 第十六部分:终极综合题(领导最爱问)
-
- [Q57:假如你要从零开始为开发板添加一个 I2C 温湿度传感器(如 SI7021),请描述完整流程,包括:设备树修改、驱动编写或用已有框架、应用层读取方式。](#Q57:假如你要从零开始为开发板添加一个 I2C 温湿度传感器(如 SI7021),请描述完整流程,包括:设备树修改、驱动编写或用已有框架、应用层读取方式。)
- Q58:领导要求你优化一个按键驱动,原驱动在快速连续按下时偶尔上报两次相同的键值,请分析可能原因并给出解决方案。
- [Q59:产品上电后 LCD 显示正常,但 Qt 程序启动后屏幕黑屏,可能的原因有哪些?](#Q59:产品上电后 LCD 显示正常,但 Qt 程序启动后屏幕黑屏,可能的原因有哪些?)
- [Q60:你在开发板上调试一个程序,发现 `printf` 不输出任何信息,但 `write(1, "xxx", 3)` 可以输出,为什么?如何解决?](#Q60:你在开发板上调试一个程序,发现
printf不输出任何信息,但write(1, "xxx", 3)可以输出,为什么?如何解决?)
第一部分:字符设备驱动与文件IO(基础但高频)
Q1:register_chrdev 和 cdev 接口的区别?为什么推荐 cdev?
难度:⭐⭐
答案
register_chrdev:一次性分配主设备号并注册 0~255 个次设备号,浪费资源,不支持动态管理多个设备。cdev接口:分步alloc_chrdev_region→cdev_init→cdev_add,按需申请次设备号,一个驱动可管理多个设备,更灵活且符合现代内核设计。- 推荐原因:资源利用率高、设备管理清晰、与设备模型结合紧密。
Q2:copy_to_user 和 copy_from_user 能直接用 memcpy 替换吗?为什么?
难度:⭐⭐
答案
不能 。用户空间地址可能非法、未映射或触发缺页异常,直接访问会导致内核崩溃。copy_to/from_user 会检查地址合法性并处理缺页,确保安全。
Q3:系统调用 open/read/write 与标准 C 库 fopen/fread/fwrite 的根本区别?什么时候必须用系统调用?
难度:⭐⭐
答案
- 系统调用 IO:无用户态缓冲区,每次操作直接陷入内核 → 适合设备文件(如 GPIO、I2C、LCD)。
- 标准 IO:带有用户态缓冲区,减少系统调用次数 → 适合普通文件读写,效率高。
- 必须用系统调用的场景:操作硬件设备(串口、Framebuffer、I2C 设备节点)以及对实时性要求极高的场合。
Q4:文件描述符(fd)在内核中是如何与 struct file 关联的?
难度:⭐⭐
答案
每个进程的 task_struct 中有 files_struct,其中包含 fdtable,本质是一个 struct file* 数组。调用 open 时内核分配一个空闲 fd,创建 struct file 对象(包含 f_op、f_pos、private_data 等),并将指针存入数组对应下标。后续 read/write 通过 fd 直接索引到 struct file,调用其 f_op->read/write。
Q5:写出 open 一个普通文件并读取 64 字节的标准代码片段,要求包含错误处理。
难度:⭐
答案
c
int fd = open("/tmp/test.txt", O_RDONLY);
if (fd < 0) { perror("open"); return -1; }
char buf[64];
ssize_t n = read(fd, buf, sizeof(buf));
if (n < 0) { perror("read"); close(fd); return -1; }
// 处理数据...
close(fd);
第二部分:GPIO 子系统与中断(高频考点)
Q6:如何查看一个 GPIO 在内核中的编号(以 GPIO4_19 为例)?
难度:⭐
答案
- 方法1:使用 debugfs
cat /sys/kernel/debug/gpio查看所有引脚映射。 - 方法2:计算
(bank-1)*32 + pin(i.MX6ULL 每 bank 32 引脚)→(4-1)*32+19 = 115。 - 方法3:查看
/sys/class/gpio/gpiochip*/label确定每个 controller 的 base 编号再叠加偏移。
Q7:request_irq 的参数 dev_id 有什么作用?为什么 free_irq 需要相同的指针?
难度:⭐⭐
答案
dev_id在共享中断(IRQF_SHARED)时用于区分不同设备;同时也是中断处理函数获取私有数据的入口。free_irq需要根据(irq, dev_id)唯一标识一个中断处理函数,传入不同指针无法定位到要释放的 handler。
Q8:中断处理函数(ISR)中能调用 copy_to_user 吗?为什么?
难度:⭐⭐
答案
绝对不能 。copy_to_user 可能引起缺页异常导致睡眠,而 ISR 运行在中断上下文(不属于任何进程),睡眠会引发内核崩溃。正确做法:ISR 只保存数据、唤醒等待队列,数据拷贝放在进程上下文(如 read 函数)中。
Q9:按键驱动中防抖的原理是什么?用定时器如何实现?
难度:⭐⭐
答案
机械按键按下时会产生多次跳动,若每次中断都上报会重复。
实现:ISR 中调用 mod_timer(&timer, jiffies + msecs_to_jiffies(20)) 不断推迟定时器。仅在最后一次中断后 20ms 无新中断时,定时器回调才读取 GPIO 稳定电平并上报。从而一次按下只上报一次。
第三部分:等待队列、阻塞/非阻塞、Poll、异步通知
Q10:wait_event_interruptible 和 wake_up_interruptible 配合原理是什么?
难度:⭐⭐
答案
wait_event_interruptible(wq, cond):若cond为假,将当前进程加入wq队列,状态设为TASK_INTERRUPTIBLE,调用schedule()让出 CPU。- 中断或其他上下文调用
wake_up_interruptible(&wq)将队列中进程状态改为TASK_RUNNING,等待调度。 - 被唤醒后重新检查
cond,为真则继续执行,否则可能再次睡眠。这防止了虚假唤醒。
Q11:驱动中如何支持非阻塞 I/O(O_NONBLOCK)?
难度:⭐⭐
答案
在 read 函数中检查 file->f_flags & O_NONBLOCK:
c
if (is_empty() && (filp->f_flags & O_NONBLOCK))
return -EAGAIN;
wait_event_interruptible(wq, !is_empty());
Q12:驱动中的 .poll 函数做了哪两件关键事情?poll_wait 会让进程立即睡眠吗?
难度:⭐⭐⭐
答案
- 调用
poll_wait将当前进程注册到等待队列(不睡眠,仅建立唤醒路径)。 - 检查数据是否可用,返回
POLLIN或0。
真正的睡眠由内核do_poll循环调用schedule_timeout完成。poll函数可以被反复调用,直到数据就绪或超时。
Q13:实现异步通知(SIGIO)驱动需要提供哪个 file_operation?ISR 中调用哪个函数发送信号?
难度:⭐⭐
答案
- 驱动需实现
.fasync函数,内部调用fasync_helper管理struct fasync_struct *。 - ISR 中调用
kill_fasync(&fasync_struct, SIGIO, POLL_IN)向注册过的进程发信号。
第四部分:设备树与 Platform 驱动(企业级开发核心)
Q14:设备树中 compatible 属性的作用是什么?驱动如何匹配?
难度:⭐⭐
答案
compatible = "厂商,型号"是设备与驱动的"身份证"。- 驱动中定义
of_match_table,内核在注册 platform_driver 时会遍历所有设备树节点,比较compatible字符串,匹配则调用probe。
Q15:probe 函数一般需要完成哪些初始化工作?
难度:⭐⭐
答案
- 从设备树或 platform 资源获取硬件信息(GPIO、中断号、地址)。
- 申请 GPIO、注册中断、使能时钟。
- 注册字符设备(
alloc_chrdev_region+cdev_add)。 - 创建类与设备节点(
class_create+device_create)。 - 保存私有数据用
platform_set_drvdata。
Q16:platform_driver 的匹配机制有哪几种?优先级顺序如何?
难度:⭐⭐⭐
答案
按 platform_match 尝试顺序(源码 drivers/base/platform.c):
driver_override强制覆盖。- 设备树匹配(
.of_match_table)。 - ACPI 匹配。
id_table匹配。- 名字直接匹配(
pdev->name == drv->driver.name)。
Q17:在设备树中描述一个 GPIO 引脚的标准格式是什么?驱动中如何获取?
难度:⭐
答案
dts
gpios = <&gpio4 19 GPIO_ACTIVE_LOW>;
驱动中通过 of_get_gpio(np, index) 或 gpiod_get 获取引脚编号,再用 gpio_to_irq 获得中断号。
第五部分:I2C 驱动与用户空间编程
Q18:I2C 驱动的 i2c_drv_read 为什么需要两条 i2c_msg?一条读消息行不行?
难度:⭐⭐
答案
不行。EEPROM/传感器需要先发送设备地址+写方向,并指定内部寄存器/存储地址,然后 Repeat Start 切换为读方向才能读取数据。一条读消息无法告知从设备起始地址。两条消息通过 i2c_transfer 一次性提交,硬件自动插入 Repeat Start 保证原子性。
Q19:AT24C02 的页写限制是多少?驱动中如何实现分页写入?
难度:⭐⭐
答案
每页 8 字节。驱动中循环写入,每次最大 8 字节,写完等待 EEPROM 内部编程时间(mdelay(20)),并更新写入地址。注意跨页时需手动分页,因为芯片会自动从页首回卷。
Q20:如何在不编写内核驱动的情况下,直接在用户空间访问 I2C 设备(如 AT24C02)?
难度:⭐
答案
使用内核提供的 i2c-dev 驱动,打开 /dev/i2c-N,通过 ioctl(fd, I2C_SLAVE, addr) 设置从设备地址,然后使用 read/write 或 ioctl(fd, I2C_RDWR, ...) 进行数据传输。命令行工具 i2cget、i2cset、i2ctransfer 也是基于此机制。
Q21:解释 i2ctransfer -y 0 w1@0x50 0x10 r8 这条命令的含义。
难度:⭐⭐
答案
- 总线 0,设备地址 0x50。
w1:写 1 个字节(0x10),即发送内存地址。r8:接着读 8 个字节。
整体实现随机读(Random Read)时序,相当于两条 I2C 消息(写地址 + 读数据)。
第六部分:SPI 驱动
Q22:SPI 的四种模式由哪两个参数决定?设备树中如何指定模式 1?
难度:⭐⭐
答案
由 CPOL (时钟极性)和 CPHA (时钟相位)决定。
模式 1:CPOL=0(空闲低),CPHA=1(第二个边沿采样)。设备树中通过 spi-cpha; 属性指定(不加 spi-cpol 则 CPOL=0)。
Q23:spi_transfer 和 spi_message 的关系是什么?为什么需要 spi_message?
难度:⭐⭐
答案
spi_transfer描述一次传输(tx_buf、rx_buf、len)。spi_message是多个spi_transfer的链表,保证它们被原子执行(CS 在整个消息期间保持有效)。- 需要
spi_message是因为有些 SPI 协议要求连续收发多段数据(如先发命令再读数据)且期间不能释放 CS。
Q24:SPI 驱动中,CS(片选)信号需要驱动手动控制吗?
难度:⭐
答案
不需要。Linux SPI 核心层和控制器驱动会自动管理 CS:传输开始时拉低,结束时拉高。驱动只需调用 spi_sync_transfer 即可。
第七部分:并发控制与竞态条件
Q25:自旋锁(spinlock)和互斥锁(mutex)的区别?分别在什么场景使用?
难度:⭐⭐⭐
答案
| 特性 | 自旋锁 | 互斥锁 |
|---|---|---|
| 获取失败行为 | 忙等待(占 CPU) | 睡眠,让出 CPU |
| 可用上下文 | 中断、软中断、进程上下文 | 仅进程上下文(可能睡眠) |
| 持锁时间 | 极短(微秒级) | 可长(毫秒级) |
| 典型场景 | 保护硬件寄存器、中断共享数据 | 保护普通共享资源、文件操作 |
原则:持锁时间短 + 可能在中断中使用 → 自旋锁;持锁时间长 + 只在进程上下文 → 互斥锁。
Q26:DS18B20 驱动中为什么要用 spin_lock_irqsave 而不是普通 spin_lock?
难度:⭐⭐⭐
答案
DS18B20 单总线时序要求微秒级精度,必须关闭当前 CPU 的中断以防止被抢占。spin_lock_irqsave 会保存中断状态并关中断,释放时恢复,确保临界区不被本 CPU 的中断打断。普通 spin_lock 只防止其他 CPU,不会关中断,不符合要求。
Q27:中断处理函数中能使用互斥锁吗?为什么?
难度:⭐⭐
答案
绝对不能。互斥锁在获取失败时会导致进程睡眠,而中断上下文不属于任何进程,无法被调度,睡眠直接导致内核 panic。应使用自旋锁或将需要睡眠的工作放到工作队列(workqueue)中。
第八部分:Linux 应用编程基础(GCC、Makefile、文件 IO)
Q28:GCC 编译一个 C 程序需要哪四个步骤?分别用什么选项?
难度:⭐
答案
- 预处理
-E:展开宏、包含头文件 →.i文件。 - 编译
-S:生成汇编代码 →.s文件。 - 汇编
-c:生成目标文件 →.o文件。 - 链接
-o:生成可执行文件。
一条命令gcc -o hello hello.c串起所有步骤。
Q29:如何制作一个动态库(.so)?使用时如何指定路径?
难度:⭐⭐
答案
制作:
bash
gcc -c -fPIC -o sub.o sub.c
gcc -shared -o libsub.so sub.o
编译时 :gcc main.o -L. -lsub -o test
运行时 :设置 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. 或把 .so 放到 /lib。
Q30:Makefile 中 $@、$<、$^ 分别代表什么?写出一个模式规则 %.o: %.c 的典型命令。
难度:⭐
答案
$@:目标文件名。$<:第一个依赖文件名。$^:所有依赖文件名(去重)。
模式规则:
makefile
%.o: %.c
$(CC) -c -o $@ $<
Q31:如何在 Makefile 中自动生成头文件依赖关系?(GCC 选项)
难度:⭐⭐
答案
使用 -MD 或 -Wp,-MD,.$@.d 选项,编译 .c 时生成一个 .d 文件,里面记录了该 .o 所依赖的头文件。然后在 Makefile 中用 include 包含这些 .d 文件,Make 就会自动跟踪头文件变更。
第九部分:Framebuffer 与 LCD 显示
Q32:Framebuffer 是什么?应用程序如何将像素写入 LCD?
难度:⭐
答案
Framebuffer 是内核分配的一块内存区域,存放屏幕上每个像素的颜色值。LCD 控制器自动读取该内存并刷新屏幕。
应用程序步骤:
open("/dev/fb0")ioctl(fd, FBIOGET_VSCREENINFO, &var)获取分辨率、bpp。mmap(NULL, screen_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)映射到用户空间。- 通过计算地址
base + y*line_width + x*pixel_width写像素。 munmap+close。
Q33:已知 LCD 分辨率为 800×480,bpp=32,计算一行占用的字节数和屏幕总字节数。
难度:⭐
答案
line_width = xres * (bpp/8) = 800 * 4 = 3200 字节。screen_size = xres * yres * (bpp/8) = 800*480*4 = 1,536,000 字节 ≈ 1.46 MB。
Q34:在 16bpp(RGB565)的 Framebuffer 上如何将一个 RGB888 颜色(0xRRGGBB)转换为 RGB565?
难度:⭐⭐
答案
c
unsigned int r = (color >> 16) & 0xff;
unsigned int g = (color >> 8) & 0xff;
unsigned int b = color & 0xff;
unsigned short rgb565 = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
如果驱动使用了不同的位域布局(如 BGR565),应通过 var.red.offset 等动态组装。
第十部分:字符编码与汉字显示
Q35:ASCII、GB2312、UNICODE、UTF-8 之间的关系是什么?
难度:⭐⭐
答案
- ASCII:7 位编码,用于英文。
- GB2312:中文国家标准,用两个字节表示汉字(兼容 ASCII)。
- UNICODE:统一码点,给每个字符分配唯一数字(如
'中'=0x4E2D)。 - UTF-8:UNICODE 的一种变长编码实现,兼容 ASCII,1~4 字节表示一个字符,是 Linux 下最常用的编码。
Q36:如何在 C 程序中将一个 UTF-8 编码的字符串"中"转换为 GB2312 编码以便用 HZK16 字库显示?
难度:⭐⭐
答案
- 编译时转换:
gcc -finput-charset=UTF-8 -fexec-charset=GB2312 -o prog prog.c,字符串常量自动转为 GB2312。 - 运行时转换:使用
iconv库函数(iconv_open("GB2312","UTF-8"))。 - 手动硬编码:直接写十六进制
unsigned char ch[] = {0xD6, 0xD0, 0x00};表示"中"的 GB2312 编码。
Q37:HZK16 字库中一个汉字的点阵占多少字节?如何根据 GB2312 编码计算在文件中的偏移?
难度:⭐⭐
答案
每个汉字点阵为 16×16 像素,占用 16*16/8 = 32 字节。
GB2312 编码两个字节 区码 和 位码 (均 0xA1~0xFE)。
偏移 = ((区码 - 0xA1) * 94 + (位码 - 0xA1)) * 32。
例如"中"的 GB2312 是 0xD6 0xD0,区=0xD6-0xA1=53,位=0xD0-0xA1=47,偏移 = (53*94+47)*32。
第十一部分:Qt 开发与交叉编译
Q38:在 Ubuntu 中为 ARM 开发板交叉编译 Qt 程序需要配置哪些工具链组件?
难度:⭐⭐
答案
需要在 Qt Creator 的 Kits 中配置:
- 编译器:交叉 C/C++ 编译器(如
arm-buildroot-linux-gnueabihf-g++)。 - 调试器:交叉 GDB(
arm-buildroot-linux-gnueabihf-gdb)。 - Qt 版本:指向交叉编译生成的
qmake(位于 Buildroot output/host/bin)。 - Sysroot:交叉编译工具链的 sysroot 路径。
Q39:在开发板上运行 Qt 程序需要设置哪些环境变量?各有什么作用?
难度:⭐
答案
bash
export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb0 # 使用 Linux 帧缓冲显示
export QT_QPA_GENERIC_PLUGINS=tslib:/dev/input/event1 # 触摸屏输入
export QT_QPA_FONTDIR=/usr/lib/fonts/ # 字体路径
Q40:Qt 程序如何通过 RPC 调用后台服务的函数?简述步骤。
难度:⭐⭐
答案
- 在 .pro 中添加
QT += network。 - 创建
QTcpSocket对象,连接到后台服务器 IP 和端口。 - 构造 JSON-RPC 请求字符串(包含
method、params、id)。 - 通过 socket 发送请求,等待响应。
- 接收响应 JSON,解析
result字段。 - 将前后台分离后,前台不直接操作硬件,所有硬件功能通过 RPC 调用后台完成。
第十二部分:前后台分离与 JsonRPC
Q41:前后台分离(前台 Qt + 后台 C 服务)有什么好处?
难度:⭐⭐
答案
- 隔离变化:更换硬件或修改硬件驱动,只需修改后台程序,前台无需改动。
- 团队协作:界面开发与底层开发可以并行,只需约定 RPC 接口。
- 稳定性:前台崩溃不会影响后台硬件控制,后台崩溃可由看门狗重启。
- 可扩展:前后台可运行在不同设备上(通过网络),实现远程控制。
Q42:TCP 和 UDP 有什么区别?JsonRPC 为什么选择 TCP?
难度:⭐
答案
- TCP:面向连接、可靠、有序、有重传,开销大。
- UDP:无连接、不可靠、无序,开销小。
JsonRPC 要求请求和响应必须完整到达且顺序正确,因此选择 TCP。
Q43:服务器端使用 socket → bind → listen → accept 分别做什么?客户端使用 socket → connect 做什么?
难度:⭐⭐
答案
socket:创建通信端点(返回 fd)。bind:将 socket 绑定到本地 IP 和端口(服务器必须做)。listen:使 socket 进入监听状态。accept:阻塞等待客户端连接,返回新 socket 用于与该客户端通信。connect:客户端主动连接服务器。
Q44:JSON-RPC 请求的典型格式是什么?给出一个调用 add 方法并传参 [3,5] 的例子。
难度:⭐
答案
json
{
"jsonrpc": "2.0",
"method": "add",
"params": [3,5],
"id": 1
}
响应格式:
json
{
"jsonrpc": "2.0",
"result": 8,
"id": 1
}
第十三部分:硬件操作(LED、DHT11)文件 IO
Q45:如何通过 sysfs 控制一个 GPIO 输出高低电平?写出命令序列。
难度:⭐
答案
bash
echo 131 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio131/direction
echo 1 > /sys/class/gpio/gpio131/value # 高电平
echo 0 > /sys/class/gpio/gpio131/value # 低电平
echo 131 > /sys/class/gpio/unexport
Q46:DHT11 驱动在应用层提供了什么接口?如何读取温湿度?
难度:⭐
答案
驱动创建 /dev/mydht11,应用层 open 后每次 read(fd, buf, 2) 得到两个字节:buf[0] 湿度(0~100),buf[1] 温度(0~50)。驱动程序内部实现了单总线时序,应用无需关心 GPIO 操作。
Q47:在 Qt 中读取 DHT11 数据时为什么要用单独的线程?如果直接在 UI 线程中 read 会有什么问题?
难度:⭐⭐
答案
若 DHT11 读取遇忙或出错,read 可能阻塞(即使设为非阻塞也可能短时阻塞),阻塞 UI 线程会导致界面卡死,用户体验极差。单独线程可让 UI 线程保持响应,读取线程定期更新数据并通过信号槽刷新界面。
第十四部分:调试与常见错误排查
Q48:insmod 报 File exists 错误,可能原因及排查步骤?
难度:⭐⭐
答案
- 模块已加载:
lsmod | grep xxx,rmmod卸载。 - 设备节点残留:
rm -f /dev/xxx。 - sysfs 类目录残留:
rmdir /sys/class/xxx_class。 - 设备号被占用:
cat /proc/devices查看主设备号。 - 重启清临时文件(最后手段)。
Q49:request_irq 失败,内核打印 tried to flag a GPIO set as output for IRQ,原因及修复?
难度:⭐⭐
答案
GPIO 当前是输出模式,不能作为中断源。必须在 request_irq 前调用 gpio_direction_input(gpio) 设为输入模式。
Q50:交叉编译时报 gnu/stubs-soft.h: No such file or directory 怎么解决?
难度:⭐⭐
答案
环境变量 C_INCLUDE_PATH 被指向了交叉编译器的 include 目录,导致主机编译工具(如 modpost)误用了 ARM 头文件。解决方法:unset C_INCLUDE_PATH unset LIBRARY_PATH,编译内核时用 make ARCH=arm CROSS_COMPILE=... 传递参数,不要 source 交叉环境脚本。
第十五部分:综合与扩展(考察融会贯通)
Q51:设计一个同时支持 LED 控制和 DHT11 读取的 RPC 后台服务,需要包含哪些注册的 RPC 方法?给出方法名和参数。
难度:⭐⭐
答案
led_control(led_num, onoff):控制某个 LED 亮灭。dht11_read(void):返回{humidity, temperature}对象。- 可选的
get_device_status()等。
注册示例:
c
jrpc_register_procedure(&server, rpc_led_control, "led_control", NULL);
jrpc_register_procedure(&server, rpc_dht11_read, "dht11_read", NULL);
Q52:如果要把开发板的 Qt 界面通过 VNC 远程显示到 PC 上,应该修改哪个环境变量?
难度:⭐⭐
答案
将 QT_QPA_PLATFORM 改为 vnc 或使用 qt-vnc 插件,例如 export QT_QPA_PLATFORM=vnc:0。此时 Qt 程序不再使用 linuxfb,而是启动一个 VNC 服务器,PC 端用 VNC 客户端连接即可。
Q53:写一个简单的 Makefile,要求:编译 main.c 和 sub.c,生成 test,并自动处理头文件依赖。
难度:⭐⭐
答案
makefile
objs = main.o sub.o
dep_files := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files))
ifneq ($(dep_files),)
include $(dep_files)
endif
test: $(objs)
gcc -o $@ $^
%.o: %.c
gcc -Wp,-MD,.$@.d -c -o $@ $<
clean:
rm -f test $(objs)
distclean:
rm -f $(dep_files) test $(objs)
Q54:如何查看一个可执行文件是 x86 架构还是 ARM 架构?
难度:⭐
答案
使用 file 命令,例如 file hello。输出包含 x86-64 表示 PC 程序,包含 ARM 表示交叉编译的 ARM 程序。
Q55:在 Framebuffer 中画一个红色矩形,请写出核心代码片段。
难度:⭐⭐
答案
假设已经 mmap 得到 fb_base,已知 line_width,pixel_width=4(32bpp),矩形左上角 (x0,y0) 宽 w 高 h。
c
unsigned int *p;
for (int y = y0; y < y0 + h; y++) {
p = (unsigned int*)(fb_base + y * line_width + x0 * pixel_width);
for (int x = 0; x < w; x++)
*p++ = 0xFF0000; // 红色
}
Q56:解释什么是"野指针"和"内存泄漏"?在驱动开发中如何避免?
难度:⭐⭐
答案
- 野指针:指向已释放内存或非法地址的指针。避免方法:释放内存后将指针置为 NULL,不返回局部变量地址。
- 内存泄漏 :动态分配内存后未释放。避免方法:
kmalloc后必须匹配kfree,且在错误路径中释放已分配资源。使用devm_*系列函数可自动释放。
第十六部分:终极综合题(领导最爱问)
Q57:假如你要从零开始为开发板添加一个 I2C 温湿度传感器(如 SI7021),请描述完整流程,包括:设备树修改、驱动编写或用已有框架、应用层读取方式。
难度:⭐⭐⭐
答案
- 硬件确认:传感器 SDA/SCL 接到开发板 I2C 总线引脚。
- 设备树 :在对应 I2C 控制器节点下添加子节点,指定
compatible、reg(设备地址)、status="okay"。 - 驱动 :
- 若内核已有通用驱动(如
si7020.c),只需配置设备树 compatible 匹配即可。 - 若无,可编写简单的字符设备驱动:在 probe 中注册设备节点,read 函数中调用
i2c_smbus_read_word_data或i2c_transfer读取温湿度。
- 若内核已有通用驱动(如
- 应用 :打开
/dev/xxx,读取 4 字节(温度+湿度)或通过 ioctl 获取数据。 - 也可以不写驱动,直接在用户空间用
i2c-dev+i2c_smbus_read_word_data访问。
Q58:领导要求你优化一个按键驱动,原驱动在快速连续按下时偶尔上报两次相同的键值,请分析可能原因并给出解决方案。
难度:⭐⭐⭐
答案
原因 :防抖定时器时间过短(例如 10ms),按键抖动稍长导致定时器超时前仍有中断,但最后一次中断后又过了 10ms 上报一次,可能产生两次上报。或者 ISR 中直接上报(无定时器)。
解决方案:
- 增加定时器延迟(如 30~50ms)。
- 确保定时器回调中读取 GPIO 稳定电平后,与上次状态比较,若相同则不上报。
- 使用状态机(等待释放/按下),避免重复上报。
- 上报前检查环形缓冲区最后一个值,若相同则丢弃。
Q59:产品上电后 LCD 显示正常,但 Qt 程序启动后屏幕黑屏,可能的原因有哪些?
难度:⭐⭐
答案
- 屏幕保护触发了:执行
echo -e "\033[9;0]" > /dev/tty0禁止。 - Framebuffer 被其他程序占用(如 LVGL、另一个 Qt 实例):
fuser /dev/fb0查看并 kill。 - Qt 程序崩溃但未显示错误:检查环境变量是否正确(
QT_QPA_PLATFORM)。 - 没有正确 mmap 或清屏导致显示随机数据:驱动中应先
memset(fb_base, 0, screen_size)清屏。 - 背光被关闭:检查
/sys/class/backlight下亮度值。
Q60:你在开发板上调试一个程序,发现 printf 不输出任何信息,但 write(1, "xxx", 3) 可以输出,为什么?如何解决?
难度:⭐⭐
答案
printf 是标准库函数,带有用户态缓冲区(行缓冲或全缓冲)。当输出到终端时,默认行缓冲,遇到 \n 或缓冲区满才刷新。若程序崩溃或没有 \n,缓冲区未刷新。
解决方法:在 printf 后调用 fflush(stdout),或使用 setvbuf(stdout, NULL, _IONBF, 0) 关闭缓冲,或改用 write。嵌入式开发调试时推荐使用 fprintf(stderr, ...) 或 write,因为 stderr 默认无缓冲。
最后叮嘱:以上 60 道题覆盖了你提供的所有文档的核心知识点。建议逐题自测,对答不上的章节回到原文再复习。面试/考核时领导往往喜欢从一个基础问题开始,然后层层深入(比如先问"中断里能做什么",再问"如果一定要拷贝数据怎么办")。祝考核顺利!