全面复习2

你说这个比malloc效率高,那malloc是个什么样的过程你知道吗?

知道。malloc 并不是每次分配内存都会直接向操作系统申请物理内存。

首先它会对申请大小进行对齐,并优先从线程缓存,比如 tcache,以及分配器维护的其他空闲内存块中寻找合适的块。free 释放的小块内存通常也不会马上归还操作系统,而是先缓存起来供后续复用。

如果现有的空闲内存不足,malloc 才会通过 brk 扩展堆,或者通过 mmap 申请新的虚拟地址空间。

这里申请到的主要是虚拟地址空间,对应的物理页通常是按需分配的。程序真正访问这些页面时,如果还没有建立有效的物理页映射,就会发生缺页异常,由操作系统分配物理页并更新页表。

哈希表你知道是个什么功能吗?

哈希表是一种通过哈希函数把键映射到数组下标的数据结构,用来实现键值对的快速查找、插入和删除。

平均时间复杂度是 O(1) ,发生哈希冲突时通常通过链地址法或开放寻址法解决;。C++ 中常用的 unordered_map 和 unordered_set 就是哈希表。

创建本地分支可以使用:

复制代码
git branch 分支名

这条命令只创建分支,不会切换过去。

如果希望创建后立即切换,可以使用:

复制代码
git switch -c 分支名

操作系统这门课主要讲了哪些操作系统?

课程主要以 Linux/Unix 为重点,学习了进程与线程、内存管理、文件系统、I/O 等内容;

这些基本模块里,哪个模块最擅长?

进程和线程

每个进程都有自己的进程地址空间,内部有pcb,有指向文件描述符表的指针,指向虚拟地址空间的指针和一些其他的属性,

fork() 创建子进程。子进程会继承父进程的大部分资源如虚拟地址空间,文件描述符表,但地址空间使用写时复制,刚创建时并不会立即复制所有内存。

之后通常调用:

复制代码
execve(...);

execve 不会创建新进程,而是用新的程序内容替换当前进程的地址空间。

父进程可以调用:

复制代码
waitpid(pid, ...);

等待并回收子进程。

线程是轻量级进程,进程是操作系统分配资源的基本单位,线程是操作系统调度的基本单位

一个进程可以包含多个线程。多个线程共享整个进程的虚拟地址空间和文件描述符表,但在这个地址空间内部,每个线程有自己单独的一块栈空间。每个线程独立拥有自己的执行上下文,包括程序计数器、寄存器上下文、栈和调度信息。寄存器并不是每个线程物理独占一套,而是线程切换时由操作系统保存和恢复,从而让每个线程看起来都有自己的一套寄存器状态。

C++ 中:

复制代码
std::thread t(func);

底层通常会进入 pthread,再通过 Linux 的线程创建机制创建内核可调度的线程。

内存管理

程序使用的是虚拟地址,不是直接使用物理内存地址。

CPU 访问虚拟地址时,大致经过:

复制代码
虚拟地址
    ↓
TLB 或多级页表
    ↓
物理内存地址

硬件中的 MMU 负责地址转换。

每个进程通常拥有独立的虚拟地址空间,因此不同进程可以使用相同的虚拟地址,而实际映射到不同的物理内存。

一个典型进程的地址空间包括:

复制代码
代码段
只读数据段
已初始化数据段
未初始化数据段
堆
共享库和 mmap 区域
栈

用户空间和内核空间通常分开:

  • 用户程序运行在用户态
  • Linux 内核运行在内核态
  • 用户程序通过系统调用进入内核态

这种隔离可以防止普通程序直接破坏内核或其他进程。

3. 分页和页表

Linux 通常以页为单位管理内存,常见页大小是 4 KB。

页表记录:

复制代码
虚拟页号 → 物理页框

页表项中还包含:对与这个地址的一些访问权限等

TLB 是 CPU 中保存部分地址转换结果的高速缓存,可以减少频繁查询页表的开销。

4. 缺页异常

如果进程访问的虚拟页没有映射到物理内存,就会产生缺页异常。

缺页异常不一定代表错误,可能是正常行为,例如:

  • 第一次访问刚分配的内存
  • 需要把文件内容加载到内存
  • 需要执行写时复制
  • 页面被换出到磁盘,需要重新加载

内核处理完成后,程序通常可以继续执行。

如果访问了完全非法的地址,或者违反了页面权限,就可能收到:

复制代码
SIGSEGV

也就是常说的段错误。

6. 写时复制

fork() 后,父子进程最初可以共享相同的物理页面。

当某一方尝试修改页面时,内核才复制出一份新的物理页,再让修改方使用新页面。这就是写时复制,简称 COW。

