Linux 驱动与应用开发核心自测题库(面试官问答完整版)

Linux 驱动与应用开发核心自测题库(面试官问答完整版)

本题库基于你提供的全部文档(驱动开发、I2C/SPI、设备树、Platform、Qt、JsonRPC、GCC、Makefile、Framebuffer、汉字编码、网络编程等)提炼,覆盖驱动开发、应用编程、系统构建、前后台分离四大领域。每题标注难度⭐~⭐⭐⭐,部分题目含代码填空或排错。建议先口头回答,再对照解析。


文章目录

  • [Linux 驱动与应用开发核心自测题库(面试官问答完整版)](#Linux 驱动与应用开发核心自测题库(面试官问答完整版))
    • 第一部分:字符设备驱动与文件IO(基础但高频)
      • [Q1:`register_chrdev` 和 `cdev` 接口的区别?为什么推荐 `cdev`?](#Q1:register_chrdevcdev 接口的区别?为什么推荐 cdev?)
      • [Q2:`copy_to_user` 和 `copy_from_user` 能直接用 `memcpy` 替换吗?为什么?](#Q2:copy_to_usercopy_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 字节的标准代码片段,要求包含错误处理。)
    • [第二部分: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:按键驱动中防抖的原理是什么?用定时器如何实现?
    • 第三部分:等待队列、阻塞/非阻塞、Poll、异步通知
      • [Q10:`wait_event_interruptible` 和 `wake_up_interruptible` 配合原理是什么?](#Q10:wait_event_interruptiblewake_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 中调用哪个函数发送信号?)
    • [第四部分:设备树与 Platform 驱动(企业级开发核心)](#第四部分:设备树与 Platform 驱动(企业级开发核心))
      • [Q14:设备树中 `compatible` 属性的作用是什么?驱动如何匹配?](#Q14:设备树中 compatible 属性的作用是什么?驱动如何匹配?)
      • [Q15:`probe` 函数一般需要完成哪些初始化工作?](#Q15:probe 函数一般需要完成哪些初始化工作?)
      • [Q16:`platform_driver` 的匹配机制有哪几种?优先级顺序如何?](#Q16:platform_driver 的匹配机制有哪几种?优先级顺序如何?)
      • [Q17:在设备树中描述一个 GPIO 引脚的标准格式是什么?驱动中如何获取?](#Q17:在设备树中描述一个 GPIO 引脚的标准格式是什么?驱动中如何获取?)
    • [第五部分: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 这条命令的含义。)
    • [第六部分:SPI 驱动](#第六部分:SPI 驱动)
      • [Q22:SPI 的四种模式由哪两个参数决定?设备树中如何指定模式 1?](#Q22:SPI 的四种模式由哪两个参数决定?设备树中如何指定模式 1?)
      • [Q23:`spi_transfer` 和 `spi_message` 的关系是什么?为什么需要 `spi_message`?](#Q23:spi_transferspi_message 的关系是什么?为什么需要 spi_message?)
      • [Q24:SPI 驱动中,CS(片选)信号需要驱动手动控制吗?](#Q24:SPI 驱动中,CS(片选)信号需要驱动手动控制吗?)
    • 第七部分:并发控制与竞态条件
    • [第八部分: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:服务器端使用 socketbindlistenaccept 分别做什么?客户端使用 socketconnect 做什么?)
      • [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:insmodFile 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 怎么解决?)
    • 第十五部分:综合与扩展(考察融会贯通)
      • [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.csub.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_chrdevcdev 接口的区别?为什么推荐 cdev

难度:⭐⭐
答案

  • register_chrdev:一次性分配主设备号并注册 0~255 个次设备号,浪费资源,不支持动态管理多个设备。
  • cdev 接口:分步 alloc_chrdev_regioncdev_initcdev_add,按需申请次设备号,一个驱动可管理多个设备,更灵活且符合现代内核设计。
  • 推荐原因:资源利用率高、设备管理清晰、与设备模型结合紧密。

Q2:copy_to_usercopy_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_opf_posprivate_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_interruptiblewake_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 会让进程立即睡眠吗?

难度:⭐⭐⭐
答案

  1. 调用 poll_wait 将当前进程注册到等待队列(不睡眠,仅建立唤醒路径)。
  2. 检查数据是否可用,返回 POLLIN0
    真正的睡眠由内核 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 函数一般需要完成哪些初始化工作?

难度:⭐⭐
答案

  1. 从设备树或 platform 资源获取硬件信息(GPIO、中断号、地址)。
  2. 申请 GPIO、注册中断、使能时钟。
  3. 注册字符设备(alloc_chrdev_region + cdev_add)。
  4. 创建类与设备节点(class_create + device_create)。
  5. 保存私有数据用 platform_set_drvdata

Q16:platform_driver 的匹配机制有哪几种?优先级顺序如何?

难度:⭐⭐⭐
答案

platform_match 尝试顺序(源码 drivers/base/platform.c):

  1. driver_override 强制覆盖。
  2. 设备树匹配(.of_match_table)。
  3. ACPI 匹配。
  4. id_table 匹配。
  5. 名字直接匹配(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/writeioctl(fd, I2C_RDWR, ...) 进行数据传输。命令行工具 i2cgeti2cseti2ctransfer 也是基于此机制。

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_transferspi_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 程序需要哪四个步骤?分别用什么选项?

难度:⭐
答案

  1. 预处理 -E:展开宏、包含头文件 → .i 文件。
  2. 编译 -S:生成汇编代码 → .s 文件。
  3. 汇编 -c:生成目标文件 → .o 文件。
  4. 链接 -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 控制器自动读取该内存并刷新屏幕。

应用程序步骤:

  1. open("/dev/fb0")
  2. ioctl(fd, FBIOGET_VSCREENINFO, &var) 获取分辨率、bpp。
  3. mmap(NULL, screen_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0) 映射到用户空间。
  4. 通过计算地址 base + y*line_width + x*pixel_width 写像素。
  5. 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 调用后台服务的函数?简述步骤。

难度:⭐⭐
答案

  1. 在 .pro 中添加 QT += network
  2. 创建 QTcpSocket 对象,连接到后台服务器 IP 和端口。
  3. 构造 JSON-RPC 请求字符串(包含 methodparamsid)。
  4. 通过 socket 发送请求,等待响应。
  5. 接收响应 JSON,解析 result 字段。
  6. 将前后台分离后,前台不直接操作硬件,所有硬件功能通过 RPC 调用后台完成。

第十二部分:前后台分离与 JsonRPC

Q41:前后台分离(前台 Qt + 后台 C 服务)有什么好处?

难度:⭐⭐
答案

  • 隔离变化:更换硬件或修改硬件驱动,只需修改后台程序,前台无需改动。
  • 团队协作:界面开发与底层开发可以并行,只需约定 RPC 接口。
  • 稳定性:前台崩溃不会影响后台硬件控制,后台崩溃可由看门狗重启。
  • 可扩展:前后台可运行在不同设备上(通过网络),实现远程控制。

Q42:TCP 和 UDP 有什么区别?JsonRPC 为什么选择 TCP?

难度:⭐
答案

  • TCP:面向连接、可靠、有序、有重传,开销大。
  • UDP:无连接、不可靠、无序,开销小。
    JsonRPC 要求请求和响应必须完整到达且顺序正确,因此选择 TCP。

Q43:服务器端使用 socketbindlistenaccept 分别做什么?客户端使用 socketconnect 做什么?

难度:⭐⭐
答案

  • 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:insmodFile exists 错误,可能原因及排查步骤?

难度:⭐⭐
答案

  1. 模块已加载:lsmod | grep xxxrmmod 卸载。
  2. 设备节点残留:rm -f /dev/xxx
  3. sysfs 类目录残留:rmdir /sys/class/xxx_class
  4. 设备号被占用:cat /proc/devices 查看主设备号。
  5. 重启清临时文件(最后手段)。

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.csub.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_widthpixel_width=4(32bpp),矩形左上角 (x0,y0)wh

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),请描述完整流程,包括:设备树修改、驱动编写或用已有框架、应用层读取方式。

难度:⭐⭐⭐
答案

  1. 硬件确认:传感器 SDA/SCL 接到开发板 I2C 总线引脚。
  2. 设备树 :在对应 I2C 控制器节点下添加子节点,指定 compatiblereg(设备地址)、status="okay"
  3. 驱动
    • 若内核已有通用驱动(如 si7020.c),只需配置设备树 compatible 匹配即可。
    • 若无,可编写简单的字符设备驱动:在 probe 中注册设备节点,read 函数中调用 i2c_smbus_read_word_datai2c_transfer 读取温湿度。
  4. 应用 :打开 /dev/xxx,读取 4 字节(温度+湿度)或通过 ioctl 获取数据。
  5. 也可以不写驱动,直接在用户空间用 i2c-dev + i2c_smbus_read_word_data 访问。

Q58:领导要求你优化一个按键驱动,原驱动在快速连续按下时偶尔上报两次相同的键值,请分析可能原因并给出解决方案。

难度:⭐⭐⭐
答案

原因 :防抖定时器时间过短(例如 10ms),按键抖动稍长导致定时器超时前仍有中断,但最后一次中断后又过了 10ms 上报一次,可能产生两次上报。或者 ISR 中直接上报(无定时器)。
解决方案

  1. 增加定时器延迟(如 30~50ms)。
  2. 确保定时器回调中读取 GPIO 稳定电平后,与上次状态比较,若相同则不上报。
  3. 使用状态机(等待释放/按下),避免重复上报。
  4. 上报前检查环形缓冲区最后一个值,若相同则丢弃。

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 道题覆盖了你提供的所有文档的核心知识点。建议逐题自测,对答不上的章节回到原文再复习。面试/考核时领导往往喜欢从一个基础问题开始,然后层层深入(比如先问"中断里能做什么",再问"如果一定要拷贝数据怎么办")。祝考核顺利!

相关推荐
eastyuxiao4 小时前
企业 OpenClaw 文档自动化落地项目 - 思维导图范围拆解
运维·自动化
薛定谔的悦4 小时前
储能充放电状态机执行逻辑详解
linux·数据库·能源·储能·bms
Sirens.4 小时前
Umami:从Cloud迁移到服务器
运维·服务器
zhangfeng11334 小时前
CI/CD 是软件开发中的两个核心实践,合起来指代一套自动化的软件交付流程
运维·ci/cd·自动化
fengci.4 小时前
CTF+随机困难题目
android·开发语言·前端·学习·php
嵌入式×边缘AI:打怪升级日志4 小时前
Tina SDK Linux Kernel 基本使用(实战篇:为7寸RGB LCD触摸屏添加驱动支持).md
linux·运维·服务器
xxjj998a4 小时前
PHP vs C#:两大编程语言终极对比
开发语言·c#·php
十 一 丶5 小时前
如何在客户端实现ssh的免密登录?
运维·rust·ssh
前端之虎陈随易5 小时前
为什么今天还会有新语言?MoonBit 想解决什么问题?
大数据·linux·javascript·人工智能·算法·microsoft·typescript