📋 摘要
- 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 下自动回收);SIGCHLDhandler 里循环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多路复用才有意义。
扩展知识
- 普通磁盘文件非阻塞通常无效(数据总是「就绪」),主要用在管道/套接字。
- epoll 是 Linux 高性能网络的核心,详见 03-Resources/cpp/C++ 网络编程。
- 详细讲解:03-Resources/cpp/C++ Linux 编程
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 权重与时间片;多核下还有负载均衡。
扩展知识
chrt/nice/renice调整优先级;音视频/机器人控制用实时调度。- CFS 前身是 O(1) 调度器。
- 详细讲解:03-Resources/cpp/C++ Linux 编程
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;NUMAnumactl --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 并发用线程池/协程/事件驱动。
扩展知识
- 混合模式常见:多进程 + 每进程内多线程(MySQL、PostgreSQL、浏览器),兼顾隔离与性能。
- 回答时给出「按场景取舍」的判断框架最加分。
- 基础对比见 03-Resources/cpp/C++ Linux 编程。
- 详细讲解:03-Resources/cpp/C++ Linux 编程
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 管理(写
.serviceunit),自动处理脱离终端、日志、重启。
扩展知识
- 二次 fork 的目的:让孙进程不是会话首进程,永远无法重新获得控制终端。
- glibc 的
daemon()函数是等价封装,灵活性差。 - 结合孤儿/僵尸处理保证进程干净,见 03-Resources/cpp/C++ Linux 编程。
- 详细讲解:03-Resources/cpp/C++ Linux 编程