它可以避免 fork 时立即复制整个进程的内存,提高创建进程的效率。

7. 内存回收

物理内存紧张时,Linux 可能:

  • 回收文件页缓存
  • 回收不常用的匿名页
  • 将页面换出到 Swap
  • 触发 OOM Killer,终止某些进程

因此,进程的虚拟内存大小不一定等于它当前实际占用的物理内存。

文件系统

磁盘上是按柱面磁道扇区来定位地址的,通过文件系统我们可以以连续地址的方式访问磁盘数据

磁盘先分区然后在分区上创建文件系统,把这个分区分成多个 block group,每个组中会有superblock inode bitmap、block bitmap、inode table 和 data block 等结构。访问一个文件时,VFS 会按照路径逐级解析,并优先查询 dentry cache。如果 dentry 命中,就可以快速得到对应 inode;如果没有命中,则通过根目录一层一层找到父目录 inode 找到该目录的数据块,在目录数据中查找"文件名到 inode 号"的映射,然后再获取对应 inode,在对应组内部添加相应信息,并建立 dentry 缓存。看看页缓存有没有inode对应数据,有的话直接拿没有去磁盘取根据 inode 中记录的 extent 等数据块映射信息,找到文件实际的数据块并读取内容。之后再次读取文件就通过fd指向struct file内部指向inode结构体,直接找到数据位置。

Linux 通过 VFS,也就是虚拟文件系统,向应用程序提供统一接口,应用程序不需要关心底层

硬链接和软链接的区别:

  • 硬链接:多个文件名指向同一个 inode
  • 软链接:保存另一个文件的路径

如果原文件删除,硬链接仍然可以访问数据;软链接可能变成悬空链接。

IO

就从操作系统向磁盘这些硬件设备读取写入内容

阻塞 I/O 的特点是:如果数据还没有准备好,调用线程会睡眠。

设置非阻塞后,如果数据暂时没有准备好,系统调用不会睡眠,而是立即返回,例如返回:

复制代码
EAGAIN

应用程序需要自己再次尝试,这可能造成忙轮询,所以通常会和 I/O 多路复用一起使用。多路复用允许一个线程同时监控多个文件描述符。哪个文件描述符准备好了才通知对方

主要用epoll 有两种常见工作方式:

  • 水平触发:只要还有数据可读,就会持续通知
  • 边缘触发:状态从未就绪变为就绪时通知一次

边缘触发通常需要配合非阻塞 I/O,并循环读取,直到返回 EAGAIN,否则可能遗漏后续事件。

在您未来的职业规划中,希望寻找一个什么样的平台和团队?

我希望加入一个有实际业务和技术挑战的平台,能够参与核心 C++ 模块的开发,在项目中持续提升自己的工程能力。团队方面,希望大家目标一致、互相支持,也能给新人承担责任和成长的机会。长期来看,我希望和平台一起发展,持续创造价值。

对于岗位本身的发展,您的自我规划是什么?

我会分阶段规划。前期先熟悉业务、代码和开发流程,夯实 C++、Linux、并发和网络等基础,尽快独立负责模块;中期提升系统设计、性能优化和问题排查能力,能够承担更复杂的任务;长期希望成为团队中可靠的技术骨干,为项目持续创造价值。

您在使用 AI 工具吗?主要包括哪些?AI 是如何赋能您目前的工作的?请详细描述过程和当前掌握程度。

我平时会使用 AI 工具,主要包括 ChatGPT、GLM等通用大模型,以及 opencode或 mimocode codex等 这类代码辅助工具。

我的使用流程通常是:先自己分析背景,目标,解决问题步骤,让ai如何响应或者是针对什么用户针对什么情况把这些喂给ai,再把必要的代码、日志、接口和错误信息提供给 AI,让它辅助进行知识学习、方案设计、代码补全、测试编写和问题排查。AI 给出结果后,我会结合代码和实际运行结果进行验证,最后再自己修改和 review。

对 C++ 开发来说,AI 可以帮助我快速理解陌生代码,分析内存、并发、Linux 和网络问题,生成样例代码和测试用例,也能比较不同实现方案,提高开发和学习效率。

目前我掌握的是 AI 的应用层能力,能够拆解问题、组织上下文、编写有效提示词,并验证和落地 AI 的输出。对langchain有基础了解,但目前主要把 AI 作为开发辅助工具。涉及公司代码时,我也会进行脱敏,并遵守数据安全规范。

在必须按时交付的压力下,你是如何兼顾速度与质量的?

