0%

1 服务熔断

服务熔断(Circuit Breaker)是一种用于构建分布式系统的设计模式,用于增强系统的稳定性和可靠性。服务熔断的核心思想是在出现服务故障或异常时,及时地中断对该服务的请求,防止故障进一步扩散,并且允许系统在出现问题时快速失败而不是无限期地等待响应。

服务熔断的用途和优势包括:

  1. 防止级联故障: 当某个服务或组件出现故障时,服务熔断可以快速地停止对该服务的请求,防止故障扩散到其他部分,从而保护整个系统的稳定性。

  2. 快速失败: 服务熔断允许系统在出现问题时快速失败,而不是等待超时,这可以减少用户等待时间,并快速释放资源以减轻系统负担。

  3. 降级处理: 当服务熔断触发时,可以采取降级处理策略,例如返回预先定义的默认值、执行备用逻辑或者从缓存中获取数据,以保证系统的基本功能继续可用。

  4. 自我修复: 当服务熔断一段时间后,可以尝试重新发起请求,如果服务恢复正常,则关闭熔断器,继续正常提供服务。

常见问题和挑战包括:

  1. 熔断器状态管理: 需要有效地管理熔断器的状态,包括打开、关闭和半开状态的切换,以及对状态的监控和调整,确保熔断器的行为符合预期。

  2. 故障诊断和处理: 需要及时地检测和诊断服务的故障,并采取相应的措施进行处理,例如记录错误日志、发送警报通知等。

  3. 降级策略设计: 需要设计合适的降级处理策略,以保证在服务熔断时系统依然能够提供基本的功能,而不影响用户体验。

  4. 性能影响: 在服务熔断时,可能会增加系统的负载和响应时间,因此需要合理评估熔断器的触发条件和恢复机制,以减少性能影响。

总的来说,服务熔断是一种重要的分布式系统设计模式,通过及时地中断对故障服务的请求,可以提高系统的稳定性和可靠性,但同时也需要综合考虑各种因素,合理设计和管理熔断策略,以确保系统的正常运行。

2 服务降级

服务降级(Service Degradation)是一种在面对系统资源不足或者系统负载过大时,为了保证系统的核心功能或者关键服务的可用性而采取的一种策略。服务降级的核心思想是在系统压力过大时,有选择性地降低非核心或次要功能的服务质量,以确保系统的核心功能仍然能够正常运行。

服务降级的目的在于保证系统在遇到异常或高负载情况下仍能够提供基本的服务能力,从而提高系统的稳定性和可用性。在实际应用中,服务降级通常伴随着一些特定的策略和实践,例如:

  1. 优先级划分: 将系统中的功能和服务按照其重要性和优先级进行划分,确保核心功能拥有更高的优先级,并在资源不足时优先保证核心功能的运行。

  2. 限流和限速: 通过限制对系统的访问速率或者并发请求数量,以防止系统过载,从而减轻系统负载压力。

  3. 降级策略设计: 设计合适的降级策略,当系统资源不足或者系统负载过大时,有选择性地降低非核心功能或次要功能的服务质量,例如降低服务响应时间、减少服务数据的返回量等。

  4. 实时监控和调整: 实时监控系统的负载情况和性能指标,根据实际情况动态调整降级策略,以确保系统的稳定性和性能表现。

服务降级的优点包括:

  • 提高系统的稳定性: 在面对异常情况或者高负载情况时,通过降级非核心功能或次要功能的服务质量,可以确保系统的核心功能仍能够正常运行,从而提高系统的稳定性。

  • 保证核心功能的可用性: 通过有选择性地降级非核心功能或次要功能,可以确保系统的核心功能在异常或高负载情况下仍能够提供基本的服务能力,从而保证核心功能的可用性。

  • 减少系统压力: 通过降级非核心功能或次要功能的服务质量,可以减少系统的负载压力,从而提高系统的整体性能和响应速度。

服务降级也存在一些潜在的缺点

  • 可能影响用户体验

  • 需要精细的策略设计和实时监控

  • 可能引起业务方面的不满等。 因此,在实施服务降级策略时,需要综合考虑各种因素,并设计合理的降级策略,以确保系统的整体性能和用户体验。

  • 工厂设计模式 : Spring 使用工厂模式通过 BeanFactoryApplicationContext 创建 bean 对象。

  • 代理设计模式 : Spring AOP 功能的实现。

  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。

  • 模板方法模式 : Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。

  • 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。

  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。

  • 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller

1 工厂设计模式

