• 正文
  • 相关推荐
申请入驻 产业图谱

整理的嵌入式Linux驱动面试题,拿去背吧!

3小时前
91
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

一、基础部分

1.、C/C++ 的内存分配?

静态分配:栈(局部变量 / 函数参数,自动分配释放)、全局 / 静态区(.data/.bss,程序启动时分配);动态分配:堆(C 用malloc/calloc/realloc/free,C++ 用new/delete,手动分配释放)。

2、如何使用指针去操作内存?

指针指向内存:int *p = &var;(变量地址)或 p = (int *)0x40000000;(物理地址映射);

读写内存:*p = 10;(写)、int val = *p;(读);

连续内存:指针加减偏移(如p++访问数组下一个元素)。

3、使用指针如何避免越界访问?

明确内存边界(如数组长度),校验下标 / 偏移量;避免随意指针加减,使用安全函数(strncpy替代strcpy);调试开启内存检测(如valgrind),用volatile监控共享内存。

4、 栈内是保存什么的?malloc 出来的地址在栈还是堆?

栈保存:局部变量、函数参数、返回地址、栈帧信息;

malloc分配的内存在堆,栈仅存储

malloc返回的指针变量本身。

5、 全局变量和静态变量保存在哪里?

    均存于数据段(.data 区:初始化非 0 值)或 BSS 段(未初始化 / 初始化为 0 值);静态变量仅限制作用域,存储位置与全局变量一致。

6、 哈希表的实现原理

哈希函数映射:将键映射到哈希桶下标,快速定位存储位置;

冲突解决:采用链地址法(冲突元素串成链表)或开放地址法;

核心特性:平均查询 / 插入时间复杂度 O (1)。

7、 平衡二叉树的实现原理?

    基于二叉搜索树,通过左旋 / 右旋维持左右子树高度差≤1(AVL 树),避免树退化为链表;保证查询、插入、删除的时间复杂度为 O (logn)。

8. 选择题

以下哪种线程同步机制可用于实现 “线程 A 等待线程 B 完成某操作后再执行” 的逻辑?()

A. 互斥锁(Mutex) B. 条件变量(Condition Variable) C. 二进制信号量 D. 计数信号量

答案:B

解析:条件变量通过 “等待 - 通知” 机制实现:线程 A 调用pthread_cond_wait等待,线程 B 完成后调用pthread_cond_signal唤醒 A。

9. 数据结构的了解

常用数据结构按逻辑分类:

线性结构:数组:连续存储,随机访问快(O (1)),增删慢(O (n));链表:非连续存储,增删快(O (1)),访问慢(O (n));栈(FILO)、队列(FIFO):基于数组 / 链表实现,用于缓存、任务调度。

树形结构:二叉树:每个节点最多两子树,用于排序、搜索;红黑树:自平衡二叉树,O (log n) 操作,用于 map/set;堆:完全二叉树,用于优先队列(如调度算法)。

哈希结构:哈希表通过键映射存储,平均 O (1) 操作,用于缓存、字典。

图结构:由节点和边组成,用于网络拓扑、路径规划(如 Dijkstra 算法)。

10、选择题

以下哪种进程间通信方式是最快的?()

A. 管道(Pipe) B. 消息队列 C. 共享内存 D. 套接字(Socket)

答案:C

解析:共享内存通过内核分配物理内存并映射到多进程地址空间,进程直接读写内存,无需内核中转(无数据拷贝),是所有 IPC 中效率最高的。

11、 简答题

fork()vfork()创建子进程的核心区别是什么?

参考答案

地址空间处理:

fork()采用写时复制(COW),子进程与父进程共享内存直到修改时复制;

vfork()子进程完全共享父进程地址空间,修改会直接影响父进程。父进程状态:

fork()父进程不阻塞,与子进程并发执行;

vfork()父进程阻塞,直到子进程调用execexit才恢复。

12、 分析题

某程序中,两个线程通过全局变量int data = 0交换数据,线程 A 负责写入data = 100,线程 B 负责读取并打印data。但运行时线程 B 有时打印 0,有时打印 100,原因是什么?如何解决?

参考答案

原因:线程 A 和 B 同时访问data导致数据竞争(线程 B 可能在线程 A 写入前读取)。

解决:用互斥锁(pthread_mutex_t)保护data的读写,确保同一时间只有一个线程访问。

