📋 摘要
  • C++ Linux 编程面试高频问答,每题保留回答重点与扩展知识
  • 详细原理解析与代码示例见 03-Resources/cpp/C++ Linux 编程
  • 持续补充中

1. 进程和线程的区别?Linux 下如何创建?

📋 回答重点
  • 进程是资源分配的基本单位,拥有独立地址空间、文件描述符表;线程是 CPU 调度的基本单位,共享进程地址空间、打开的文件,但有独立栈和寄存器。
  • 进程创建/切换开销大、隔离性好;线程开销小、通信方便但需同步、一损俱损。
  • 进程通信靠 IPC(管道/共享内存/信号),线程直接读写共享内存。
  • Linux 用 fork() 创建进程、pthread_create 创建线程,二者底层都是 clone() 系统调用。

扩展知识

  • Linux 内核不区分进程/线程,统一用 task_struct 表示,clone() 通过 CLONE_VM/CLONE_FILES 等标志决定共享多少资源。
  • C++ 里用 std::thread 封装 pthread,std::async/std::thread 是高层封装。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

2. fork 的原理?什么是写时复制?

📋 回答重点
  • fork() 复制调用进程创建子进程,仅返回值不同:父进程得到子进程 pid,子进程得到 0。
  • 写时复制(COW):fork 后父子共享同一份物理内存页、页表项标记只读,任一方写时才触发缺页并真正复制该页。
  • COW 大幅降低 fork 开销,避免无用拷贝。
  • vfork 直接共享地址空间、父进程阻塞,专为「fork 后立刻 exec」优化。

扩展知识

  • fork 后文件描述符是复制的(引用计数 +1),父子共享同一文件偏移;线程则共享同一份 fd。
  • 多线程程序中 fork 只复制调用线程,可能因锁被其它线程持有而死锁。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

3. 僵尸进程和孤儿进程?如何避免?

📋 回答重点
  • 孤儿进程:父进程先退出,子进程被 init/systemd(PID 1)收养并回收,无害。
  • 僵尸进程:子进程退出但父进程未 wait,内核保留 task_struct(状态 Z),长期堆积耗尽 PID。
  • 避免:父进程显式 wait/waitpid;忽略 SIGCHLD(Linux 下自动回收);SIGCHLD handler 里循环 waitpid(..., WNOHANG);经典二次 fork 让孙进程变孤儿。
  • 僵尸进程杀不掉(已死),只能靠父进程回收或杀掉父进程。

扩展知识

  • ps aux | grep Z 或 top 可观察僵尸数量。
  • SIG_IGN 忽略 SIGCHLD 只在 Linux 上自动回收,POSIX 不保证。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

4. Linux 文件 IO(open/read/write)?阻塞 vs 非阻塞?

📋 回答重点
  • 以文件描述符(fd)为核心:open 返回 fd,read/write 读写,close 释放。
  • 阻塞:无数据时 read 一直等待;非阻塞:设置 O_NONBLOCK 后立即返回 -1 且 errno == EAGAIN。
  • 可用 fcntl(fd, F_SETFL, flags | O_NONBLOCK) 运行时切换。
  • 非阻塞 IO 必须配合轮询或 select/poll/epoll 多路复用才有意义。

扩展知识


5. 标准 IO 和系统 IO 的区别?

📋 回答重点
  • 系统 IO(open/read/write):直接陷入内核,无用户态缓冲,每次调用有系统调用开销。
  • 标准 IO(fopen/fread/fwrite):用户态维护缓冲区,攒满或 fflush 才调系统 IO,减少 syscall 次数。
  • 缓冲模式:无缓冲 _IONBF、行缓冲 _IOLBF(终端)、全缓冲 _IOFBF(文件)。
  • 性能敏感/低延迟用系统 IO;日常读写文件用标准 IO 方便。

