IO 多路复用
1️⃣ 同步阻塞 IO造成的问题 两处阻塞: 1. 进程刚调用 recv ; 2. 等待内核将 数据 拷贝到 用户缓冲区 两处进程切换: 1. 连接持续没有数据到达; 2. 网卡将数据从 内核 复制到 socket 的 等待队列,进程会被唤醒 单对单连接:一个进程只能等待一条连接 2️⃣ 同步非阻塞 IO优点将数据从 网卡 拷贝到 socket 的内核空间 这一阶段变成 非阻塞模式。 用户进程调用 recvfrom() 会重复发出请求,检查数据是否到达 socket 的 等待队列。如果没有到,会立即返回。 缺点没有解决:当数据到达 socket 的 等待队列,用户进程需要将数据从 内核空间 拷贝到 用户空间。 3️⃣ 同步阻塞 IO 的数据接收流程 服务端使用系统调用 socket 会陷入到内核态,内核就会创建 socket 内核对象,其主要包含两个重要结构体: (进程)等待队列: 存放了进程的 进程描述符 和 回调函数。 (数据)接收队列: 存放了网卡接收到的需要该 socket 的数据 服务端当通过 socket 调用 recv 函数时,会执行...
零拷贝
1️⃣ 传统 I/O 过程在没有 DMA 技术之前,I/O 过程如下所述 (以 read 举例): 用户执行系统调用 read:将控制权从 用户态 转换到 内核态,此时进程阻塞在这里。 CPU 向磁盘发送 I/O 请求:磁盘接收到 I/O 请求后,就需要的数据填充到自己的磁盘缓冲区,然后产生中断信号。 磁盘向 CPU 发送 I/O 中断信号: CPU 先就把磁盘缓冲区的数据一次一个字节的读进到自己的 寄存器。 再把 寄存器的数据 写入到 内存 中,但是传输过程中 CPU 无法执行其他任务。 系统调用 read 返回:将控制权从 内核态 转换到 用户态。 2️⃣ DMA 技术定义: 在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务。 用户执行系统调用 read:将控制权从 用户态 转换到 内核态,此时进程还是会阻塞在这里。 CPU 向 DMA 发送 I/O 请求:此时 CPU 就可以执行其他任务,搬运工作就交给 DMA。 DMA...
进程虚拟内存管理
1️⃣ 进程的虚拟地址空间 2️⃣ 内核如何管理进程?内核通过 task_strcut 来对进程进行管理 因为在进程描述符 task_struct 结构中,有一个专门描述 进程虚拟地址空间 的内存描述符 mm_struct。由于每个进程都有唯一的 mm_struct 结构体,因此每个进程的虚拟地址空间都是 独立的。 12345678910struct task_struct { // 进程id pid_t pid; // 用于标识线程所属的进程 pid pid_t tgid; // 用于标识线程所属的进程 pid pid_t tgid; // 内存描述符表示进程虚拟地址空间 struct mm_struct *mm;}; 子进程是如何被创建的? 调用 fork() 函数创建进程的时候, 子进程地址空间 的 mm_struct 结构会随着子进程描述符 task_struct 的创建而创建。 copy_process 函数中创建 task_struct...
操作系统常问问题
虚拟内存1️⃣ 什么是虚拟地址?操作系统 为每个进程都分配了独立的 虚拟地址。并且,操作系统提供一种机制,将不同进程的虚拟地址和不同的物理内存地址映射起来。如果程序要访问虚拟地址,就由操作系统转换成不同的物理内存地址。 之后,进程持有的虚拟内存地址会通过 CPU 芯片中的 MMU 的映射关系,将其转换成物理内存地址。 作用: 虚拟内存可以使得进程对运行内存超过物理内存大小: 因为程序运行符合局部性原理,CPU 访问内存会有很明显的重复访问的倾向性 对于那些没有被经常使用到的内存,我们可以把它换出到物理内存之外,比如硬盘上的 swap 区域。 由于每个进程都有自己的页表,所以每个进程的虚拟内存空间就是相互独立的 页表里的页表项中除了物理地址之外,还有对页的标志位 2️⃣ 操作系统是如何管理虚拟地址与物理地址之间的关系?主要有两种方式,分别是 内存分段 和 内存分页 3️⃣ 分段机制下,虚拟地址和物理地址是如何映射的?分段机制下的虚拟地址由两部分组成,段选择因子 和 段内偏移量。 段选择因子 最重要的就是 段号,用作 段表 的索引。其中 段表 保存了 段的基地址 以及...
了解 Linux 防火墙
1️⃣ Netfilter提到 Linux 防火墙,很多人会先想到 iptables、nftables、firewalld、ufw,它们本质上都只是 用户态 的 配置工具。 真正负责 “拦包、改包、放行 /丢弃” 的核心,是 Linux 内核里的 Netfilter。 从上图可以看出:流量流动 是先从网卡,再到 linux 内核,最后抵达到网络应用。 流量过滤 指的是先让流量从网卡进来,然后在 linux 内核里进行过滤处理,最后进入网络应用。这样网络应用前面就有一道防火墙,防火墙就位于 linux 内核的实现中。 挂载点Netfilter 向 内核协议栈 中 不同的位置 注册 钩子函数 HOOK 来对数据包进行过滤或者修改操作,这些位置称为 挂载点。 Netlfilter 通常有 5 个 HOOK 点: PREROUTING:数据包进入路由表之前 INPUT:通过路由表后目的地为本机 FORWARD:通过路由表后,目的地不为本机 OUTPUT:由本机产生,向外发送 POSTROUTIONG:发送到网卡接口之前。 2️⃣ iptablesiptables...
多态及其虚函数底层原理
1️⃣ 什么是多态?众所周知,多态 是面向对象三大特性的核心。很多朋友停留在 “加个 virtual 关键字就能实现多态” 的表层认知。 多态的核心在于:同一个接口,不同的实现。举个简单的例子: 假设有一台通用机器人(基类),它能干很多事,比如打扫、修理等。咱们根据实际需要,造出了清洁机器人(派生类)和修理机器人(另一个派生类),它们具体干的活就不一样了。 那么,在 C++ 实现多态的底层原理是什么呢?这一切的答案就在于 虚函数表。接下来,将会从以下几个方面进行讲解: C++ 的绑定方式 虚函数表 vtable 的结构 虚指针 vptr 与 vtable 的关系以及内存布局 虚函数的实现流程 面试常问问题 2️⃣ C++ 的绑定方式在 C++ 中,“绑定” 通常是将 “函数调用” 和 具体的 “函数实现” 关联起来。这个过程可能发生在 编译期,也可能发生在 运行期,因此,C++ 中的绑定方式分为 静态绑定 和 动态绑定。 静态绑定静态绑定 又称 早绑定,是 C++ 中默认的绑定方式。 发生时机: 在 编译阶段 由编译器完成。 核心逻辑:...
智能指针
1️⃣ 背景在 C++ 中,动态内存的管理通过运算符 new/ delete 来完成的: new:在动态内存中为对象分配空间并返回一个指向该对象的指针。 delete:接收一个动态对象的指针,销毁该对象,并释放与之相关的内存。 动态内存的使用很容易出现问题,因为确保在正确的时间释放内存是极其困难的 !!! 忘记释放内存:内存泄露 释放一块仍有指针引用的内存:产生引用非法内存的指针 为了更安全地使用动态内存,新标准库提供了两种智能指针来管理动态内存: 智能指针的行为类似于常规指针,区别在于它自动释放所指向的对象 shared_ptr:允许多个指针指向同一个对象。 unique_ptr:只允许单个指针独占一个对象。 2️⃣ shared_ptr 类概念std::shared_ptr<T> 是一个类模板,采用 RAII 手法,它的对象行为像指针,但是它可以记录有多少个对象共享它所管理的内存对象。 使用经常通过如下两种方式创建 std::shared_ptr 对象: auto p = std::shared_ptr<T>(new...
HttpServer 模块
1️⃣ 概述在 sylar 框架中,HttpServer 是 TcpServer 的核心派生类,也是整个框架在 Web 开发领域的直接入口。如果说 TcpServer 负责的是 底层 的 “连接管理”,那么 HttpServer 负责的就是 “业务协议生命周期的全链路治理”。 HttpServer 不仅继承了 TcpServer 高并发、协程驱动 的优良基因,还引入了现代 Web 框架必备的 SSL 安全通信、中间件插件化、**Session 状态管理** 以及 Servlet 路由分发 等高级特性。 2️⃣ 设计核心HttpServer 的 核心逻辑 高度凝练在重写的 handleClient 方法中。 handleClient 将 socket 接收到的 Http 请求 转化成最终的 HTTP 响应,这其中经历了一套标准化的 “流水线” 操作: 安全加固 (SSL/TLS): 识别连接属性。若是 HTTPS 请求,则在最前端完成 非对称加密握手 逻辑(doHandShake),确保后续数据在加密流(SslSocketStream)中传输。 状态化封装...
TcpServer 模块
1️⃣ 概述在高性能网络框架中,TcpServer 模块往往处于 “承上启下” 的核心地位: 向下 封装了底层的 Socket 套接字细节, 向上 为各类 应用层协议 提供基础的连接管理和数据传输能力。 在 sylar 框架中,TcpServer 是一个高层次的封装,旨在提供一个 简单、稳定 且 支持多核并发 的 TCP 服务器基类。TcpServer 深度集成了 协程调度器 (IOManager) 和 套接字封装 (Socket),使得开发者可以专注于业务逻辑,而无需处理复杂的非阻塞 I/O 和多线程竞争。 2️⃣ 设计理念在 sylar 框架中,TcpServer 不仅仅是一个简单的套接字监听器,它是底层网络组件与复杂业务协议之间的核心桥梁。其架构设计的核心灵魂在于:通过高度解耦实现极致的并发效率与扩展性。 基于 Reactor 模式的双调度器架构这是 TcpServer 高并发能力的核心。它将网络事件处理划分为两个阶段,并允许通过不同的 IOManager 进行物理隔离: Accept 调度器:...
HOOK 模块
1️⃣ 什么是 HOOKHOOK ,中文简称:钩子 ,是对 函数/系统调用 API 进行一次封装。主要是将其封装成一个与原始的 函数/系统调用 API 同名的接口。 调用这个接口的时候,会 先进行封装的操作,再执行 原始 的 函数/系统调用API sylar 中的 HOOK,就是为了在不重写代码的情况下,把原有代码中的 同步 socket api 都转换为 异步 操作,以提升性能。 还可以用 C++ 的子类重载来理解 HOOK。在 C++ 中,子类在重载父类的同名方法时,一种常见的实现方式是子类先完成自己的操作,再调用父类的操作,如下: 12345678910111213141516class Base {public: void Print() { cout << "This is Base" << endl; }}; class Child : public Base {public: ///...
