用户态切换到内核态(中断,异常)
内中断(异常)
:来源于cpu
内部。例如当想发生系统调用时,在用户态应用程序会先执行一些传参指令,将参数放到cpu
的寄存器中,操作系统会根据传入的参数判断需要提供的系统服务。之后应用程序会执行一条陷入指令
,引发内中断,转入到相应的中断处理程序-即系统调用的入口,切换到内核态。例如/0
外中断
:来源于cpu
外部。例如时钟部件,当一个进程时间片用完后,时钟部件会给cpu
发送一个中断信号,接着执行相应的内核处理程序来处理中断信号,进入内核态。实现并发。
进程和线程的区别
- 每个进程有自己的内存空间,多个线程共享内存空间
- 一个进程下的多个线程共享
代码区
,全局数据区
,堆区
,文件(打开的文件描述符)
- 一个进程下的多个线程有属于自己的
栈区
,寄存器(内核中)
- 线程更加节省系统资源, 效率不仅可以保持的, 而且能够更高
- 一个进程下的多个线程共享
- 线程是程序的最小执行单位, 进程是操作系统中最小的资源分配单位
- 每个进程对应一个虚拟地址空间,一个进程只能抢一个
CPU
时间片 - 一个地址空间中可以划分出多个线程, 在有效的资源基础上, 能够抢更多的
CPU
时间片
- 每个进程对应一个虚拟地址空间,一个进程只能抢一个
$cmake$ 的 $targetlink$ 的作用
- 链接静态动态库
软硬链接
查看一个端口正在被哪个进程占用的命令
- 使用
netstat
命令
sudo netstat -tuln | grep :<端口号>
-t:显示 TCP 连接。
-u:显示 UDP 连接。
-l:仅显示监听状态的连接。
-n:以数字形式显示地址和端口。
$InnoDB$ 和 $MyISAM$ 区别
$InnoDB$ 为什么使用 $B+$ 树作为索引
不能用二叉树作为 $InnoDB$ 的索引吗
$MySql$ 的存储引擎
进程切换时,$CPU$ 上下文切换的是什么
- 进程是交给 $CPU$ 运行的,那么在每个进程运行前,$CPU$ 需要知道进程从哪里加载,又从哪里开始运行。
- 所以,操作系统需要事先帮 $CPU$ 设置好 $CPU$寄存器和程序计数器。
- $CPU$ 寄存器是 $CPU$ 内部一个容量小,但是速度极快的内存 (缓存)
- 再来,程序计数器则是用来存储 $CPU$ 正在执行的指令位置、或者即将执行的下一条指令位置
- 所以说,$CPU$ 寄存器和程序计数是 $CPU$ 在运行任何任务前,所必须依赖的环境,这些环境就叫做 $CPU$ 上下文
进程上下文切换的是什么
- 进程是由内核管理和调度的,所以进程的切换只能发生在内核态。
- 所以,进程的上下文切换不仅包含了 虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间 的资源。
线程上下文切换的是什么
- 这还得看线程是不是属于同一个进程
- 当两个线程不是属于同一个进程,则切换的过程就跟进程上下文切换一样,
- 当两个线程是属于同一个进程,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据
上下文切换线程寄存器需要存什么内容
$string$ 是怎么实现的
$weak\_ptr$ 怎么解决循环引用的问题
进程间通信方式
- 匿名管道,有名管道,信号,信号量,消息队列,内存共享,内存映射。
字节流如何保证收到的信息完整
$tcp$ 和 $udp$ 的区别和使用场景
$mqsql$中 $innodb$ 和 $myisam$ 的区别和使用场景
$mysql$的事务隔离级别
异步写可以在一段程序中多次调用吗,如果发生会出现什么问题
日志如何写入
虚拟内存,黑神话悟空
$thread$工作函数,传的是参数还是引用
- 默认是值传递,传递引用需要使用
ref()
说一个 $shard\_ptr$ 产生循环引用的实际场景,不要背概念
- 一个实际场景中可能会导致
shared_ptr
产生循环引用的情况是 树形结构 或 图形结构 中的父子节点之间互相持有shared_ptr
的情况。具体来说,在这种情况下,父节点和子节点会互相引用,从而造成循环引用。
什么是命名空间,他有什么作用?
- 命名空间(namespace)是一种封装标识符(如类、函数、变量等)的方法,用于解决名字冲突的问题。它通过为标识符指定一个独立的作用域来组织代码,避免不同部分的代码中使用相同名字的标识符而导致冲突。
编译期多态和运行时多态
- 编译期多态:发生在编译时,通常通过函数重载、模板等机制实现,决策由编译器完成。编译期多态的优点是没有运行时开销,执行效率较高。
- 运行期多态:发生在程序运行时,通过虚函数和继承机制实现,决策由程序在运行时完成。运行期多态的优点是非常灵活,能够支持动态类型分派,但可能会引入一定的性能开销。
什么是C++
中的智能指针?它们的类型和使用场景
std::unique_ptr
- 最简单的一种智能指针,表示对某个对象的独占所有权。它在构造时获取资源,并在析构时自动释放资源。一个
unique_ptr
只能有一个拥有者,不能被复制,但可以通过移动语义(std::move
)转移所有权。 - 使用场景:当你确定一个对象只能有一个所有者。例如某个对象的生命周期完全由一个特定的地方管理,或者一个对象不需要共享
- 最简单的一种智能指针,表示对某个对象的独占所有权。它在构造时获取资源,并在析构时自动释放资源。一个
std::shared_ptr
- 是一种引用计数智能指针,它允许多个
shared_ptr
指向同一个对象。只有最后一个指向对象的shared_ptr
被销毁时,才会释放对象的内存。 - 线程安全:引用计数本身是线程安全的,多个线程可以同时修改
shared_ptr
- 适用于多个对象或模块需要共享资源的情况,或者当多个对象可能会同时拥有一个资源时。例如图结构、树结构,多个节点可能同时指向某些共享对象
- 是一种引用计数智能指针,它允许多个
weak_ptr
- 是与
std::shared_ptr
配合使用的智能指针,它并不增加引用计数。weak_ptr
主要用于解决 循环引用 问题,即避免两个shared_ptr
互相持有对方导致内存无法释放。
- 是与
shared_ptr
的引用计数是如何实现的
C++
写一个简单版本的智能指针
- 主页
从浏览器输入URL
到显示页面发生了什么
*
vector
的扩容机制
- 通常情况下,
std::vector
在需要扩容时,会将其容量 扩大为当前容量的 2 倍
如果使用未知关键字,在哪个编译环节报错,对常量赋值在哪报错
- 都是编译期,编译期会进行词法分析(常量,关键字,标识符)和语法分析。
linux
如何在一个超大(2,3G)的日志系统中搜索包含某个子段的行
grep str path
为什么大部分用局部变量而不用全局变量
- 局部变量会自动释放内存,无需手动释放。
- 便于调试,减少命名冲突,提高代码可读性。
- 局部变量是线程安全的,只会在线程内部访问
如何检测循环引用
- 使用静态分析工具
(如 Clang Static Analyzer、Cppcheck)
HTTPS如何分别真实服务器?
二进制协议的了解(protobuf)
一个数组拷贝到另一个数组,如何降低拷贝时间
- 如果数组中的数据是简单的类型(如基本数据类型:
int、char、double
等),可以使用 $C$ 风格的memcpy
。memcpy
在底层实现时,通常是通过更高效的内存复制方式(如使用内存块传输)来进行数据复制,比逐个元素的循环拷贝要快
- 考虑并行拷贝(多线程拷贝) 对于非常大的数据集,可以考虑并行化数组拷贝操作,利用多核 CPU 来提高性能。通过将数据分割成多个部分,并在多个线程中并行拷贝,可以显著缩短总的拷贝时间
$TCP$ 建立连接后,如果突然拔断网线,会发生什么事情
智能指针是线程安全的吗
std::shared_ptr
的引用计数线程安全(因为是atomic
类型),但对指针本身的操作(如reset
)不是线程安全的。std::unique_ptr
是独占所有权的,不能在多个线程之间共享,转移所有权时需要外部同步。std::weak_ptr
是线程安全的,但通过lock()
转换为std::shared_ptr
时需要外部同步。
RAII机制
- 通过将资源的获取与对象的生命周期绑定,从而在对象的构造和析构中自动管理资源,避免资源泄漏和手动释放资源的麻烦。
- 简单来说:
- 资源获取时:资源在对象构造时被初始化。
- 资源释放时:资源在对象析构时被自动释放。
unique_ptr
可以作为返回值吗
- 可以。
- 当
unique_ptr
作为函数返回值时,编译器会利用移动语义将返回的unique_ptr
的所有权从函数内部转移到调用方 - 返回值优化会让
std::unique_ptr
在创建时直接构造到返回值位置,避免多余的中间临时对象和移动操作。 std::unique_ptr
不支持拷贝返回,所有权只能通过移动语义转移
inline
可以修饰虚函数吗
- 如果虚函数通过对象的类型调用(即 静态绑定,例如
obj.method()
,而 非通过指针或引用调用 ),编译器可以将其作为普通函数优化,此时inline
有效。 - 但如果虚函数是通过基类指针或引用调用(即 动态绑定 ),此时
inline
失效。因为inline
是在编译期将函数展开,而虚函数是在运行时通过虚表解析。无法确定调用哪个函数。
超时重传的时间和次数有限制吗
unordered_map
和map
的底层迭代器是否会失效
unordered_map
由于使用哈希表存储数据,重新哈希操作会导致所有迭代器失效。- 负载因子到达一定阈值会扩容然后重新哈希。
- 负载因子是哈希表中元素个数与桶的数量的比值
map
使用平衡二叉搜索树(如红黑树),通常不会在插入时导致迭代器失效,删除元素时会使指向该元素的迭代器失效。
函数调用压栈顺序, 有那些调用约定
内存访问的过程,了解tlb
吗
介绍一下异常,传播异常的细节
怎么检查内存泄漏
线程安全的 hashmap
怎么实现
虚函数可以是 static
的吗
- 不可以。
- 静态成员函数属于类,在编译时期就被绑定了。虚函数是运行时动态绑定的。
https
协议中加密过程,密钥交换的过程
介绍tcp
和udp
协议,两者的特点,应用场景
mysql
中,什么情况下需要索引,什么情况不需要索引
介绍一下乐观锁和悲观锁,他们有什么区别
unqiue_ptr
的底层实现
vector
如何释放内存
- 使用
swap
和一个空的vector
交换或者resize(0)
一个无参的构造函数,有必要用explicit
吗
- 没必要,隐式转换通常是指编译器自动将某个类型转换为另一个类型。对于无参构造函数,因为没有参数,所以 没有隐式转换的机会
条件变量调用wait
方法时,底层发生了什么,wait
的两个参数是什么?
std::condition_variable::wait
调用会使得线程释放它所持有的互斥锁,并将线程放入条件变量的等待队列,直到它被另一个线程唤醒。
构造函数为什么一般不定义为虚函数
- 虚函数的调用需要虚函数表指针,而该指针存放在对象的内存空间中;若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表地址用来调用虚函数即构造函数了
什么情况下会进行拷贝构造(例举三种情况)
- 对象以值的方式进行函数传递
- 返回值为对象类型,而非指针或引用
- 对象需要通过另一个对象初始化
模板的用法与适用场景 实现原理
- 同一段代码逻辑,因为内部变量的类型不同,导致多次出现相同逻辑的代码。
- 编译器会对函数模板进行两次编译:第一次编译在声明的地方对模板代码本身进行编译,这次编译只会进行一个语法检查,并不会生成具体的代码。在第二次时对代码进行参数替换后再进行编译,生成具体的函数代码。
为什么用成员初始化列表会快一些(性能优势)
- 使用初始化列表才是真的初始化,函数体中的是赋值运算。
- 初始化列表里面初始化,是直接调用对象成员的构造函数进行初始化
- 进入函数体后,成员已经初始化完成,再修改就是赋值操作
- 另外,有三种情况是必须使用成员初始化列表进行初始化的:
- 常量成员的初始化,因为常量成员只能初始化不能赋值
- 引用类型
- 没有默认构造函数的对象必须使用成员初始化列表的方式进行初始化
进程/线程 挂起和阻塞的区别
- 线程挂起通常指的是线程被外部条件控制而暂时停止执行,直到某些条件满足或显式的唤醒操作才会恢复执行。挂起的线程不是因为等待某个资源或事件,而是因为被程序显式地挂起了。
- 线程阻塞指的是线程由于等待某个资源(如
I/O
操作、锁、或者其他同步机制)而无法继续执行的状态。线程阻塞通常由资源不可用或需要等待其他线程完成某些操作引起,阻塞的线程通常是处于等待某个条件或事件发生的状态。