一、动态库加载流程
1、文件查找与打开:
- 内核先根据环境变量
LD_LIBRARY_PATH 或者系统默认路径/lib、/usr/lib查找动态库文件路径; - 再通过 cwd + 文件名 查找 dentry,通过 dentry 获取 iNode,iNode 指向磁盘块,准备加载文件内容。
2、文件加载到内存
- 内核为 ELF 文件分配物理页,把 ELF 各段:
.text、.data、.rodata、.bss等内容从磁盘读取到物理页,映射到进程的虚拟地址空间,设置访问权限(如.text可执行只读,.data可读写)。 - ELF 中 PLT、GOT、.rela.plt 等表也被映射到虚拟内存。
3、静态重定位
- 动态链接器会先对全局变量、静态符号等立即做重定位。
- 更新 GOT 表,让访问这些符号时直接使用真实地址。
4、函数的延迟绑定(PLT表/GOT表机制)
(1)第一次调用某个动态函数时,CPU 执行 call PLT 表项,跳转到对应 GOT 表项;此时 GOT 表中存放的仍是指向 PLT 内部的地址,因此会继续执行 PLT 后续指令,将重定位索引号压入栈中,再跳转到动态链接器的解析函数;
(2)动态链接器根据该索引号找到 .rela.plt 中对应的重定位条目,获取函数符号名并完成符号解析,得到该函数在内存中的真实虚拟地址;随后将这个真实地址写入对应的 GOT 表项。
(3)后续再次调用该函数时,PLT 直接从 GOT 表中读取真实地址并跳转,不再触发动态链接器解析。
二、在一个机器上既有udp服务器又有tcp服务器,两个服务器用同一个端口,可以吗
可以,TCP 和 UDP 是不同的协议
- TCP 和 UDP 是不同的传输层协议,内核维护 独立的套接字表 :
- TCP 套接字表:存储
(IP, TCP port)对应的连接 - UDP 套接字表:存储
(IP, UDP port)对应的端点
- TCP 套接字表:存储
- 因此,TCP 的 8080 端口和 UDP 的 8080 端口是互不冲突的。
- 区分 socket唯一标识符 = 协议类型 + 本地 IP + 本地端口
- 所以:
- TCP 和 UDP 可以同端口共存(协议不同)
- 同协议不同 IP 可以绑定同端口
- 同协议同 IP 同端口只能有一个套接字(或用特定复用选项:SO_REUSEPORT)
- 只要有一个地方不同,那么内核维护 独立的套接字 就不同。
三、TCP如何保证可靠性传输?
1、序列号 + 确认应答(ACK)
发送方:发送数据,每个字节都有序列号
接收方:收到后回复 ACK,告诉发送方"我收到了哪些"
2、超时重传
3、流量控制(滑动窗口)
接收方告诉发送方自己的缓冲区剩余大小 ,防止发太快把接收方撑爆。

