Java开发者需要了解的硬件知识 (二)、操作系统篇
上一篇讲了CPU,作为整个计算机的核心计算硬件,讲解了它于JAVA语言间的瓜葛。这一篇讲讲计算机软件里的老大哥OS ----操作系统。
因为学习内容里有些不那么重要的知识点,往往就是截图或者少量文字带过,个人笔记不会记录那么多细节,详细资料请读者自己查询,见谅。
正文:
计算机的启动过程
通电 -> bios uefi 工作 -> 自检 -> 到硬盘固定位置加载bootloader -> 读取可配置信息 -> CMOS
OS 操作系统
内核分类
微内核 - 弹性部署 5G IoT
宏内核 - PC phone
外核 - 科研 实验中 为应用定制操作系统 (多租户 request-based GC JVM)
用户态和内核态
cpu分不同的指令级别
linux内核跑在ring 0级, 用户程序跑在ring 3,对于系统的关键访问,需要经过kernel的同意,保证系统健壮性
内核执行的操作 - > 200多个内核方法,通过系统调用 sendfile read write pthread fork
JVM -> 站在OS老大的角度,就是个普通程序
进程 线程 纤程(协程) 中断
面试高频:进程和线程有什么区别?
专业答案:进程是OS分配资源的基本单位,线程是执行调度的基本单位。
进程最重要的分配资源是:独立的内存空间,线程调度执行(线程共享进程的内存空间,没有自己独立的内存空间)
进程
Linux中也称为task,是系统分配资源的基本单位
资源包括:独立的地址空间,内核数据结构(进程描述符),全局变量, 数据段。。
Linux进程描述符:PCB (Process Control Block),用于Linux的进程管理(线程有他的PCB)
进程在Linux中的创建与启动
调用系统函数 fork()创建进程 ,exec()启动进程
从 A 中fork B 的话,A称之为B的父进程,B称之为A的子进程
僵尸进程
ps -ef |grep defunct (defunct表示无用的僵尸进程)
父进程产生子进程后,会维护子进程的PCB结构,子进程退出后,由父进程释放,如果父进程没有释放,那么子进程会成为一个僵尸进程(defunct)
孤儿进程
子进程结束之前,父进程已经退出,就会产生孤儿进程,孤儿进程会成为Init进程的子进程,由1号进程维护 (在图形化Linux中是1457号线程)
进程调度
内核进程调度器决定该哪个进程运行,何时开始,运行多长时间。
每个进程都可以自定义不同的调度方案
多任务执行分为
-
抢占式(大多数现代系统采用):由进程调度器强制开始或暂停(抢占)某一进程的执行
-
非抢占式:除非进程主动让出(yielding)cpu,否则将一直运行
调度策略
Linux2.6采用CFS调度策略:Completely Fair Scheduler
按优先级分配时间片的比例,记录每个进程的执行时间,如果有一个进程执行时间不到他应该分配的比例,优先执行
默认调度策略:
-
实时进程 : 优先级分高低 - FIFO (First In First Out),优先级一样 - RR(Round Robin)
-
普通:CFS
线程在Linux中的实现
线程在Linux就是一个普通的进程,只不过和其他进程共享资源(内存空间,全局数据等。。。)
其他系统都有各自所谓的LWP的实现 Light Weight Process
内核线程
内核启动后需要做一些后台操作,这些操作由Kernel Thread 来完成,它只在内核空间中运行
在JVM中的线程
创建一个JVM中线程,即申请一个操作系统的线程(重量级线程),1:1关系
纤程 Fiber
纤程可以理解为线程里面的线程,用户态的线程而非内核态的线程。
它处于线程内部,非常轻量级,可以在线程中快速切换。JVM自己管理,自己切换,与操作系统无关。
优势:
-
占有资源很少 ,OS 线程要1M ,而Fiber只需要4K
-
切换比较简单
-
可以启动很多个纤程,可以达到10W+
目前2020-03-22支持内置纤程的语言:Kotlin Scala Go Python(lib)...... Java14并没有(open jdk : loom库)
Java中对于纤程的支持:没有内置,盼望内置
纤程的应用场景
纤程 vs 线程池: 纤程适合很短的计算任务, 不需要和内核打交道,并发量高 的场景
中断
硬中断
硬件跟操作系统内核打交道的一种机制
软中断(80中断)
系统调用:int 0x80 或者 sysenter原语
通过ax寄存器填入调用号(调用号指定了对应的内核方法),参数通过bx cx dx si di 寄存器传入内核,最后返回值通过ax返回
java的一个中断例子
内存管理
windows9x - 多个进程装入内存,如此就会出现两个问题
-
内存不够用
-
互相打扰
为了解决这两个问题,诞生了现在的内存管理系统:虚拟地址 分页装入 软硬件结合寻址
-
分页(内存不够用),内存中分成固定大小的页框(4K),把程序(硬盘上)分成4K大小的块,用到哪一块,加载那一块,加载的过程中,如果内存已经满了,会把最不常用的一块放到swap分区(即交换区,系统运行内存不够时,与Swap进行交换), 把最新的一块加载进来,这个就是著名的LRU算法
-
LRU算法 LeetCode146题Least Recently Used 算法(翻译为最不常用)
-
哈希表(保证 查找操作复杂度为O(1)) + 链表 (保证 排序操作和新增操作复杂度为 O(1)))
-
双向链表 (保证查找效率)
-
-
虚拟内存(解决相互打扰问题)
-
DOS Win31 ... 这些旧的系统,都会直接操作内存,可能导致进程间互相干掉对方
-
为了保证互不影响 - 让进程工作在虚拟空间,程序中用到的空间地址不再是直接的物理地址,而是虚拟的地址,这样,A进程永远不可能访问到B进程的空间
-
虚拟空间多大呢?寻址空间 - 64位系统 2 ^ 64,比物理空间大很多 ,单位是byte
-
站在虚拟的角度,进程是独享整个系统 + CPU
-
内存映射:偏移量 + 段的基地址 = 线性地址 (虚拟空间)
-
线性地址通过 OS + MMU(硬件 Memory Management Unit),得到对应的物理内存地址
-
-
缺页中断(不是很重要):
-
需要用到页面内存中没有,产生缺页异常(中断),由内核处理并加载
-