来源包括:腾讯客户端岗 IEG 四面面经、腾讯 IEG 客户端四面面经、腾讯 IEG C++ 后台面经、游戏客户端方向面经总结、腾讯 PCG 客户端面经。

一、操作系统高频

1. 进程和线程的区别?

进程是资源分配单位,线程是 CPU 调度单位。一个进程有独立的虚拟地址空间、堆、文件句柄等资源;一个进程可以包含多个线程,线程共享进程资源,但各自有自己的栈、寄存器和程序计数器。

面试重点:

进程切换开销更大,因为要切换地址空间、页表等资源。线程切换更轻量,因为同一进程内线程共享地址空间。

游戏里主线程负责输入、逻辑、动画、AI 等,如果被网络请求、文件加载、大模型请求阻塞,就会卡帧。

2. 线程共享哪些资源,独占哪些资源?

共享:代码段、堆、全局变量、静态变量、文件句柄、进程地址空间。

独占:线程栈、寄存器、程序计数器、线程局部存储 TLS。

注意追问:

因为堆和全局变量共享,所以多个线程同时读写同一数据时必须同步,否则会有数据竞争。

3. 单核 CPU 上多线程为什么也能“同时运行”?

单核 CPU 上多线程不是物理并行,而是并发。操作系统通过时间片轮转,让不同线程轮流获得 CPU。切换速度很快,所以看起来像同时运行。

多核 CPU 才能真正并行,多个线程可以在不同核心上同时执行。

面试官可能想听:

并发是逻辑上的同时推进,并行是物理上的同时执行。线程太多不一定更快,因为调度、上下文切换、cache miss 和锁竞争都会增加。

4. 上下文切换是什么?为什么有开销?

上下文切换是 CPU 从一个线程/进程切换到另一个线程/进程时,保存当前执行现场并恢复另一个执行现场的过程。

需要保存/恢复寄存器、程序计数器、栈指针等信息。进程切换还可能切换页表和地址空间,导致 TLB/cache 失效,所以开销更大。

游戏开发里,线程不是越多越好。线程过多会让调度和同步成本超过并行收益。

5. 线程同步方式有哪些?

常见有 mutex、semaphore、condition_variable、atomic、读写锁、自旋锁。

mutex:保护复杂临界区。atomic:适合简单变量的原子读写,比如计数器、状态位。semaphore:控制资源数量,比如对象池有 N 个可用资源。condition_variable:用于线程等待某个条件成立。

面试回答:

如果只是简单计数,用 atomic;如果保护一段复杂逻辑或容器,用 mutex;如果生产者消费者模型,可以用 mutex + condition_variable。

6. 什么是死锁?条件是什么?怎么避免?

死锁是多个线程互相等待对方持有的资源,导致所有线程都无法继续执行。

四个必要条件:

互斥占有且等待不可抢占循环等待

避免方法:

固定加锁顺序,破坏循环等待。一次性申请所需资源,破坏占有且等待。使用超时锁。减少锁粒度,避免嵌套锁。用 RAII 管理锁,避免忘记释放。

7. 虚拟内存是什么?

虚拟内存是操作系统给每个进程提供的独立地址空间。程序看到的是连续的虚拟地址,不直接访问物理内存。CPU 通过 MMU、TLB、页表把虚拟地址转换成物理地址。

好处:

进程隔离提高安全性按需加载支持换页让每个进程像拥有独立连续内存

一句话:

虚拟内存解决的是“程序地址空间”和“真实物理内存”之间的抽象与隔离问题。

8. 虚拟地址到物理地址怎么转换?

CPU 访问虚拟地址时,先查 TLB。TLB 是页表缓存,如果命中,直接得到物理页地址。

如果 TLB 未命中,就查页表。页表记录虚拟页到物理页的映射。找到物理页后,加上页内偏移得到物理地址。

如果页表发现这个页不在内存中,就触发缺页中断,由操作系统把数据从磁盘加载到内存。

9. 缺页中断是什么?

当进程访问的虚拟页没有映射到物理内存时,会触发缺页中断。

操作系统会暂停当前进程,找到需要的数据页,把它从磁盘或交换区加载到物理内存,更新页表,然后恢复进程执行。

如果访问的是非法地址,就不是正常缺页,而是访问违规或段错误。

10. 堆和栈的区别?

栈由系统自动管理,用于函数调用、局部变量、返回地址等。速度快,但空间有限,生命周期随函数返回结束。

堆由程序动态申请,比如 new/malloc。空间更大,生命周期灵活,但需要手动或智能指针管理,容易泄漏和碎片。

游戏里不要在栈上放很大的数组,也不要递归过深,否则容易栈溢出。

11. malloc 分配的是虚拟内存还是物理内存?