4、拥塞控制: 防止把网络打爆(区别于流量控制是防止把接收方打爆)。
5、三次握手 / 四次挥手: 建立连接时确认双方收发能力正常,断开时确保数据全部传输完毕。
6、校验和 :对每个 TCP 报文段计算校验和,接收方验证,检测数据在传输中是否损坏。
四、多线程、多进程在使用场景上的区别?
1、核心区别
进程:独立的内存空间 ,资源隔离
线程:共享同一进程的内存空间,资源共享
2、选多线程的场景
(1) 任务间需要大量共享数据
vector<int> shared_data; // 所有线程直接访问
(2)频繁创建销毁并发单元 :线程比进程轻量得多,进程创建需要复制父进程地址空间,线程不需要
(3)典型场景 :Web 服务器处理请求:并发处理大量请求(I/O 密集),每个请求一个线程。
**同一服务内的任务并发处理:**业务的拆分,多线程处理子任务(计算、数据处理)
3、选多进程的场景
(1)需要强隔离,一个进程崩溃不影响其他进程
(2)安全性要求高
(3)典型场景: 浏览器、IDE(插件隔离) ;微服务架构(每个服务独立进程)
五、任务的同步和异步的区别
(1)同步: 调用方发起操作后,必须等待结果返回,才能继续往下执行。
(2)异步: 调用方发起操作后,不等待结果,可以继续执行,结果通过回调/通知获取。
//仿muduo库,在外部线程调用RunInLoop,不需要立刻得到结果,所以可以把任务放到EventLoop的任务队列,后面执行到任务队列的任务时,执行结果通过回调函数返回--》这里是当前项目可以改善的地方:比如我可以【任务 + 回调】一起丢给IO线程的RunInLoop。
六、在浏览器地址栏输入一个URL后回车,将会发生的事情
(1)当在浏览器地址栏输入一个 URL 后,首先浏览器会对 URL 进行解析,提取出协议、域名和请求路径。
(2)接下来会进行 DNS 解析,将域名转换为 IP 地址。DNS 查询会先查浏览器缓存、操作系统缓存和本地 hosts 文件,如果没有命中,就向本地 DNS 服务器发起请求,通过递归或迭代查询,依次访问根服务器、顶级域服务器和二级域服务器,最终得到目标服务器的 IP 地址。
(3)得到 IP 后,浏览器会与服务器建立连接。如果是 HTTP,会进行 TCP 三次握手;如果是 HTTPS,还会在 TCP 之上进行 TLS 握手建立安全连接。
(4)在发送请求前 ,操作系统的网络栈会进行一系列处理:首先在 IP 层 ,根据目标 IP 查路由表,通过最长前缀匹配确定数据包的出口网卡以及下一跳地址。
(5)然后在 ARP 协议中,根据下一跳 IP 地址(如果是同一子网就是目标主机,否则是网关)查询对应的 MAC 地址,如果本地 ARP 缓存没有,就会广播 ARP 请求获取目标 MAC。
(6)当获取到 MAC 地址后,就进入数据封装过程:应用层生成 HTTP 请求报文,传给 TCP 层封装成 TCP 段,再由 IP 层封装成 IP 数据报,最后在数据链路层封装成以太网帧,其中包含源 MAC 和目标 MAC 地址,然后通过网卡发送出去。
(7)数据到达服务器后,服务器会进行反向解封装,交给应用程序处理,请求处理完成后返回 HTTP 响应。
(8)浏览器接收到响应后,会解析 HTML 构建 DOM 树,解析 CSS 构建 CSSOM,生成渲染树,并进行布局和绘制,最终将页面展示出来。
七、本地DNS服务器查询目标IP地址使用UDP?
DNS 查询默认使用 UDP,是因为 DNS 查询通常是一次请求一次响应,数据量小,UDP 无连接、开销低、效率高,适合高并发场景。
如果返回数据超过 UDP 限制或需要可靠传输时,DNS 才会使用 TCP。
八、HTTPS 握手过程?
HTTPS 握手基于 TLS 协议。
客户端首先发送 ClientHello,包含支持的协议版本、加密套件和随机数;
服务器返回 ServerHello,选择加密方式并发送证书。 客户端验证证书后,通过 RSA 或 ECDHE 算法与服务器协商出共享密钥。随后双方根据随机数生成对称会话密钥,并通过 Finished 消息确认握手完成,之后所有通信都使用对称加密进行。
HTTPS 的安全性依赖于 CA 体系、非对称加密以及对称加密的组合,同时通过随机数防止重放攻击。
九、证书是怎么验证的?
客户端在 HTTPS 握手中会验证服务器证书。首先检查证书是否由受信任的 CA 签发,然后验证证书是否过期、域名是否匹配,以及是否被吊销。
最核心的是通过 CA 的公钥验证证书签名,确保证书内容没有被篡改。同时浏览器会根据证书链逐级验证,从服务器证书到中间 CA,再到根 CA,最终建立信任关系。


十、ip路由的过程,ip怎么去转发?查路由表返回什么样的结果?
在 Linux 中,IP 转发的核心是查路由表。系统会根据 目标 IP做最长前缀匹配,找到最合适的路由项。路由项会返回下一跳地址、出接口以及源地址等信息。
如果目标在同一子网,就直接 ARP 获取目标 MAC;如果不在,就发给默认网关,最终通过网卡把数据帧发送出去。
Linux 查路由用的是 FIB (Forwarding Information Base),而不是简单遍历路由表。