13、选择题

Linux 中,以下哪个指令可用于查看进程占用的 CPU 和内存资源?()

A. ps -ef B. top C. netstat -tuln D. df -h

答案:B

解析top是动态进程监控工具,实时显示进程的 CPU 使用率、内存占用等;ps -ef仅静态列出进程信息。

14、 TCP/IP 协议

TCP/IP 是互联网的核心协议族,采用分层模型(通常简化为 4 层):

链路层:处理物理传输(如以太网、Wi-Fi),封装 MAC 地址,协议有 ARP(地址解析)。

网络层:负责跨网络路由,核心协议是 IP(定义 IP 地址和数据包格式),辅助协议有 ICMP(ping 命令基于此)、IGMP(组播)。

传输层:提供端到端通信,核心协议:

      • TCP:面向连接、可靠传输(三次握手建立连接,四次挥手断开,重传机制、流量控制、拥塞控制);UDP:无连接、不可靠但高效(适合实时通信,如视频、语音)。

应用层:直接为应用服务,协议有 HTTP(网页)、FTP(文件传输)、DNS(域名解析)、SSH(远程登录)等。

15、TCP4次握手

TCP 四次挥手的过程中,主动关闭方在第四次挥手后进入TIME_WAIT状态,等待2MSL的目的是什么?

参考答案:确保最后一个 ACK 被被动关闭方收到:若被动方未收到,会重发 FIN,主动方可在TIME_WAIT期间再次回复。

防止旧连接的报文干扰新连接:2MSL是报文最大生存时间的 2 倍,确保本次连接的所有报文已从网络中消失。

16、工厂模式的理解以及为什么需要工厂模式?

工厂模式:创建型设计模式,定义一个工厂类负责创建其他类的实例,隐藏对象创建的细节(如具体类名、初始化参数)。分为简单工厂、工厂方法、抽象工厂。

为什么需要:

    • 解耦:调用方无需知道具体类名,只需通过工厂接口获取对象,降低依赖。扩展性:添加新类型时,只需扩展工厂类,无需修改调用方代码(符合开闭原则)。统一管理:对象创建逻辑集中在工厂,便于维护(如统一初始化、日志记录)。示例:日志系统中,工厂根据配置创建 “文件日志” 或 “控制台日志” 实例,调用方无需修改。

17、分析题

某设备 ping 通目标 IP(如 192.168.1.1),但无法解析域名(如www.baidu.com),可能的原因有哪些?

参考答案

DNS 服务器配置错误:/etc/resolv.confnameserver未配置或指向无效 IP。

本地 Hosts 文件异常:

/etc/hosts中错误映射目标域名,导致解析冲突。DNS 端口被拦截:防火墙封禁 UDP 53 端口(DNS 默认端口),无法向 DNS 服务器发送请求。DNS 缓存污染:本地 DNS 缓存记录错误,需清理(如systemctl restart systemd-resolved)。

18、libjpeg 等库做了什么操作?

libjpeg 是处理 JPEG 图像的开源库,核心操作包括:

解码:将 JPEG 压缩格式(二进制流)转换为 RGB/BGR 等未压缩格式,步骤:解析文件头、霍夫曼解码、逆 DCT 变换、去量化、色彩空间转换(YCrCb→RGB)。

编码:将 RGB 等格式压缩为 JPEG,步骤:色彩空间转换(RGB→YCrCb)、DCT 变换、量化、霍夫曼编码、生成文件头。提供 API 屏蔽底层算法细节(如jpeg_read_headerjpeg_start_decompress),方便应用快速实现图像编解码,无需关注 JPEG 标准的复杂细节。

二、内核+驱动

1、简述嵌入式 Linux 的启动流程(从上电到应用程序运行)

核心要点:需经过 “硬件初始化→Bootloader→内核启动→根文件系统挂载→用户程序启动” 五个阶段:

硬件上电:CPU 从复位向量(如 0x0 地址)执行,初始化片内 SRAM、时钟等基础硬件。

Bootloader 阶段:

初始化 DRAM、外设(如串口),将内核镜像(zImage)和设备树(.dtb)从 Flash 加载到 DRAM。设置内核启动参数(如 bootargs),跳转到内核入口地址(如start_kernel)。

