操作系统学习笔记

内核

操作系统是什么

操作系统是拥有文件管理、内存管理、设备管理、进程管理等管理系统资源的、对用户以及其他应用友好、对硬件进行操作的系统软件

内核是什么

操作系统的内核是操作系统的核心部分,它最接近硬件

在微内核系统中,它是只具有系统原语(具有原子性的指令)、时钟管理、中断处理功能的软件

在大内核系统中,它还负责系统的内存管理,IO 管理,进程管理等,内核拥有自己的内存部分,一些重要数据存放在这里

比如一些用户进程,做一些多线程任务的时候,需要转换为内核态调用内核的线程调度实现。为什么要这样呢?

举个例子:我们现在想发送一段数据给其他的计算机,当我们把一个地址通过 JNI 传递给底层的 C 库的时候,有一个基本的要求,就是这个地址上的内容不能失效。然而,在 GC 管理下的对象是会在 Java 堆中移动的。也就是说,有可能我把一个地址传给底层的 write,但是这段内存却因为 GC 被回收掉了。所以必须要把待发送的数据放到一个 GC 管不着的地方,即由操作系统控制处理的内存位置

因此,我们在调用一些操作系统功能的时候,需要做内核态的转换

内核态和用户态

用户态:用户态运行的进程或可以直接读取用户程序的数据,并执行一些不调用系统资源的指令(比如加减乘除以及调用用户态内存的指令),即非特权指令

系统态:可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制,即特权指令

系统调用

在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成

执行系统调用之前,首先应用程序向操作系统发送中断信号(信号是常见通信方式之一)

注意,系统调用是非常消耗资源的,它的成本主要来自用户态与内核态的转换,而转换需要环境切换才可以让线程运行,环境的切换需要响应中断、恢复执行现场,比如进行一些把环境从内存 copy 到寄存器等一系列操作,因此成本非常大

信息的表示与存储

对于计算机系统而言,从机器的角度来看,程序仅仅是一个个的字节序列,按此序列机器执行相应的动作

虚拟地址空间与字长

程序以字节(8 bite)为单位编址,这些微不足道的二进制数字,构成了数字革命的基础。机器级程序将内存看作一个非常的大的字节数组,在数组中存储所有信息。内存中的每个字节都会由一个唯一的数字来表示,称为他们的地址,所有可能的地址集合就称为虚拟地址空间

字长是一个存储单元的位长度,决定了虚拟地址空间大小。指针型变量用全字长来存储。字长为w,地址空间的最大值为2w-1字节,字长是用来寻址的

64位程序和32位程序区别在于运行程序的机器类型字长不一样,他们的字长为64或者32,因此该程序的编译方式也不一样。比如,数据类型 long 在32位程序中占据4字节,在64位程序中占据8字节。int 类型数据在64位和32位系统中通常都占用4字节。为了提升程序可移植性,将数据类型大小固定,不随编译器和机器设置变化是不错的选择。不然我们只能出两款程序来适配两个不同的机器了,同时,32位程序是可以在64位机器上运行的,这是一种向后兼容

对于跨越多字节的程序对象,地址采用所使用的字节中最小的地址,同时附上他们占了多少位,由此来获取完整的数据。程序对象在多字节中的存储顺序分为大端法和小端法,他们的区别只是在内存中是从前往后存储的还是从后往前存储的

位运算

虽然用一比特就可以表示 boolean 了,但是在大多数编程语言中,Boolean 还是占一字节的,该值是位运算的起源,普通的 int 类型也可以使用位运算,比如 &,|,^(异或),~(取反),<<、>>()这些

额外聊一下位运算中比较有趣的符号 << 与 >>。这是位移操作,有两种位移方式,一种是逻辑位移,移位时连同符号位一起位移。即左移和右移都对空闲位补0,第二种是算数右移,空闲位数值与符号位相同,但是如果该值是一个有符号数并且为负数(最高位为1),则进行右移后所有的高位都为1

实际上,几乎所有的编译器/机器组合都对有符号数进行算数右移。java 中可以使用 >> 来指定机器使用逻辑位移,用 >>> 来指定机器使用算数位移

逻辑运算符相对于位运算符是成对出现的,结果只能为 0x00(false)或 0x01(true)。在逻辑运算中,如果只对第一个参数求值就可以确定表达式结果,则不对第二个参数求值

字符串、整数与浮点的表示

C 语言中字符串字符用 ASCII 码表示,并且自动在末尾补上 null(值为0)字符。那如何区分 “” 与 null 呢?ASCII 中有正文开始与正文结束的符号表示,因此这不算什么难事

