找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2073

积分

0

好友

290

主题
发表于 昨天 01:26 | 查看: 5| 回复: 0

一、基础部分

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 的目的是什么?

参考答案

  1. 确保最后一个 ACK 被被动关闭方收到:若被动方未收到,会重发 FIN,主动方可在 TIME_WAIT 期间再次回复。
  2. 防止旧连接的报文干扰新连接:2MSL 是报文最大生存时间的 2 倍,确保本次连接的所有报文已从网络中消失。

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

  • 工厂模式:创建型设计模式,定义一个工厂类负责创建其他类的实例,隐藏对象创建的细节(如具体类名、初始化参数)。分为简单工厂、工厂方法、抽象工厂。
  • 为什么需要:
    • 解耦:调用方无需知道具体类名,只需通过工厂接口获取对象,降低依赖。
    • 扩展性:添加新类型时,只需扩展工厂类,无需修改调用方代码(符合开闭原则)。
    • 统一管理:对象创建逻辑集中在工厂,便于维护(如统一初始化、日志记录)。
    • 示例:日志系统中,工厂根据配置创建 “文件日志” 或 “控制台日志” 实例,调用方无需修改。

17、分析题

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

参考答案

  1. DNS 服务器配置错误:/etc/resolv.confnameserver 未配置或指向无效 IP。
  2. 本地 Hosts 文件异常:/etc/hosts 中错误映射目标域名,导致解析冲突。
  3. DNS 端口被拦截:防火墙封禁 UDP 53 端口(DNS 默认端口),无法向 DNS 服务器发送请求。
  4. 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→内核启动→根文件系统挂载→用户程序启动” 五个阶段:

  1. 硬件上电:CPU 从复位向量(如 0x0 地址)执行,初始化片内 SRAM、时钟等基础硬件。
  2. Bootloader 阶段:
    • 初始化 DRAM、外设(如串口),将内核镜像(zImage)和设备树(.dtb)从 Flash 加载到 DRAM。
    • 设置内核启动参数(如 bootargs),跳转到内核入口地址(如 start_kernel)。
  3. 内核启动:
    • 初始化内核核心组件(进程管理、内存管理、中断系统),解析设备树,初始化驱动(平台设备、总线驱动匹配)。
    • 挂载根文件系统(根据 bootargs 中的 root=/dev/mmcblk0p2 等参数)。
  4. 用户空间初始化:启动 init 进程(PID=1),解析 /etc/inittab 或 systemd 配置,启动服务(如网络、日志)。
  5. 运行应用程序: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  // 动态分配主设备号

static int major = DEV_MAJOR;
static struct cdev my_cdev;
static char data_buf[1024];  // 数据缓冲区

// open操作
static int my_open(struct inode *inode, struct file *filp)
{
    printk("mychar device opened\n");
    return 0;
}

// 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;
}

// 文件操作集合
static struct 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=%d\n", major);
    return 0;
}

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

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

测试方法

  1. 编译驱动为 .ko 模块,加载:insmod mychar.ko
  2. 创建设备节点:mknod /dev/mychar c <major> 0(major 为驱动打印的主设备号)。
  3. 编写测试程序(用户态):
    #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: %s\n", buf);  // 输出 "hello"
    close(fd);
    return 0;
    }

5、中断的概念?

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

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

  • 裸机(STM32):HAL_NVIC_EnableIRQ()(使能中断)、HAL_GPIO_EXTI_Callback()(中断回调);
  • Linux 内核:request_irq()(注册中断服务函数)。

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

  • 执行时间尽量短,避免阻塞 / 休眠;
  • 用自旋锁(而非互斥锁)保护共享资源;
  • 禁止不必要的中断嵌套,用 volatile 修饰共享变量;
  • 及时保存和恢复现场。

8、如何写一个中断?

步骤:

  1. 获取中断号:
    • 设备树中指定:interrupts = <GIC_SPI 16 IRQ_TYPE_EDGE_FALLING>;(边缘触发);
    • 驱动中解析:irq = platform_get_irq(pdev, 0);
  2. 注册中断处理函数:
    int ret = request_irq(irq, irq_handler, IRQF_TRIGGER_FALLING, "my_device", dev);
    • irq_handler:上半部函数(快速处理,如清中断标志),返回 IRQ_HANDLED
  3. 处理下半部:若需耗时操作(如数据处理),在中断上半部中调度下半部(如 workqueue):
    static irqreturn_t irq_handler(int irq, void *dev_id)
    {
    schedule_work(&dev->work); // 调度workqueue
    return IRQ_HANDLED;
    }
  4. 注销中断: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):
    • 功能:处理耗时操作(如数据处理、唤醒任务),允许中断嵌套(不阻塞其他中断)。
    • 实现方式:
      1. tasklet:基于软中断,适合小任务(原子上下文,不能睡眠);
      2. 工作队列(workqueue):基于内核线程,适合大任务(可睡眠,允许阻塞操作);
      3. 软中断:内核级异步处理(如网络、块设备,不建议驱动直接使用)。

