操作系统基础总结

操作系统基础

什么是进程?

进程就是正在进行的一个过程或者任务,也就是正在运⾏的应用程序,是系统进行资源分配和调度的基本单位 各个进程之间互不⼲扰。同时进程保存着程序每⼀个时刻运⾏的状态。 进程让操作系统的并发成为了可能。

进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础

什么是线程?

线程是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位。 让⼀个线程执⾏⼀个⼦任务,这样⼀个进程就包含了多个线程,每个线程负责⼀个单独的⼦任务。

进程让操作系统的并发性成为了可能,⽽线程让进程的内部并发成为了可能。

进程和线程的区别

  • 进程单独占有⼀定的内存地址空间,所以进程间存在内存隔离,数据是分开 的,数据共享复杂但是同步简单,各个进程之间互不⼲扰;⽽线程共享的是进 程占有的内存地址空间和资源,数据共享简单,但是同步复杂
  • ⼀个进程出现问题不会影响其他进程,不影响主程序的稳定性,可靠性⾼;⼀个线程崩溃可能影响整个程序的稳定性, 可靠性较低。
  • 进程的创建和销毁不仅需要保存寄存器和 栈信息,还需要资源的分配回收以及⻚调度,开销较⼤;线程只需要保存寄存器和栈信息,开销较⼩。

什么是进程和进程表

进程就是正在执行程序的实例,比如说 Web 程序就是一个进程,shell 也是一个进程,文章编辑器 typora 也是一个进程。

操作系统负责管理所有正在运行的进程,操作系统会为每个进程分配特定的时间来占用 CPU,操作系统还会为每个进程分配特定的资源。

操作系统为了跟踪每个进程的活动状态,维护了一个进程表。在进程表的内部,记录了每个进程的状态以及每个进程使用的资源等。

什么是同步和互斥

显然,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。也就是说互斥是两个任务之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行,而同步也是不能同时运行,但他是必须要安照某种次序来运行相应的线程(也是一种互斥)!因此互斥具有唯一性和排它性,但互斥并不限制任务的运行顺序,即任务是无序的,而同步的任务之间则有顺序关系。”

什么是多进程?

多进程就是指计算机同时执行多个进程,一般是同时运行多个软件。

多进程优点:

1、每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;

2、 通过增加CPU,就可以容易扩充性能;

多进程缺点:

1、逻辑控制复杂,需要和主程序交互;

2、需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算 多进程调度开销比较大

什么是多线程?

多线程就是指一个进程中同时有多个线程正在执行

多线程的优点:

3、所有线程可以直接共享内存和变量等;

4、线程方式消耗的总资源比进程方式好。

​ 5.占用大量处理时间的任务使用多线程可以提高CPU利用率

​ 6.多线程技术使程序的响应速度更快

多线程缺点:

2、线程之间的同步和加锁控制比较麻烦

3、一个线程的崩溃可能影响到整个程序的稳定性

5、线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU。

多线程和多进程的区别?

  • 多进程中占用内存多,切换复杂,CPU利用率低;多线程中占用内存少,切换简单,CPU利用率高。
  • 多进程中编程简单,调试简单;多线程中编程复杂,调试复杂。
  • 多进程中进程间不会相互影响;多线程中一个线程挂掉将导致整个进程挂掉。”
  • 进程单独占有⼀定的内存地址空间,所以进程间存在内存隔离,数据是分开 的,数据共享复杂但是同步简单,各个进程之间互不⼲扰;⽽线程共享的是进 程占有的内存地址空间和资源,数据共享简单,但是同步复杂

多进程和多线程应该如何选择

这么解释问题吧:

1。单进程单线程:一个人在一个桌子上吃菜。

2。单进程多线程:多个人在同一个桌子上一起吃菜。

3。多进程单线程:多个人每个人在自己的桌子上吃菜。

多线程的问题是多个人同时吃一道菜的时候容易发生争抢,例如两个人同时夹一个菜,一个人刚伸出筷子,结果伸到的时候已经被夹走菜了。。。此时就必须等一个人夹一口之后,在还给另外一个人夹菜,也就是说资源共享就会发生冲突争抢。

  • 对于 Windows 系统来说,【开桌子】的开销很大,因此 Windows 鼓励大家在一个桌子上吃菜。因此 Windows 多线程学习重点是要大量面对资源争抢与同步方面的问题。
  • 对于 Linux 系统来说,【开桌子】的开销很小,因此 Linux 鼓励大家尽量每个人都开自己的桌子吃菜。这带来新的问题是:坐在两张不同的桌子上,说话不方便。因此,Linux 下的学习重点大家要学习进程间通讯的方法。

开桌子的意思是指创建进程。开销这里主要指的是时间开销。