同样的文本代码,在使用 ASCII 码作为字符码的任意系统都可以得到相同的结果。但是对二进制代码,在不同的系统中即使是相同的进程也有不一样的二进制码,不同机器类型使用不同的不兼容的指令和编码方式。因此,文本程序比二进制程序有更好的平台独立性和移植性

无符号编码以及补码编码都具有唯一性,可以区分出原码+0和-0的不同。补码的表示范围是不对称的| TMin | = | TMax | + 1,补码所有位都是1时表示十进制数字-1,补码的出现是为了方便计算,使减法运算可以当作加法运算,a – b 可以看做 a补 + (-b)补,在上述表达式中,-b 的补码就是将其二进制原码变换成一个无符号的二进制数,使该无符号二进制数 +a 的结果与 a – b 的结果等同

浮点数的表示相对麻烦,之前的厂商有各种千奇百怪的实现方式,我们一般使用 IEEE 对应标准实现。大致是以下过程,并且这么表示是不准确的,如果存储金额等等比较重要的数据,推荐使用大精度对象
在这里插入图片描述
补码与 IEEE 表示都是为了方便计算

进程与线程

进程与线程有什么区别

一个进程可以拥有系统为它分配的各种资源(内存、CPU资源)

在JVM层面,java程序的执行就是一个进程,里面包括了多个线程(栈),以及它们共享的资源(堆,方法区)

在操作系统层面,进程是各种资源分配的基本单位(把一个程序从磁盘拉到内存中),线程是CPU中寄存器执行的基本单位(内存中的数据读到CPU中)

内存与 CPU 组成

内存中存放的数据被 CPU 读取的时候,是一行一行读的。经过缓存时,缓存中的单位称为缓存行(cache line),一行数据8bit(2的幂,最常见的是8bit,对应64位操作系统CPU读取与处理一次数据的大小,遵守缓存行对齐会让程序执行变快,比如java的对齐填充,以及disruptor环形队列)

CPU由ALU(算术逻辑单元),寄存器(数据存放在CPU的寄存器中),PC(指向线程下一条指令的地址)组成

多核CPU中,每一个核包括ALU寄存器PC还有一级缓存(L1)、二级缓存(L2),一个CPU只有一个三级缓存(L3)

超线程是指,一个核中的ALU对应两个寄存器与PC,这样在两个线程之间切换起来十分容易,平时说的四核八线程就是这个

进程的状态

1,新建:进程正在被创建,系统为这个进程分配各种资源
2,就绪:进程其他的资源已就绪,得到 CPU 资源即可运行
3,执行:进程运行中
4,阻塞:进程被阻塞,一般是因为系统调用或者等待其他优先级高的线程
5,死亡:进程正在被撤销,系统回收这个进程的资源

还有七状态模型,多了两种——就绪挂起和阻塞挂起,它们的区别就是就绪挂起状态其实还是在内存中的,而后者是在外存中的

阻塞(pend)就是任务释放 CPU,其他任务可以运行,一般在等待某种资源或信号量的时候出现。挂起(suspend)不释放 CPU,如果任务优先级高就永远轮不到其他任务运行。一般挂起用于程序调试中的条件中断,当出现某个条件的情况下挂起,然后进行单步调试

线程的状态

操作系统层面的线程五种状态,可以对应进程的状态记忆

1,新建(new):创建了一个新的线程对象
2,就绪(runnable):调用线程的start()方法,处于就绪状态
3,运行(running):获得了CPU时间片,执行程序代码,就绪状态是进入到运行状态的唯一入口
4,阻塞(block):因为某种原因,线程放弃对CPU的使用权,停止执行,直到进入就绪状态在有可能再次被CPU调度

阻塞又分为三种:

  • 1)等待阻塞:运行状态的线程执行 wait 方法,JVM 会把线程放在等待队列中,使本线程进入阻塞状态
  • 2)同步阻塞:线程在获得 synchronized 同步锁失败,JVM 会把线程放入锁池中,线程进入同步阻塞。对于锁池和等待池,可以看这篇文章
  • 3)其他阻塞:调用线程的 sleep 或者 join 后,线程会进入道阻塞状态,当 sleep 超时或者 join 终止或超时,线程重新转入就绪状态

5,死亡(dead):线程 run、main 方法执行结束,或者因为异常退出了 run 方法,则该线程结束生命周期

进程间的通信方式

进程间的同步是进程间的通信方式的一种,不过由于只能传递很少的数据量,并且对用户来说不透明,只能算低级的通信方式。在计算机操作系统中,进程间的通信方式应该分为四类

