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

相关推荐
A小辣椒1 天前
TShark:基础知识
linux
BingoGo1 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack1 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户3074596982072 天前
PHP 扩展——从入门到理解
php
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
鹏仔先生3 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
大树883 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai