操作系统-处理器原子操作如何实现


有了进程,为什么还要有线程?

进程是资源分配的基本单位,一个进程在同一时间只能执行一件事,如果要充分利用多个cpu,就是线程的粒度更小,线程是cpu调度的基本单位。还有就是如果一个进程阻塞的话,整个进程就挂起了,对于不依赖于阻塞资源的工作,也不能执行。

进程的状态转换

image-20220201152025068

写几个关键词:进程调度,优先级,请求资源阻塞,请求资源就绪,时间片。

进程间通信方式

管道:半双工、亲缘进程通信,存在于内存里的文件

命名管道:FIFO,无关进程通信,有路径名。

消息队列:存在于内核里的消息链表,可以有格式和优先级

共享内存:映射一个存储空间,多个进程共享,最快的IPC。

信号量:实现进程之间的互斥和同步,而不是存储数据

socket

进程调度算法

先来后到:最先到达的执行完为止

时间片轮转:分时系统,雨露均沾。

短作业优先:所用时间段的优先(估计运行时间)

最短剩余时间:比上面的增加了抢占机制,就是新加入的进程就绪之后,如果比当前执行的进程预计运行时间更短,就进行抢占。

优先级调度:优先级高的进程先执行,或者在运行的时候,分配更多的时间片。

避免死锁

相同的资源请求顺序

银行家算法:看参考链接。维护need矩阵记录每个进程对每个资源的需求量,allocation矩阵记录目前的资源数目,在下次分配的,依次对于每个进程p1,p2,p3,先尝试将所有资源分配给p1,如果满足资源条件就分配,不然就下一个。

死锁检测:

  • 每个资源类中单个资源:判断进程、资源分配图中的环路。如果满足了环路等待条件,就会发生死锁。
  • 每个资源类中多个资源:找到一个既不阻塞又和其它进程因为资源请求关联的进程,他能获取所有需要的资源而继续执行,然后释放资源。重复这种方式,如果能消去所有边,就是非死锁。

分页和分段的区别

它们都是操作系统中的内存管理技术。

分段:像是分为代码段、堆、栈,最开始是为了解决进程地址空间映射到物理内存出现的堆和栈之间内部碎片问题,段的大小是不固定的,分段是由编译器完成的,还是会有外部碎片的问题。段是信息的逻辑单位,大小没有限制。

分页:程序被分为相同大小的部分,称为页。页的大小一般是512B~8KB。当程序大小小于确定的页面大小时,分页会导致内部碎片。 页是信息的物理单元。分页是由操作系统完成的。

页面置换算法

在进程运行过程中,如果要访问的物理块不再内存中,就需要从硬盘载入内存,但是此时如果内存无空闲空间,就需要算法来选择内存中要被置换的页面。

最佳置换算法:淘汰未来最远将使用的页。理想算法,无法实现

先入先出。

LRU,LFU

LRU和LFU是不同的!

LRU是最近最少使用页面置换算法(Least Recently Used),也就是首先淘汰最长时间未被使用的页面!

LFU是最近最不常用页面置换算法(Least Frequently Used),也就是淘汰一定时期内被访问次数最少的页!

比如,第二种方法的时期T为10分钟,如果每分钟进行一次调页,主存块为3,若所需页面走向为2 1 2 1 2 3 4

注意,当调页面4时会发生缺页中断

若按LRU算法,应换页面1(1页面最久未被使用) 但按LFU算法应换页面3(十分钟内,页面3只使用了一次)

可见LRU关键是看页面最后一次被使用到发生调度的时间长短,

而LFU关键是看一定时间段内页面被使用的频率!

如果页被修改变成脏页,踢出它需要写回磁盘,代价很高。硬件应该再增加一个修改位,写入页时设置它。时钟算法就可以将脏页考虑在内。

动静态链接库的理解

静态链接:编译链接的时候将需要的执行代码拷贝到调用的地方,在程序发布的时候就是一个整体,不需要链接库,就是体积会大一些。

动态链接:程序在运行的时候,操作系统将需要的动态库加载到内存里,程序运行时,去动向内存库里面执行代码,它的优点就是多个程序可以执行同一段代码。

用户态内核态

异常、中断、系统调用