malloc 返回的是虚拟地址。操作系统通常不会在 malloc 时立刻分配全部物理内存,而是按需分配。真正访问这块内存时,可能触发缺页中断,再分配物理页。

所以可以说:

malloc 先在进程虚拟地址空间中分配区域,物理内存可能在实际访问时才分配。

12. 怎么定位内存泄漏?

先看现象:进程内存持续增长且不回落。

常见方法:

使用 ASan、Valgrind、Visual Studio Diagnostic Tools、Unreal Insights 等工具。检查 new/delete、malloc/free 是否匹配。检查 shared_ptr 循环引用。检查容器是否长期持有对象。检查委托/事件绑定后是否解绑。给对象构造/析构加计数日志。

UE 项目里还要注意 UObject 引用链,某个 UPROPERTY 或委托引用可能导致对象一直无法被 GC。

13. 野指针是什么?怎么避免?

野指针是指向无效内存的指针。比如对象已经 delete,但指针还在被使用;或者返回了局部变量地址。

避免方法:

释放后置空。使用智能指针。明确对象所有权。避免返回局部变量地址。多线程场景中确保对象生命周期覆盖访问周期。UE 中 UObject 引用要用 UPROPERTY、TObjectPtr、TWeakObjectPtr 等方式管理。

14. 进程间通信方式有哪些?

常见 IPC:

管道命名管道消息队列共享内存信号量Socket文件信号

共享内存最快,因为多个进程直接映射同一块物理内存,不需要频繁数据拷贝。但它本身不提供同步,必须配合锁、信号量等机制。

15. 用户态和内核态是什么?为什么要区分?

用户态运行普通应用程序,权限受限,不能直接访问硬件和内核资源。内核态运行操作系统内核代码,权限高,可以管理内存、进程、设备和文件系统。

区分的原因是安全和稳定。用户程序出错不能直接破坏内核。

系统调用就是用户态进入内核态的一种方式,比如读写文件、网络 IO、创建线程等。


二、计算机网络高频

1. TCP 和 UDP 区别?

TCP 是面向连接、可靠、有序、面向字节流的协议。它有三次握手、ACK、重传、流量控制、拥塞控制等机制。

UDP 是无连接、不保证可靠、不保证顺序、面向报文的协议。它开销小、延迟低,但丢包、乱序、重复包都要应用层自己处理。

游戏里:

登录、支付、下载、聊天适合 TCP/HTTP。实时移动、战斗同步、帧输入更适合 UDP。关键事件可以在 UDP 上自己实现可靠层。

2. TCP 三次握手过程?为什么必须三次?

流程:

客户端发送 SYN。服务端回复 SYN + ACK。客户端回复 ACK。

目的:

让双方确认自己和对方的发送、接收能力都正常。

为什么不是两次:

如果只有两次,服务端无法确认客户端是否收到了自己的 SYN + ACK,可能建立一个客户端并不知道的连接。三次握手可以避免历史连接请求导致的错误连接。

3. TCP 四次挥手过程?为什么通常四次?

流程:

主动关闭方发送 FIN。被动关闭方回复 ACK。被动关闭方数据发送完后发送 FIN。主动关闭方回复 ACK。

为什么通常四次:

TCP 是全双工协议,两个方向可以独立关闭。一方不发送数据了,不代表另一方也没有数据要发送,所以 FIN 和 ACK 通常分开发。

4. TCP 如何保证可靠传输?

核心机制:

序号:保证数据可排序、可去重。ACK:确认哪些数据已收到。超时重传:丢包后重新发送。滑动窗口:提高连续发送效率。校验和:检测数据损坏。拥塞控制:防止网络被打爆。流量控制:防止接收方缓冲区被打爆。

一句话:

TCP 的可靠性不是单一机制,而是序号、确认、重传、窗口和控制算法共同保证。

5. 流量控制和拥塞控制区别?

流量控制是接收方控制发送方,防止发送方发太快,把接收方缓冲区撑爆。主要靠接收窗口 rwnd。

拥塞控制是发送方根据网络状态控制发送速度,防止网络过载。主要靠拥塞窗口 cwnd。

区别:

流量控制看接收方能力。拥塞控制看网络承载能力。

6. TCP 拥塞控制有哪些阶段?

经典阶段:

慢启动拥塞避免快重传快恢复

慢启动:cwnd 从小开始指数增长。拥塞避免:达到阈值后线性增长。快重传:收到多个重复 ACK,认为包丢了,不等超时,直接重传。快恢复:重传后不完全回到慢启动,而是降低窗口继续传输。

面试说清楚即可,不用背每个公式。

7. TIME_WAIT 是什么?为什么要等 2MSL?