示例:串口接收中断

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

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

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

  1. CFS(Completely Fair Scheduler):
    • 非实时调度,默认策略(SCHED_OTHER/SCHED_NORMAL),基于 “虚拟运行时间” 分配 CPU,确保各进程公平执行。
    • 适用:普通应用程序(如后台服务、UI 界面)。
  2. 实时调度:
    • SCHED_FIFO(先进先出):高优先级进程一旦获取 CPU,一直运行直到主动放弃或被更高优先级进程抢占。
    • SCHED_RR(时间片轮转):同优先级实时进程按时间片轮流执行。
    • 适用:嵌入式实时任务(如传感器数据采集、电机控制,需严格截止时间)。
  3. 其他策略:
    • SCHED_IDLE:最低优先级,仅当系统空闲时运行(如后台日志清理)。

嵌入式选择原则

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

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

  • 中断不能堵塞的原因:中断是硬件触发的紧急事件(如按键按下、数据到达),需要快速响应;中断处理时会屏蔽同类型中断,若堵塞(如执行耗时操作),会导致后续同类型中断丢失。
  • 堵塞的后果:
    • 错过关键硬件事件(如传感器数据溢出、通信超时);
    • 系统响应延迟,甚至卡顿(其他中断也可能被间接影响);
    • 严重时导致 watchdog 超时(硬件复位),系统崩溃。

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

核心要点:

  • 定义:MMU 是 CPU 内的硬件单元,负责虚拟地址到物理地址的转换,提供内存保护。
  • 核心作用:
    1. 虚拟内存管理:每个进程拥有独立的 4GB 虚拟地址空间(32 位系统),通过页表(Page Table)映射到物理内存,进程间地址隔离(避免相互干扰)。
    2. 内存保护:设置内存页的访问权限(只读、读写、执行),防止非法访问(如用户态程序写内核空间→触发段错误)。
    3. 内存映射:支持将物理内存(如外设寄存器)、文件映射到虚拟地址空间(如 mmap 系统调用)。
  • 嵌入式场景:
    • 多进程系统必须启用 MMU(保证进程隔离);
    • 部分极简嵌入式系统(如单任务)可禁用 MMU,直接使用物理地址(节省资源)。

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