Spring 使用工厂模式可以通过 BeanFactoryApplicationContext 创建 bean 对象。

两者对比:

  • BeanFactory:延迟注入(使用到某个 bean 的时候才会注入),相比于ApplicationContext 来说会占用更少的内存,程序启动速度更快。

  • ApplicationContext:容器启动的时候,不管你用没用到,一次性创建所有 bean 。BeanFactory 仅提供了最基本的依赖注入支持,ApplicationContext 扩展了 BeanFactory ,除了有BeanFactory的功能还有额外更多功能

2 单例设计模式

在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。

使用单例模式的好处 :

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;

  • 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。

3 代理设计模式

3.1 代理模式在AOP中的应用

AOP(Aspect-Oriented Programming,面向切面编程) 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy 去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:

image.png

3.2 Spring AOP和AspectJ AOP

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

4 模板方法

模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。

5 观察者模式

6 适配器模式

7 装饰者模式

8 参考

Spring 中的设计模式详解 | JavaGuide

1 创建线程方式

public interfact Runnable{
void run();
}

1.1 实现runnable接口

创建一个类实现Runnable接口,实现run方法,然后创建Thread类的实例,将实现了Runnable接口的对象作为参数传递给Thread的构造方法,并调用start方法启动线程。

  1. 将任务代码移到实现了Runnable接口的类的run方法中(由于Runnable 是一个函数式接口,可以用lambda 表达式建立一个实例)

  2. Runnable创建一个Thread对象

  3. 启动线程

public class demo1 {
public static void main(String[] args){
Runnable r1=new Runnable() {
@Override
public void run() {
System.out.println("任务开始");
}
};
Thread t1=new Thread(r1);
t1.start();

Runnable r2=()->{
System.out.println("lambda表达式:任务开始");
};
Thread t2=new Thread(r2);
t2.start();


Thread t3=new Thread(()->{
System.out.println("在thread中lambda表达式");
});
t3.start();
}
}

1.2 继承Thread类

创建一个类继承自Thread类,并重写run方法,然后创建该类的实例并调用start方法启动线程。

class Thread implements Runnable{

}
public class MyThread extends Thread{
public void run(){
System.out.println("继承Thread类,重写run方法");
}
public static void main(String[] args){
MyThread myThread=new MyThread();
myThread.start();
}
}

1.3 实现Callable接口

public interface Callable<V>{
V call() throws Exception;
}
  • 继承Callable实现call方法

  • FutureTask包装器将Callable转换成Future和Runnable

public class CallableDemo implements Callable<String> {

@Override
public String call() throws Exception {
return "Hello World";
}

public static void main(String[] args) throws Exception {
CallableDemo callableDemo = new CallableDemo();
FutureTask<String> futureTask = new FutureTask<>(callableDemo);
Thread t=new Thread(futureTask);
t.start();
System.out.println(futureTask.get());
}
}

2 线程池

2.1 newCachedThreadPool

创建一个可缓存的线程池,线程池的大小可根据需要进行自动扩展,但在某些情况下可能会回收线程。

ExecutorService executor = Executors.newCachedThreadPool();

2.2 newFixedThreadPool

创建一个固定大小的线程池,线程数始终保持不变。

2.3 newSingleThreadExecutor

  • 创建一个单线程的线程池,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行。

2.4 newScheduledThreadPool

  • 创建一个定时执行任务的线程池。

1 评论系统

设计一个基于 Redis 的简单评论系统,需要支持发布评论、回复评论和分页展示。请描述你的数据结构和设计。

在设计一个评论系统中,你如何处理多级评论问题,如何设计数据结构?

追问:如何高效的查询和展示评论,你会采用什么样的算法或技术来优化查询性能?

2 设计一个定时任务执行系统

3 库存超卖

[!note]
高并发卖5000张券

  • Redis缓存扣减库存

  • 分布式锁+分段缓存:借鉴ConcurrenthashMap分段锁的机制,把100个商品,分在3个段上,key为分段名字,value为库存数量。用户下单时对用户id进行%3计算,看落在哪个redis的key上,就去取哪个。

  • Redis原子操作(Redis incr)+乐观锁:

  • stockCount记录库存总量,stockUsedCount记录已使用库存量,对占用的库存设置分布式锁

第19节:设计滑动库存分布式锁处理活动秒杀 · Wiki · KnowledgePlanet / Lottery · GitCode
【并发】高并发下库存超卖问题如何解决?-阿里云开发者社区 (aliyun.com)
万级并发!电商库存扣减如何设计,如何做到不超卖? - 知乎 (zhihu.com)
超卖问题及其解决方法-CSDN博客