扩展知识

  • 二者勿混用同一 fd,标准 IO 缓冲会让系统 IO 读到过期数据。
  • fork 后标准 IO 缓冲可能被父子「双重写入」。
  • strace 可观察标准 IO 一次写多字节、系统 IO 逐字节调用的差异。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

6. 虚拟内存的原理?分页机制?

📋 回答重点
  • 虚拟内存让每个进程有独立连续的虚拟地址空间,由 MMU 翻译为物理地址。
  • 三大能力:隔离、共享、换页;好处有按需加载、共享库、内存超售。
  • 分页:地址空间切成固定大小页(4KB),页表记录「虚拟页 → 物理页框」映射,PTE 含物理页号、存在位、权限位等。
  • TLB 缓存最近翻译结果加速查表;多级页表解决单级过大问题。

扩展知识

  • x86-64 用 4 级页表(PGD→P4D→PUD→PMD→PTE)。
  • 分页固定大小、无外部碎片但可能有内部碎片;分段按逻辑单元划分,现代主要用分页。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

7. mmap 的原理和适用场景?

📋 回答重点
  • mmap 把文件或匿名内存映射到进程虚拟地址空间,访问映射区如同访问内存,由页缓存承担读写。
  • 相对 read/write 少一次「内核缓冲区 → 用户缓冲区」拷贝(零拷贝读)。
  • MAP_SHARED 修改写回文件且跨进程共享;MAP_PRIVATE 走 COW 不影响文件。
  • 场景:大文件随机访问、共享内存 IPC、malloc 大块分配、零拷贝 IO。

扩展知识

  • 缺页时才读磁盘(major fault);文件修改靠脏页延迟写回,msync 强制同步。
  • 访问文件大小之外越界会触发 SIGBUS;失败返回 MAP_FAILED。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

8. 缺页中断的过程?

📋 回答重点
  • CPU 访问虚拟地址时 MMU 发现页不在物理内存(PTE 存在位为 0),触发缺页异常陷入内核。
  • 内核查 do_page_fault:地址非法则发 SIGSEGV,合法则找后备存储(文件/匿名页/swap)。
  • minor fault:页已在物理内存,只建立映射;major fault:需从磁盘读入、分配页框。
  • 更新页表、刷新 TLB,返回用户态重新执行触发异常的那条指令。

扩展知识

  • 缺页是可恢复异常,与不可恢复的 SIGSEGV 区分;COW、按需加载、swap 都靠缺页实现。
  • 大页(Huge Page)可减少页表项与缺页次数。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

9. malloc 的底层实现(brk 和 mmap)?

📋 回答重点
  • glibc 的 malloc(ptmalloc)维护堆内存,不每次都向内核要。
  • brk/sbrk:调整 program break,得到连续堆内存,适合小块(默认 < 128KB)。
  • mmap:独立映射一块内存,适合大块(≥ 阈值 M_MMAP_THRESHOLD),free 后立即 munmap 归还。
  • 释放后进空闲链表(tcache/fastbins/small/large/unsorted bins)复用。

扩展知识

  • 小内存 free 后不一定归还内核(RSS 不下降),大块 mmap 才真正归还。
  • 高并发可用 jemalloc/tcmalloc(多线程 arena、减少锁竞争)。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

10. Linux 信号机制?常见信号有哪些?

📋 回答重点
  • 信号是内核/进程间异步通知机制:产生 → 递送 → 处理;处理方式有默认动作、忽略、自定义 handler。
  • 常见信号:SIGINT(2)、SIGTERM(15)、SIGKILL(9,不可捕获)、SIGSEGV(11)、SIGPIPE(13)、SIGCHLD(17)、SIGSTOP/SIGCONT。
  • 用 signal 或 sigaction 注册 handler;SIG_IGN 表示忽略。
  • handler 内只能调用 async-signal-safe 函数(write/_exit),printf/malloc 不安全。

扩展知识

  • 标准信号不排队,实时信号(SIGRTMIN+)才排队。
  • 网络程序要处理 SIGPIPE,否则向已关闭 socket 写会杀进程。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

