100个为什么

文章目录
  1. 1. CPU执行的第一条指令是什么
  2. 2. 为什么创建进程是个耗时间和资源的操作
  3. 3. 为什么创建线程是轻量级操作
  4. 4. 为什么系统调用比较耗时
  5. 5. 为什么操作系统同步指令TestAndSet 和swap 原子操作在多CPU下执行也不会存在问题
  6. 6. CPU如何知道操作数中是立即数还是内存地址?
  7. 7. OS进程调度(CPU执行用户程序如何切回OS调度程序)
  8. 8. 多处理器(CPU)体系架构如何实现程序的同步执行(互斥)?
  9. 9. 通过逻辑门如何实现加法?
  10. 10. CPU使用率是如何计算的?
  11. 11. CPU空闲含义?
  12. 12. 如何才能让CPU休眠
  13. 13. CPU如何知道一条指令的长度
  14. 14. CPU从内存中根据地址获取数据如何知道一次获取多少个字节
  15. 15. 进程执行完所有代码后如何自动退出
  16. 16. 中断服务过程
  17. 17. 内存管理为啥要使用多级页表,使用一个页表不行吗?
  18. 18. 在开启包含模式下对内存的访问是否会都会经过MMU
  19. 19. 页目录基址是CR3,当用户进程系统调用陷入内核,是否会使cr3切换到内核页表。
  20. 20. 为什么需要栈?
  21. 21. 为什么需要引导扇区?
  22. 22. 从实模式切换到保护模式时需要注意什么?
  23. 23. 中断号与中断向量
  24. 24. cgroups是什么,有什么用。
  25. 25. volatile的作用。
  26. 26. volatile关键字是如何防止指令重排的
  27. 27. 为什么volatile变量在赋值后加上lock前缀指令能实现内存屏障功能
  28. 28. synchronized能防止指令重排吗?
  29. 29. synchronized实现原理
  30. 30. ReentrantLock

CPU执行的第一条指令是什么

引导指令,将操作系统加载到内存中
当用户启动计算机的电源时,计算机硬件会自动产生一个中断信号,这个中断信号触发计算机处理器(CPU)中的一段指令执行。该段指令的执行结果将是发现外部存储设备中操作系统引导区(boot block)的位置。如果已经安装了操作系统,则操作系统引导区中的代码将被自动导入计算机的内存,并开始执行。引导区代码的执行结果是将操作系统程序加载到计算机内存中的指定区域,并初始化计算机的有关硬件,例如存储器,终端设备,以及各种计算机运行所需的数据结果等。至此操作系统程序开始启动,并未用户提供相应的用户界面,开始提供各种服务

为什么创建进程是个耗时间和资源的操作

首先需要为进程创建PCB和内存分配还有资源的分配。

为什么创建线程是轻量级操作

同进程下的线程可以共享代码,数据、资源。

为什么系统调用比较耗时

从用户态到内核态转换,需要保存用户态程序运行过程的现场,内核态的数据准备带来了一定资源的浪费。然后再从内核态回到用户态也需要通过一定的系统中断或者通知。这些由服务程序运行带来的时间消化。

为什么操作系统同步指令TestAndSet 和swap 原子操作在多CPU下执行也不会存在问题

因为是又操作系统实现的原子操作,就表示执行过程当中不可被中断。目前在多CPU下通常是通过锁总线,在执行testAndSet的时候锁主总线,执行完了以后再释放总线

CPU如何知道操作数中是立即数还是内存地址?

操作数是地址还是立即数对应的操作码是不一样的,我们写汇编代码使用的操作码都是同一个那是因为汇编器帮我们做了处理

OS进程调度(CPU执行用户程序如何切回OS调度程序)

linux是分时系统,操作系统会为每个进程分配一个时间片,程序执行一个时间片后,操作系统会重新选择一个任务来执行,问题的关键是CPU是怎么知道时间片到了,又是如何触发任务选择?
(时钟中断)关键原理是CPU有个外部时钟,这是一个倒数计时器,初始时会设置一个数字,比如1000,然后每个时钟秒冲数字会减一,减到0的时候,就会给CPU发送一个信号,CPU会中断当前程序,来处理这个信号,这个信号的处理程序会重置计时器,并执行信号处理函数,如此反复,起到了时间分片的效果。信号处理函数可能会重新选择另一个任务来执行,这就是进程切换。