4 秒杀系统

“秒杀”,就是在同一个时刻有大量请求争抢购买同一个商品,并完成交易的过程,其间涉及大量的并发读和并发写,并要求高可靠和高性能的系统支持。

  • 高性能。 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键。本专栏将从设计数据的动静分离方案、热点的发现与隔离、请求的削峰与分层过滤、服务端的极致优化这 4 个方面重点介绍。

  • 一致性。 秒杀中商品减库存的实现方式同样关键。可想而知,有限数量的商品在同一时刻被很多倍的请求同时来减库存,减库存又分为“拍下减库存”“付款减库存”以及预扣等几种,在大并发更新的过程中都要保证数据的准确性,其难度可想而知。因此,我将用一篇文章来专门讲解如何设计秒杀减库存方案。

  • 高可用。 虽然我介绍了很多极致的优化思路,但现实中总难免出现一些我们考虑不到的情况,所以要保证系统的高可用和正确性,我们还要设计一个 PlanB 来兜底,以便在最坏情况发生时仍然能够从容应对。专栏的最后,我将带你思考可以从哪些环节来设计兜底方案。

  • 过滤无效流量

  • 库存可以少卖不可以超卖

  • 热点商品预热

  • 热点商品信息提前缓存

  • 利用redis缓存

  • 静态资源提前加载

  • 利用消息队列进行流量削峰

  • 切片集群

  • 在用户发起请求前,对用户进行一些校验操作,比如答题、输入验证码

  • 服务限流

过滤无效流量

将用户请求分散

提高QPS

高频面试题:秒杀场景设计 - 知乎 (zhihu.com)
面试官问我:如何设计一个秒杀场景?-腾讯云开发者社区-腾讯云 (tencent.com)
秒杀场景的设计思路和方案-阿里云开发者社区 (aliyun.com)
万字超详解秒杀系统! - 知乎 (zhihu.com)
面试必备:秒杀场景九个细节-CSDN博客

5 商品点击率排行榜

top K排序

多并行优化

多个top K排序,在合并求top K

[!question] 全国的酒店价格(千万级数据)需要在某个瞬间比如7点发生变动(比如说晚上折扣或者升价之类的),怎样高性能准点去进行变更

  • 定时消息+缓存提前预热

  • 提前计算缓存 定时切换Redis命名空间 然后异步做持久化(使用Hash数据结构,key为商家id,value为价格)

[!question] 外卖系统,一天一千万条数据,用户需要查到近30天的数据,商家也要查询到30天的数据,怎么设计表

数据库冷热分离分库分表

6 游戏开发

1.以lol为例,玩家的匹配怎么做。

2.排行榜(其他面经有)。(Redis的zset数据结构)

3.范围技能命中判定。(其他面经有)。

1.饥荒里食物放置在地上隔段时间会有新鲜度的变化,从绿色到黄色到红色最终腐烂,如何设计算法使这个服务器每帧更新食物状态?
我说饥荒联机版搞的是加个组件直接计时,结果就是太多堆积会很卡顿。单纯考虑算法的话整个单调栈,每次食物放地上就把它什么时间需要更新状态存进去,然后按时间读就行

堆排序,取出一个元素后,为下一个元素设置定时器更新

2.抽卡问题,n张卡给个排序要求随机?
直接rand(1,n)取第一张index k然后把k和n位置换一下,接着rand(1,n-1)取第二张indexj ,j和n-1位置交换 依次类推

3.手榴弹爆炸范围,对二维空间内造成aoe伤害,怎么找影响的实体单位?
不太会,后来说可以哈希

从爆炸中心进行BFS找影响的单位

问题1:系统设计(40min)
设计一个长链接转换为短链接的转换?
怎么进行转换?
怎么存储?
怎么设计给别人用的http的接口?
设计这个系统的重点是什么,需要关注什么?
长变短除了随机还有什么比较好的方式转化嘛?

长链接转成短链接的原理和实现详解_长链接转化成短链接-CSDN博客

问题2:java中多线程模型,给了一段代码让输出最终结果?多种情况(10min,这个很简单)

问题3:烧绳法计算15分钟(10min,脑子没转过来,原来可以两端烧)

1.一条公路上有多个点,每个点都有一辆车,给定公路坐标轴,车的速度和行驶方向,求最早两辆车相遇的时间;
第一题:无论相向还是相反,最先相遇的必然会发生在相邻的两部车之间。时间=abs(两相邻点之间的距离差)/abs(速度差),速度差为零永远不会相遇,取最小值就行啦