我会先和负责人确认交付范围、优先级和验收标准,把任务拆分成核心功能和后续优化,优先完成验收标准。开发过程中尽量复用成熟方案,采用小步提交,并同步进行单元测试、关键路径测试和代码 review。必要时调整范围或安排资源,确保在按时交付的同时守住功能正确性和稳定性底线。

你最近一段实习没有考虑转正是吗?为何选择这家初创公司?

是的,当时没有考虑转正,主要是实习岗位的后续发展方向与我的长期规划不完全一致,属于职业选择,并不是对公司或团队不认可。

选择初创公司,是因为岗位内容与我的 C++ 技术方向比较匹配,也能参与从需求、开发到上线的完整流程,承担更多实际责任。我了解初创公司的节奏比较快、要求综合能力较强,也愿意在这样的环境中持续积累和成长。

你认为自己更偏向哪种角色类型?比如技术深耕 or 团队领导?

现阶段我更偏向技术深耕型,希望先在 C++、操作系统、并发和性能优化等方面建立扎实能力,成为可靠的技术骨干。同时我也重视沟通协作,积累经验后愿意承担项目推进、技术分享和新人指导,逐步向技术负责人发展。

你能不能讲一下你当时是遇到了什么问题,接着你是怎么找到解决方案的?讲一下这整个过程。

可以这样回答:

我当时在做基于 Linux Reactor 的 HTTP 服务器,压测时发现 500 个慢客户端同时请求大文件,会导致服务内存持续上涨,正常请求也几乎无法响应。

我先让ai帮我测试几项监控指标,包括进程实际内存 RSS、每个连接待发送的数据量、当前打开的文件描述符数量,以及 Socket 的 EPOLLOUT 状态,用来确认是否存在慢客户端导致的发送阻塞和资源堆积。。最后定位到:客户端发送速度很慢,但服务端仍持续读请求、生成响应,未发送数据不断堆积在队列中,缺少有效的背压和资源上限。

我的解决方案是建立唯一的 FIFO 输出队列,遇到短写或 EAGAIN 时保存发送进度;队列达到高水位就暂停 EPOLLIN,降到低水位后再恢复。同时在响应入队前统一预留内存、文件 FD 和队列节点配额,使用 RAII 保证异常、关闭和超时路径都能正确释放资源。

最后我用功能测试、ASan、TSan 和 Release 压测验证。500 个慢客户端场景下,内存从约 2.6GB 降到 5.3MB,正常请求从无法完成提升到约 4 万 QPS。这个过程让我确认,解决并发问题要先复现和量化,再根据状态和资源流向定位根因,最后用专项测试和压测验证方案。

"我当时遇到的问题是,大模型流式返回的数据,不能直接按每次网络回调来解析。一次回调可能只有半条 JSON,也可能包含多条消息,直接解析容易失败或者丢失内容。

我沿着'接收数据、解析 JSON、回调输出'这条链路排查,发现核心原因是:网络接收块的边界和业务消息的边界不一致。

所以我增加了接收缓冲区,先把数据拼接起来,再按协议分隔符提取完整消息;不完整的部分留到下次继续处理。比如 DeepSeek 按 SSE 的空行分隔,Ollama 按换行分隔,解析后统一通过回调输出增量文本。

验证重点是半条消息、多条消息合并,以及结束标记这些边界情况,检查内容是否完整、顺序是否正确。"

相关推荐
无名猿40 分钟前
移动构造与移动赋值:把资源偷过来
c++·性能优化·内存管理·现代c++·语法基础
All for pursuit.1 小时前
【回溯-7】494.目标和
数据结构·c++·算法·leetcode
白杨尚青1 小时前
C++入门篇(十三):vector(上)——动态数组:构造、空间增长与迭代器失效
开发语言·c++·笔记·算法·stl
(Charon)3 小时前
【C++面试】RAII到底是什么:从资源管理理解C++对象生命周期
c++·算法·面试
无名猿3 小时前
成员初始化列表与初始化顺序陷阱:按声明顺序,不是书写顺序
c++·现代c++·语法基础
91刘仁德3 小时前
传输层协议深度拆解:端口寻址、UDP 报文与 TCP 可靠性机制全解析
linux·网络·c++·网络协议·tcp/ip·udp·c
Liuchang09113 小时前
洛谷 P2670 [NOIP 2015 普及组] 扫雷游戏
c++·算法·游戏
顶点多余3 小时前
C/C++常见面试题
开发语言·c++
HugoStudio_SWAN3 小时前
洛谷 B4575 [GESP202609 二级] 直角三角形——临场定义理解与边界质疑
c++·学习·程序人生·算法