11. gdb 调试的常用命令?

📋 回答重点
  • 编译加 -g,常用命令:break(断点)、run、next(n 不进入函数)、step(s 进入函数)、continue、print、backtrace(bt 调用栈)。
  • watch 监视变量被修改、条件断点 break func if x > 10、finish 执行完当前函数。
  • 多线程:info threads、thread N、thread apply all bt(定位死锁)。
  • 崩溃用 core dump:gdb ./app core + bt 定位崩溃栈。

扩展知识

  • next 与 step 的区别是是否进入函数;-O0 关优化便于调试。
  • -tui 或 layout split 显示源码;IDE 调试器多基于 gdb/lldb。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

12. perf 如何分析性能瓶颈?

📋 回答重点
  • perf stat 统计整体计数(IPC、cache miss、上下文切换)。
  • perf record -g 采样热点 + perf report 查看耗时函数;perf top 实时看热点。
  • 可指定事件 perf stat -e cache-misses;结合火焰图定位到代码行。
  • 方法论:先看整体,再看热点函数,最后定位具体代码。

扩展知识

  • 基于内核 perf_event 子系统,无需改代码、开销低;对比 gprof(插桩、需重编译)。
  • CPU 高看热点函数,cache miss 高优化数据结构内存布局。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

13. strace 的作用?

📋 回答重点
  • 跟踪进程发出的系统调用及其参数、返回值,定位「程序对内核做了什么」。
  • 常用:strace -f(跟踪子进程)、-e trace=open(只看指定调用)、-c(统计次数/耗时)、-p(附加进程)。
  • 能定位:文件打开失败(ENOENT)、卡在哪个 syscall(poll/futex/read)、哪个调用次数异常。

扩展知识

  • -c 统计能快速发现「read 只读 1 字节」这类无缓冲问题。
  • 程序卡住时 strace 看它阻塞在哪个系统调用。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

14. valgrind 如何检测内存问题?

📋 回答重点
  • valgrind 是动态分析工具,memcheck 插桩检测非法访问、未初始化内存、内存泄漏,无需改源码。
  • 常用:valgrind --leak-check=full ./app、--track-origins=yes 追踪未初始化值来源。
  • 能检测:越界读写、未初始化值、重复/非法 free、泄漏(definitely/indirectly/possibly lost、still reachable)。
  • 缺点:程序变慢 10~50 倍,只适合开发/测试。

扩展知识

  • definitely lost 必须修,still reachable(全局指针仍指向)常可忽略。
  • 替代:AddressSanitizer(-fsanitize=address,编译期插桩更快)。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

15. gcc 编译的四个阶段?

📋 回答重点
  • 预处理(gcc -E):展开宏、头文件、条件编译,.c → .i。
  • 编译(gcc -S):生成汇编,.i → .s。
  • 汇编(gcc -c):生成目标文件,.s → .o。
  • 链接(默认):.o + 库 → 可执行文件,做符号解析与重定位。

扩展知识

  • 编译阶段做词法/语法/语义分析、生成中间表示并优化;链接填外部符号地址。
  • 大工程分模块编译(-c 生成 .o)最后统一链接,靠 make/cmake 管理依赖。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

16. 静态库和动态库的区别?如何生成?

📋 回答重点
  • 静态库 .a:编译期直接拷进可执行文件,体积大、无运行依赖、更新需重编。
  • 动态库 .so:运行时加载,体积小、多进程共享物理页、可热更新(ABI 兼容前提)。
  • 生成静态库:gcc -c + ar rcs libx.a x.o,链接 -L. -lx。
  • 生成动态库:gcc -fPIC -shared -o libx.so x.o;运行时找库靠 LD_LIBRARY_PATH、rpath、ldconfig。

扩展知识

  • -fPIC 位置无关代码让 .so 加载到任意地址;动态链接经 PLT/GOT 延迟绑定。
  • 静态库依赖顺序:被依赖库放后面;C++ 库注意 ABI 兼容。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