内核启动:初始化内核核心组件(进程管理、内存管理、中断系统),解析设备树,初始化驱动(平台设备、总线驱动匹配)。挂载根文件系统(根据 bootargs 中的root=/dev/mmcblk0p2等参数)。

用户空间初始化:启动 init 进程(PID=1),解析/etc/inittab或 systemd 配置,启动服务(如网络、日志)。

运行应用程序:init 进程启动用户态应用(如 APP、脚本),系统进入正常运行状态。

2、什么是设备树(Device Tree)?它解决了什么问题?如何在驱动中获取设备树节点的属性?

核心要点:

定义:是一种描述硬件信息的结构化数据(.dts 文件,编译为.dtb),包含 CPU、内存、外设的型号、地址、引脚等信息。

解决的问题:替代传统 Linux 的 “板级代码(board.c)”,将硬件信息与内核代码分离,实现 “一套内核适配多硬件”(避免为不同板修改内核)。

驱动中获取属性:通过 of 函数族(Open Firmware API)解析设备树节点:

// 示例:获取节点"led@0x12340000"的"gpio"属性
struct device_node *node;
int gpio_num;
node = of_find_node_by_path("/soc/led@0x12340000"); // 查找节点
of_property_read_u32(node, "gpio", &gpio_num);       // 读取u32类型属性

3、使用设备树的优点是什么?

硬件与内核解耦:硬件信息(如引脚、寄存器地址)通过设备树描述,无需修改内核源码,同一内核可适配不同硬件(如同一芯片的不同开发板)。

简化内核维护:避免大量 “板级代码”(如传统的platform_data),减少内核补丁数量。

动态适配:内核启动时解析设备树,自动匹配驱动(通过compatible属性),符合 Linux “设备 - 驱动分离” 模型。

跨平台兼容:统一的硬件描述格式,适配不同架构(ARM、RISC-V 等)。

4、编写一个简单的字符设备驱动(至少包含 open、read、write 操作),并说明如何测试

核心要点:字符设备驱动通过cdev结构体注册,依赖file_operations实现文件操作接口。

驱动代码框架

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>

#define DEV_NAME "mychar"
#define DEV_MAJOR 0  // 动态分配主设备号

staticint major = DEV_MAJOR;
staticstruct cdev my_cdev;
staticchar data_buf[1024];  // 数据缓冲区

// open操作
static int my_open(struct inode *inode, struct file *filp) {
    printk("mychar device openedn");
    return0;
}

// read操作:从内核缓冲区读数据到用户空间
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {
    int ret = copy_to_user(buf, data_buf, count);  // 内核→用户空间(必须用copy_to_user)
    return count - ret;
}

// write操作:从用户空间写数据到内核缓冲区
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {
    int ret = copy_from_user(data_buf, buf, count); // 用户→内核空间(必须用copy_from_user)
    return count - ret;
}

// 文件操作集合
staticstruct file_operations my_fops = {
    .owner = THIS_MODULE,
    .open = my_open,
    .read = my_read,
    .write = my_write,
};

// 模块初始化
static int __init mychar_init(void) {
    dev_t dev;
    // 分配设备号
    if (major == 0) {
        alloc_chrdev_region(&dev, 0, 1, DEV_NAME);  // 动态分配
        major = MAJOR(dev);
    } else {
        dev = MKDEV(major, 0);
        register_chrdev_region(dev, 1, DEV_NAME);    // 静态注册
    }
    // 初始化cdev并添加到内核
    cdev_init(&my_cdev, &my_fops);
    cdev_add(&my_cdev, dev, 1);
    printk("mychar driver loaded, major=%dn", major);
    return0;
}

// 模块退出
static void __exit mychar_exit(void) {
    cdev_del(&my_cdev);
    unregister_chrdev_region(MKDEV(major, 0), 1);
    printk("mychar driver unloadedn");
}

module_init(mychar_init);
module_exit(mychar_exit);
MODULE_LICENSE("GPL");

测试方法

编译驱动为.ko 模块,加载:insmod mychar.ko

创建设备节点:mknod /dev/mychar c <major> 0(major 为驱动打印的主设备号)。

编写测试程序(用户态):