主动关闭 TCP 的一方会进入 TIME_WAIT 状态,等待 2MSL。

作用:

确保最后一个 ACK 能被对方收到。如果 ACK 丢了,对方会重发 FIN,主动关闭方还能再次回复 ACK。让旧连接中的残留报文在网络中消失,避免影响后续相同四元组的新连接。

MSL 是报文最大生存时间,2MSL 覆盖一个来回的最大生命周期。

8. TCP 粘包是什么?怎么解决?

TCP 是字节流协议,没有消息边界。发送方多次 send 的数据,接收方可能一次 recv 收到;也可能一次 send 的数据被多次 recv 收到,这就是粘包/拆包问题。

解决方法:

固定长度消息。消息头里带 body 长度。特殊分隔符。应用层协议自己做拆包和缓存。

UDP 是面向报文的,一次 sendto 对应一个 datagram,天然有报文边界,但可能丢包。

9. UDP 如何实现可靠传输?

应用层自己做可靠层:

序号/帧号ACK 或 NACK超时重传去重乱序缓存滑动窗口心跳检测

游戏里可以区分消息类型:

移动包:高频发送,丢旧用新。关键事件:技能释放、购买、任务提交,必须可靠确认。状态快照:可以定期覆盖旧状态。

10. 游戏为什么常用 UDP?

实时游戏更重视低延迟。位置、朝向、输入等数据过期后价值很低,旧包即使重传回来也没意义。

TCP 的可靠有序会导致队头阻塞:前面一个包丢了,后面的包即使到了也不能交付应用层,可能造成卡顿。

所以游戏常用 UDP 做实时同步,再对关键消息做应用层可靠机制。

11. 状态同步和帧同步区别?

状态同步:

服务器维护权威状态,把位置、血量、技能结果等同步给客户端。客户端做插值、预测、修正。优点是反作弊和重连好,适合动作、FPS、MMO。

帧同步:

服务器同步所有玩家输入,每个客户端在同一帧执行相同逻辑。优点是带宽低,适合 RTS、MOBA;缺点是要求强确定性,断线重连和反作弊更复杂。

面试回答:

动作游戏通常更偏状态同步或混合同步;策略类、MOBA 可以用帧同步。

12. 断线重连怎么设计?

客户端重连时带账号、房间号、角色 ID、最后确认帧号或状态版本号。

服务器校验身份后:

状态同步:下发当前权威快照,客户端恢复位置、血量、Buff、任务等状态。帧同步:补发缺失帧输入,或者发送当前快照后继续同步。

还要处理:

重复登录重连超时战斗中托管资源重新加载防作弊校验

13. TCP 和 UDP 能不能绑定同一个端口?

可以。TCP 和 UDP 是不同协议,端口空间相互独立。系统区分连接时会看协议类型、IP、端口等信息。

所以 TCP 8080 和 UDP 8080 可以同时存在。

14. select、poll、epoll 区别?

它们都是 IO 多路复用,用一个线程监听多个 socket。

select:使用 fd_set,有数量限制,每次都要拷贝和线性扫描。poll:用数组保存 fd,没有 select 那种固定数量限制,但仍然要线性扫描。epoll:事件驱动,注册一次 fd,有事件时内核通知,适合大量连接。

总结:

select 老、限制多;poll 去掉部分限制但仍线性扫描;epoll 更适合高并发。

15. 非阻塞 IO 怎么设计?

把 socket 设置为非阻塞,读写时不等待数据到达,而是立刻返回。

通常配合 epoll/select:

监听可读可写事件。有数据就读,没数据不阻塞。发送数据先放发送缓冲区,socket 可写时再继续发。应用层处理半包、粘包、超时、断开。

游戏客户端里,网络 IO 不应该阻塞主线程,通常放在网络线程或异步任务中。

16. 网络字节序是什么?为什么需要?

网络字节序统一是大端序。不同 CPU 的主机字节序可能不同,比如 x86 常见是小端。

如果传输多字节整数时不统一字节序,不同机器解析出来的值可能不同。

所以发送前用 htonl/htons 转网络序,接收后用 ntohl/ntohs 转回主机序。


你复试前最该背的优先级

P0:

进程 vs 线程线程共享/独占资源虚拟内存和页表堆 vs 栈死锁TCP vs UDP三次握手/四次挥手可靠传输拥塞控制 vs 流量控制UDP 可靠层游戏为什么用 UDP状态同步 vs 帧同步

P1:

上下文切换malloc 与虚拟内存内存泄漏/野指针select/poll/epoll粘包拆包TIME_WAIT断线重连

如果面试官问得偏岗位,你要主动把网络题往游戏场景上靠:低延迟、可靠关键事件、服务器权威、断线恢复、防作弊。

原文:计网 / OS