17. 进程调度的算法有哪些?

📋 回答重点
  • CFS(完全公平调度器,默认):普通进程 SCHED_OTHER,用 vruntime 选最小者运行,红黑树维护。
  • 实时调度:SCHED_FIFO(先到先服务)、SCHED_RR(FIFO + 时间片轮转),优先级永远高于普通进程。
  • nice 值(-20~19)影响 CFS 权重与时间片;多核下还有负载均衡。

扩展知识


18. 内核态和用户态?系统调用的过程?

📋 回答重点
  • x86 用特权级隔离:Ring 0 内核态(可执行特权指令、访问所有内存),Ring 3 用户态(受限)。
  • 系统调用过程:用户态把调用号放 rax、参数放 rdi/rsi/rdx 等,执行 syscall(x86-64)切换到 Ring 0。
  • 内核查系统调用表跳转到对应函数,执行后返回并切回 Ring 3。
  • 系统调用引发上下文切换,开销远大于普通函数调用。

扩展知识

  • 32 位用 int 0x80(软中断,慢),已被 sysenter/syscall 取代。
  • 频繁 syscall 是性能瓶颈,标准 IO 缓冲、readv/writev、io_uring 都在减少 syscall 次数。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

19. 内存屏障和缓存一致性?

📋 回答重点
  • 缓存一致性:多核 CPU 通过 MESI 协议(M/E/S/I)保证各核看到的内存一致,写操作使其它核缓存行失效。
  • 内存屏障:约束编译器/CPU 的指令重排,保证屏障前后内存操作的顺序可见。
  • C++ 用 std::atomic 的 memory_order(release/acquire)实现同步;CPU 屏障有 mfence/lfence/sfence。
  • volatile 不保证原子性/有序性、不是内存屏障,多线程同步必须用原子或锁。

扩展知识

  • 无锁编程(lock-free 队列)依赖内存序。
  • 伪共享:多线程写同一缓存行不同变量会互相失效,用 alignas(64) 隔离。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

20. make 和 cmake 的区别?

📋 回答重点
  • make:读取 Makefile 中的规则(目标/依赖/命令),判断文件更新、按依赖顺序执行编译命令,规则手写、跨平台弱。
  • cmake:构建系统生成器,读取 CMakeLists.txt 生成 Makefile/Ninja/VS 工程,再交给 make/ninja 执行。
  • cmake 抽象编译器与平台差异,适合大工程与跨平台。
  • 流程:cmake -B build 生成 → cmake --build build 编译;关系是 cmake 生成规则、make 执行。

扩展知识

  • 小项目一个 Makefile 够用;跨平台库必用 CMake。
  • 现代大项目常用 CMake + Ninja(更快),可配 ccache、-j 并行。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程


21. Linux 文件系统的 inode 是什么?软链接和硬链接有什么区别?

📋 回答重点
  • inode 是文件的「元数据」,存类型/权限/大小/时间戳/链接数/数据块指针,不含文件名;文件名在目录项里,目录项是「文件名 → inode 号」的映射。
  • 硬链接:与源文件同一 inode,是数据的别名;软链接:独立 inode,内容存「目标路径」,是指向路径的快捷方式。
  • 硬链接不能跨文件系统、不能链接目录;软链接可以跨 FS、可以链接目录。
  • 删除原文件:硬链接仍可访问(链接数 -1,归 0 才删数据);软链接变悬空失效。

扩展知识

  • 一个 inode 可对应多个文件名(硬链接),stat 的 st_nlink 链接数降到 0 才真正释放数据块。
  • 命令:ls -i 看 inode 号、stat 看元数据、ln/ln -s 建链接、find -inum 按 inode 找所有硬链接。
  • 应用:硬链接做备份防误删;软链接做版本切换(current -> releases/v1.2.0)、部署原子切换。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

22. Linux 共享内存如何实现进程间通信?如何用信号量同步?