11、ARP协议的输入输出:
ARP 协议的输入是目标 IP 地址,输出是对应的 MAC 地址。
当主机需要发送数据时,如果不知道目标 MAC,就会广播(报文的目的MAC是0xfffffff) ARP 请求,目标主机返回自己的 MAC 地址,随后缓存该映射关系用于后续通信。
12、计算机操作系统的cpu 利用率是怎么计算的
CPU利用率 = (总时间 - 空闲时间) / 总时间
CPU 利用过统计一段时间内 CPU 非 idle 时间 占比计算的,Linux 通过 /proc/stat 的累计时间差来计算。率是通
ps:(空间维度)在云服务器内存打满时,可以通过free -h 查看整体内存(看available),通过 top 或 ps 找占用最多的进程。
13、线程安全
(1)线程安全是指在多线程环境下访问共享资源时,不会出现数据不一致的问题。
(2)线程安全问题通常由多个线程同时修改数据、操作被分割(比如a++不是原子)、一个线程修改的数据,另一个线程看不到 引起。
常见的解决方法包括加锁、使用原子操作、线程安全的数据结构,以及尽量避免共享数据。
14、哪些数据结构线程安全
(1)大多数 STL 容器如 vector、map、unordered_map 等默认都是线程不安全的,因为它们没有内置同步机制。在多线程环境下同时读写会导致数据竞争甚至程序崩溃。
(2)只有像 std::atomic 这样的原子类型是线程安全的。
(3)如果需要在多线程中安全使用容器,通常需要通过 mutex 或读写锁进行同步控制,或者使用无锁编程技术。
15、mutex vs shared_mutex vs spinlock 区别 + 使用场景
(1)mutex :互斥锁,同一时间只允许一个线程访问临界区,抢不到锁的线程会进入阻塞状态。
适用场景:
- 临界区较大(对比上下文,上下文切换开销小)
- 锁竞争不激烈
(2)shared_mutex :读写锁,允许多个线程同时读,但写操作必须独占。
- 适用于缓存、读多写少场景。
(3)spinlock :自旋锁,线程抢不到锁不会阻塞,而是一直忙等待,占用 CPU。
- 适用于临界区非常短的高性能场景。
16、滑动窗口和拥塞控制
滑动窗口是 TCP 的流量控制机制,用于限制发送方发送速率,防止接收方处理不过来;
拥塞控制则是用于避免网络拥堵,通过 cwnd 控制发送速率。
发送方的实际发送窗口取 rwnd 和 cwnd 的最小值,其中 rwnd 由接收方决定,cwnd 由网络状况动态调整。

17、分页和分段
(1)分页是将内存划分为固定大小的页,主要用于物理内存管理,页与页之间不要求连续,通过页表进行映射;
(2)分段是按照程序的逻辑结构划分内存,如代码段、数据段等,每段大小不固定,段与段之间需要连续,反映程序的逻辑意义。
(3)分页主要解决内存利用率问题,而分段更符合程序结构。
18、虚拟内存 + 页表 + TLB + 缺页中断完整流程
(1)虚拟内存为每个进程提供独立的地址空间,通过页表将虚拟地址映射到物理地址。
(2)TLB 在CPU 内部,不需要访问内存,所以快!!!
- CPU 访问内存时首先查 TLB,如果命中则直接得到物理地址;
- 如果未命中则查页表,回填 TLB。
- 如果页表发现该页不在内存中,则触发缺页中断,由操作系统从磁盘加载该页到内存并更新页表,随后重新执行指令。
19、为什么要多级页表
虚拟地址空间非常大,如果使用单级页表,会导致页表占用内存过大且需要连续存储,浪费严重。多级页表通过将页表分层,只为实际使用的虚拟地址空间分配对应的页表,从而实现按需分配,显著减少内存开销,同时支持更大的地址空间。
19.1 单级链表连续?多级链表不连续?原理
(1)单级页表本质是一个线性数组,需要覆盖整个虚拟地址空间,因此必须整体分配,逻辑上是连续结构,即使未使用的区域也会占用页表空间。
(2)而多级页表通过将页表拆分为多级结构,每一级页表只在需要时才分配,因此页表不需要一次性连续分配,从而节省大量内存。
二者本质区别不是"连续与否",而是"全量预分配 vs 按需分配"
| 概念 | 是否"连续" |
|---|---|
| 虚拟地址空间 | 必然连续(对程序而言) |
| 单级页表 | 逻辑上"全覆盖数组"(虚拟页号 = 数组下标) |
| 多级页表 | 逻辑上"稀疏结构"(虚拟地址 = 分段索引 + 指针跳转) |
20、为什么页表访问是多次内存访问?
多级页表结构中,每一级页表项都存放在内存中。
以二级页表为例,CPU 在进行地址转换时,首先访问页目录获取页表地址,再访问页表获取物理页框号,最后再访问实际数据,因此至少需要两次甚至更多次内存访问。TLB 命中时可以避免这些额外访问。
21、单级页表+哈希为什么不行
单级页表结合哈希表虽然在平均情况下可以达到 O(1) 查找,但存在哈希冲突导致性能不稳定的问题,最坏情况下会退化为 O(n)。
同时哈希结构访问路径不固定,不利于硬件 MMU 实现,也破坏了内存访问的局部性。相比之下,多级页表虽然访问次数更多,但结构简单、可预测、缓存友好,并且支持按需分配,因此成为主流方案。
哈希页表访问路径不固定,是因为每次地址转换需要经过 hash 计算以及冲突处理(如链表遍历或探测),导致访问次数不可预测。
而 MMU 需要确定且简单的访问流程,才能高效实现硬件级地址转换。
同时哈希结构会使页表项分散在内存中,破坏空间局部性,导致 cache 命中率下降。
相比之下,多级页表虽然需要多次访问,但访问路径固定、结构规则,更符合硬件实现和 cache 优化