#include <stdio.h>
#include <fcntl.h>
int main() {
    int fd = open("/dev/mychar", O_RDWR);
    write(fd, "hello", 5);  // 写入数据
    char buf[10];
    read(fd, buf, 5);       // 读取数据
    printf("read: %sn", buf);  // 输出"hello"
    close(fd);
    return 0;
}

5、中断的概念?

    由硬件 / 软件触发的异步事件,用于暂停当前程序执行,转而去执行中断服务函数处理事件,处理完成后返回原程序继续执行。

6、用什么函数注册中断?

    • 裸机(STM32):

HAL_NVIC_EnableIRQ()

    • (使能中断)、

HAL_GPIO_EXTI_Callback()

    • (中断回调);Linux 内核:

request_irq()

    (注册中断服务函数)。

7、在中断中要注意什么?**

    • 执行时间尽量短,避免阻塞 / 休眠;用自旋锁(而非互斥锁)保护共享资源;禁止不必要的中断嵌套,用

volatile

    修饰共享变量;及时保存和恢复现场。

8、如何写一个中断?

步骤:

获取中断号:

设备树中指定:interrupts = <GIC_SPI 16 IRQ_TYPE_EDGE_FALLING>;

(边缘触发);驱动中解析:irq = platform_get_irq(pdev, 0);

注册中断处理函数:

int ret = request_irq(irq, irq_handler, IRQF_TRIGGER_FALLING, "my_device", dev);

irq_handler:上半部函数(快速处理,如清中断标志),返回IRQ_HANDLED

处理下半部:若需耗时操作(如数据处理),在中断上半部中调度下半部(如 workqueue):

static irqreturn_t irq_handler(int irq, void *dev_id) {
  schedule_work(&dev->work); // 调度workqueue
  return IRQ_HANDLED;
}

注销中断:free_irq(irq, dev);

9、下半部里面 work_queue 和 irq_thread 的区别?

维度 work_queue irq_thread
运行上下文 进程上下文(可睡眠) 进程上下文(可睡眠)
调度方式 由内核线程(如 kworker)调度 作为独立线程调度,优先级高于普通线程
触发方式 需手动调用schedule_work 中断触发后自动唤醒线程
适用场景 通用延迟任务(如数据上报) 中断处理较复杂但需睡眠(如 I2C 数据读取)
延迟性 可能被低优先级任务延迟 实时性较好(线程可设高优先级)

10、解释 Linux 中断处理的 “顶半部(Top Half)” 和 “底半部(Bottom Half)”,并说明各自的实现方式

核心要点:中断处理分为两部分,平衡 “响应速度” 和 “处理效率”:

顶半部(TH):

功能:快速响应中断,完成最紧急的操作(如清除中断标志、保存硬件状态),禁止中断嵌套(避免干扰)。

实现:通过request_irq注册中断服务函数(ISR),在 ISR 中执行顶半部逻辑。

底半部(BH):功能:处理耗时操作(如数据处理、唤醒任务),允许中断嵌套(不阻塞其他中断)。

实现方式:

tasklet:基于软中断,适合小任务(原子上下文,不能睡眠);

工作队列(workqueue):基于内核线程,适合大任务(可睡眠,允许阻塞操作);软中断:内核级异步处理(如网络、块设备,不建议驱动直接使用)。

示例:串口接收中断

    顶半部:读取硬件 FIFO 数据到内核缓冲区,清除中断标志。底半部(工作队列):解析缓冲区数据,发送到用户空间。

11、 Linux 内核的进程调度策略有哪些?嵌入式场景中如何选择?

核心要点:Linux 内核支持多种调度策略,按实时性分为:

CFS(Completely Fair Scheduler):

非实时调度,默认策略(SCHED_OTHER/SCHED_NORMAL),基于 “虚拟运行时间” 分配 CPU,确保各进程公平执行。适用:普通应用程序(如后台服务、UI 界面)。

实时调度:

SCHED_FIFO(先进先出):高优先级进程一旦获取 CPU,一直运行直到主动放弃或被更高优先级进程抢占。

SCHED_RR(时间片轮转):同优先级实时进程按时间片轮流执行。适用:嵌入式实时任务(如传感器数据采集电机控制,需严格截止时间)。

其他策略:

SCHED_IDLE:最低优先级,仅当系统空闲时运行(如后台日志清理)。

嵌入式选择原则

