面经 csig_0318
面经 select/poll/epoll ,常见的有哪些输入事件? 针对 epoll,常见的有:读事件(EPOLLIN)、写事件(EPOLLPUT)、异常事件(EPOLLPRI) 环形缓冲区 解决了哪些问题? 环形缓冲区 通过 首尾相接 的方式,让读写指针循环移动,在 固定大小内存 里反复利用空间,这样可以减少数据搬移、减少频繁内存申请释放的开销。 编程常用的数据结构(数组 vector、链表 list、红黑树 map、哈希表 unordered_map) 链表环的检测方法?(快慢指针) 如何解决哈希冲突? 链地址法: 把哈希表的每个桶设计成一个链表,发生冲突时,就把元素挂到同一个桶后面。 开放定址法: 冲突后,不用链表,而是继续在表里找别的空位置存放。 线性探测: 冲突了就往后 一个一个 找; 二次探测: 冲突后按 平方步长 去找: rehash: 冲突太多,往往说明 表太小了。 这时通常会 申请更大的哈希表把原来的元素重新哈希后搬过去 常见排序算法?(快排、归并、堆排序) map...
面经 wxg_0312
面经 进程和线程的区别?(进程是资源分配的基本单位,线程是 CPU 调度的基本单位) 进程和线程的开销有没有差异?(进程切换时要切换更完整的上下文,线程切换通常更轻量) 协程了解过嘛?(协程是用户态线程,是程序员自己进行切换) 在应用程序中读文件涉及进程的切换嘛?(读文件一定会发生 用户态到内核态的切换;是否发生进程/线程切换,要看会不会 阻塞) 为什么要切换进程?(CPU 是并发处理操作系统的任务的,当前进程阻塞,就需要让出 CPU 去做其他事情) 为什么不在当前进程内直接读而是切换到其他进程?(为了提高 CPU 利用率) 什么 IO 会阻塞当前线程?(同步阻塞 IO) 访问磁盘的时是直接访问到硬件还是用过一些系统的转换?(DMA 请求过程) 虚拟内存的概念?(虚拟内存 就是操作系统给每个进程看到的一套 “虚拟的、连续的内存空间” ) 如果把换页交给应用进程可以嘛? 理论上应用进程可以参与内存管理策略,但不能把换页完全交给应用进程。 因为 换页 需要了解整个系统的内存使用情况,还涉及 页表修改、物理页分配 和 缺页异常处理,这些都属于 内核权限。 内存管理...
STL 常问问题
序列式容器1️⃣ C++ 容器有哪些? 序列式容器: vector、list 关联式容器: map、unoredred_map 2️⃣ vector 超出容量会怎样? vector 会先申请一块 1.5/2 倍 的当前内存大小的连续内存; 把原来元素 拷贝/移动 到新内存; 释放旧内存,再把新元素插进去 3️⃣ vector 扩容后会带来什么后果? 因为元素移动到了新内存,所以原来元素的地址可能全变了。 凡是指向原来 vector 内部元素的 迭代器、指针、引用失效 4️⃣ vector 如果申请新内存失败会怎样 可能抛出异常,一般是 std::bad_alloc 5️⃣ size() 和 capacity() 的区别? size(): 当前已经有多少个元素 capacity(): 当前底层内存最多还能容纳多少个元素而不触发扩容 6️⃣ resize() 和 reserve() 的区别? reserve(): 让 vector 的内存容量至少达到 n,避免后续频繁扩容。 n <= capacity():通常什么都不做; n >...
协程服务器常问问题
1️⃣ 面经一 (腾讯) 什么是协程(用户态线程,用户栈,用户态切换) 协程和线程相比有什么优势(轻量级、切换快、开销小,用户管理,配合非阻塞 IO 更好地实现异步并发) 更好地实现异步并发是怎么理解的(IO 操作时检测到需要等待缓冲时切换协程) 线程也可以在等待时切换,协程的优势是什么(用户自己操作) 轻量级怎么理解(线程的栈是 MB 级别的,协程的栈是 KB 级别的,线程在内核中切换,协程在用户态中切换) 怎么设置非阻塞(fctnl) 返回 EAGAIN 是什么意思(缓冲区没准备好) 互斥锁怎么实现的(访问锁时如果锁被占用就阻塞,加入阻塞队列,锁被释放时唤醒阻塞队列的中的线程进入就绪队列) http协议怎么解析的(引入了其他项目的解析器) http 是什么格式(消息行 消息头 消息体) N:M 的协程调度器是什么(N线程处理M协程) 如何调度(先来先服务,线程依次去协程队列取任务执行,如果没有任务就执行空闲协程,陷入 epoll_wait,有任务时再唤醒空闲线程去执行任务) 如果所有线程都不空闲,其他协程是不是就不调度了...
插件常问问题
1️⃣ 项目难点 流量线程 会根据 虚拟地址 高频查询客户端信息,但客户端断连时 pcc 会从 在线上下文 里移除。如果流量线程直接依赖 pccMap,就会面临 pcc 生命周期不稳定的问题。 所以我没有让 流量线程 直接访问 pcc,而是专门维护了一个 virtual_address_map,把流量线程需要的字段提前从 pcc 中抽出来,按 virtual_address -> Json 的形式保存。这样流量线程只读 轻量级映射,不依赖 pcc 本身,从而规避了断连场景下访问失效上下文的问题,同时也减少了重复组装 JSON 的开销。 2️⃣ 项目优化请求链路优化 根据场景不同,将 证书校验、用户规则下载、连接状态上报 进行拆分: 证书校验: 走 **同步 fast_fail**(服务器不可访问则立即返回),因为证书校验在主链路上,必须立即返回; 用户访问规则下载: 走 **异步 gRPC**,基于用户的 完成队列 (Completion Queue) 提前下载用户规则,同时避免阻塞 OpenVPN...
面经 csig_0309
面经 C++ 的智能指针你用过吗?(主要使用过 shared_ptr、weak_ptr、unique_ptr) C++ 里面总共有多少种智能指针?每一种的用法和应用场景是什么?(三种) unique_ptr: 定义:表示对所拥有的对象的独占权,一个对象只能被一个 unique_ptr 拥有 特点:只可以移动,不可以被复制,经常配合 std::move 来使用; shared_ptr: 定义:表示对所拥有的对象的共享权,一个对象可以被多个 shared_ptr 拥有; 特点:需要维持一个控制块,来进行引用计数; weak_ptr: 定义:不拥有对象,但是 weak_ptr 可以观察 shared_ptr 所拥有的对象; 特点:解决循环依赖问题;作为缓存对象; C++ 在 main 函数执行之前,还有哪些函数会先执行? main 之前通常会先执行运行时启动代码,然后完成 全局变量、静态变量的初始化。 如果这些对象是 类类型,就会 先调用它们的构造函数; 如果它们的初始化表达式里调用了普通函数,这些函数也会在 main 前执行。 程序真正的入口一般不是...
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...
面经 wxg_0307
1️⃣ Linux 是怎么实现并发的?有了进程和线程就可以实现并发了吗?第一层:任务抽象Linux 把 程序执行流 抽象成 “任务”,常见表现为: 进程: 资源分配的基本单位,拥有独立地址空间 线程: CPU 调度的基本单位,同一进程内线程共享地址空间、文件描述符等资源 第二层:调度器Linux 通过 调度器 把 CPU 时间切给不同任务。 即使只有一个 CPU 核,也可以通过 时间片轮转 + 上下文切换,让多个任务 “看起来同时在运行”,这叫 并发。 第三层:中断与系统调用用户程序一旦发生: 时钟中断 缺页中断 系统调用CPU 会陷入内核,内核就有机会切换任务等。 第四层:阻塞/非阻塞 + 多路复用2️⃣ 单核 CPU 可以实现多线程吗? 单核 CPU 上只能做到:多线程并发,不能做到真正的并行。 3️⃣ 虚拟地址 虚拟地址 是程序看到的地址,不是物理内存条上的真实地址。 4️⃣ 程序的地址空间 .text 段 .rodata 段 .data 段 .bss 段 堆 heap 内存映射区 栈...