1,共享内存:两个进程共享同一块物理内存空间,这种方式需要依靠某种同步操作,如互斥锁和信号量等,因为可能会出现修改丢失问题,所以两个进程对内存空间的访问必须是互斥的

2,管道通信:管道是一种在内存中具有一定空间的缓冲区,在Linux系统中叫pipe文件,与在磁盘中的.txt等文件相似,只能一个进程向文件中写入数据,另一个进程从文件中读取数据,一个进程写数据时另外一个进程不能读数据,两个进程需要互斥访问管道;管道又分匿名管道和有名管道

3,消息传递:直接将消息从一个进程传到另一个进程中,消息(message)是一种封装了数据的东西,比如报文;这种通信方式又分直接通信(通过系统源语实现,一个进程通过发送源语将消息挂到另一个进程的消息访问队列中,进程通过接受源语接受)和间接通信(消息队列),信号和消息队列应该是属于消息传递的

4,CS系统:主要实现为套接字(一个存放了目的地址、目的端口、传输层协议、自己地址的数据结构)、RPC,主要用与网络间进程的通信,不过也可以在同一台主机上通信(原始套接字)

进程间的互斥方式

进程互斥的软件实现方法,互斥一般是为了实现同步,主要为了线程安全,互斥是手段,同步是方式:

1,单标志法:不是自己的标记不能进入代码
2,双标志先检查法:设置一个布尔类型的数组flag[],数组中的各个元素用来标记各进程想进入临界区的意愿,每个进程进入临界区之前先检查当前有没有别的进程想进入临界区,如果没有,则把自己对应的标志flag[i]设为true,之后开始访问临界区)
3,双标志后检查法:先上锁后检查
4,Peterson算法,前三种都有严重的功能缺陷,这一种在自己想要进入临界区之前先将标识位主动设置为其他进程的,让其他进程优先进入,如果其他进程没有进入意向,则自己进入

进程互斥的硬件实现方法:

1,中断屏蔽方法:使用关中断源语使处理器不能上下文切换,这么做有两个缺点,一是其他处理器可以调用其他线程,二是用户程序无法调用这个指令
2,TestAndSet(TS指令/TSL指令):使用old变量记录之前锁的值,将锁的值设置为true,如果old变量为false,说明其他进程没有对锁上锁,我们可以进入自己的代码
3,Swap指令(exchange指令):这个和TSL指令从代码逻辑上看其实是一模一样的,但是在硬件初次上实现不一样

比如文件锁,就是进程互斥的一种实现,也叫记录锁。对于有些应用程序,如数据库,各个进程需要保证它正在单独地写一个文件,这时就要用到文件锁。文件锁的作用是,当一个进程读写文件的某部分时,其他进程就无法修改同一文件区域

线程间的同步方式

1,信号量:是一个计数器,实现多个线程的同步(同步是指在互斥的基础上,通过其它机制实现访问者对资源的有序访问),又分整数型信号量(如果值小于0,循环等待)和记录型信号量(每次进代码值减一,如果值小于0,加入等待队列中并且进程阻塞,出代码时值加一并且唤醒第一个队列值)

2,互斥量、配合通知机制:Mutex,同一时间只有一个线程访问这个资源,保证公共资源不会被多个线程同时访问;通知机制就是Wait/Notify,通过通知其他线程的方式进行多线程同步操作

进程的调度算法

为了确定首先执行哪个进程以及最后执行哪个进程以实现最大 CPU 利用率,计算机科学家已经定义了一些算法

1,先来先服务
2,短作业优先
3,优先级调度
4,时间片轮转

5,多级反馈队列:融合了上面四个算法的优点,算法如下:

有多个优先级不同的队列,每个队列遵循的先来先服务算法;

每一进程分配一定的时间片,若时间片运行完时进程未结束,则进入下一优先级队列的末尾;

优先级越低,所能分配到的时间片越多

内存管理

操作系统的内存管理主要负责:
1,为进程进行内存的分配与回收
2,将不同进程的虚拟地址和不同内存的物理地址映射起来,也叫CPU寻址
3,使用虚拟内存技术让程序的内存空间从逻辑上增加,一般使用覆盖技术或者交换技术
4,内存保护,让一个进程的指令不能访问其他进程,一般由上下限来判断

