百度实习面经,瑟瑟发抖...
1 内存分区,进程内存映象,linux内存模型
-
内存分区(Memory Partitioning): 内存分区是指将计算机的物理内存划分成不同的区域,每个区域用于存储不同类型的数据。常见的内存分区包括:
- 代码段(Code Segment):存储程序的执行代码。
- 数据段(Data Segment):存储全局变量等数据。
- 堆(Heap):用于动态分配内存,由程序员进行手动管理。
- 栈(Stack):用于存储函数调用的局部变量、函数参数等。
-
进程内存映像(Process Memory Image): 进程内存映像是指一个运行的进程在内存中的布局和组织。它包括以下部分:
- 代码段:存储进程的执行代码。
- 数据段:包括初始化的全局变量和静态变量。
- 堆:用于动态分配内存,比如通过
malloc
和free
。 - 栈:存储函数调用的局部变量、函数参数等。
进程内存映像使得操作系统能够有效地管理多个进程,并确保它们之间不会相互干扰。
-
Linux内存模型: Linux操作系统采用了分页机制来管理内存,其中有两个关键概念:
-
虚拟内存(Virtual Memory):允许程序使用比实际物理内存更大的地址空间。这使得每个进程都有独立的地址空间,提高了系统的稳定性和安全性。
-
分页(Paging):将物理内存划分成大小固定的页面,与虚拟内存中的页面相对应。当一个程序需要访问某个页面时,操作系统将其加载到物理内存中。这有助于提高内存的利用率和管理效率。
-
2 堆是谁来分配
在大多数编程语言中,堆内存的分配和释放通常是由程序员手动管理的,而不是由编程语言或操作系统自动处理。
-
手动管理(Manual Management):
- 在C语言中,通过使用
malloc
函数来动态分配堆内存,而使用free
函数来释放已分配的内存。 - 在C++中,可以使用
new
运算符来分配堆内存,而使用delete
运算符来释放内存。
- 在C语言中,通过使用
-
自动管理(Automatic Management):
- 有一些编程语言提供自动内存管理,例如Java、C#和Python。在这些语言中,垃圾回收器(Garbage Collector)负责自动识别不再被程序使用的内存,并释放它们,包括堆中的内存。
在手动管理的情况下,程序员负责确保正确地分配和释放内存,以避免内存泄漏(内存未释放)或悬挂指针(引用已释放的内存)等问题。在自动管理的情况下,垃圾回收器会自动处理内存的分配和释放,减轻了程序员的负担,但也可能引入一些性能开销。
总体而言,堆内存的分配和释放取决于编程语言和程序员的选择。
3 开发时碰到内存溢出,内存泄露
处理内存溢出:
-
检查算法和数据结构:
- 优化算法和数据结构,确保它们在处理大数据量时具有良好的性能。
-
释放不必要的资源:
- 在使用完资源后及时释放,包括文件句柄、网络连接等。
-
增加堆栈大小:
- 对于一些递归或深度调用的情况,可能需要增加堆栈大小。
-
使用内存池:
- 对于频繁的内存分配和释放,考虑使用内存池,减少内存碎片化。
-
分析内存使用情况:
- 使用工具分析内存使用情况,例如内存分析器,以找出哪些部分占用了大量内存。
处理内存泄漏:
-
使用内存分析工具:
- 使用专业的内存分析工具,例如Valgrind(对C/C++很有用)或内存分析器,来检测和报告内存泄漏。
-
代码审查:
- 定期进行代码审查,特别关注内存分配和释放的代码块,确保它们的使用是正确的。
-
使用智能指针:
- 对于支持智能指针的语言,使用它们可以减少手动内存管理的错误。
-
确保资源释放:
- 在对象生命周期结束时,确保释放与之关联的资源,包括关闭文件、释放网络连接等。
-
编写单元测试:
- 编写测试用例,特别是针对内存管理的测试,以及时捕捉潜在的问题。
-
日志和监控:
- 在应用程序中实施日志和监控机制,以及时发现和诊断内存泄漏问题。
-
定期性能优化:
- 定期进行性能优化,包括内存方面的优化,以确保应用程序在长时间运行后不会因为内存泄漏而出现问题。
4 了解过检测内存泄露的工具或是方法
检测内存泄漏的工具和方法有很多,具体的选择通常取决于你所使用的编程语言和开发环境。以下是一些常见的工具和方法:
内存分析工具:
-
Valgrind(C/C++):
- Valgrind是一个强大的工具,可用于检测内存泄漏、不初始化的内存访问等问题。它能够在运行时对程序进行详细的内存分析。
-
AddressSanitizer(C/C++):
- AddressSanitizer是GCC和Clang的一个特性,用于检测内存错误,包括内存泄漏、访问已释放内存等。
-
LeakSanitizer(C/C++):
- LeakSanitizer是GCC和Clang的另一个特性,专门用于检测内存泄漏。
-
Dr. Memory(Windows,C/C++):
- Dr. Memory是一款用于检测内存泄漏和内存错误的工具,特别适用于Windows环境。
-
Heap Profiler(Java):
- Java有内置的Heap Profiler,可以用于分析Java应用程序的内存使用情况,包括泄漏检测。
日志和手动检查:
-
内存日志:
- 在代码中插入日志语句,记录内存分配和释放的信息,以便分析程序的内存使用情况。
-
手动检查:
- 定期审查代码,特别关注内存分配和释放的地方,确保资源的正确释放。
静态代码分析工具:
-
Clang Static Analyzer:
- Clang Static Analyzer是一个用于静态代码分析的工具,可以帮助检测代码中的潜在问题,包括内存泄漏。
-
Coverity:
- Coverity是一种商业静态代码分析工具,可以帮助发现和修复各种代码缺陷,包括内存泄漏。
运行时检测:
-
自定义检测机制:
- 在代码中插入特殊的标记或计数,以便在运行时检测内存泄漏。
-
使用内存管理库:
- 一些编程语言或框架提供了内置的内存管理库,可以用于检测内存泄漏,例如C++中的
_CrtDumpMemoryLeaks
。
- 一些编程语言或框架提供了内置的内存管理库,可以用于检测内存泄漏,例如C++中的
5 extern c语法,具体什么作用
extern "C"
是一种 C++ 中的语法特性,用于在 C++ 代码中声明使用 C 语言编写的函数或变量。它告诉编译器按照 C 语言的方式进行名称修饰和链接。
在 C++ 中,默认情况下,函数和变量的名称会经过名称修饰(name mangling)以支持函数重载和其他 C++ 特性。而在 C 语言中,不进行名称修饰。
使用 extern "C"
可以使 C++ 代码与 C 代码进行混合编译和链接,解决了在 C++ 中调用 C 函数或变量时名称不匹配的问题。
用法示例:
- 函数声明:
假设有一个 C 语言的头文件 example.h
:
// example.h
#ifdef __cplusplus
extern "C" {
#endif
void c_function(); // C 函数声明
#ifdef __cplusplus
}
#endif
在 C++ 中的源文件中可以这样使用:
// cpp_file.cpp
#include "example.h"
int main() {
c_function(); // 可以在 C++ 代码中调用 C 函数
return 0;
}
变量声明:
// example.h
#ifdef __cplusplus
extern "C" {
#endif
extern int c_variable; // C 变量声明
#ifdef __cplusplus
}
#endif
在 C++ 中的源文件中可以这样使用:
// cpp_file.cpp
#include "example.h"
int main() {
int value = c_variable; // 可以在 C++ 代码中使用 C 变量
return 0;
}
使用 extern "C"
时需要注意以下几点:
extern "C"
应该用于 C++ 代码中,通过#ifdef __cplusplus
来确保只在 C++ 环境下进行声明。extern "C"
的作用是告诉编译器使用 C 语言的命名和链接规则,因此在{}
内部的函数和变量会按照 C 的方式进行处理。- 使用
extern "C"
不会改变函数的实际实现,只是改变了函数的名称在符号表中的表示方式。
6 进程几种状态,切换的时机;interruptible和uninterrupted状态
在操作系统中,一个进程可以处于多个状态之一,而进程的状态转换是由操作系统内核根据进程执行的情况来管理的。以下是常见的进程状态以及状态之间的切换时机:
进程的几种状态:
-
运行(Running):
- 进程占用 CPU 运行,是当前正在执行的进程。
-
就绪(Ready):
- 进程已经准备好运行,但由于其他进程正在执行,它暂时无法获得 CPU 时间。
-
阻塞(Blocked)(也称为等待或睡眠):
- 进程暂时不能执行,因为它正在等待某个事件的发生,例如等待用户输入或等待磁盘I/O完成。
进程状态之间的切换时机:
-
创建(Creation):
- 进程被创建时,处于创建状态。
-
就绪和运行状态切换:
- 进程在就绪状态等待 CPU 时间,当操作系统调度它时,切换到运行状态。
-
运行和阻塞状态切换:
- 进程在执行过程中可能遇到需要等待的事件,例如等待I/O操作完成,此时它从运行状态切换到阻塞状态。
-
阻塞和就绪状态切换:
- 当某个等待的事件发生,阻塞的进程切换回就绪状态,等待CPU的调度。
-
运行和终止状态切换:
- 进程执行完毕或发生致命错误时,从运行状态切换到终止状态。
interruptible 和 uninterruptible 状态:
-
interruptible 状态:
- 进程处于可以被中断的状态。在等待某些事件(如I/O操作)时,进程可能处于interruptible状态,这表示它可以被操作系统中断,从而响应其他事件。
-
uninterruptible 状态:
- 进程处于无法被中断的状态。在某些情况下,例如正在等待磁盘I/O完成时,进程可能处于uninterruptible状态,这表示它不能被中断,必须等待事件完成才能继续执行。
这两种状态的选择通常取决于进程等待的事件的性质。例如,对于磁盘I/O,可能会选择uninterruptible状态,因为中断可能导致数据不一致。而对于网络I/O,可能选择interruptible状态,以便更及时地响应其他事件。
7 进程和线程区别
进程(Process)和线程(Thread)是操作系统中用于执行程序的两个基本的执行单位,它们之间有一些关键的区别:
- 定义:
-
进程:是一个独立的执行单元,有自己的地址空间、数据栈,以及其他系统资源(文件描述符、信号处理等)。进程之间相互独立,通信需要使用进程间通信(IPC)机制。
-
线程:是进程的一部分,共享进程的地址空间和其他资源,但拥有独立的执行栈。线程之间更容易共享数据和通信,因为它们共享相同的地址空间。
- 资源开销:
-
进程:相对较大的资源开销,每个进程都有独立的内存空间、文件描述符等。
-
线程:较小的资源开销,因为它们共享相同的资源。
- 通信:
-
进程:通信需要使用IPC机制,如管道、消息队列、共享内存等。
-
线程:通信更容易,因为它们共享相同的地址空间。
- 独立性:
-
进程:相对独立,一个进程的崩溃通常不会影响其他进程。
-
线程:一个线程的崩溃可能导致整个进程的崩溃,因为它们共享相同的地址空间和资源。
- 创建和销毁:
-
进程:相对较慢,涉及到分配独立的地址空间、资源初始化等。
-
线程:相对较快,因为它们共享相同的资源,创建和销毁的开销较小。
- 安全性:
-
进程:由于进程有独立的地址空间,一个进程的错误通常不会直接影响其他进程。
-
线程:由于共享相同的地址空间,一个线程的错误可能会影响其他线程。
- 适用场景:
-
进程:适用于多核心系统,能够实现真正的并行计算。
-
线程:适用于需要共享数据和通信的任务,因为线程可以更方便地共享相同的地址空间。
8 内存管理的buddy,伙伴系统
Buddy系统是一种内存管理的算法,常用于管理动态分配的内存块。它主要用于解决内存碎片化的问题,提高内存利用率。Buddy系统基于二叉树的概念,将内存分割为大小为2的幂的块,这些块的大小就像一对伙伴。
下面是Buddy系统的基本原理和操作:
-
内存分配:
-
初始时,整个可用内存看作是一个大小为2的幂的块。
-
当需要分配一块大小为2^k的内存时,系统在2^k的空闲块链表中查找可用块。如果找到,则分配该块;否则,向上合并块,直到找到合适大小的块。
-
内存释放:
-
当一块内存释放时,将其加入到对应大小的空闲块链表。
-
检查其伙伴块是否也是空闲的,如果是,就合并这两个块,并将合并后的块加入到上一级的空闲块链表中。这一过程一直持续,直到合并到最大块或者找到一个非空闲的块。
-
优点:
- 解决了内存碎片问题,因为大小为2的幂的块容易合并和分割。
- 分配和释放操作的时间复杂度为O(1)。
- 缺点:
- 内存浪费:由于每个块的大小必须是2的幂,因此可能出现一些内存浪费。
- 外部碎片:释放的内存块可能不能直接合并到相邻的块中,导致外部碎片。
- 应用场景:
- 适用于需要频繁分配和释放小块内存的场景,例如操作系统中的内核内存管理。
- 不适用于大对象的分配,因为可能导致较大的内存浪费。
9 CFS,和O(1)对比,结合实例说了一下
CFS(Completely Fair Scheduler)和O(1)调度器是Linux内核中用于进程调度的两种不同的调度算法。
O(1)调度器:
O(1)调度器是早期Linux内核中使用的一种调度算法,它的设计目标是使调度的时间复杂度保持为O(1)。这是通过维护一个任务数组和一个就绪队列实现的,通过简单的数据结构,O(1)调度器可以在常数时间内找到下一个要执行的任务。
实例:
#include <linux/sched.h>
void some_function(void) {
while (1) {
// 进行一些工作
schedule(); // 使用O(1)调度器进行进程切换
}
}
在上述示例中,schedule()
函数用于调用O(1)调度器进行任务切换。O(1)调度器在实时性能方面表现较好,但在处理大量任务时,可能导致某些任务饥饿。
CFS调度器:
CFS调度器是Linux内核中较新的调度算法,引入了红黑树的概念,通过对任务运行时间的虚拟时钟进行动态的调整,实现对任务的公平调度。CFS调度器的设计目标是提供更好的公平性和负载均衡。
实例:
#include <linux/sched.h>
void some_function(void) {
while (1) {
// 进行一些工作
schedule(); // 使用CFS调度器进行进程切换
}
}
在这个示例中,schedule()
调用用于调用CFS调度器进行任务切换。CFS调度器会根据任务的虚拟运行时间来选择下一个要运行的任务,以实现公平性。
对比:
-
时间复杂度:
- O(1)调度器的时间复杂度为O(1)。
- CFS调度器的时间复杂度相对较高,但它更注重于提供公平性和负载均衡。
-
公平性:
- O(1)调度器可能在任务的公平性方面表现较差,容易导致某些任务长时间无法获得CPU时间。
- CFS调度器被设计为更公平,它通过动态调整虚拟运行时间来确保任务相对公平地分享CPU时间。
-
适用场景:
- O(1)调度器适用于需要较低调度延迟的场景。
- CFS调度器适用于更注重公平性和负载均衡的场景。
10 中断的基本框架,以键盘中断为例
中断是计算机系统中一种异步事件处理的机制,允许系统在执行当前任务的同时响应外部事件。以下是处理中断的基本框架,以键盘中断为例:
- 中断发生:
- 用户按下键盘上的某个键触发中断,键盘控制器产生中断请求(IRQ)。
- 硬件层处理:
- CPU检测到中断请求,停止当前执行的任务。
- CPU保存当前执行任务的上下文(程序计数器、寄存器等)。
- 中断向量表:
- 硬件通过中断向量表确定中断的类型和处理程序的入口地址。
- 在键盘中断的情况下,中断向量表会指向处理键盘中断的中断服务程序。
- 中断服务程序(Interrupt Service Routine,ISR):
- 控制权转移到相应中断的处理程序。
- 在键盘中断的情况下,键盘中断服务程序负责处理键盘输入。
keyboard_isr:
; 处理键盘中断的汇编代码
; 读取键盘输入,更新相应的数据结构或触发相应的事件
; 恢复寄存器状态等
; 中断服务程序执行完毕后,执行中断返回指令
iret
- 软件层处理:
- 中断服务程序执行完毕后,CPU从中断返回指令(iret)返回到之前被中断的任务。
- 恢复之前保存的任务上下文。
- 继续执行任务:
- 控制权返回到之前被中断的任务,任务继续执行。
在这个基本框架下,中断服务程序是中断处理的核心。键盘中断服务程序将负责读取键盘输入、更新相应的数据结构、触发事件等。整个中断处理流程实现了异步事件的响应,使得系统能够在执行任务的同时处理来自外部的事件,提高了系统的响应性。
11 IPC
IPC(Inter-Process Communication)是指不同进程之间进行数据交换和通信的机制。在多任务和多进程的操作系统中,不同的进程可能需要互相通信,共享数据或者协同完成某些任务。以下是几种常见的IPC方式:
- 管道(Pipe):
-
描述: 管道是一种半双工的通信机制,数据流只能单向流动。通常用于具有父子关系的进程之间的通信。
-
示例: 在Shell中,通过管道可以将一个进程的输出连接到另一个进程的输入,实现两者之间的通信。
$ ps aux | grep "process_name"
- 消息队列(Message Queue):
-
描述: 消息队列是一种通过消息进行通信的机制,可以实现进程之间的异步通信。
-
示例: 进程A通过消息队列向进程B发送消息,进程B从消息队列中读取消息并作出相应处理。
- 共享内存(Shared Memory):
-
描述: 共享内存允许多个进程访问同一块物理内存区域,进程可以直接读写这块内存区域。
-
示例: 进程A将数据写入共享内存,进程B可以直接从共享内存读取这些数据,实现了高效的进程间通信。
- 信号(Signal):
-
描述: 信号是一种异步通信方式,用于通知进程发生了某个事件。每个信号都有一个唯一的数字标识,例如SIGINT表示中断信号。
-
示例: 进程A通过发送信号SIGUSR1通知进程B执行某个特定操作。
- 套接字(Socket):
-
描述: 套接字是一种提供网络通信的机制,也可用于同一台主机上不同进程之间的通信。
-
示例: 在客户端-服务器模型中,服务器通过套接字监听客户端的连接请求,实现进程之间的通信。
- 信号量(Semaphore):
-
描述: 信号量是一种用于控制多个进程对共享资源访问的机制,可用于解决进程同步和互斥问题。
-
示例: 多个进程需要同时访问一个共享资源时,可以使用信号量来进行同步和协调。
这些IPC机制提供了不同层次的抽象和复杂性,选择合适的IPC方式取决于具体的应用需求和设计考虑。
12 linux查看进程状态
在Linux系统中,可以使用一系列命令来查看进程的状态。以下是一些常用的命令:
- ps命令:
ps
命令用于显示当前运行在系统上的进程。下面是一些常见的用法:
-
查看所有进程:
ps aux
-
查看指定用户的进程:
ps -u username
- top命令:
top
命令以动态的方式显示系统的活动进程。它提供了一个实时更新的任务列表,并显示各种系统资源使用情况。
top
- htop命令:
htop
是 top
的一个增强版本,提供了更多的交互式功能和更直观的界面。
htop
- pgrep命令:
pgrep
命令用于通过进程名查找进程的PID(进程标识符)。
pgrep process_name
- pkill命令:
pkill
命令用于通过进程名终止进程。
pkill process_name
- kill命令:
kill
命令用于向指定的进程发送信号,例如终止进程。
kill -9 PID
以上命令中的 PID 是进程的标识符,可以通过 ps
命令或其他进程查看命令获取。
13 线上环境服务出现问题怎么检测,挂了怎么拉起,怎么管理
线上环境服务出现问题时,检测、拉起和管理服务都是关键的运维任务。以下是一些建议和常见的做法:
-
检测服务问题:
-
监控系统:
- 使用监控系统实时监测关键指标,如 CPU 使用率、内存使用率、网络流量等。
- 设置阈值并配置警报,以便在达到或超过阈值时及时通知运维人员。
-
日志分析:
- 定期分析服务日志,查找异常信息,警报或记录异常情况。
- 使用日志聚合工具,如ELK Stack(Elasticsearch、Logstash、Kibana),以更方便地分析和监控日志。
-
心跳检测:
- 实现心跳检测机制,定期向服务发送请求并检查响应,确保服务正常运行。
-
综合健康检查:
- 定期进行综合健康检查,包括服务端口的可用性、依赖服务的连通性等。
-
服务挂了怎么拉起:
-
自动恢复机制:
- 实现自动恢复机制,例如使用进程管理工具(如systemd、supervisord)监控服务状态,当服务挂掉时自动拉起。
-
容器编排工具:
- 如果使用容器化技术,可使用容器编排工具(如Docker Compose、Kubernetes)来管理服务的自动重启和伸缩。
-
自动扩展:
- 在云环境中,通过自动扩展组件,如Auto Scaling(AWS)、Instance Group(Google Cloud)来动态调整实例数量,确保服务可用性。
-
服务管理:
-
版本控制:
- 使用版本控制系统(如Git)来管理服务代码和配置,确保可以回滚到稳定的版本。
-
灰度发布:
- 实施灰度发布策略,逐步将新版本服务引入线上环境,减少潜在问题的影响范围。
-
自动化运维工具:
- 使用自动化运维工具(如Ansible、Chef、Puppet)来部署和管理服务,确保环境一致性。
-
备份与恢复:
- 定期进行数据备份,确保在服务问题导致数据丢失时能够迅速恢复。
-
紧急手动操作:
- 提前定义好应急手动操作流程,以防止自动化机制失效时进行紧急修复。
通过以上措施,可以有效地检测服务问题、自动拉起服务、并管理服务的生命周期,提高服务的可靠性和稳定性。
14 git操作
Git 是一分布式版本控制系统,用于跟踪文件的变化和协作开发。以下是一些基本的 Git 操作:
- 初始化仓库:
git init
在当前目录下初始化一个新的 Git 仓库。
- 克隆仓库:
git clone <repository_url>
从远程仓库克隆一个本地副本。
- 添加文件:
git add <filename>
将文件添加到暂存区。
- 提交更改:
git commit -m "Commit message"
将暂存区的更改提交到本地仓库。
- 查看状态:
git status
查看工作区、暂存区和本地仓库的状态。
- 查看提交历史:
git log
查看提交历史记录,包括提交者、提交时间和提交信息。
- 创建分支:
git branch <branch_name>
创建一个新的分支。
- 切换分支:
git checkout <branch_name>
切换到指定分支。
- 合并分支:
git merge <branch_name>
将指定分支的更改合并到当前分支。
- 远程操作:
-
添加远程仓库:
git remote add origin <repository_url>
-
推送到远程仓库:
git push -u origin <branch_name>
-
拉取远程仓库的更改:
git pull origin <branch_name>
- 撤销更改:
-
撤销工作区的更改:
git checkout -- <filename>
-
撤销暂存区的更改:
git reset HEAD <filename>
-
撤销最后一次提交:
git reset --soft HEAD^
- 标签操作:
-
创建标签:
git tag -a <tag_name> -m "Tag message"
-
推送标签到远程仓库:
git push origin --tags
这些是 Git 的一些基本操作,涵盖了从初始化仓库到推送到远程仓库的基本流程。在实际使用中,你可能还会遇到其他一些高级的 Git 操作,如 rebase、stash、cherry-pick 等,具体使用取决于项目的需求和团队的工作流程。
15 虚拟化设备
虚拟化设备是指通过虚拟化技术模拟或抽象的硬件设备,使得多个虚拟机(VM)能够在同一物理主机上并发运行,各自拥有自己独立的虚拟硬件资源。这种虚拟化的方式使得多个操作系统和应用程序能够在一个物理主机上同时运行,而不需要物理硬件的完全隔离。
以下是一些常见的虚拟化设备:
-
虚拟中央处理单元(Virtual CPU,vCPU): 每个虚拟机都分配有一个或多个虚拟 CPU,这些虚拟 CPU 由宿主机的物理 CPU 进行调度和执行。
-
虚拟内存(Virtual Memory): 虚拟机使用虚拟内存来管理其内存空间,而实际的物理内存由宿主机管理。
-
虚拟磁盘(Virtual Disk): 虚拟机使用虚拟磁盘来存储其操作系统和应用程序,这些虚拟磁盘可能是文件(如VMDK、VHD)或者直接访问物理存储设备。
-
虚拟网络适配器(Virtual Network Adapter): 虚拟机通过虚拟网络适配器与物理网络通信,这样可以使得虚拟机之间和虚拟机与宿主机进行网络通信。
-
虚拟图形处理单元(Virtual GPU,vGPU): 允许虚拟机共享物理主机的图形处理能力。
-
虚拟输入/输出设备(Virtual I/O Devices): 包括键盘、鼠标、打印机等,使得虚拟机可以与这些虚拟设备进行交互。
虚拟化设备的出现使得资源可以更加灵活地分配和共享,提高了硬件的利用率,并简化了系统管理。主流的虚拟化平台包括VMware、Microsoft Hyper-V、KVM(Kernel-based Virtual Machine)等。这些平台提供了完整的虚拟化解决方案,包括虚拟化设备的模拟、管理、监控等功能。
我的公众号:Hasity学不会
#24秋招避雷总结#收录各个网友分享的各个公司的面经,并给出答案。