多处理器(CPU)体系架构如何实现程序的同步执行(互斥)?

(设置内存路障)对系统总线加锁起路障作用

通过逻辑门如何实现加法?

对二进制计算分两步第一步求和运行,第二步进位运算。
对于求和直接输出,进位作为下一个求和的输入

CPU使用率是如何计算的?

非空闲进程运行的时间/时间间隔

CPU空闲含义?

空闲进程(对Linux系统来说即0号任务)

如何才能让CPU休眠

CPU休眠状态:
1、C0-活动:CPU正在运行
2、C1-自动停止:核心时钟关闭。处理器没有执行指令,但可以几乎瞬间返回到执行状态
3、C2-停止时钟:核心和总线时钟关闭,处理器维护所有软件可见状态,但需要更长时间才能唤醒
4、C3-深度睡眠:时钟发生器关闭

CPU如何知道一条指令的长度

指令格式分为:操作码 和操作数
首先指令的操作码长度是确定的。CPU执行指令分为三个过程,取指—译指—执行。首先CPU从内存中获取操作码-然后CPU会根据指令操作码判断操作数的长度。操作码的长度+操作数的长度=指令的长度。

CPU从内存中根据地址获取数据如何知道一次获取多少个字节

指令的操作码就决定了操作数的长度

进程执行完所有代码后如何自动退出

可执行文件由编译器生成ELF(可执行可链接格式)格式,
会在程序的前后会自动插入一些代码_start,_exit。 _start才是程序的真正入口,而main函数是被_start调用的。在调用main返回后调用_exit函数结束当前进程。main的返回值会做_exit函数的参数,(gcc在编译和连接时会自动加上exit调用)

中断服务过程

当CPU接收到中断请求同时也会得到中断向量,中断向量就是中断向量表中的偏移值(索引),CPU会根据IDTR得到中断描述表的基地址然后根据中断向量,得到中断描述符。中断描述符中包含了段选择符以及过程偏移入口。(段选择符一般是内核的代码段,因为中断服务程序位于内核代码段中,选择符由三部分构成(描述符索引(处理器会*8,因为描述都是占8字节64位的),描述符在GDT中还是LDT中的一位标记,还有请求特权级别,描述符))。处理器会根据段选择符的RPL与段描述符中的DPL进行比较如果权限允许会将段选择符加载到对应的段寄存器中一般是(CS)。接着根据中断描述符中的偏移值设置EIP执行中断服务程序。在执行前处理器会吧原SS、ESP、FLAGES、CS、EIP保存到被中断任务的内核栈中。

内存管理为啥要使用多级页表,使用一个页表不行吗?

使用多级页表能节省空间,做到运行时动态分配。在32位系统中如果使用一个页表表示4G空间,那页表所占的空间为4M连续空间。

在开启包含模式下对内存的访问是否会都会经过MMU

是的,所有的内存访问都会经过MMU的转换。包括内核对内存的访问。内核对内存的访问具有特殊性,原因在于内核代码的逻辑地址与物理地址一一对应,实现的方法就是将内核的代码段、数据端基地址设置为0,limit设置成主存最大值(低版本,v1以下实现方式)。

页目录基址是CR3,当用户进程系统调用陷入内核,是否会使cr3切换到内核页表。

系统0号进程cr3与内核cr3是相同的。所有的进程都是通过0号进程复制过来的,是0号进程的子进程或子子进程。在调用fork的时候会把内存结构包括页表都会复制一份相同。所以内核的页表与进程中的页表是一直的(内核部分)。

为什么需要栈?

第一:由于寄存器数量有限,而程序中局部变量数量可能会很多,而对于局部变量我们只关心他的值与取值方便,如果把局部变量放到主存中,势必对于这个访存操作都需要记录他的存储地址。与我们所想有别。
第二:对于函数调用也提供了遍历,函数调用本质是从一个内存地址跳转到另一个地址,但是还得返回到原理地址的下一个地址。对于这种操作在没有使用栈的解决方案是需要通过一个标签定义在调用方法的下方,这种方法对程序的维护性很困难。

为什么需要引导扇区?

BIOS是一种通用的例程,而我们的操作系统是大小实现方式都不一样,为了实现把我们的操作系统加载到主存中。我们需要提供一个中间层,BIOS加载引导扇区512字节(一个扇区)的数据,而引导扇区加载我们的操作(引导扇区就提供了我们的自定义就相当于一个桥梁)。