基本概念:
存储单元:存放数据或者指令的基础大小,一般按字节储存是8bit
内存地址:用来找到对应存储单元的地址,又叫物理地址,地址的长度和内存条大小有关(比如4GB就是2的32次方,因此需要32bit的地址)
逻辑地址:又叫虚拟地址,通过逻辑地址和程序的起始地址可以找到对应的物理地址
覆盖技术和交换技术:内存不够时将内存中的部分数据放入外存,外存数据进入内存;两者的区别是,覆盖侧重单一程序的交换,交换将其他进程挂起后再进行外存置入内存的操作

内存管理机制

将内存分成块、页、段等进行管理,并且执行逻辑地址与物理地址的转化过程,主要有以下两个方式

连续分配管理

划分一个连续的物理内存空间给进程使用,这样分配会生成很多碎片空间,有以下两种方法

1,单一连续分配:将内存分成多个固定大小的块,每个块只运行一个进程
2,固定分区分配:将内存先分成多个长度可变的块,每个块自动适应进程大小
3,动态分区分配:在程序装入内存时才将内存进行划分,更加灵活,根据内存回收与分配一定会产生内存碎片,因此有了各种适应算法(空闲链表,使用首次适应、最佳适应、最坏适应算法等)

分页管理

将进程打碎放入内存中,这样可以节省很多空间,一般有以下三种方法

1,页式管理:将应用程序分为大小固定的页,内存分为大小相同的页,每个页块非连续的存放在内存中,页式管理通过页表对应逻辑地址和物理地址(页表中存放了每个页块放在哪个物理地址,我们只要根据逻辑地址就可以算出物理地址)
2,段式管理:计算机让程序按逻辑自动拆分为段,段的大小不固定,每个段记录一组完整的消息,离散的放入内存中,段式管理通过段表对应逻辑地址和物理地址,段表中记录了段号与物理地址的对应关系,只要找到偏移量就可以找到对应的物理地址
3,段页式管理:先将程序按逻辑自动拆分为段,将每个段拆分为大小相同的页,通过段表来查找物理地址

为什么要使用逻辑(虚拟)地址

1,直接把物理地址暴露给用户会发生很多安全问题
2,程序装入内存条时它的位置是不确定的,程序中的语句如果写死了物理地址,那么只能修改对应物理地址的值,不可以动态变化

页式内存管理的优化

不论是快表还是多级页表都与局部性原理有关

快表

由于内存管理有虚拟地址到逻辑地址的转化,所以在普通地址变化机制中,CPU需要访问两次内存才能得到数据(一次访问页表里的虚拟内存,一次访问物理地址上的数据)

引入catch后(catch储存转化页表),只要访问一次高速缓冲存储器,一次主存,提高了速度,当catch存不下时,需要一些算法来淘汰部分页面

多级页表

较大的进程中,光是页表就要占据很多的连续内存空间,我们可以根据页式管理内存的方式,将页表存放在离散的页块中,在页表之外又加了一层页表,来储存这个进程的页表对应关系(多级页表是因为64位系统中二级页表还是很大,于是又加入了一层页表)

二级页表可以不存在或者不在主存,所有数据必须引入内存,一级页表按需求从磁盘引入内存

虚拟内存

它为每个进程分别提供了一个从逻辑上一致的、连续的虚拟地址空间,并保护每个进程的地址空间不会被其他进程破坏

普通的存储器管理必须将所有的数据都装入内存中,虚拟内存技术允许将部分页或者段装入内存中,其他部分留在磁盘。简单来说,虚拟内存使每一个进程可用的内存大大增加

虚拟内存又叫虚拟地址空间,和虚拟地址定义是不一样的

局部性原理

时间局部性:访问一个资源之后不久很可能再次访问这个资源
空间局部性:访问一个资源之后很可能访问这个资源附近的其他资源

虚拟内存的实现

1,请求分页存储管理:建立在页式储存管理之上,只装入需要运行的页,如果找不到数据,调入需要的页面,如果内存不够,执行缺页中断算法
2,请求分段存储管理
3,请求段页式存储管理

页面置换算法

1,最佳置换算法:不可能实现,仅供参考
2,先进先出算法:将最早的页面淘汰
3,LRU最近最久未使用算法:按使用顺序将最久没有使用的页面淘汰
4,最少使用算法:将使用的最少的页面淘汰

LRU 算法实现

设计一种数据结构,满足以下条件

一个是 put(key, val) 方法插入新的或更新已有键值对,如果缓存已满的话,要删除那个最久没用过的键值对以腾出位置插入

另一个是 get(key) 方法获取 key 对应的 val,如果 key 不存在则返回 -1

LRU 可以使用 LinkedHashMap 来实现,get 和 put 时间复杂度都为1,这个算法只有一句话,哈希表里存节点
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值