Java面试之操作系统
1、冯诺依曼模型
运算器、控制器、存储器、输入设备、输出设备
32位和64位CPU最主要区别是一次性能计算多少字节数据,如果计算的数额不超过 32 位数字的情况下,32 位和 64 位 CPU 之间没什么区别的,只有当计算超过 32 位数字的情况下,64 位的优势才能体现出来。
线路位宽 cpu能操作的内存大小 比如cpu想要操作4G的内存,就需要32条地址总线。2^32=4G
2、程序执行的基本过程
一个程序执行的时候,CPU 会根据程序计数器里的内存地址,从内存里面把需要执行的指令读取到指令寄存器里面执行,然后根据指令长度自增,开始顺序读取下一条指令。
3、指令的执行速度
程序的CPU执行时间=指令数*每条指令的平均时钟周期数*时钟周期时间
时钟周期时间=1/电脑主频 如1/2.4G
4、存储
寄存器
CPU cache SRAM 静态随机存储器
- 每个 CPU 核心都有一块属于自己的 L1 高速缓存,指令和数据在 L1 是分开存放的,所以 L1 高速缓存通常分成指令缓存和数据缓存。
- L2 高速缓存位置比 L1 高速缓存距离 CPU 核心 更远 大小比 L1 高速缓存更大
- L3 高速缓存通常是多个 CPU 核心共用的
内存 DRAM (Dynamic Random Access Memory,动态随机存取存储器) 的芯片。数据会被存储在电容里,电容会不断漏电,所以需要「定时刷新」电容,才能保证数据不会被丢失
CPU 从 L1 Cache 读取数据的速度,相比从内存读取的速度,会快 100
多倍
硬盘
- 固态硬盘:断电后数据还是存在的,而内存、寄存器、高速缓存断电后数据都会丢失。内存的读写速度比 SSD 大概快
10~1000
倍。 - 机械硬盘:通过物理读写的方式来访问数据的,因此它访问速度是非常慢的,它的速度比内存慢
10W
倍左右。
每个存储器只和相邻的一层存储器设备打交道
5、如何写出让CPU 跑的快的代码
CPU Cache 的数据是从内存中读取过来的,它是以一小块一小块读取数据的
CPU L1 Cache 分为数据缓存和指令缓存,因而需要分别提高它们的缓存命中率:
- 对于数据缓存,我们在遍历数据的时候,应该按照内存布局的顺序操作,这是因为 CPU Cache 是根据 CPU Cache Line 批量操作数据的,所以顺序地操作连续内存数据时,性能能得到有效的提升;
- 对于指令缓存,有规律的条件分支语句能够让 CPU 的分支预测器发挥作用,进一步提高执行的效率;
对于多核 CPU 系统,线程可能在不同 CPU 核心来回切换,这样各个核心的缓存命中率就会受到影响,于是要想提高线程的缓存命中率,可以考虑把线程绑定 CPU 到某一个 CPU 核心。
6、cpu缓存一致性
写数据
写直达 如果cache已经存在,则更新后写入内存 如果cache不存在,则把数据更新到内存
写回 对于已经缓存在 Cache 的数据的写入,只需要更新其数据就可以,不用写入到内存,只有在需要把缓存里面的脏数据交换出去的时候,才把数据同步到内存里,这种方式在缓存命中率高的情况,性能会更好
如何缓存一致
- 写传播,也就是当某个 CPU 核心发生写入操作时,需要把该事件广播通知给其他核心;
- 第二点是事物的串行化,顺序
总线嗅探机制的 MESI 协议 已修改、独占、共享、已失效
对于在「已修改」或者「独占」状态的 Cache Line,修改更新其数据不需要发送广播给其他 CPU 核心。
7、伪共享
CPU Cache Line 大小一般是 64 个字节 这种因为多个线程同时读写同一个 Cache Line 的不同变量时,而导致 CPU Cache 失效的现象称为伪共享
如何避免?
多个线程共享的热点数据,即经常会修改的数据,应该避免这些数据刚好在同一个 Cache Line 中,否则就会出现为伪共享的问题。
在 Linux 内核中存在 __cacheline_aligned_in_smp
宏定义,是用于解决伪共享的问题。是用空间换时间
字节填充
8、cpu选择线程
- SCHED_DEADLINE:是按照 deadline 进行调度的,距离当前时间点最近的 deadline 的任务会被优先调度;实时任务总是会比普通任务优先被执行
- SCHED_FIFO:对于相同优先级的任务,按先来先服务的原则,但是优先级更高的任务,可以抢占低优先级的任务,也就是优先级高的可以「插队」;
- SCHED_RR:对于相同优先级的任务,轮流着运行,每个任务都有一定的时间片,当用完时间片的任务会被放到队列尾部,以保证相同优先级任务的公平性,但是高优先级的任务依然可以抢占低优先级的任务;
9、软中断
中断处理程序的上部分和下半部可以理解为:
- 上半部直接处理硬件请求,也就是硬中断,主要是负责耗时短的工作,特点是快速执行;
- 下半部是由内核触发,也就说软中断,主要是负责上半部未完成的工作,通常都是耗时比较长的事情,特点是延迟执行;
Linux 中的软中断包括网络收发、定时、调度、RCU 锁等各种类型 软中断的处理是通过“ksoftirqd”内核线程来实现的
10、补码
有了补码,负数的加减法操作,实际上是和正数加减法操作一样的
十进制整数转二进制使用的是「除 2 取余法」,倒序
十进制小数使用的是「乘 2 取整法」正序
计算机存小数 符号位 指数位 尾数位
小数计算不精确的原因
因为有的小数无法可以用「完整」的二进制来表示,所以计算机里只能采用近似数的方式来保存,那两个近似数相加,得到的必然也是一个近似数。
11、内核
内核作为应用连接硬件设备的桥梁
- 进程调度
- 内存管理
- 为进程与硬件设备之间提供通信
- 提供系统调用 应用程序要运行更高权限运行的服务,那么就需要有系统调用,它是用户程序与操作系统之间的接口。
linux内核设计的理念
- 多任务 并发并行
- 对称多处理 每个 CPU 的地位是相等的
- 可执行文件链接格式 Linux 可执行文件格式叫作 ELF,Windows 可执行文件格式叫作 PE。
- 宏内核 Linux 的内核是宏内核 Windows的内核设计是混合型内核,
微内核,有一个最小版本的内核,一些模块和服务则由用户态管理;
混合内核,是宏内核和微内核的结合体,内核中抽象出了微内核的概念,也就是内核中会有一个小型的内核,其他模块就在这个基础上搭建,整个内核是个完整的程序;
宏内核,包含多个模块,整个内核像一个完整的程序;
12、虚拟内存
操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。多进程之间地址冲突问题
虚拟内存还可以是的进程对运行内存超过物理内存的大小
页表里的页表项中除了物理地址之外,还有一些标记属性的比特,比如控制一个页的读写权限,标记该页是否存在等。在内存访问方面,操作系统提供了更好的安全性。
操作系统是如何管理虚拟地址与物理地址之间的关系?
内存分段 虚拟地址是通过段表与物理地址进行映射的,分段机制会把程序的虚拟地址分成 4 个段,每个段在段表中有一个项,在这一项找到段的基地址,再加上偏移量,于是就能找到物理内存中的地址
问题:内存碎片 内存交换的效率低
- 每个段的长度不固定,所以多个段未必能恰好使用所有的内存空间,会产生了多个不连续的小物理内存,导致新的程序无法被装载,所以会出现外部内存碎片的问题
内存分页
分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小 在 Linux 下,每一页的大小为 4KB
。采用了分页,页与页之间是紧密排列的,所以不会有外部碎片。如果内存空间不够,操作系统会把其他正在运行的进程中的「最近没被使用」的内存页面给释放掉,也就是暂时写在硬盘上,称为换出(Swap Out)。一旦需要的时候,再加载进来,称为换入(Swap In)。所以,一次性写入磁盘的也只有少数的一个页或者几个页,不会花太多时间,内存交换的效率就相对比较高。
问题:页表会非常的庞大
解决方案:多级页表
问题:多了几道转换的工序,这显然就降低了这俩地址转换的速度
解决方案:在 CPU 芯片中,加入了一个专门存放程序最常访问的页表项的 Cache,这个 Cache 就是 TLB
13、malloc是如何分配内存的
malloc() 源码里默认定义了一个阈值:
- 如果用户分配的内存小于 128 KB,则通过 brk() 申请内存;
- 如果用户分配的内存大于 128 KB,则通过 mmap() 申请内存;
malloc()分配的是虚拟内存 如果分配后的虚拟内存没有被访问的话,虚拟内存是不会映射到物理内存的,这样就不会占用物理内存了 只有在访问已分配的虚拟地址空间的时候,操作系统通过查找页表,发现虚拟内存对应的页没有在物理内存中,就会触发缺页中断,然后操作系统会建立虚拟内存和物理内存之间的映射关系。
malloc 申请的内存,free 释放内存会归还给操作系统吗?
- malloc 通过 brk() 方式申请的内存,free 释放内存的时候,并不会把内存归还给操作系统,而是缓存在 malloc 的内存池中,待下次使用;
- malloc 通过 mmap() 方式申请的内存,free 释放内存的时候,会把内存归还给操作系统,内存得到真正的释放
mmap()是通过系统调用来实现的,会发生运行态的切换
14、内存回收
应用程序通过 malloc 函数申请内存的时候,实际上申请的是虚拟内存,此时并不会分配物理内存。
当应用程序读写了这块虚拟内存,CPU 就会去访问这个虚拟内存, 这时会发现这个虚拟内存没有映射到物理内存, CPU 就会产生缺页中断,进程会从用户态切换到内核态,并将缺页中断交给内核的 Page Fault Handler (缺页中断函数)处理。
缺页中断处理函数会看是否有空闲的物理内存:
- 如果有,就直接分配物理内存,并建立虚拟内存与物理内存之间的映射关系。
- 如果没有空闲的物理内存,那么内核就会开始进行回收内存 (opens new window)的工作,如果回收内存工作结束后,空闲的物理内存仍然无法满足此次物理内存的申请,那么内核就会放最后的大招了触发 OOM (Out of Memory)机制。
后台内存回收 异步
直接内存回收 同步
- 文件页的回收:对于干净页是直接释放内存,这个操作不会影响性能,而对于脏页会先写回到磁盘再释放内存,这个操作会发生磁盘 I/O 的,这个操作是会影响系统性能的。
- 匿名页的回收:如果开启了 Swap 机制,那么 Swap 机制会将不常访问的匿名页换出到磁盘中,下次访问时,再从磁盘换入到内存中,这个操作是会影响系统性能的。
直接回收 但是空闲的物理内存仍然无法满足此次物理内存的申请,那么内核就会触发 OOM 机制
针对回收内存导致的性能影响,常见的解决方式。
- 设置 /proc/sys/vm/swappiness,调整文件页和匿名页的回收倾向,尽量倾向于回收文件页;
- 设置 /proc/sys/vm/min_free_kbytes,调整 kswapd 内核线程异步回收内存的时机;
- 设置 /proc/sys/vm/zone_reclaim_mode,调整 NUMA 架构下内存回收策略,建议设置为 0,这样在回收本地内存之前,会在其他 Node 寻找空闲内存,从而避免在系统还有很多空闲内存的情况下,因本地 Node 的本地内存不足,发生频繁直接内存回收导致性能下降的问题;
15、 在4G物理内存的机器上,申请8G内存会怎么样
- 在 32 位操作系统,因为进程理论上最大能申请 3 GB 大小的虚拟内存,所以直接申请 8G 内存,会申请失败。
- 在 64位 位操作系统,因为进程理论上最大能申请 128 TB 大小的虚拟内存,即使物理内存只有 4GB,申请 8G 内存也是没问题,因为申请的内存是虚拟内存。如果这块虚拟内存被访问了,要看系统有没有 Swap 分区:
- 如果没有 Swap 分区,因为物理空间不够,进程会被操作系统杀掉,原因是 OOM(内存溢出);
- 如果有 Swap 分区,即使物理内存只有 4GB,程序也能正常使用 8GB 的内存,进程可以正常运行;