2.一条直线上多个点运动 知道所有点的位置,和速度包括方向。当两个点相碰时,追及或对撞两个点消失,问什么时候达到稳定状态,也就是以后都不会发生碰撞。第二题可以用第一题的思路,首先消除最先相遇的两个点,去掉这两个点后,重新计算最先相遇的两个点…直到所有车无法相遇就达到平衡啦

可以用优先队列去存相遇时间,每次取最小的,消除之后,会新更新一条进去,用个特殊标记记录两个点不会相遇,比如MAXINT这种,也能处理特殊情况

第一个是矩阵里找最长路径问题
第二个是直线上n个移动的点,求最早相遇时间

1 单例模式

双重校验锁实现对象单例(线程安全)

volatile禁止指令重排序

  • 分配内存
  • 对象初始化
  • 设置对象引用
public class Singleton {

private volatile static Singleton uniqueInstance;

private Singleton() {
}

public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}

uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:

  1. uniqueInstance 分配内存空间

  2. 初始化 uniqueInstance

  3. uniqueInstance 指向分配的内存地址

2 模拟死锁

2.1 线程死锁

public class DeadLock {
private static Object a=new Object();
private static Object b=new Object();

public static void main(String[] args){
new Thread(()->{
synchronized (a){
System.out.println(Thread.currentThread()+ "获得资源a");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread()+"请求资源b");
synchronized (b){
System.out.println(Thread.currentThread()+ "获得资源b");
}
}
},"线程1").start();
new Thread(()->{
synchronized (b){
System.out.println(Thread.currentThread()+ "获得资源b");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread()+"请求资源a");
synchronized (a){
System.out.println(Thread.currentThread()+ "获得资源a");
}
}
},"线程2").start();
}
}

2.2 数据库事务死锁

-- 事务1
begin;
-- SQL1更新id为1的
update user set age = 1 where id = 1;
-- SQL2更新id为2的
update user set age = 2 where id = 2;
commit;


-- 事务2
begin;
-- SQL1更新id为2的
update user set age = 3 where id = 2;
-- SQL2更新id为1的
update user set age = 4 where id = 1;
commit;

面试官:请用SQL模拟一个死锁 - 问北 - 博客园 (cnblogs.com)

3 生产者、消费者模型

// 生产者
class Producer implements Runnable {
private BlockingQueue<Integer> queue;

public Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}