从实模式切换到保护模式时需要注意什么?

本质是从16位实模式切换到32为保护模式,CPU是流水线处理,在同一时刻,有的电路在执行指令,有的电路是在解析指令,有的电路是在取指令,当我们在执行切换指令后对于正在取值和解析指令的操作都将无效(因为它们是在16为模式的基础上)所以我们需要使它们的操作无效,需要执行一个长跳,

中断号与中断向量

中断号时物理的是中断芯片上的引脚,中断向量是对应程序上了,可以通过编程修改中断号与中断向量的映射

cgroups是什么,有什么用。

cgroups 全称control groups,为每种可以控制的的资源定义一个子系统,
可以限制单个或多个进程所使用的资源的机制。例如一个即部署了前端的Web服务,也部署了后端计算模块在八核的服务器上,可以使用cgroups限制前端web服务使用其中的六个核,把剩下的2个核留个后端计算模块

volatile的作用。

1、禁止编译器优化对指令进行重排序,禁止CPU执行对指令进行重排序。
2、起到内存屏障作用,保证了数据的修改对所有CPU可见,对修改的数据,先从内存中加载到缓存,对写完的数据在在从缓存中立即冲刷到内存。

1
2
3
4
int i = 0;              
boolean flag = false;
i = 1; //语句1
flag = true; //语句2

语句2可能会在语句1之前执行
重排序:一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

下面展示标准的DCL(double-checked locking)单例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null) {
instance = new Singleton();
}
}
}
return instance;
}
}

第7行“instance = new Singleton()”可以简单的拆分
1、分配内存空间
2、初始对象
3、将内存地址赋值给变量
2和3可能会被处理器优化,发生重排序,3将在2之前执行,另一个线程判断instance!=null然后得到一个不完整没有初始化好的实例可能会引发异常

volatile关键字是如何防止指令重排的

被volatile修饰的变量编译后在赋值后多执行了一个“lock addl $0x0,(%esp)”操作,这个操作相当于一个内存屏障(指重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;但如果有两个或更多CPU访问同一块内存,且其中有一个在观测另一个,就需要内存屏障来保证一致性。这句指令中“ addl $0x0,(%esp)”把ESP寄存器的值加0显然是一个空操作(采用这个空操作而不是空操作指令nop是因为IA32手册规定lock前缀不允许配合nop指令使用),关键在于lock前缀,查询IA32手册,它的作用是是的本CPU的Cache写入内存,该写入动作也会引起别的CPU或者别的内核无效化其Cache,这种操作相当于对Cache中的变量做了一次Store和write操作。所以通过这样一个空操作,可让前面的volatile变量修改对其他CPU立即可见。

CPU执行速度与访存的速度高几个数量级——》引入缓存——》缓存一致性协议——》缓存一致性带来的同步开销——》引入Store Buffer和Invalid Queue——》异步造成的访存重排序——》引入内存屏障

为什么volatile变量在赋值后加上lock前缀指令能实现内存屏障功能

问题1:如果刚好执行赋值以后发送了中断,没有执行lock指令
答:根据x86规定,在发生中断的时候需要执行完当前指令之前的所有指令
问题2:
1、CPU0执行mov eax,0x68(%rsi) 变量赋值
2、CPU1执行mov 0x68(%rsi),eax 加载变量

synchronized能防止指令重排吗?

可以
有序性:java程序中天然有序性可以总结为一句话:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内表现为串行的语义”,后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象。
java提供volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile关键字本身就包含了禁止指令重排序的语义,而synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”,这条规定则决定了持有同一个锁的两个同步块只能串行进入。

synchronized实现原理

1、无法强制已获取锁的线程释放锁。
2、无法强制正在等待锁的线程中断等待或超时退出。
3、非公平的,任何一个等待锁的线程都有机会获得锁

膨胀方向是:无锁——>偏向锁——>轻量级锁——>重量级锁。膨胀方向不可逆

ReentrantLock

1、等待可中断
2、公平锁(默认非公平,可以通过带boolean参数的构造函数要求使用公平锁,不过一旦使用了公平锁,将会导致ReentrantLock的性能急剧下降,会明显影响吞吐量)
3、锁绑定多个条件