📋 回答重点
  • 共享内存是最快的 IPC:多个进程把同一物理内存映射到各自地址空间,读写无需内核↔用户态拷贝(对比管道/socket 多次拷贝)。
  • 实现:System V(shmget/shmat)或 POSIX(shm_open/mmap),父子进程可用匿名 mmap。
  • 共享内存本身不提供同步,必须配信号量(sem_open/sem_wait/sem_post)或互斥锁。
  • 信号量是计数器:P(sem_wait)值-1 为 0 阻塞,V(sem_post)值+1 唤醒,可实现互斥与生产者-消费者计数同步。

扩展知识

  • shm_open + ftruncate + mmap 的 MAP_SHARED 是标准 POSIX 流程;shm_unlink 删除对象。
  • /dev/shm 是 tmpfs 内存文件系统,常做高性能缓存/共享内存载体。
  • 应用:大块数据共享(日志缓冲、图像帧、配置)吞吐远高于管道。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

23. MMU 和 TLB 的作用是什么?

📋 回答重点
  • MMU 是 CPU 内硬件,负责虚拟地址 → 物理地址翻译 + 权限检查,非法访问触发缺页/段错误。
  • TLB 是 MMU 内的高速缓存,缓存最近用过的「虚拟页号 → 物理页框号」,加速翻译。
  • 翻译顺序:先查 TLB,命中直接得物理地址;未命中才查多级页表,再更新 TLB。
  • TLB miss 是性能热点,要利用局部性;大页(2MB/1GB)让一个 TLB 项覆盖更大内存,减少 miss。

扩展知识

  • 页大小通常 4KB(sysconf(_SC_PAGESIZE));大页查看 /proc/meminfo 的 HugePages。
  • TLB 每核独立;进程切换换地址空间会导致 TLB 刷新,是进程切换开销来源之一。
  • 应用:数据库/JVM 开大页减 TLB miss;虚拟化靠 EPT/NPT 做二层翻译,TLB miss 代价更高。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

24. CPU 亲和性和 NUMA 是什么?如何设置?

📋 回答重点
  • CPU 亲和性:把进程/线程绑定到指定核,避免跨核迁移导致缓存失效,用 sched_setaffinity 或 taskset 设置。
  • NUMA:多路服务器把 CPU 和内存分多个 node,访问本地 node 内存快、远端慢。
  • 设置:亲和性 taskset -c 0,2 ./app;NUMA numactl --cpunodebind=0 --membind=0 ./app。
  • 目的:亲和性保持 cache 热;NUMA 减少跨 node 远端访存,内存绑定本地 node。

扩展知识

  • sched_setaffinity/sched_getaffinity 是程序级 API,CPU_ZERO/CPU_SET 操作 cpu_set_t。
  • 绑定亲和性可能破坏调度器负载均衡,适合延迟/缓存敏感场景,不是万能优化。
  • numactl --hardware 看拓扑,numastat 观察各 node 内存分配。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

25. 进程的概念?进程有哪些状态?什么是进程控制块 PCB?

📋 回答重点
  • 进程是程序的一次执行实例,是资源分配的基本单位,有独立地址空间、fd 表、信号处理。
  • 三种基本状态:就绪(Ready)、运行(Running)、阻塞(Blocked);加上创建、终止。
  • Linux 具体状态(ps 的 STAT):R 运行/就绪、S 可中断睡眠、D 不可中断睡眠(等磁盘 IO)、Z 僵尸、T 停止。
  • PCB 是内核为进程维护的数据结构,Linux 中即 task_struct,存状态/PID/现场/页表/打开文件/调度信息。

扩展知识

  • PCB 是进程在内核的「身份证」,/proc/<pid>/ 是其用户态投影;调度与切换都围绕 task_struct。
  • D 状态长期存在说明磁盘/网络 IO 异常;Z 状态是父进程未回收的僵尸。
  • 排查进程卡住先看 STAT:S 等锁/IO、D 等磁盘、Z 僵尸。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