核心要点:

  • 作用:根文件系统(rootfs)是 Linux 启动后挂载的第一个文件系统,提供用户空间基础环境(命令、库、配置文件、设备节点等),是用户态程序运行的载体。
  • 常用类型及特点:
    1. squashfs
      • 特点:只读压缩文件系统,节省存储空间,适合固定固件(如路由器、IoT 设备),需配合 overlayfs 实现可写。
    2. tmpfs
      • 特点:基于内存的临时文件系统,读写速度快,重启后数据丢失,适合存储临时文件(如 /tmp)。
    3. yaffs2
      • 特点:专为 NAND Flash 设计,支持坏块管理、磨损均衡,适合嵌入式 Flash 设备(如旧款 Android 手机)。
      • 缺点:不支持硬盘等块设备。
    4. ext4
      • 特点:支持大文件、日志功能(崩溃后可恢复),适合本地存储(如 eMMC、SSD)。
      • 缺点:占用空间较大,不适合小容量 Flash。

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

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

  1. 系统调用(syscall):
    • 应用通过库函数(如 open/read)触发系统调用,陷入内核态,内核处理后返回结果(如操作字符设备)。
  2. procfs/sysfs:
    • procfs/proc):虚拟文件系统,内核通过文件暴露进程、内存等信息(如 /proc/meminfo),应用可读写。
    • sysfs/sys):按设备层级组织的虚拟文件系统,驱动通过 sysfs 属性(如 /sys/class/leds/led0/brightness)暴露设备状态,应用直接读写文件交互。
  3. 共享内存(mmap):
    • 驱动通过 remap_pfn_range 将内核内存(如外设缓冲区)映射到用户虚拟地址,应用通过 mmap 获取地址后直接读写,适合大数据量交互(如摄像头数据)。
  4. netlink 套接字:
    • 基于网络协议的用户 - 内核通信机制,支持异步消息传递(如内核主动通知用户态事件,比系统调用更灵活)。

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

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

  1. 临时配置(重启失效):
    • 设置 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
  2. 永久配置(基于 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 常用的调试工具,并说明其用途

核心要点:

1. dmesg:
   - 用途:查看内核日志(如驱动加载信息、中断触发、Oops 错误),定位内核 / 驱动问题(如 `dmesg | grep "mychar"` 查看自定义驱动日志)。
2. strace:
   - 用途:跟踪用户态程序的系统调用(如 `open`/`read`/`write`)和信号,定位应用程序错误(如 “Permission denied” 是哪个系统调用失败)。
   - 示例:`strace ./app` 打印 app 的所有系统调用。
3. gdb + gdbserver:
   - 用途:远程调试嵌入式设备上的应用程序,支持断点、变量查看、堆栈跟踪(需设备端运行 gdbserver,开发机用 gdb 连接)。
   - 示例:设备端 `gdbserver :1234 ./app`,开发机 `arm-linux-gnueabihf-gdb ./app`,然后 `target remote 192.168.1.100:1234`。
4. perf:
   - 用途:性能分析工具,统计 CPU 使用率、函数调用次数、缓存命中率,定位程序性能瓶颈(如嵌入式设备中哪个函数占用 CPU 过高)。

### 19、Norflash、eMMC、Nand 的差异

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

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

1. **硬件与设备树**:在设备树中定义按键引脚(如 `key-gpio = <&gpio5 3 GPIO_ACTIVE_LOW>`),指定中断触发方式(如下降沿)。
2. 内核驱动实现:
   - 注册字符设备或使用 input 子系统(更标准);
   - 解析设备树,获取 GPIO 和中断号,申请 GPIO 为中断模式;
   - 注册中断处理函数(上半部):检测按键按下 / 松开,记录状态;
   - 下半部(如工作队列):通过 input 子系统上报事件(`input_report_key`)。
3. **驱动加载**:编译为 .ko 模块,`insmod` 加载,生成设备节点(如 `/dev/input/event1`)。
4. **应用层访问**:打开 `/dev/input/event1`,通过 `read` 读取 `input_event` 结构体,解析 `type`(`EV_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),实现步骤:

1. 硬件层面:芯片手册定义引脚的复用功能(如引脚 10 可复用为 GPIO5_3 或 UART2_TX),通过复用寄存器配置。
2. 设备树层面:在设备树中通过 pinctrl 节点指定引脚功能,例如:

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

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

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

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

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

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

2. 驱动中适配:解析设备树属性获取极性,复位时输出对应电平:
   - 高电平有效:复位时 `gpio_set_value(reset_gpio, 1)`,结束后拉低;
   - 低电平有效:复位时 `gpio_set_value(reset_gpio, 0)`,结束后拉高。
3. 复用通用函数:使用内核 gpio 接口(如 `gpio_set_active_low`)自动处理极性转换,无需关心硬件电平细节。

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

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

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

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

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

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


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

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

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

1. 内核层面:RTC 驱动(如 `rtc-ds1307`)注册为 `/dev/rtc0`,内核启动时通过 `rtc_read_time` 读取 RTC 时间,同步到系统时间(`xtime`)。
2. 用户层面:
   - 系统运行时,通过 `date` 命令修改系统时间后,用 `hwclock -w` 将系统时间写入 RTC;
   - 定时同步:通过 cron 定时执行 `hwclock -w`,或驱动中实现周期性同步(如每小时)。
3. 自动同步:内核配置 `CONFIG_RTC_SYSTOHC` 后,系统会定期将系统时间同步到 RTC。

### 28、看门狗的原理?

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

- 初始化时设置超时时间(如 500ms),定时器开始倒计时;
- 系统需定期 “喂狗”(通过寄存器或驱动接口重置定时器),否则超时后硬件触发系统复位(防止死机)。
  分为:
- 硬件看门狗:独立芯片(如 MAX706)或 SOC 内置模块,可靠性高;
- 软件看门狗:内核模块模拟(如 softdog),依赖内核正常运行,可靠性低。

#### 喂狗的时间?

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

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

#### 应用层是怎么喂狗的,定时器还是直接 while 1?

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

- 定时器方式:通过 `setitimer` 或 `timerfd` 创建定时事件(如 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,若一直占用会导致低优先级线程 “饿死”,解决方法:

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

### 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 接口封装等。”

### 更多学习资源
希望这份涵盖从[C/C++](https://yunpan.plus/f/25-1)基础到[操作系统](https://yunpan.plus/f/36-1)核心概念的面试题解析能对你的学习和求职有所帮助。如果你想与更多开发者交流或寻找更系统的学习路径,可以到[云栈社区](https://yunpan.plus)探索相关技术专题和讨论。



上一篇:CockroachDB分布式SQL数据库解析:机房断电如何保障数据不丢失?
下一篇:Go依赖图谱深度解析:基于4000万个模块的Go Proxy数据洞察
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 14:18 , Processed in 0.249712 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表