进程间的通讯方式

  • 管道(Pipe):管道可用于具有亲缘关系进程间的通信

  • 有名管道(named pipe):有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;

  • 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);

  • 消息(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

  • 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。

  • 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

  • 套接字(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

线程间的通讯方式

1.锁与同步

​ lock和synchronized

2.等待/唤醒

Java多线程的等待/通知机制是基于 Object 类的 wait() ⽅法和 notify() ,notifyAll() ⽅法来实现的。

3.信号量

volatile

4.管道

5.其他

  • join()
  • sleep()
  • ThreadLocal

线程同步的方式

  • 互斥量 Synchronized/Lock:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问
  • 信号量 Semphare:它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量
  • 事件(信号),Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作

进程同步的方式

进程同步概念

1、临界资源:在系统中有许多硬件和软件资源,如打印机、公共变量等,这些资源在一段时间内只允许一个进程访问或者使用,这种资源称之为临界资源。

2、临界区:作为临界资源,不论硬件临界资源还是软件临界资源,多个并发的进程都必须互斥地访问或者使用,这时候,把每个进程访问临界资源的那段代码称为临界区。

3、进程同步:进程同步是指多个相关进程在执行次序上的协调这些进程相互合作,在一些关键点上需要相互等待或者通信。通过临界区可以协调进程间的合作关系,这就是同步。

4、进程互斥:进程互斥是指当一个程序进入临界区使用临界资源时,另一个进程必须等待。当占用临界资源的进程退出临界区后,另一个进程才被允许使用临界资源。通过临界区可以协调程序间资源共享关系,就是进程互斥。进程互斥是同步的一种特例。

进程同步机制遵循的原则

  • 空闲让进:当无进程处于临界区时,临界区处于空闲状态,可以允许一个请求进入临界区的进程进入临界区,有效地使用临界资源。

  • 忙则等待:当有进程进入自己的临界区时,意味着临界资源正在被访问,因而其他的试图进入临界区的进程必须等待,以保证进程互斥地使用临界资源。

  • 有限等待:对要求访问临界资源的进程,必须保证该进程在有效的时间内进入自己的临界区,以免出现死等的情况。

  • 让权等待:当进程不能进入自己的临界区时,应该立即释放处理器,以免陷入“忙等”

几种进程同步机制
原子操作、信号量机制、自旋锁管程、会合、分布式系统

  • 信号量
  • 用P、V原语 p++ v–
  • 生产者-消费者模型

什么是上下文切换

上下⽂切换(有时也称做进程切换或任务切换)是指 CPU 从⼀个进程(或线程) 切换到另⼀个进程(或线程)。上下⽂是指某⼀时间点 CPU 寄存器和程序计数器的内容。

寄存器是cpu内部的少量的速度很快的闪存,通常存储和访问计算过程的中 间值提⾼计算机程序的运⾏速度。

程序计数器是⼀个专⽤的寄存器,⽤于表明指令序列中 CPU 正在执⾏的位 置,存的值为正在执⾏的指令的位置或者下⼀个将要被执⾏的指令的位置,

具体实现依赖于特定的系统。

举例说明 线程A - B

1.先挂起线程A,将其在cpu中的状态保存在内存中。

2.在内存中检索下⼀个线程B的上下⽂并将其在 CPU 的寄存器中恢复,执⾏B 线程。

3.当B执⾏完,根据程序计数器中指向的位置恢复线程A。

CPU通过为每个线程分配CPU时间⽚来实现多线程机制。CPU通过时间⽚分配算 法来循环执⾏任务,当前任务执⾏⼀个时间⽚后会切换到下⼀个任务。

但是,在切换前会保存上⼀个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是⼀次上下⽂切换。

上下文切换的基本原理就是当发生任务切换时, 保存当前任务的寄存器状态到内存中, 将下一个即将要切换过来的任务的寄存器状态恢复到当前CPU寄存器中, 使其继续执行, 同一时刻只允许一个任务独享寄存器。

上下⽂切换通常是计算密集型的,意味着此操作会消耗⼤量的 CPU 时间,故线程 也不是越多越好。

系统调用的上下文切换

从用户态到内核态的转变,需要通过系统调用来完成。比如,当我们查看文件内容时,就需要多次系统调用来完成:首先调用 open() 打开文件,然后调用 read() 读取文件内容,并调用 write() 将内容写到标准输出,最后再调用 close() 关闭文件。

系统调用会将CPU从用户态切换到核心态,以便 CPU 访问受到保护的内核内存。

系统调用的过程会发生 CPU 上下文的切换,CPU 寄存器里原来用户态的指令位置,需要先保存起来。接着,为了执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。

而系统调用结束后,CPU 寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。所以,一次系统调用的过程,其实是发生了两次 CPU 上下文切换。

注意:系统调用过程中,并不会涉及到虚拟内存等进程用户态的资源,也不会切换进程。

系统调用过程通常称为特权模式切换,而不是进程上下文切换。

进程上下文切换

进程上下文切换跟系统调用又有什么区别呢?

首先,进程是由内核来管理和调度的,进程的切换只能发生在内核态。所以,进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。

因此,进程的上下文切换就比系统调用时多了一步:

在发生进程上下文切换前会发生软中断进入内核,进程的切换只能发生在内核态,首先保存当前进程的虚拟内存、用户栈等用户空间资源和 CPU 寄存器,以及保存当前进程的内核状态,加载下一个进程的内核态以及这个进程的虚拟内存和用户栈,加载完成后就可以切换回用户态下执行下一个进程。

根据 Tsuna 的测试报告,每次上下文切换都需要几十纳秒到数微秒的 CPU 时间,在进程上下文切换次数较多的情况下,这个时间对于CPU来说是相当可观的,会大大缩短CPU真正用于运行进程的时间。

什么时候会切换进程上下文?
只有在进程调度的时候,才需要切换上下文。

Linux 为每个 CPU 都维护了一个就绪队列,将活跃进程(即正在运行和正在等待 CPU 的进程)按照优先级和等待 CPU 的时间排序,然后选择最需要 CPU 的进程,也就是优先级最高和等待 CPU 时间最长的进程来运行。

新进程在什么时候才会被调度到 CPU 上运行呢?
	1. 运行中的进程执行完终止了,CPU 会释放出来,新的基础进程就可以被调度到CPU上运行了。
	2. 运行中的进程时间片用完,进程被挂起 
	3. 运行中的进程资源不足,进程被挂起 
	4. 运行中的进程执行Sleep方法主动挂起
	5. 新进程优先级更高,运行中的进程被挂起 
	6. 发生硬件中断,运行中的进程会被中断挂起,转而执行内核中的中断服务程序。


线程上下文切换

线程是调度的基本单位,而进程则是资源拥有的基本单位。

所谓内核中的任务调度,实际上的调度对象是线程;而进程只是给线程提供了虚拟内存、全局变量等资源。

当进程只有一个线程时,可以认为进程就等于线程,当进程拥有多个线程时,这些线程会共享进程的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。

线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。

线程的上下文切换其实就可以分为两种情况:

  • 两个线程属于不同进程,因为资源不共享,切换过程和进程上线文切换一样
  • 两个线程属于同一个进程,只需要切换线程的私有数据、寄存器等不共享的数据

如何减少上下文切换

  • 无锁并发编程:用一些办法来避免使用锁,如将数据的ID按照Hash取模分段,不同线程处理不同段数据。

  • CAS算法:Java的Atomic包使用CAS算法来更新数据,而不需加锁

  • 使用最少线程:避免创建不需要的线程

  • 协程:在单线程里实现多任务的调度,维持多任务间的切换。

    协程:相较于线程更加轻量级,不被操作系统内核管理,完全是由程序所控制。

    是一种比线程更加轻量级的存在。 正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。

    协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。

    1、性能提升,不会像线程切换那样消耗资源。

    2、不需要多线程锁的机制,因为只有一个线程,不存在同时写变量冲突。因此在协程中控制共享资源不加锁,只需判断状态,因此执行效率比线程高很多。

    分时复用系统 就是把多个工作的每个工作都分成多个时间段,然后各个工作的各个时间段交叉使用,这样就好像多个工作同时运行。

线程的私有资源和共享资源

线程共享的环境包括:虚拟内存和全局变量进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。

私有资源 :线程ID 寄存器组的值 线程的堆栈

进程间状态模型

进程的三态模型

img

进程的五态模型

在三态模型的基础上,增加了两个状态,即 新建 和 终止 状态。

img

  • 新建态:进程的新建态就是进程刚创建出来的时候
    创建进程需要两个步骤:即为新进程分配所需要的资源和空间,设置进程为就绪态,并等待调度执行。
  • 终止态:进程的终止态就是指进程执行完毕,到达结束点,或者因为错误而不得不中止进程。

终止一个进程需要两个步骤:

  1. 先等待操作系统或相关的进程进行善后处理。

  2. 然后回收占用的资源并被系统删除。

进程调度策略

先来先服务调度算法FCFS

如果早就绪的进程排在就绪队列的前面,迟就绪的进程排在就绪队列的后面,那么先来先服务(FCFS: first come first service)总是把当前处于就绪队列之首的那个进程调度到运行状态。

短作业(进程)优先调度算法SJ§F

对预计执行时间短的作业(进程)优先分派处理机.通常后来的短作业不抢先正在执行的作业.

时间片轮转法RR

让每个进程在就绪队列中的等待时间与享受服务的时间成正比例。每当执行进程调度时,进程调度程序总是选出就绪队列的对首进程,让它在CPU上运行一个时间片的时间。当进程用完分给它的时间片后,调度程序便停止该进程的运行,并把它放入就绪队列的末尾。

优先级调度算法(HPF)

在进程等待队列中选择优先级最高的来执行。

多级反馈队列算法

设置多个就绪队列,分别赋予不同的优先级,如逐级降低,队列1的优先级最高。每个队列执行时间片的长度也不同,规定优先级越低则时间片越长,如逐级加倍。

新进程进入内存后,先投入队列1的末尾,按FCFS算法调度;若按队列1一个时间片未能执行完,则降低投入到队列2的末尾,同样按FCFS算法调度;如此下去,降低到最后的队列,则按“时间片轮转”算法调度直到完成。

仅当较高优先级的队列为空,才调度较低优先级的队列中的进程执行。如果进程执行时有新进程进入较高优先级的队列,则抢先执行新进程,并把被抢先的进程投入原队列的末尾。

高响应比优先调度算法

根据“响应比=(进程执行时间+进程等待时间)/ 进程执行时间”这个公式得到的响应比来进行调度。

线程调度策略

Linux下的线程调度策略:

1、SCHED_OTHER:普通任务调度策略。

2、SCHED_FIFO:实时任务调度策略,先到先服务。一旦占用cpu则一直运行,直到有更高优先级任务到达或自己放弃。

3、SCHED_RR:实时任务调度策略,时间片轮转。当任务的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾

Java的线程调度策略:

JVM采用抢占式调度模型。

守护、用户线程

用户线程:运行在前台,执行具体任务,如程序的主线程,连接网络的子线程等都是用户线程。

守护线程:运行在后台,为其他前台线程服务。一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作。(如垃圾回收线程)

查看线程运行的方法

Windows
任务管理器查看or杀死

打开cmd命令行窗口,使用tasklist + taskkill命令杀死进程。具体如下:

jps
taskkill /F /PID 33736 (F是强烈杀死)

linux

ps -fe 查看所有进程
ID、当前状态、启动时间、占用CPU时间、CPU占用百分比、内存占用百分比、占用虚拟内存的大小、占用常驻内存的大小
ps -fT -p <PID> 查看某个进程(PID)的所有线程
kill杀死进程
top 按大写 H 切换是否显示线程
top -H -p <PID>查看某个进程(PID)的所有线程

Java
注意:需要到对应的java\lib下面运行

jsp查看所有Java命令

jstack<PID>查看某个Java进程(PID)的所有线程状态

jconsole查看某个Java进程中线程的运行情况(图形界面)

原语

计算机进程的控制通常由原语完成。所谓原语,**一般是指由若干条指令组成的程序段,用来实现某个特定功能,在执行过程中不可被中断。**在操作系统中,某些被进程调用的操作,如队列操作、对信号量的操作、检查启动外设操作等,一旦开始执行,就不能被中断,否则就会出现操作错误,造成系统混乱。所以,这些操作都要用原语来实现 原语是操作系统核心(不是由进程,而是由一组程序模块组成)的一个组成部分,并且常驻内存,通常在管态下执行。原语一旦开始执行,就要连续执行完,不允许中断。

解释一下什么是操作系统

操作系统是管理硬件和软件的一种应用程序。操作系统是运行在计算机上最重要的一种软件,它管理计算机的资源和进程以及所有的硬件和软件。它为计算机硬件和软件提供了一种中间层,使应用软件和硬件进行分离,让我们无需关注硬件的实现,把关注点更多放在软件应用上。

img

通常情况下,计算机上会运行着许多应用程序,它们都需要对内存和 CPU 进行交互,操作系统的目的就是为了保证这些访问和交互能够准确无误的进行。

操作系统的主要功能

一般来说,现代操作系统主要提供下面几种功能

  • 进程管理: 进程管理的主要作用就是任务调度,在单核处理器下,操作系统会为每个进程分配一个任务,进程管理的工作十分简单;而在多核处理器下,操作系统除了要为进程分配任务外,还要解决处理器的调度、分配和回收等问题
  • 内存管理:内存管理主要是操作系统负责管理内存的分配、回收,在进程需要时分配内存以及在进程完成时回收内存,协调内存资源,通过合理的页面置换算法进行页面的换入换出
  • 设备管理:根据确定的设备分配原则对设备进行分配,使设备与主机能够并行工作,为用户提供良好的设备使用界面。
  • 文件管理:有效地管理文件的存储空间,合理地组织和管理文件系统,为文件访问和文件保护提供更有效的方法及手段。
  • 提供用户接口:操作系统提供了访问应用程序和硬件的接口,使用户能够通过应用程序发起系统调用从而操纵硬件,实现想要的功能。

为什么 Linux 系统下的应用程序不能直接在 Windows 下运行

这是一个老生常谈的问题了,在这里给出具体的回答。

其中一点是因为 Linux 系统和 Windows 系统的格式不同格式就是协议,就是在固定位置有意义的数据。Linux 下的可执行程序文件格式是 elf,可以使用 readelf 命令查看 elf 文件头。

img

而 Windows 下的可执行程序是 PE 格式,它是一种可移植的可执行文件。

还有一点是因为 Linux 系统和 Windows 系统的 API 不同,这个 API 指的就是操作系统的 API,Linux 中的 API 被称为系统调用,是通过int 0x80这个软中断实现的。而 Windows 中的 API 是放在动态链接库文件中的,也就是 Windows 开发人员所说的 DLL ,这是一个库,里面包含代码和数据。Linux 中的可执行程序获得系统资源的方法和 Windows 不一样,所以显然是不能在 Windows 中运行的。

系统调用

由操作系统实现提供的所有系统调用所构成的集合即程序接口或应用编程接口(Application Programming Interface,API)。是应用程序同系统之间的接口。内核提供一系列具备预定功能的多内核函数,通过一组称为系统调用(system call)的接口呈现给用户。

系统调用是封装了多个内核函数的一组程序接口

操作系统为程序的运行提供服务,典型的服务包括执行新程序、打开文件、读写文件、分配内存、获取当前时间等。操作系统通过一组数量有限并且定义良好的入口点来“暴露”其提供的服务,这些入口点就是系统调用。

为什么称为陷入内核

如果把软件结构进行分层说明的话,应该是这个样子的,最外层是应用程序,里面是操作系统内核。

img

应用程序处于特权级 3,操作系统内核处于特权级 0 。如果用户程序想要访问操作系统资源时,会发起系统调用,陷入内核,这样 CPU 就进入了内核态,执行内核代码。至于为什么是陷入,我们看图,内核是一个凹陷的构造,有陷下去的感觉,所以称为陷入。

什么是用户态和内核态

用户态和内核态是操作系统的两种运行状态。

  • 内核态处于内核态的 CPU 可以访问任意的数据包括外围设备,比如网卡、硬盘等,处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况,一般处于特权级 0 的状态我们称之为内核态。
  • 用户态处于用户态的 CPU 只能受限的访问内存并且不允许访问外围设备,用户态下的 CPU 不允许独占,也就是说 CPU 能够被其他程序获取。

那么为什么要有用户态和内核态呢?

这个主要是访问能力的限制的考量,计算机中有一些比较危险的操作,比如设置时钟、内存清理,这些都需要在内核态下完成,如果随意进行这些操作,那你的系统得崩溃多少次。

什么情况下会发生从用户态向内核态切换

1、发生系统调用时

这是处于用户态的进程主动请求切换到内核态的一种方式。用户态的进程通过系统调用申请使用操作系统提供的系统调用服务例程来处理任务。而系统调用的机制,其核心仍是使用了操作系统为用户特别开发的一个中断机制来实现的,即软中断

2、产生异常时

当CPU执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行的进程切换到处理此异常的内核相关的程序中,也就是转到了内核态,如缺页异常

3、外设产生中断时

当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作的完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
可以看到上述三种由用户态切换到内核态的情况中,只有系统调用是进程主动请求发生切换的,中断和异常都是被动的。

用户态和内核态是如何切换的?

所有的用户进程都是运行在用户态的,但是我们上面也说了,用户程序的访问能力有限,一些比较重要的比如从硬盘读取数据,从键盘获取数据的操作则是内核态才能做的事情,而这些数据却又对用户程序来说非常重要。所以就涉及到两种模式下的转换,即用户态 -> 内核态 -> 用户态,而唯一能够做这些操作的只有 系统调用,而能够执行系统调用的就只有 操作系统。

一般用户态 -> 内核态的转换我们都称之为 trap 进内核,也被称之为 陷阱指令(trap instruction)。那么在Linux下,这个异常具体就是调用int $0x80的汇编指令,这条汇编指令将产生向量为0x80的编程异常。先通过软件中断调用了0x80的这个编程异常,这个编程异常对应的是中断描述符表IDT中的第128项——也就是对应的系统门描述符。门描述符中含有一个预设的内核空间地址,它指向了系统调用处理程序:system_call(),然后从eax寄存器中得到数据,然后再去系统调用表中寻找相应服务例程了。除了需要传递系统调用号以外,许多系统调用还需要传递一些参数到内核,执行系统调用处理程序。

他们的工作流程如下:

img

在这里插入图片描述

什么是软中断和硬中断

  • 硬中断是由硬件产生的,比如,像磁盘,网卡,键盘,时钟等。每个设备或设备集都有它自己的IRQ(中断请求)。基于IRQ,CPU可以将相应的请求分发到对应的硬件驱动上(注:硬件驱动通常是内核中的一个子程序,而不是一个独立的进程)。

    处理中断的驱动是需要运行在CPU上的,因此,当中断产生的时候,CPU会中断当前正在运行的任务,来处理中断。在有多核心的系统上,一个中断通常只能中断一颗CPU(也有一种特殊的情况,就是在大型主机上是有硬件通道的,它可以在没有主CPU的支持下,可以同时处理多个中断。)。

  • 软中断的处理非常像硬中断。然而,它们仅仅是由当前正在运行的进程所产生的

    通常,软中断是一些对I/O的请求。这些请求会调用内核中可以调度I/O发生的程序。对于某些设备,I/O请求需要被立即处理,而磁盘I/O请求通常可以排队并且可以稍后处理。根据I/O模型的不同,进程或许会被挂起直到I/O完成,此时内核调度器就会选择另一个进程去运行。I/O可以在进程之间产生并且调度过程通常和磁盘I/O的方式是相同。

    软中断并不会直接中断CPU。也只有当前正在运行的代码(或进程)才会产生软中断。这种中断是一种需要内核为正在运行的进程去做一些事情(通常为I/O)的请求。有一个特殊的软中断是Yield调用,它的作用是请求内核调度器去查看是否有一些其他的进程可以运行。

软中断所经过的操作流程是比硬中断的少吗?换句话说,对于软中断就是:进程 ->内核中的设备驱动程序;对于硬中断:硬件->CPU->内核中的设备驱动程序?

答:是的,软中断比硬中断少了一个硬件发送信号的步骤。产生软中断的进程一定是当前正在运行的进程,因此它们不会中断CPU。但是它们会中断调用代码的流程。

如果硬件需要CPU去做一些事情,那么这个硬件会使CPU中断当前正在运行的代码。而后CPU会将当前正在运行进程的当前状态放到堆栈(stack)中,以至于之后可以返回继续运行。这种中断可以停止一个正在运行的进程;可以停止正处理另一个中断的内核代码;或者可以停止空闲进程。

什么是内核

**在计算机中,内核是一个计算机程序,它是操作系统的核心,可以控制操作系统中所有的内容。**内核通常是在 boot loader 装载程序之前加载的第一个程序。

这里还需要了解一下什么是 boot loader。

boot loader 又被称为引导加载程序,能够将计算机的操作系统放入内存中。在电源通电或者计算机重启时,BIOS 会执行一些初始测试,然后将控制权转移到引导加载程序所在的主引导记录(MBR) 。

什么是按需分页

在操作系统中,进程是以页为单位加载到内存中的,按需分页是一种虚拟内存的管理方式在使用请求分页的系统中,只有在尝试访问页面所在的磁盘并且该页面尚未在内存中时也就发生了缺页异常,操作系统才会将磁盘页面复制到内存中。

分页和分段有什么区别?

段式存储管理

用户视角。

将程序的地址空间划分为若干段(segment),如代码段,数据段,堆栈段;

这样每个进程有一个二维地址空间,相互独立,互不干扰。

段式管理的优点是:没有内碎片(因为段大小可变,改变段大小来消除内碎片)。但段换入换出时,会产生外碎片(比如4k的段换5k的段,会产生1k的外碎片)

在这里插入图片描述

页式存储管理

存储器视角。

将程序的逻辑地址划分为固定大小的页(page),而物理内存划分为同样大小的帧,程序加载时,可以将任意一页放入内存中任意一个帧,这些帧不必连续,从而实现了离散分离。

页式存储管理的优点是:没有外碎片(因为页的大小固定),但会产生内碎片(一个页可能填充不满)。

在这里插入图片描述

二者区别

  • 目的不同:分页是系统管理的需要,是信息的物理单位;分段是满足用户的需要,它是信息的逻辑单位,它含有一组其意义相对完整的信息;
  • 大小不同:页的大小固定且由系统决定,而段的长度却不固定,由其所完成的功能决定;
  • 地址空间不同: 段向用户提供二维地址空间;页向用户提供的是一维地址空间;
  • 信息共享:段是信息的逻辑单位,便于存储保护和信息的共享,页的保护和共享受到限制;
  • 内存碎片:页式存储管理的优点是没有外碎片(因为页的大小固定),但会产生内碎片(一个页可能填充不满);而段式管理的优点是没有内碎片(因为段大小可变,改变段大小来消除内碎片)。但段换入换出时,会产生外碎片(比如4k的段换5k的段,会产生1k的外碎片)。

物理地址 虚拟地址

物理地址:物理地址就是内存中真正的地址

虚拟地址:利用一种间接的地址访问方法访问物理内存。按照这种方法,程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址(Virtual Addressing),然后由操作系统将这个虚拟地址映射到适当的物理内存地址上。 这样,只要操作系统处理好虚拟地址到物理内存地址的映射,就可以保证不同的程序最终访问的内存地址位于不同的区域,彼此没有重叠,就可以达到内存地址空间隔离的效果。

什么是虚拟内存

**虚拟内存是一种内存分配方案,是一项可以用来辅助内存分配的机制。我们知道,应用程序是按页装载进内存中的。但并不是所有的页都会装载到内存中,计算机中的硬件和软件会将数据从 RAM 临时传输到磁盘中来弥补内存的不足。**如果没有虚拟内存的话,一旦你将计算机内存填满后,计算机会对你说

对不起,您无法再加载任何应用程序,请关闭另一个应用程序以加载新的应用程序。对于虚拟内存,计算机可以执行操作是查看内存中最近未使用过的区域,然后将其复制到硬盘上。虚拟内存通过复制技术实现了 装这么多程序 的资本。复制是自动进行的,你无法感知到它的存在。

基本思想:

每个进程拥有独立的地址空间,这个空间被分为大小相等的多个块,称为页(Page),每个页都是一段连续的地址。

这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。

当程序引用到一部分在物理内存中的地址空间时,由硬件立刻进行必要的映射。

当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的命令。

这样,对于进程而言,逻辑上似乎有很大的内存空间,实际上其中一部分对应物理内存上的一块(称为帧,通常页和帧大小相等),还有一些没加载在内存中的对应在硬盘上

注意

1、请求分页系统、请求分段系统和请求段页式系统都是针对虚拟内存的,通过请求实现内存与外存的信息置换。

2、如果虚拟内存的页并不存在于物理内存中(如图5的3,4),会产生缺页中断,从磁盘中取得缺的页放入内存,如果内存已满,还会根据某种算法将磁盘中的页换出。

虚拟内存的实现方式

虚拟内存中,允许将一个作业分多次调入内存。釆用连续分配方式时,会使相当一部分内存空间都处于暂时或永久的空闲状态,造成内存资源的严重浪费,而且也无法从逻辑上扩大内存容量。因此,虚拟内存的实需要建立在离散分配的内存管理方式的基础上。虚拟内存的实现有以下三种方式:

  • 请求分页存储管理。
  • 请求分段存储管理。
  • 请求段页式存储管理。

不管哪种方式,都需要有一定的硬件支持。一般需要的支持有以下几个方面:

  • 一定容量的内存和外存。
  • 页表机制(或段表机制),作为主要的数据结构。
  • 中断机构,当用户程序要访问的部分尚未调入内存,则产生中断。
  • 地址变换机构,逻辑地址到物理地址的变换。

页面置换算法

FIFO 先进先出算法:在操作系统中经常被用到,比如作业调度(主要实现简单,很容易想到)
LRU(Least recently use)最近最少使用算法:根据使用时间到现在的长短来判断;
OPT(Optimal replacement)最优置换算法:理论的最优,理论;就是要保证置换出去的是不再被使用的页,或者是在实际内存中最晚使用的算法。该算法淘汰在访问串中将来再也不出现的或是在离当前最远的位置上出现的页。这样,淘汰掉该页将不会造成因需要访问该页又立即把它调入的现象。
它是一种理想化的算法,性能最好,但在实际上难于实现。因为要确定哪一个页面是未来最长时间内不再被访问的,实现起来是很难估计的,所以该算法通常用来评价其它算法。

虚拟内存的应用与优点

适合多道程序设计系统,许多程序的片段同时保存在内存中。

当一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用。

优点

1、在内存中可以保留多个进程,系统并发度提高
2、解除了用户与内存之间的紧密约束,进程可以比内存的全部空间还大

颠簸

本质:频繁的页调度行为

具体来讲:进程发生缺页中断,这时,必须置换某一页。然而,其他所有的页都在使用,它置换一个页,但又立刻再次需要这个页。因此,会不断产生缺页中断,导致整个系统的效率急剧下降,这种现象称为颠簸(抖动)。

解决策略

  • 如果是因为页面替换策略失误,可以修改替换算法来解决这个问题;
  • 如果是因为运行的程序太多,造成程序无法同时将所有频繁访问的页面调入内存,则要降低多道程序的数量
  • 否则,还剩下两个办法:终止该进程或增加物理内存容量

局部性原理

(1). 时间上的局部性:最近被访问的页在不久的将来还会被访问;
(2). 空间上的局部性:内存中被访问的页周围的页也很可能被访问。

孤儿进程和僵尸进程有什么区别?

  • 孤儿进程就是说一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被 init 进程(进程ID为1的进程)所收养,并由 init 进程对它们完成状态收集工作。因为孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。
  • 僵尸进程就是一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait() 或 waitpid() 获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用 wait() 或 waitpid(),那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。僵尸进程通过 ps 命令显示出来的状态为 Z。

系统所能使用的进程号是有限的,如果产生大量僵尸进程,可能会因为没有可用的进程号而导致系统不能产生新的进程。如果要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时僵尸进程就会变成孤儿进程,从而被 init 进程所收养,这样 init 进程就会释放所有的僵尸进程所占有的资源,从而结束僵尸进程。

什么是死锁?

在两个或者多个并发进程中,如果每个进程持有某种资源而又等待其它进程释放它或它们现在保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁。通俗的讲,就是两个或多个进程无限期的阻塞、相互等待的一种状态。

死锁产生的必要条件

  • 互斥:至少有一个资源必须属于非共享模式,即一次只能被一个进程使用;若其他申请使用该资源,那么申请进程必须等到该资源被释放为止;
  • 占有并等待:一个进程必须占有至少一个资源,并等待另一个资源,而该资源为其他进程所占有;
  • 非抢占:进程不能被抢占,即资源只能被进程在完成任务后自愿释放
  • 循环等待:若干进程之间形成一种头尾相接的环形等待资源关系

死锁的处理

解决死锁的基本方法主要有 预防死锁、避免死锁、检测死锁、解除死锁 等。

死锁预防

死锁预防的基本思想是 只要确保死锁发生的四个必要条件中至少有一个不成立,就能预防死锁的发生

  • 打破互斥条件:允许进程同时访问某些资源。但是,有些资源是不能被多个进程所共享的,这是由资源本身属性所决定的,因此,这种办法通常并无实用价值。
  • 打破占有并等待条件:可以实行资源预先分配策略(进程在运行前一次性向系统申请它所需要的全部资源,若所需全部资源得不到满足,则不分配任何资源,此进程暂不运行;只有当系统能满足当前进程所需的全部资源时,才一次性将所申请资源全部分配给该线程)或者只允许进程在没有占用资源时才可以申请资源(一个进程可申请一些资源并使用它们,但是在当前进程申请更多资源之前,它必须全部释放当前所占有的资源)。但是这种策略也存在一些缺点:在很多情况下,无法预知一个进程执行前所需的全部资源,因为进程是动态执行的,不可预知的;同时,会降低资源利用率,导致降低了进程的并发性。
  • 打破非抢占条件:允许进程强行从占有者哪里夺取某些资源。也就是说,但一个进程占有了一部分资源,在其申请新的资源且得不到满足时,它必须释放所有占有的资源以便让其它线程使用。这种预防死锁的方式实现起来困难,会降低系统性能。
  • 打破循环等待条件:实行资源有序分配策略。对所有资源排序编号,所有进程对资源的请求必须严格按资源序号递增的顺序提出,即只有占用了小号资源才能申请大号资源,这样就不回产生环路,预防死锁的发生。也就是:设置超时锁。利用ReentrantLock的tryLock解决哲学家就餐问题。

死锁避免

死锁避免的基本思想是动态地检测资源分配状态,以确保循环等待条件不成立,从而确保系统处于安全状态。

所谓安全状态是指:如果系统能按某个顺序为每个进程分配资源(不超过其最大值),那么系统状态是安全的,换句话说就是,如果存在一个安全序列,那么系统处于安全状态。

资源分配图算法和银行家算法是两种经典的死锁避免的算法,其可以确保系统始终处于安全状态。

其中,资源分配图算法应用场景为每种资源类型只有一个实例(申请边,分配边,需求边,不形成环才允许分配),而银行家算法应用于每种资源类型可以有多个实例的场景。

死锁解除

1. 进程终止

所谓进程终止是指简单地终止一个或多个进程以打破循环等待,包括两种方式:终止所有死锁进程和一次只终止一个进程直到取消死锁循环为止;

2. 资源抢占

指从一个或多个死锁进程那里抢占一个或多个资源,此时必须考虑三个问题:
  (I). 选择一个牺牲品
  (II). 回滚:回滚到安全状态
  (III). 饥饿(在代价因素中加上回滚次数,回滚的越多则越不可能继续被作为牺牲品,避免一个进程总是被回滚)

通过回滚进行恢复
如果系统设计者和机器操作员知道有可能发生死锁,那么就可以定期检查流程。进程的检测点意味着进程的状态可以被写入到文件以便后面进行恢复。检测点不仅包含存储映像(memory image),还包含资源状态(resource state)。一种更有效的解决方式是不要覆盖原有的检测点,而是每出现一个检测点都要把它写入到文件中,这样当进程执行时,就会有一系列的检查点文件被累积起来。

为了进行恢复,要从上一个较早的检查点上开始,这样所需要资源的进程会回滚到上一个时间点,在这个时间点上,死锁进程还没有获取所需要的资源,可以在此时对其进行资源分配。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值