ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

影石系统组 一二面OC面经

影石系统组 一二面OC面经 一面1. 封装、继承、多态分别是什么什么是封装继承有哪些继承方式封装将属性和方法封装到类中隐藏内部实现细节对外提供访问接口保护数据。继承子类复用父类的成员扩展功能。C 继承权限公有 public、保护 protected、私有 private还有形式上单继承、多继承、菱形继承。Java 只支持单继承无保护 / 私有继承。多态同一个方法调用根据对象实际类型执行不同实现依托继承 方法重写 父类引用指向子类对象。2. 类中使用static修饰函数和变量分别有什么作用静态成员变量什么时候初始化静态成员函数和普通成员函数相比有什么限制static 修饰变量静态成员变量属于类所有对象共享同一份,无需实例对象即可访问。Cmain 执行前类外初始化仅一次可以用来统计对象数量、全局计数器、类公共常量等。不随对象创建而分配内存不随对象销毁释放。static 修饰函数静态成员函数属于类无需实例对象即可调用。静态函数属于类不需要创建对象就可以直接调用类名::函数名()。 一般用来操作静态成员变量提供类层面的工具方法。静态函数限制没有 this 指针无法访问非静态成员变量、无法调用非静态成员函数。3. 类对象有哪些初始化方式默认构造初始化有参构造函数初始化拷贝构造初始化初始化列表构造初始化列表推荐优先执行移动构造初始化4. 移动构造函数有什么特点参数为本类右值引用T转移临时对象资源不做深拷贝性能高接管资源后源对象要置空防止析构重复释放仅接收右值可通过std::move将左值转为右值强制移动若自己写了析构 / 拷贝构造 / 拷贝赋值编译器不再自动生成默认移动构造建议加noexcept便于 STL 容器优化扩容。5. 如何理解C中的左值和右值左值有名字、可取地址生命周期长右值临时对象 / 字面量无名字不可取地址包含纯右值、将亡值T绑左值const T左右都能绑T只能绑右值std::move 仅仅类型转换不移动资源将左值转为将亡值移动构造参数是T接收右值实现资源转移。6. Lambda表达式的机制是什么Lambda表达式有什么作用一、Lambda 作用Lambda 是匿名内联函数可以在代码原地快速定义一个短小的函数不用单独写全局函数 / 类成员函数。简化代码不用单独命名函数适合 STL 算法sort、for_each的回调可以捕获当前作用域的变量使用外面的数据普通函数做不到可作为回调、异步任务、线程函数传入。示例sort(vec.begin(), vec.end(), [](int a,int b){return ab;});二、底层机制核心考点Lambda 本质编译器自动生成一个匿名类仿函数类然后创建这个类的临时对象。仿函数重载了operator()的类对象可以像函数一样调用。完整语法回顾[捕获列表](参数列表) mutable - 返回类型 {函数体}[]捕获列表决定怎么拿外部变量()形参列表mutable允许修改值捕获的变量默认 const-显式指定返回值可省略C14 支持自动推导{}函数体捕获方式[]值捕获拷贝外部变量到仿函数成员默认 const不能直接修改要加mutable[]引用捕获保存变量引用直接操作外部原变量[this]捕获 this 指针访问类内成员[x]只值捕获 x[x]只引用捕获 x[]不捕获任何变量捕获时机Lambda 对象创建的时候捕获不是调用的时候值捕获是拷贝那一刻的值引用捕获是引用原变量。7. Lambda所谓的“更方便”具体体现在哪里不用单独定义函数 / 仿函数就地写回调就近捕获局部变量代码逻辑集中在一起不用来回翻代码。8. Lambda和普通函数相比有什么优势能否从Lambda的底层实现讲讲它为什么更方便优势就地定义回调逻辑和调用代码放在一起阅读方便支持捕获局部变量自动保存上下文不需要手动封装结构体匿名不污染命名空间适合一次性短逻辑捕获方式灵活值捕获 / 引用捕获 / 捕获 this 按需选择。底层原理 Lambda 本质是编译器自动生成匿名仿函数类重载operator()捕获列表决定编译器生成哪些成员变量来保存外部上下文。原来手写仿函数要写的类、成员、构造函数全部由编译器自动生成所以代码写起来更简洁9. 是否了解智能指针什么是共享智能指针shared_ptr智能指针是基于 RAII 封装裸指针的类对象析构时自动释放内存避免内存泄漏。shared_ptr 是共享智能指针底层采用引用计数多个 shared_ptr 可以共享同一块堆资源每新增一个管理指针计数 1每销毁一个计数 - 1计数归 0 则释放内存。 缺点有额外开销要维护控制块存在循环引用问题需要 weak_ptr 解决。10. 除了shared_ptr其他智能指针有什么特点unique_ptr独占不能拷贝支持 move无引用计数开销最小资源只能归一个管理者。shared_ptr共享引用计数支持拷贝多指针共管资源存在循环引用问题有额外控制块开销。weak_ptr弱指针配合 shared_ptr不增加引用计数仅观察解决循环引用不能单独使用。11. unique_ptr有什么特点unique_ptr独占不能拷贝支持 move无引用计数开销最小资源只能归一个管理者。12. weak_ptr有什么作用weak_ptr弱指针配合 shared_ptr不增加引用计数仅观察解决循环引用不能单独使用。13. 数组和链表有什么区别数组和链表分别适合什么场景什么时候使用数组什么时候使用链表数组是连续内存支持随机访问查找快但中间增删要移动元素链表节点分散靠指针连接不支持随机访问查找慢但找到节点后增删只改指针。需要随机访问、查询多增删少用数组频繁在中间 / 头部插入删除不需要随机访问时用链表14. Linux中进程和线程有什么区别一、核心定义进程资源分配的最小单位。每个进程有独立的地址空间、文件描述符、信号处理、堆、环境变量。进程之间完全隔离。线程CPU 调度的最小单位。一个进程内的多个线程共享进程地址空间线程有自己私有的栈、寄存器、线程局部存储 TLS。二、详细对比地址空间进程独立虚拟地址空间进程互不访问。线程同进程内所有线程共享同一个虚拟地址空间可以直接读写全局变量。资源进程独立 fd 表、信号处理、工作目录、用户 ID。线程共享 fd、信号处理、工作目录、用户 ID。每个线程只有私有栈。创建开销进程fork()拷贝页表开销大。线程pthread_create()只创建 task_struct 线程栈开销远小于 fork。通信方式进程间通信 IPC管道、消息队列、共享内存、信号量、socket。跨进程通信麻烦。线程间通信直接读写全局变量配合互斥锁 / 条件变量简单但容易出现竞态。崩溃影响一个进程崩溃不会影响其他进程。一个线程崩溃比如段错误整个进程直接退出所有线程一起挂掉。切换开销进程切换要切换页表、刷新 TLB开销大。线程切换同一地址空间不用切换页表TLB 不用刷新开销小很多。三、适用场景✅多进程任务之间隔离性要求高一个任务崩溃不能影响其他任务nginx 多进程、web 服务多核充分利用不受 GIL 限制Python计算任务相互独立✅多线程任务之间数据交互频繁需要大量共享数据IO 密集型场景网络服务等待 socket 读写内存资源有限不想重复占用大量内存四、面试一句话精简版口述进程是资源分配最小单位拥有独立地址空间相互隔离创建和切换开销大线程是 CPU 调度最小单位同进程线程共享地址空间切换开销小一个线程崩溃会导致整个进程退出。进程通信要用 IPC线程共享全局变量需要锁保护。15. 进程中的堆和栈是否在线程间共享堆属于进程地址空间同一个进程内所有线程共享堆内存栈是每个线程私有的每个线程拥有独立栈空间不能互相访问。全局变量共享线程栈上的局部变量不共享。16. 同一进程的线程还共享哪些资源同一进程的线程共享虚拟地址空间代码段、全局变量、堆、文件描述符表、信号处理函数、工作目录、uid/gid、加载的动态库、进程资源限制。线程私有线程栈、寄存器、TLS、信号掩码、tid、errno。17. mmap区域应该怎么理解mmap得到的空间叫什么mmap是 Linux 系统调用把一段虚拟内存区域映射到某个物理载体可以是文件、匿名物理内存。进程的虚拟地址空间里这一块区域就叫mmap 映射区。注意mmap 分配出来的内存不属于堆heap堆是 brk 系统调用管理的mmap 是独立的虚拟内存区域。两种常用 mmap文件映射file-backed mmap将文件的一部分映射到进程虚拟地址。访问这段虚拟地址就相当于读写文件由内核自动完成页故障加载磁盘数据。用途大文件读写、动态库.so加载so 就是通过 mmap 映射进进程地址空间匿名映射anonymous mmapMAP_ANONYMOUS不关联任何文件映射到内核的匿名物理内存。用途分配大块内存malloc分配大内存128KB 左右不同 libc 配置底层就是调用mmap匿名映射。mmap 得到的空间叫什么标准叫法mmap 映射区 / 映射虚拟内存区域匿名 mmap匿名映射区文件 mmap文件映射区进程地址空间布局 代码段 → data/BSS → 堆 (brk 向上增长) →mmap 映射区向下增长→ 线程栈核心要点mmap 只建立虚拟地址到物理 / 文件的映射关系调用 mmap 时不会立刻分配物理内存只有第一次访问该虚拟地址时触发缺页异常内核才分配物理页惰性分配。虚拟内存属于进程同进程多个线程都可以访问这块 mmap 区域共享。映射区域释放munmap不能用 free 释放 mmap 出来的内存。malloc小内存用brk在堆上扩展大块内存直接mmapfree的时候会调用munmap直接归还物理内存。18. mmap区域一般位于哪里mmap区域有没有大小限制mmap 区域位于进程虚拟地址空间中堆和线程栈之间堆向上增长mmap 区域向下增长动态库、匿名映射都放在这里。mmap 存在多层限制一是vm.max_map_count限制映射段的最大数量二是RLIMIT_AS限制进程整体虚拟地址空间上限三是物理内存 swap 的实际容量。64 位下虚拟地址空间很大一般容易先碰到映射段数量限制或者物理内存不足。19. 文件描述符FD是否在线程间共享结论同一个进程内所有线程共享文件描述符表所以 FD 是线程共享的。20. 全局变量是否在线程间共享同进程内所有线程共享全局变量以及静态 static 变量。21. 一个进程有100个线程向第99个线程发送SIGKILL是该线程退出还是整个进程退出整个进程直接退出。22. malloc中常说的128 KB阈值是怎么确定的128KB 是 glibc ptmalloc 中 M_MMAP_THRESHOLD 的默认值是工程经验权衡出来的不是内核硬性规定。大于等于这个阈值的内存申请直接使用 mmap 匿名映射free 时 munmap 一次性归还内存防止大内存造成堆碎片小于阈值用 sbrk 在堆上分配小块内存可以复用堆中空闲块减少系统调用开销。这个阈值默认支持动态调整也可以通过 mallopt 手动修改。23. 为什么超过128 KB使用mmap小于128 KB使用其他方式核心是权衡系统调用开销和内存碎片。小于 128KB 的小块内存使用 brk 堆分配free 后的 chunk 放入空闲链表可以复用减少系统调用小块就算产生碎片浪费有限。大于等于 128KB 的大块内存采用 mmap 分配free 时直接 munmap 将内存归还内核防止大块空闲内存卡在堆中间造成严重外部碎片。128KB 是 glibc ptmalloc 的经验阈值不是内核强制规定也可以用 mallopt 修改。24. 小于128 KB的内存是怎么申请的小于 128KB 的内存malloc 优先在用户态从 fastbin、smallbin 空闲链表寻找空闲 chunk找不到就尝试切割堆末尾的 top chunk。如果 top chunk 空间不足调用 sbrk 系统调用扩展堆的虚拟地址空间。sbrk 只扩大虚拟地址物理内存在访问时才分配。free 后小块 chunk 放回空闲链表内存保留在进程堆中一般不归还内核。25. brk扩展堆之后malloc如何从堆中分配brk 扩展堆后新增加的虚拟内存合并到 top chunk。后续 malloc 先查找 fastbin、smallbin 空闲链表找不到合适 chunk 时就切割 top chunk分出需要的内存给用户剩余部分继续保留为 top chunk整个切割过程是用户态不触发系统调用。如果 top chunk 空间不足才再次调用 brk 扩容堆。top chunk 是堆尾部的空闲块不属于任何 bin 链表。26. 释放内存时会发生什么free 首先根据用户指针找到 chunk 头校验合法性。如果是小于 128KB 的堆 chunk标记为空闲尝试合并相邻空闲块放入 fastbin 或 bin 链表如果空闲块是堆尾 top chunk 且大小超过 M_TRIM_THRESHOLD则调用 sbrk 收缩堆将内存归还内核。堆中间的空闲块不会还给内核只能后续复用。如果是≥128KB 的 mmap 大块free 直接调用 munmap 释放映射内存一次性还给内核不加入空闲链表。free 不会清空内存内容。27. 被释放的内存缓存在哪里是用户态还是内核态小于 128KB 堆内存 free 后空闲 chunk 保存在 glibc 在用户态维护的 fastbin、smallbin 等空闲链表内核不知道堆内部 chunk 空闲状态这部分内存不会交还内核。大于等于 128KB 的 mmap 大块 free 时调用 munmap直接归还内核不会在用户态缓存。堆内部内存块的管理是用户态库的工作内核只维护进程整体虚拟地址空间。28. 如果申请11字节brk扩大堆后释放这11字节时brk会把堆缩小吗不会缩小堆。申请 11 字节ptmalloc 分配对齐后的完整 chunkfree 之后这个 chunk 变成堆中间的空闲块进入 fastbin不会合并到 top chunk。堆收缩需要堆尾部 top chunk 空闲内存超过 M_TRIM_THRESHOLD 默认 128KB 才会触发。堆中间释放的内存无论大小都不会调用 brk 缩小堆。29. 如果不断申请小于128 KB的内存堆岂不是会无限增长不断申请小于 128KB 的内存如果只申请不释放堆会持续 sbrk 扩容但不会无限增长受限于进程虚拟地址限制和物理内存 swap 上限内存耗尽会触发 OOM。如果申请后释放空闲块会放在用户态 fastbin/smallbin 供后续复用堆边界不会回落堆中间的空闲 chunk 无法归还内核只有堆尾 top chunk 空闲超过 M_TRIM_THRESHOLD 才会收缩堆。反复 mallocfree 小内存时堆增长到一定程度就停止但 RSS 不会下降。RSS 是常驻内存集代表进程当前存放在物理内存中的页面大小不包含交换到磁盘的内存。Swap 是磁盘划分出的交换空间物理内存紧张时内核会把进程不常使用的匿名内存页写入 swap 磁盘腾出物理内存访问被换出的内存时再加载回内存。swap 由内核管理用户态 malloc 库感知不到 swap。30. 用户态分配器维护的内存池不是无限的这个问题如何解决用户态内存池不是无限的解决办法是内存池耗尽时通过 mmap 等系统调用动态向内核扩容空闲大块内存可以 munmap 归还内核同时使用多级内存池、空闲块合并减少碎片还可设置最大内存上限做 OOM 兜底。31. glibc如何拆分大块内存并管理不同大小的空闲块glibc ptmalloc 使用 bins 数组分类管理空闲块小块放 fastbin/smallbin大块空闲块存放在 large binlarge bin 内空闲块按大小排序采用 best-fit 最佳适配。分配时找到足够大的大块 chunk若剩余部分能构成最小 chunk则拆分用户需要部分返回剩余 remainder 放入 unsorted binunsorted bin 作为临时缓冲区在下次 malloc 时将空闲块分拣归入对应的 small/large bin。超大内存超过 mmap 阈值直接 mmap释放 munmap不进 bin 链表。32. 什么是页表找到物理页之后如何找到要访问的具体地址一个物理页是4 KB如何定位其中具体的一个字节1. 什么是页表页表是内核维护的一张映射表用来完成「虚拟地址 → 物理地址」的翻译。 CPU 的内存管理单元 MMU在每次访问内存时都会查页表把进程给出的虚拟地址转换成物理内存地址同时页表还保存权限位可读、可写、是否存在做缺页判断、内存保护。每个进程有独立的页表CR3 寄存器保存当前进程页表基地址所以不同进程相同虚拟地址可以映射到不同物理页。2. 找到物理页之后如何找到要访问的具体地址虚拟地址拆成两部分虚拟页号 页内偏移MMU 使用虚拟页号去查页表得到物理页的起始地址物理页基址页内偏移不变直接复用最终物理地址 物理页基址 页内偏移。核心页内偏移不需要转换虚拟地址和物理地址的偏移是完全一样的。3. 4KB 物理页怎么定位页内某个字节4KB 4096 字节\(2^{12}4096\)所以页内偏移占虚拟地址低 12 位。低 12bit页内偏移范围 0 ~ 4095正好可以索引一页内全部 4096 个字节。高地址位虚拟页号用来查页表拿到物理页首地址。举例 假设虚拟地址0x12345ABC4K 页低 12 位0xABC 页内偏移十进制 2748查页表得到物理页起始地址0x80004000目标物理字节地址 0x80004000 0xABC也就是说物理页基址 12 位页内偏移直接定位页内的那 1 个字节。一句话精简背诵版页表是 MMU 用来完成虚拟地址到物理地址转换的映射表。虚拟地址分为虚拟页号和页内偏移虚拟页号查页表得到物理页起始地址页内偏移保持不变。4KB 页面大小对应 12 位页内偏移用物理页基址加上这个 12 位偏移就能定位物理页内的具体字节。33. 页表索引和页内偏移分别来自虚拟地址的哪些位页表转换最开始的根页表基地址从哪里获得1. 虚拟地址的位划分虚拟地址有效只用低 48 位高位是符号扩展位不参与地址翻译。bit0 ~ bit11最低 12 位页内偏移不参与页表查询最后直接加到物理页基址。bit12 ~ bit20PTE 索引第 4 级页表索引bit21 ~ bit29PDE 索引第 3 级bit30 ~ bit38PDPTE 索引第 2 级bit39 ~ bit47PML4 索引根页表索引第 1 级记忆最低 12 位 页内偏移往上 4 段每段 9bit是 4 级页表各自的索引。根页表的物理基地址存放在 CPU 的 CR3 寄存器进程切换时由操作系统内核写入 CR3MMU 从 CR3 读取该地址开始页表遍历34. ARM架构中用户态使用TTBR0还是TTBR1进程切换时TTBR0和TTBR1是否都会切换ARMv8-AArch64 下用户态访问用户虚拟地址使用 TTBR0_EL1TTBR1_EL1 用于内核地址空间所有进程共享。普通进程上下文切换只修改 TTBR0TTBR1 不会跟着切换。35. 地址转换时是先查询缓存还是先完成页表转换是否了解TLBTLB命中发生在页表遍历之前还是之后地址转换优先查询 TLB。TLB 是 CPU 内置的高速缓存缓存虚拟页到物理页的映射。TLB 命中发生在页表遍历之前命中则直接拿到物理页基址不用访问内存遍历页表TLB 缺失才去内存遍历页表并把结果回填 TLB。36. 能否讲一次Linux中中断从上到下的完整流程中断上半部和下半部分别做什么softirq、tasklet、工作队列和线程化中断分别怎么处理你平常使用哪一种下半部机制几种中断处理方式分别有什么区别一、Linux 中断从上到下完整流程硬件中断 → CPU → 内核中断上半部 → 下半部硬件触发中断外设通过中断引脚向 CPU 发送中断信号。CPU 响应CPU 在执行完当前指令后暂停当前程序保存现场通用寄存器、程序计数器等进入内核态。硬件层面识别中断号CPU 查询中断控制器APIC/x86GIC/ARM拿到中断向量 / 中断号。进入中断入口汇编跳转到内核的中断入口汇编代码保存中断上下文。中断上半部hardirq执行调用该中断对应的中断处理函数上半部。上半部快速退出上半部只做紧急、必须关中断的操作把耗时工作延后交给下半部。退出 hardirq 阶段内核检查是否有待处理的下半部softirq。执行下半部在中断上下文执行 softirq/tasklet工作队列是内核进程上下文交给内核线程去跑。全部处理完毕恢复寄存器现场返回被打断的代码继续执行。二、上半部 vs 下半部 职责上半部HardIRQ硬中断特点运行在中断上下文通常关中断 / 关本地中断执行要极快不能睡眠。做的事情确认中断来源清除外设硬件中断标志否则中断会反复触发读取硬件寄存器拿到必要数据把剩下耗时工作调度交给下半部快速退出硬中断尽快开放中断避免屏蔽其他中断太久。一句话紧急、硬件相关、短时间的操作放上面耗时、非硬件紧急操作丢下半部。下半部bottom half目的把硬中断里耗时任务剥离缩短关中断时间提升系统实时性。 下半部有 4 种softirq、tasklet、workqueue、线程化中断threaded irq1. softirq 软中断上下文中断上下文不能睡眠静态定义编译时注册不能动态随便新增内核预定义固定类型网络、定时器、块设备等同类型 softirq 可在多个 CPU 上并行并发执行触发raise_softirq()特点高性能并发但是并发会带来竞态需要锁驱动开发者一般不直接新增 softirq内核内部使用居多。2. tasklet基于 softirq 实现属于 softirq 的封装上下文中断上下文不能睡眠动态创建驱动最常用的旧方案同一份 tasklet 不会在多个 CPU 并发执行自动串行降低锁开销底层依赖HI_SOFTIRQ/TASKLET_SOFTIRQ这两个 softirq触发tasklet_schedule()3. workqueue 工作队列上下文内核进程上下文内核线程可以睡眠、可以阻塞、可以申请 mutex把任务交给内核工作线程执行不在中断上下文触发schedule_work()适用需要睡眠的场景分配内存、互斥锁、读写磁盘4. 线程化中断 threaded irqrequest_threaded_irq上半部hardirq只做最少量硬件清标志下半部内核线程进程上下文可以睡眠本质内核帮你创建一个专属内核线程中断来了唤醒该线程执行业务逻辑适合中断处理比较耗时不想占用硬中断 / 软中断上下文机制上下文能否睡眠并发特性使用者softirq中断上下文❌禁止睡眠同类型可多 CPU 并发内核子系统网络、timer驱动不推荐新增tasklet中断上下文❌禁止睡眠同一个 tasklet 串行不会并行旧驱动简单短任务workqueue内核进程上下文✅可以睡眠可配置并发 / 串行绝大多数驱动最常用threaded irq上半部中断上下文下半部内核线程下半部✅可睡眠线程调度受调度器管理耗时中断处理37. 在键盘输入hello world最终显示到屏幕上整个链路是什么键盘事件经过中断处理后最终是怎么显示到屏幕上的按下键盘 H 键键盘控制器触发硬件中断CPU 进入硬中断上半部读取硬件扫描码下半部交给 input 子系统input 子系统把扫描码转为 keycode封装 input_event 事件放入事件缓冲区bash 终端通过 read 读取按键事件翻译成 ASCII 字符 h终端开启回显调用 write将 h 写入 TTY内核 TTY 子系统交给控制台驱动加载字库把字符像素写入帧缓冲显存显示控制器不断扫描帧缓冲输出像素信号到显示器屏幕上显示 h。38. I²C的通信机制是什么I²C是不是半双工I²C完成数据传输后还要做什么1. 起始信号 SStartSCL 保持高电平不变SDA 从高 → 拉低SCL高SDA高 → 低要点只有 SCL 为高的时候SDA 跳变才是起始SCL 低时改变 SDA 只是普通数据 bit。2. 发送从机地址 读写位8bitMSB 先行一共 8 个时钟脉冲每一个 bitSCL 先拉低 → 稳定 SDA → SCL 拉高采样循环对每一位SCL 低主机设置 SDA 电平准备好当前 bitSCL 高从机在 SCL 高的时候读取 SDA 上的 bit8bit 依次送出7 位从地址 R/W 位高位先发3. ACK 应答第 9 个时钟8 个 bit 发完进入第 9 个时钟周期主机把 SCL 拉低主机释放 SDA不再驱动 SDASDA 交给从机控制主机拉高 SCL✅ ACK从机把 SDA 拉低❌ NACK从机不动作SDA 保持高主机再把 SCL 拉低结束 ACK 周期接收方从机在 SCL 高时拉低 SDA 代表 ACK。4. 数据字节传输每一个数据字节和地址发送时序完全一样每个字节 8bit高位优先 每个 bitSCL 拉低 → 发送方设置 SDA 电平SCL 拉高 → 接收方采样 SDA 8bit 发送完成同样进入第 9 个时钟做 ACK/NACK。5. 停止信号 PStopSCL 保持高电平不变SDA 由低 → 拉高SCL高SDA低 → 高 总线回到空闲状态SCL 高SDA 高。I²C 是同步串行总线SDA 数据线、SCL 时钟线开漏带上拉主机产生时钟通信以起始信号开始发送从机地址 读写位每字节后接收方回复 ACK高位先行逐位传输。I²C 属于半双工单根 SDA 无法同时收发。传输完成后主机发送停止信号释放总线校验 ACK/NACK 判断传输状态驱动释放总线锁也可使用重复起始信号不释放总线继续传输。39. SPI相对于I²C有什么区别SPI有哪些工作模式SPI的四种模式由哪些因素决定一、SPI 和 I²C 的区别表格对比项I²CSPI信号线2 根SDA、SCL开漏输出需要外部上拉4 根标准SCLK、MOSI、MISO、CS推挽输出无需上拉通信方式半双工全双工MOSI/MISO 同时收发时钟从机没有时钟主机产生 SCL同步主机产生 SCLK 同步时钟寻址7/10 位从机地址多从机靠地址区分无地址靠独立 CS 片选线选择从机速率相对低一般几百 kHz~ 几 MHz更快几十 MHz 很常见拓扑两线总线多个从机并联在同一总线每个从机单独 CS 线也可以菊花链应答机制有 ACK/NACK 应答没有硬件应答需要软件校验数据硬件复杂度简单IO 少IO 引脚更多一句话口述 I2C 是半双工两根线靠地址寻址有 ACK速率较低SPI 是全双工四根线靠 CS 片选选设备无硬件应答通信速度更快。二、SPI 的四种工作模式SPI 模式由CPOL时钟极性和CPHA时钟相位两个参数组合决定共 4 种模式CPOLClock Polarity时钟空闲电平CPOL0空闲时 SCLK 低电平CPOL1空闲时 SCLK 高电平CPHAClock Phase采样相位什么时候采样数据CPHA0SCLK 第一个边沿采样数据CPHA1SCLK 第二个边沿采样数据边沿定义 上升沿低→高下降沿高→低4 种模式Mode0CPOL0CPHA0最常用 空闲 SCLK 低第一个上升沿采样Mode1CPOL0CPHA1 空闲 SCLK 低第二个下降沿采样Mode2CPOL1CPHA0 空闲 SCLK 高第一个下降沿采样Mode3CPOL1CPHA1 空闲 SCLK 高第二个上升沿采样重点记忆Mode0 使用最多很多 Flash、ADC 默认是 Mode0✅面试精简背诵版SPI 相比 I2CSPI 全双工、四根信号线靠 CS 片选选择从设备无硬件 ACK通信速率更高I2C 半双工两根线使用从机地址寻址自带 ACK 应答速率更低。 SPI 一共 4 种模式由CPOL 时钟极性和CPHA 时钟相位共同决定。CPOL 决定 SCLK 空闲状态电平CPHA 决定在第几个时钟边沿采样数据。40. UART的通信机制是什么UART是全双工还是半双工UART有几根主要信号线UART是否需要时钟线1. UART 通信机制UART 是异步串行通信发送端把并行数据转为串行 bit 流接收端把串行 bit 恢复成并行数据。异步收发双方不共用时钟线依靠双方预先约定好相同波特率、数据位、校验位、停止位。帧格式一帧起始位 (1bit 低电平) 数据位 (5~8bit低位先行 LSB first) 可选校验位 停止位 (1~2bit 高电平)空闲状态总线保持高电平。发送空闲高 → 拉低作为起始位通知接收方数据来了然后依次发送数据 bit、校验位、停止位。接收接收端持续检测电平检测到下降沿起始位按约定波特率采样每一位把串行 bit 拼成字节。核心没有共享时钟靠波特率采样对时钟精度有要求误差大会丢数据。2. UART 是全双工还是半双工标准 UART 是全双工。 TX发送、RX接收是两根独立信号线可以同时收发。注意单总线的半双工版本如 RS485不属于 UART 本身是外部收发芯片做的。单纯 UART (TX/RX) 全双工。3. UART 主要信号线最简 UART3 线TX发送端输出RX接收端输入GND共地必须参考电平硬件流控版本5 线RTS/CTS RTS请求发送、CTS清除发送用于流量控制防止接收缓冲区溢出。面试回答优先最简是 TX、RX、GND 三根带硬件流控增加 RTS、CTS。4. UART 是否需要时钟线不需要单独的外部时钟线。 UART 属于异步通信收发双方各自使用本地内部时钟只要双方波特率配置一致即可不需要像 I2C/SPI 那样传输共享时钟信号。✅ 面试精简背诵版UART 是异步串行通信收发双方约定波特率数据以帧传输起始位 数据位 校验位 停止位检测到起始位下降沿后按波特率采样 bit。UART 是全双工基础信号线为 TX、RX、GND 三根带硬件流控增加 RTS/CTS。UART 不需要外部共享时钟线收发使用各自本地时钟。二面1. 什么是生产者、消费者消费者包含哪些类型生产者消费者是经典并发模型生产者生成数据放入共享缓冲区消费者从缓冲区取出数据处理缓冲区用来解耦两者。 消费者分三类①中断上下文消费者硬中断、softirq、tasklet不可睡眠②内核线程消费者工作队列、线程化中断进程上下文可以睡眠③用户态应用进程在用户空间读取缓冲区数据。2. 项目如何保障缓冲区buffer同步1. 互斥保证同一时刻只有一方操作 buffer保护临界区互斥锁 mutex适用进程上下文允许睡眠。 场景工作队列、内核线程、用户态 pthread。 限制中断上下文不能用 mutexmutex 睡眠会崩溃。自旋锁 spinlock适用中断上下文 进程上下文并发。 特点拿不到锁时原地忙等不能睡眠。 注意进程和中断竞争时要用spin_lock_irqsave关本地中断防止中断抢占自旋锁持有者造成死锁。原子变量 / 位操作极简场景只保护单个整形标记比如 buffer 的读写索引、count不适合保护复杂缓冲区。2. 条件等待解决缓冲区空 / 满避免忙轮询条件变量 wait_queue等待队列内核 /pthread_cond用户态buffer 空 → 消费者休眠加入等待队列生产者放数据后唤醒消费者buffer 满 → 生产者休眠消费者取走数据后唤醒生产者优点不占用 CPU不是死循环轮询。内核里等待队列 自旋锁 /mutex 组合是环形 buffer 标配。3. 无锁环形缓冲区单生产者单消费者SPSC不需要锁依靠读写索引原子操作、内存屏障smp_mb()。 前提只有一个生产者、一个消费者。 多生产者或者多消费者无锁方案会失效仍然需要锁。3. gdb常用命令有哪些如何查看单个变量如何查看单线程/全部线程调用栈gdb 常用命令r 运行、b 设断点、n 单步跳过、s 单步进入、c 继续、finish 函数返回、q 退出。 查看单个变量使用print(p)命令支持指定进制打印p *ptr查看指针指向内容。bt查看当前线程调用栈info threads查看全部线程thread apply all bt打印所有线程的调用栈。4. top如何查看us、sy占用对应优化思路是什么执行 topCPU 行直接看到 us、sy。us 是用户态 CPU 占用是应用用户代码消耗sy 是内核态占用是系统调用、内核代码、锁、中断等内核行为消耗。us 高优化应用算法消除无效循环减少重复计算。sy 高减少频繁系统调用优化锁竞争减少频繁内存申请释放优化中断与缺页。5. 多路摄像头存在哪些性能瓶颈会遇到哪些问题摄像头数量能否无限制增加多路摄像头瓶颈主要是总线带宽、CPU/ISP 算力、内存、中断与 I2C 等外设资源。常见问题丢帧、帧延迟抖动、多相机时间不同步、OOM 内存溢出、USB 设备掉线、锁竞争、功耗散热问题。摄像头数量不能无限制增加受接口数量、带宽、算力、内存、外设资源约束USB 集线器扩展端口也不能突破 USB 控制器总带宽上限。6. 两个线程循环执行int i、i操作最终打印i的值大概是多少i是否原子操作结果有可能等于201吗i 不是原子操作分为读、加、写三步会产生竞态。两个线程各循环 100 次 i最终 i 的值落在 100~200 之间最大是 200。不可能等于 201总共只有 200 次自增i 不可能超过 200。7. 给i添加volatile修饰后最终结果会如何原因是什么volatile典型使用场景添加 volatile 修饰后i 的结果依旧在 100~200 之间无法保证等于 200。volatile 仅禁止编译器优化强制每次读写访问内存但不能保证 i 是原子操作也无法阻止 CPU 指令重排无法解决多线程竞态。8. 想要保证i最终结果固定为200有哪些实现方案互斥锁 mutex将 i 包裹在加锁解锁临界区保证同一时刻只有一个线程执行自增适合用户态允许睡眠。原子变量 atomic使用硬件原子指令完成 i 自增无需锁性能更高C 用 std::atomic内核用 atomic_t。自旋锁 spinlock内核场景使用忙等不能睡眠。取消多线程单线程串行执行无并发竞争。 仅 volatile 修饰变量无法保证结果正确不能解决竞态。9. 开放问题谈谈你对AI的理解以及日常使用AI的方式10. 设计题设计AI接入自动化调试工具你认为核心卡点是什么
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进