26. 线程的概念?用户级线程和内核级线程有什么区别?

📋 回答重点
  • 线程是 CPU 调度的基本单位,共享进程地址空间与打开文件,但有独立栈、寄存器、PC。
  • 用户级线程(ULT):用户态库调度,内核无感知,切换快但一个阻塞全阻塞、无法多核并行。
  • 内核级线程(KLT):内核直接调度,可真正并行,一个阻塞不影响其它,但切换开销大。
  • 现代 Linux 用 NPTL(1:1 模型);Go 的 goroutine 是 M:N 混合模型。

扩展知识

  • ULT 代表:协程、绿色线程(早期 Go、gevent);KLT 代表:pthread、Linux NPTL。
  • 内核级线程才是真正并发;协程优势是切换快、可海量创建,适合 IO 密集。
  • 查看线程:ps -eLf、top -H。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

27. 什么是上下文切换?进程切换和线程切换哪个开销大?

📋 回答重点
  • 上下文切换:CPU 从一任务切到另一任务,保存当前现场(寄存器/PC/SP),恢复新任务现场。
  • 触发时机:时间片耗尽、主动阻塞/让出、被抢占、系统调用返回。
  • 进程切换开销大:要换页表基址并刷新 TLB、缓存变冷;线程切换(同进程)开销小:不换页表。
  • 观测:vmstat 的 cs 列、pidstat -w 的 cswch/nvcswch。

扩展知识

  • 进程切换贵在换页表 + 刷新 TLB;线程切换不换页表,但跨核迁移仍丢缓存。
  • cs 过高说明线程/进程过多或锁竞争,可减线程或改协程。
  • 高并发服务(Nginx worker + 事件循环、线程池)本质是减少切换开销。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

28. 多进程和多线程如何选择?各自的优缺点?

📋 回答重点
  • 多进程:隔离强、稳定(一崩不影响其它),但通信需 IPC 慢、内存大、创建切换开销大。
  • 多线程:共享内存通信快、资源省、切换快,但一崩可能带崩进程、数据竞争/死锁难调试。
  • 强隔离/高稳定 → 多进程(Chrome 每 tab 一进程、Nginx worker);大量共享数据/频繁通信 → 多线程。
  • CPU 密集多核两者皆可(多进程更安全);海量 IO 并发用线程池/协程/事件驱动。

扩展知识


29. 什么是线程安全和可重入?它们有什么区别?

📋 回答重点
  • 线程安全:多线程并发调用同一函数不会数据竞争/结果不一致,靠锁、原子、局部变量实现。
  • 可重入:函数执行中可被中断(信号/递归)后再次进入仍正确,不用全局/静态数据、不调不可重入函数。
  • 关系:可重入 ⇒ 线程安全(可重入一定线程安全,反过来不一定)。
  • 线程安全可用锁;可重入不能依赖锁(信号中断时可能死锁)。

扩展知识

  • strtok/gmtime/localtime 不可重入,有 strtok_r/gmtime_r/localtime_r 可重入版本。
  • 信号 handler 只能用 async-signal-safe(可重入)函数;errno 已改为线程局部(TLS)保线程安全。
  • 写库/回调尽量可重入;多线程优先用 _r 版本或传入缓冲区。
  • 详细讲解:03-Resources/cpp/C++ Linux 编程

30. 什么是守护进程?如何创建守护进程?

📋 回答重点
  • 守护进程是后台长期运行、脱离控制终端的进程,如 sshd、nginx、cron,PPID=1、无 TTY。
  • 创建(setsid 法):fork 父退 → setsid() 建新会话脱离终端 → 二次 fork 防重获终端 → chdir("/") → umask(0) → 重定向标准 IO 到 /dev/null。
  • setsid() 让子进程成为会话首进程、脱离控制终端,是关键一步。
  • 现代建议用 systemd 管理(写 .service unit),自动处理脱离终端、日志、重启。

扩展知识