实时性要求高的任务(如 10ms 内响应)→ 用SCHED_FIFO/SCHED_RR,并设置合适优先级。普通任务→ CFS,避免抢占实时任务。

12、为什么中断不能堵塞,堵塞会引起什么?

中断不能堵塞的原因:中断是硬件触发的紧急事件(如按键按下、数据到达),需要快速响应;中断处理时会屏蔽同类型中断,若堵塞(如执行耗时操作),会导致后续同类型中断丢失。

堵塞的后果:

    • 错过关键硬件事件(如传感器数据溢出、通信超时);系统响应延迟,甚至卡顿(其他中断也可能被间接影响);严重时导致 watchdog 超时(硬件复位),系统崩溃。

13、什么是 MMU(内存管理单元)?嵌入式 Linux 中 MMU 的作用是什么?

核心要点:

定义:MMU 是 CPU 内的硬件单元,负责虚拟地址到物理地址的转换,提供内存保护。

核心作用:

虚拟内存管理:每个进程拥有独立的 4GB 虚拟地址空间(32 位系统),通过页表(Page Table)映射到物理内存,进程间地址隔离(避免相互干扰)。

内存保护:设置内存页的访问权限(只读、读写、执行),防止非法访问(如用户态程序写内核空间→触发段错误)。

内存映射:支持将物理内存(如外设寄存器)、文件映射到虚拟地址空间(如mmap系统调用)。

嵌入式场景:多进程系统必须启用 MMU(保证进程隔离);部分极简嵌入式系统(如单任务)可禁用 MMU,直接使用物理地址(节省资源)。

14、 根文件系统的作用是什么?列举至少 3 种嵌入式常用的根文件系统类型,并说明特点

核心要点:

作用:根文件系统(rootfs)是 Linux 启动后挂载的第一个文件系统,提供用户空间基础环境(命令、库、配置文件、设备节点等),是用户态程序运行的载体。

常用类型及特点:

特点:只读压缩文件系统,节省存储空间,适合固定固件(如路由器、IoT 设备),需配合 overlayfs 实现可写。

特点:基于内存的临时文件系统,读写速度快,重启后数据丢失,适合存储临时文件(如/tmp)。

特点:专为 NAND Flash 设计,支持坏块管理、磨损均衡,适合嵌入式 Flash 设备(如旧款 Android 手机)。缺点:不支持硬盘等块设备。

特点:支持大文件、日志功能(崩溃后可恢复),适合本地存储(如 eMMC、SSD)。缺点:占用空间较大,不适合小容量 Flash。

ext4:

yaffs2:

tmpfs:

squashfs:

15、如何在嵌入式 Linux 中实现用户空间与内核空间的数据交互?至少列举 3 种方式

核心要点:用户态(应用程序)与内核态(驱动 / 内核模块)隔离,需通过内核提供的接口交互:

系统调用(syscall):

应用通过库函数(如open/read)触发系统调用,陷入内核态,内核处理后返回结果(如操作字符设备)。

procfs/sysfs:

procfs/proc):虚拟文件系统,内核通过文件暴露进程、内存等信息(如/proc/meminfo),应用可读写。

sysfs/sys):按设备层级组织的虚拟文件系统,驱动通过 sysfs 属性(如/sys/class/leds/led0/brightness)暴露设备状态,应用直接读写文件交互。

共享内存(mmap):驱动通过remap_pfn_range将内核内存(如外设缓冲区)映射到用户虚拟地址,应用通过mmap获取地址后直接读写,适合大数据量交互(如摄像头数据)。

netlink 套接字:基于网络协议的用户 - 内核通信机制,支持异步消息传递(如内核主动通知用户态事件,比系统调用更灵活)。

16、 嵌入式 Linux 中,如何配置网络(静态 IP、DNS)?简述流程

核心要点:配置网络需设置 IP 地址、子网掩码、网关、DNS 服务器,常用方式:

临时配置(重启失效):

设置 IP:ifconfig eth0 192.168.1.100 netmask 255.255.255.0

设置网关:route add default gw 192.168.1.1

设置 DNS:echo "nameserver 8.8.8.8" > /etc/resolv.conf

永久配置(基于 systemd):

创建配置文件

/etc/systemd/network/eth0.network
[Match]
Name=eth0  # 匹配网卡名