public void run() {
try {
while (true) {
int value = produce(); // 生产数据
queue.put(value); // 将数据放入队列
Thread.sleep(1000); // 模拟生产过程
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

private int produce() {
// 生产过程
return 1;
}
}

// 消费者
class Consumer implements Runnable {
private BlockingQueue<Integer> queue;

public Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}

public void run() {
try {
while (true) {
int value = queue.take(); // 从队列中取出数据
consume(value); // 消费数据
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

private void consume(int value) {
// 消费过程
}
}

磁盘调度算法

磁盘调度算法是操作系统中对磁盘访问请求进行排序和调度的算法,其目的是提高磁盘的访问效率。

一次磁盘读写操作的时间由磁盘寻道/寻找时间、延迟时间和传输时间决定。磁盘调度算法可以通过改变到达磁盘请求的处理顺序,减少磁盘寻道时间和延迟时间。

  1. 先来先服务算法(First-Come First-Served,FCFS):按照请求到达磁盘调度器的顺序进行处理,先到达的请求的先被服务。FCFS 算法实现起来比较简单,不存在算法开销。不过,由于没有考虑磁头移动的路径和方向,平均寻道时间较长。同时,该算法容易出现饥饿问题,即一些后到的磁盘请求可能需要等待很长时间才能得到服务。

  2. 最短寻道时间优先算法(Shortest Seek Time First,SSTF):也被称为最佳服务优先(Shortest Service Time First,SSTF)算法,优先选择距离当前磁头位置最近的请求进行服务。SSTF 算法能够最小化磁头的寻道时间,但容易出现饥饿问题,即磁头附近的请求不断被服务,远离磁头的请求长时间得不到响应。实际应用中,需要优化一下该算法的实现,避免出现饥饿问题。

  3. 扫描算法(SCAN):也被称为电梯(Elevator)算法,基本思想和电梯非常类似。磁头沿着一个方向扫描磁盘,如果经过的磁道有请求就处理,直到到达磁盘的边界,然后改变移动方向,依此往复。SCAN 算法能够保证所有的请求得到服务,解决了饥饿问题。但是,如果磁头从一个方向刚扫描完,请求才到的话。这个请求就需要等到磁头从相反方向过来之后才能得到处理。

  4. 循环扫描算法(Circular Scan,C-SCAN):SCAN 算法的变体,只在磁盘的一侧进行扫描,并且只按照一个方向扫描,直到到达磁盘边界,然后回到磁盘起点,重新开始循环。

  5. 边扫描边观察算法(LOOK):SCAN 算法中磁头到了磁盘的边界才改变移动方向,这样可能会做很多无用功,因为磁头移动方向上可能已经没有请求需要处理了。LOOK 算法对 SCAN 算法进行了改进,如果磁头移动方向上已经没有别的请求,就可以立即改变磁头移动方向,依此往复。也就是边扫描边观察指定方向上还有无请求,因此叫 LOOK。

  6. 均衡循环扫描算法(C-LOOK):C-SCAN 只有到达磁盘边界时才能改变磁头移动方向,并且磁头返回时也需要返回到磁盘起点,这样可能会做很多无用功。C-LOOK 算法对 C-SCAN 算法进行了改进,如果磁头移动的方向上已经没有磁道访问请求了,就可以立即让磁头返回,并且磁头只需要返回到有磁道访问请求的位置即可。

进程管理、内存管理、文件管理、设备管理

  • 设备管理:完成设备(输入输出设备和外部存储设备等)的请求或释放,以及设备启动等功能。
  • 文件管理:完成文件的读、写、创建及删除等功能。
  • 进程管理:进程的创建、撤销、阻塞、唤醒,进程间的通信等功能。
  • 内存管理:完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。

1 用户态和内核态

  • 用户态(User Mode) : 用户态运行的进程可以直接读取用户程序的数据,拥有较低的权限。当应用程序需要执行某些需要特殊权限的操作,例如读写磁盘、网络通信等,就需要向操作系统发起系统调用请求,进入内核态。

  • 内核态(Kernel Mode):内核态运行的进程几乎可以访问计算机的任何资源包括系统的内存空间、设备、驱动程序等,不受限制,拥有非常高的权限。当操作系统接收到进程的系统调用请求时,就会从用户态切换到内核态,执行相应的系统调用,并将结果返回给进程,最后再从内核态切换回用户态。

内核态相比用户态拥有更高的特权级别,因此能够执行更底层、更敏感的操作。不过,由于进入内核态需要付出较高的开销(需要进行一系列的上下文切换和权限检查),应该尽量减少进入内核态的次数,以提高系统的性能和稳定性。

内核态和用户态是操作系统中的两种运行模式。它们的主要区别在于权限和可执行的操作:

  • 内核态(Kernel Mode):在内核态下,CPU可以执行所有的指令和访问所有的硬件资源。这种模式下的操作具有更高的权限,主要用于操作系统内核的运行。

  • 用户态(User Mode):在用户态下,CPU只能执行部分指令集,无法直接访问硬件资源。这种模式下的操作权限较低,主要用于运行用户程序。
    内核态的底层操作主要包括:内存管理、进程管理、设备驱动程序控制、系统调用等。这些操作涉及到操作系统的核心功能,需要较高的权限来执行。

分为内核态和用户态的原因主要有以下几点:

  • 安全性:通过对权限的划分,用户程序无法直接访问硬件资源,从而避免了恶意程序对系统资源的破坏。

  • 稳定性:用户态程序出现问题时,不会影响到整个系统,避免了程序故障导致系统崩溃的风险。

  • 隔离性:内核态和用户态的划分使得操作系统内核与用户程序之间有了明确的边界,有利于系统的模块化和维护。内核态和用户态的划分有助于保证操作系统的安全性、稳定性和易维护性。

1.1 用户态和内核态切换

切换方式:系统调用(Trap)、中断(Interrupt)、异常(Exception)

  1. 系统调用(Trap):用户态进程 主动 要求切换到内核态的一种方式,主要是为了使用内核态才能做的事情比如读取磁盘资源。系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现。

  2. 中断(Interrupt):当外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

  3. 异常(Exception):当 CPU 在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。

在系统的处理上,中断和异常类似,都是通过中断向量表来找到相应的处理程序进行处理。区别在于,中断来自处理器外部,不是由任何一条专门的指令造成,而异常是执行当前指令的结果。

1.2 堆、栈

堆:

  • 动态内存分配

  • 向高地址方向增长栈:

  • 向低地址方向增长

【数据结构/操作系统 堆和栈】区别及应用场景、底层原理图解_堆和栈的操作系统底层实现-CSDN博客