CPU有一个标志字段,标志着线程的运行状态,用户态为3,内核态为0。

进程终止方式

自愿:正常退出、错误退出

严重错误:如除零。

被其他进程杀死,如kill。

守护进程、孤儿进程、僵尸进程

守护:独立于控制终端的,在后台运行的程序。

如何创建守护进程?

image-20220201214127945

  • fork创建子进程,然后父进程退出。
  • 子进程创建新会话,作用:脱离原会话、进程组、控制中断的控制,但是这样此时的进程是会话首进程,还是可以重新申请打开控制终端。
  • 再次fork(此时子进程不是会话首进程,不能打开控制终端),父进程退出。
  • chdir根目录,设置文件掩码,close不需要的文件描述符、

和nohup的区别?

&:变为后台进程,但是继承当前sessin的stdout和stderr,会在终端显示。

sighup信号:用户退出session时,会发出sighup信号,给所有子进程,子进程收到sighup之后,自动退出。

孤儿进程:子进程还在,父进程挂了,子进程就交给默认的init进程。init进程是linux内核启动的第一个用户级进程,可以实现运行级别,处理孤儿进程。

僵尸进程:子进程先退出,父进程还没退出,子进程就必须等待父进程捕获子进程的处理状态才能真正结束。设置僵尸进程的目的是维护子进程的信息,以便父进程在以后某个时候获取。

如何避免僵尸进程:signal(SIGCHLD, SIG_IGN)或者wait函数父进程阻塞等待子进程结束。

内存交换中,被换出的进程保存在哪里?

交换空间,硬盘上。

梳理一下硬件访问内存的流程:硬件从虚拟地址获取VPN(虚拟页面号),检查是否TLB命中,命中直接获取物理地址从内存中取回。不命中就根据虚拟地址的VPN和偏移量查找该页的页表项(PTE)得到物理内存地址来访问物理内存,并将其插入到TLB。目前的问题是页可能不在物理内存中,而是在交换空间中。所以需要一个在PTE中增加存在位来表示页是否在内存中。如果不在物理内存中,就会触发一个page fault来访问交换空间。

抖动你知道是什么吗?它也叫颠簸现象

页面在外存和内存之间反复切换,称为颠簸。主要原因:可用的物理内存太少,放不下频繁访问的页面。

处理器原子操作如何实现

首先,处理器会保证基本内存操作的原子性,一个处理器获取一个字节是,其他处理器不能访问这个字节的内存地址。(如正在读的时候,不包括读完了)

它可能存在的问题是,多个处理器可能从各自的缓存里读取遍历,然后分别写入内存。

总线锁

CPU总线,负责CPU与外界部件的通信。

就要保证一个cpu读写共享变量的时候,cpu2不能操作它对应的缓存。引出了总线锁。是处理器提供的一个LOCK信号,当一个处理器在总线上输出这个信号时,其他处理器的请求就被阻塞住,这个处理器可以独占使用共享内存。

它把内存和cpu之间通信锁住了,在锁定期间,其他处理器不能操作其他内存地址的数据,开销大。

缓存锁定

频繁使用的内存会缓存在处理器的三级高速缓存里,原子操作可以直接在缓存里进行。

缓存一致性机制就整体来说,是当某块CPU对缓存中的数据进行操作了之后,就通知其他CPU放弃储存在它们内部的缓存,从内存中重新读取。

MESI协议:每个缓存行维护两个状态位。

M:被修改的,只有本cpu中有缓存数据,尚未更新到内存里。需要监听试图读取该缓存行对应的内存地址的操作在操作前写回到CPU。

E:独占的,只在本cpu缓存,且数据没有修改。需要监听读取内存地址操作,,如果监听到,设置为S.

S:共享的,多个cpu都有缓存,且数据没有修改,监听无效或者独享请求,如果监听到,设置为I。

I:invalid。本cpu缓存失效

如果被操作的数据跨越了多个缓存行,就会用总线锁定,有些处理器不支持缓存锁定。

具体回看:volatile实现之缓存锁定

参考

银行家算法与死锁解除

LRU和LFU的区别

操作系统交换空间机制和策略

面试必备:Java 原子操作的实现原理

原子操作的是如何实现的

volatile实现之缓存锁定