[Network]
Address=192.168.1.100/24  # IP+子网掩码
Gateway=192.168.1.1
DNS=8.8.8.8
DNS=114.114.114.114

重启网络服务:systemctl restart systemd-networkd

17、基于 BusyBox(极简系统):

/etc/init.d/rcS脚本中添加临时配置命令,开机自动执行。

18、 列举 3 种嵌入式 Linux 常用的调试工具,并说明其用途

核心要点:

dmesg:

用途:查看内核日志(如驱动加载信息、中断触发、Oops 错误),定位内核 / 驱动问题(如dmesg | grep "mychar"查看自定义驱动日志)。

strace:用途:跟踪用户态程序的系统调用(如open/read/write)和信号,定位应用程序错误(如 “Permission denied” 是哪个系统调用失败)。

示例:strace ./app打印 app 的所有系统调用。

gdb + gdbserver:

用途:远程调试嵌入式设备上的应用程序,支持断点、变量查看、堆栈跟踪(需设备端运行 gdbserver,开发机用 gdb 连接)。

示例:设备端gdbserver :1234 ./app,开发机arm-linux-gnueabihf-gdb ./app,然后target remote 192.168.1.100:1234

perf:

    • 用途:性能分析工具,统计 CPU 使用率、函数调用次数、缓存命中率,定位程序性能瓶颈(如嵌入式设备中哪个函数占用 CPU 过高)。

19、Norflash、eMMC、Nand 的差异

维度 Norflash Nandflash eMMC
存储介质 或非门(NOR)结构 与非门(NAND)结构 封装 Nandflash + 控制器
读写特性 支持随机读取(像内存),写 / 擦除慢 顺序读写快,随机读慢(需页缓存) 继承 Nand 特性,控制器优化读写
擦除单位 大(如 64KB) 小(如 128KB) 由控制器管理
可靠性 位反转少,无需 ECC 易位反转,需 ECC 校验 控制器集成 ECC,可靠性高
用途 存启动代码(如 U-Boot) 存大量数据(如系统镜像) 嵌入式设备主存储(如手机、开发板)

20、写一个按键驱动从内核到应用层的过程

硬件与设备树:在设备树中定义按键引脚(如key-gpio = <&gpio5 3 GPIO_ACTIVE_LOW>),指定中断触发方式(如下降沿)。

内核驱动实现:注册字符设备或使用 input 子系统(更标准);解析设备树,获取 GPIO 和中断号,申请 GPIO 为中断模式;

注册中断处理函数(上半部):检测按键按下 / 松开,记录状态;下半部(如工作队列):通过 input 子系统上报事件(input_report_key)。

驱动加载:编译为.ko 模块,insmod加载,生成设备节点(如/dev/input/event1)。

应用层访问:打开/dev/input/event1,通过read读取input_event结构体,解析typeEV_KEY)、code(按键码)、value(0/1 表示松开 / 按下)。

21、I2C 如何保证主从设备通信一致?

约定相同波特率、设备地址和帧格式;

通过起始 / 停止位同步通信时序;

从设备以 ACK 应答确认接收,时钟拉伸协调主从速率;

上拉电阻 + 开漏输出实现线与逻辑,避免总线冲突。

22、 I2C、UART、SPI 通信协议的差异

