OPPO 嵌入式软件 一面面经
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 在 Linux 环境下,进程与线程在资源归属和调度对象层面各扮演什么角色,隔离性与共享性如何体现?
- 同一进程内的线程切换为何通常比跨进程切换代价小,哪些因素会让这个结论并非绝对?
- Linux 内核以什么方式在可运行任务之间分配处理器时间,任务状态会经历哪些转换?
- 哪些事件会触发调度器重新选择执行流,是否只有时间片到期才会发生切换?
- 指向已失效或未合法化内存的指针因何形成,工程上有哪些手段降低这类风险?
- C++ 引入智能指针想解决哪些资源管理问题,独占所有权与共享所有权在语义和使用上有何差异?
- 怎样理解线程安全,判断一段函数是否具备线程安全时应观察哪些方面?
《参考解析》
- 进程与线程:进程是资源分配的基本单位,持有独立的虚拟地址空间、页表、文件描述符表和信号处理等资源;线程是调度的基本单位,同一进程内的线程共享地址空间、代码段、全局数据与打开的文件,但各自有独立的栈、寄存器上下文和线程局部存储。所以隔离性来自进程边界(一个进程崩了通常不影响另一个),共享性来自线程——线程间通信几乎零成本,代价是共享数据必须自己做同步。
- 线程切换为什么更便宜:同进程内切换只需要换寄存器上下文和栈指针,地址空间不变,页表不用切、TLB 和 CPU 缓存里的映射大多仍然有效;跨进程切换要切换页表基址寄存器,导致 TLB 大面积失效、缓存局部性被破坏。但这个结论不是绝对的:同进程内两个线程如果跑在不同核上、工作集很大,寄存器与缓存上下文的搬运同样可观;而跨进程切换如果目标进程刚跑过(缓存还热),代价也会低不少;此外切换还受内核抢占、调度类和 cgroup 限流影响。
- 调度与状态转换:Linux 用 CFS 按虚拟运行时间和权重在可运行任务间分配 CPU 时间,不是简单轮转固定时间片。任务主要状态是 TASK_RUNNING(可运行或正在运行)、TASK_INTERRUPTIBLE / TASK_UNINTERRUPTIBLE(睡眠等待事件或 IO)、TASK_STOPPED、以及退出时的 EXIT_ZOMBIE;状态转换由阻塞、被唤醒、被信号打断、被抢占和退出这些事件驱动。
- 触发调度的时机:不只有时间片到期。常见触发点包括:任务主动让出(
sched_yield)、因等待 IO 或锁而睡眠、被更高优先级任务抢占(need_resched置位后在内核返回用户态或中断返回点检查)、中断或系统调用返回时的抢占检查、时间片/虚拟运行时间耗尽、以及新任务被唤醒后抢占当前任务。所以”只有时间片用完才切换”是错的,抢占式内核里切换随时可能发生。 - 失效指针的成因与防护:悬垂指针来自 free/delete 之后没有置空、返回局部变量的地址、容器扩容后原迭代器或指针失效;野指针来自未初始化;越界与 use-after-free 往往在释放后重用同一块内存时才暴露。工程手段包括:初始化即置空、释放后立刻置 NULL、优先用
std::vector/std::string/ 智能指针替代裸指针和裸数组、用 RAII 绑定生命周期、接口上用gsl::span或引用而非裸指针、再加 ASan/Valgrind 这类工具在测试阶段拦截。 - 智能指针的语义:裸指针把”指向”和”拥有”混在一起,谁负责释放说不清,异常路径上容易泄漏。
unique_ptr表达独占所有权,不可拷贝、只能 move,零额外开销,适合明确唯一所有者的资源;shared_ptr用引用计数表达共享所有权,拷贝即增加计数、计数归零才释放,代价是控制块和原子操作,还有循环引用导致泄漏的风险——需要共享且可能成环时用weak_ptr打破环。shared_ptr的计数是原子的但对象本身不线程安全,这一点在面试里经常被追问。 - 线程安全的判断:线程安全指多线程并发调用同一段代码时行为仍然正确,观察三个层面:有没有共享可变状态(函数内只用局部变量和入参天然安全)、对共享状态的操作是不是原子的(读改写序列要加锁或用原子操作)、以及可见性与有序性(一个线程的写何时对另一个线程可见,是否需要内存屏障或 atomic)。实践上先看临界区是否被完整保护、锁的粒度与顺序是否会造成死锁、是否有线程局部缓存这类隐式共享;无锁方案则要能说清内存序的取舍。