维度 I2C UART SPI
信号线 SDA(数据)、SCL(时钟) TX(发送)、RX(接收) SCK(时钟)、MOSI(主发从收)、MISO(主收从发)、CS(片选)
同步方式 同步(SCL 时钟) 异步(波特率约定) 同步(SCK 时钟)
速率 低速(标准 100kbps,高速 400kbps) 低速(通常≤1Mbps) 高速(可达几十 Mbps)
拓扑结构 多主多从(通过地址区分) 点对点 一主多从(通过 CS 片选)
应用场景 传感器(温湿度、陀螺仪 调试打印、模块通信(GPS) 高速外设(显示屏、Flash)

23、 Recovery 系统的理解,以及变砖了怎么处理?

Recovery 系统:独立于主系统的小型引导环境,用于系统修复、升级、恢复出厂设置。通常存储在单独的分区(如 recovery 分区),由 bootloader 启动。核心功能:挂载系统分区、执行 OTA 升级脚本、清除数据。

变砖处理:若能进入 Recovery:通过 SD 卡或 ADB 推送正确固件,执行 “从 SD 卡更新”。若无法进入 Recovery(硬砖):使用线刷工具(如 SP Flash Tool、J-Link),通过 USB 或调试接口强制刷写 bootloader 和系统分区,恢复启动链。

24、pin 复用是怎么做的?**

pin 复用指一个物理引脚可映射到多个功能(如 GPIO、UART_TX、SPI_CLK),实现步骤:

硬件层面:芯片手册定义引脚的复用功能(如引脚 10 可复用为 GPIO5_3 或 UART2_TX),通过复用寄存器配置。

设备树层面:在设备树中通过pinctrl节点指定引脚功能,例如:

pinctrl_uart2: uart2grp {
  pins = "pin10";
  function = "uart2"; // 复用为UART2_TX
};

驱动层面:驱动通过devm_pinctrl_get_select绑定设备树的 pinctrl 配置,内核自动解析并配置复用寄存器,完成引脚功能切换。

25、复位高低电平不一样,怎么处理?

复位信号有 “高电平有效” 和 “低电平有效”,处理方式:

设备树中声明极性:通过属性指定复位电平,例如:

reset-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>; // 高电平有效

驱动中适配:

解析设备树属性获取极性,复位时输出对应电平:

高电平有效:复位时gpio_set_value(reset_gpio, 1),结束后拉低;

低电平有效:复位时gpio_set_value(reset_gpio, 0),结束后拉高。

复用通用函数:使用内核 gpio 接口(如gpio_set_active_low)自动处理极性转换,无需关心硬件电平细节。

26、复位延时不一样,怎么处理?

不同外设复位所需延时不同(如有的需 10ms,有的需 100ms),处理方式:

设备树动态配置:通过自定义属性指定延时,例如:

reset-delay-ms = <50>; // 复位延时50ms

驱动中读取延时:驱动解析设备树属性,使用内核延时函数等待:

unsigned int delay;
of_property_read_u32(dev->of_node, "reset-delay-ms", &delay);
msleep(delay); // 毫秒级延时(进程上下文)
// 若在中断上下文,用udelay(微秒级,不睡眠)

兼容固定延时:对无设备树配置的场景,设置默认延时(参考外设 datasheet),确保兼容性。

27、RTC 怎么和 Linux 进行时间同步?

Linux RTC 的时间同步通过 RTC 驱动和用户态工具实现:

内核层面:RTC 驱动(如rtc-ds1307)注册为/dev/rtc0,内核启动时通过rtc_read_time读取 RTC 时间,同步到系统时间(xtime)。

用户层面:

系统运行时,通过date命令修改系统时间后,用hwclock -w将系统时间写入 RTC;定时同步:通过 cron 定时执行hwclock -w,或驱动中实现周期性同步(如每小时)。

自动同步:内核配置CONFIG_RTC_SYSTOHC后,系统会定期将系统时间同步到 RTC。

28、看门狗的原理?

看门狗(Watchdog)是硬件定时器,核心原理:

初始化时设置超时时间(如 500ms),定时器开始倒计时;

系统需定期 “喂狗”(通过寄存器或驱动接口重置定时器),否则超时后硬件触发系统复位(防止死机)。

分为:

硬件看门狗:独立芯片(如 MAX706)或 SOC 内置模块,可靠性高;

软件看门狗:内核模块模拟(如 softdog),依赖内核正常运行,可靠性低。

喂狗的时间?

喂狗时间需满足:喂狗周期 < 看门狗超时时间 < 系统最大允许无响应时间。

    例如:系统正常处理周期为 100ms,喂狗周期可设为 200ms,超时时间设为 500ms(预留容错空间)。参考依据:外设 datasheet(看门狗最小 / 最大超时时间)、系统业务最大响应延迟(如传感器采集周期)。
应用层是怎么喂狗的,定时器还是直接 while 1?

推荐用定时器喂狗,优势:

定时器方式:通过setitimertimerfd创建定时事件(如 200ms 一次),定时向/dev/watchdog写入数据(如write(fd, "1", 1)),不占用 CPU,效率高。

避免while(1):会持续占用 CPU(即使加sleep,精度也较低),且无法处理线程异常(如崩溃后无法喂狗)。

增强可靠性:结合进程监控(如 systemd),若喂狗线程崩溃,监控进程重启线程或触发复位。

29、内核有没有打实时 patch?

判断方法:

查看内核配置:zcat /proc/config.gz | grep CONFIG_PREEMPT_RT,若为 y 则打了实时补丁(PREEMPT_RT)。

查看内核版本:

uname -a,实时内核通常含 rt 标识(如 5.10.100-rt50)。

实时 patch 通过 “全抢占内核”“中断线程化” 等优化,将内核响应延时从毫秒级降至微秒级,适合工业控制自动驾驶等低延迟场景。

30、SCHED_FIFO 线程一直占用怎么办?

SCHED_FIFO 是实时调度策略(优先级 1-99),高优先级线程会抢占 CPU,若一直占用会导致低优先级线程 “饿死”,解决方法:

主动让出 CPU:线程中定期调用schedule_yield(),允许同优先级线程运行。限制运行时长:通过定时器(如 timerfd)强制线程退出临界区,避免无限循环。合理设置优先级:非关键线程设低优先级(如 50 以下),预留高优先级给紧急任务。结合同步机制:用信号量、互斥锁限制线程运行范围,确保资源释放。

31、线程间的work_queue(工作队列)和irq_thread(中断线程)有什么核心区别?

参考答案

调度方式:

work_queue由内核线程(kworker)统一调度,优先级较低;

irq_thread是独立线程,可设置高优先级。

触发方式:

work_queue需手动调用schedule_work触发;

irq_thread由中断自动唤醒(内核关联中断与线程)。

适用场景:

work_queue用于通用延迟任务;

irq_thread适合中断处理较复杂但需睡眠的场景(如 I2C 通信)。

32、实时操作系统(RTOS)与通用操作系统(如 Linux)的核心区别是什么?

参考答案

    核心目标:RTOS 聚焦 “实时性”(任务在截止时间内完成,响应时间可预测);通用 OS 优化吞吐量和资源利用率。调度策略:RTOS 采用优先级抢占式调度,高优先级任务可立即抢占 CPU;通用 OS 采用复杂调度(如 Linux CFS),响应时间可能波动。中断延迟:RTOS 中断延迟为微秒级(严格控制);通用 OS 默认中断延迟为毫秒级(非实时)。

这些题目覆盖了进程 / 线程通信、Linux 指令、网络协议、驱动开发、实时系统等核心考点,可用于检验对基础概念和实际应用的理解。

三、面试部分

1. 自我介绍

(结合岗位需求调整,以嵌入式开发岗为例)

“我叫 XX,毕业于 XX 学校 XX 专业,有 X 年嵌入式 Linux 开发经验。主要擅长 Linux 内核驱动开发(如字符设备、I2C/SPI 外设驱动)、设备树配置和应用层编程,熟悉 ARM 架构和常用通信协议(I2C/UART/SPI)。曾参与 XX 项目(如智能硬件传感器模块开发),负责从硬件驱动到应用层接口的全流程实现,解决过 XX 技术问题(如中断冲突、设备树兼容性)。熟练使用 GCC、Makefile、Git 等工具,了解数据结构和操作系统基础。期待加入贵团队,参与底层开发相关工作。”

2、之前做过哪些驱动开发?

(结合实际项目举例,突出技术栈和解决的问题)

“主要做过几类驱动开发:

字符设备驱动:如按键、LED、蜂鸣器,基于 GPIO 子系统实现,通过 input 子系统上报按键事件,支持中断触发;

总线设备驱动:I2C 接口的温湿度传感器(SHT30)、SPI 接口的显示屏(OLED),适配设备树,实现设备探测、数据读写逻辑;

外设驱动:UART 串口驱动(调试用)、RTC 实时时钟(DS3231),解决过时钟同步和中断冲突问题;

虚拟设备驱动:模拟一个字符设备用于进程间通信,通过 ioctl 提供控制接口。

开发中涉及设备树配置、中断处理、下半部机制(workqueue)、sysfs 接口封装等。”

end

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

公众号『一口Linux』号主彭老师,拥有15年嵌入式开发经验和培训经验。曾任职ZTE,某研究所,华清远见教学总监。拥有多篇网络协议相关专利和软件著作。精通计算机网络、Linux系统编程、ARM、Linux驱动、龙芯、物联网。原创内容基本从实际项目出发,保持原理+实践风格,适合Linux驱动新手入门和技术进阶。