计算机操作系统OS,学习

计算机操作系统,学习资源

计算机操作系统,知识点概述

《现代os4》
第2章讨论进程与线程,包括它们的性质以及它们之间如何通信。这一章还给出了大量关于进程间如何通信的例子以及如何避免某些错误。
第3章具体讨论地址空间以及内存管理,讨论虚拟内存等重要课题,以及相关的概念,如页处理和分段等。
第4章里,我们会讨论有关文件系统的所有重要内容。在某种程度上,用户看到的是大量文件系统。我们将研究文件系统接口和文件系统的实现。输入/输出是第5章的内容。这一章介绍设备独立性和设备依赖性的概念,将以若干重要的设备(包括磁盘、键盘以及显示设备)为例进行讲解。
第6章讨论死锁。在这一章中我们概要地说明什么是死锁,还讨论避免死锁的方法。
到此,我们完成了对单CPU操作系统基本原理的学习。不过,还有更多的高级内容要叙述。
在第7章里,我们将考察虚拟化,其中既会讨论原则,又将详细讨论一些现存的虚拟化方案。另一个高级课题是多处理机系统,包括多处理器、并行计算机以及分布式系统。这些内容放在第8章中讨论。

操作系统,定义】
我们可以给操作系统下定义了:
操作系统是一个软件系统;
操作系统使计算机变得好用(将人类从繁琐、复杂的对机器掌控的任务中解脱);
操作系统使计算机运作变得有序(操作系统掌控计算机上所有的事情)。
总结起来就是:操作系统是掌控计算机上所有事情的软件系统。
操作系统是一个系统程序,即为别的程序提供服务的程序。

操作系统具有两种功能:为用户程序提供抽象和管理计算机资源。

所有操作系统构建所依赖的基本概念是进程、存储管理、I/O管理、文件管理和安全。

操作系统是一种运行在内核态的软件——尽管这个说法并不总是符合事实:部分原因是操作系统有两个基本上独立的任务,即为应用程序员(实际上是应用程序)提供-个资源集的清晰抽象,并管理这些硬件资源,而不仅仅是一堆硬件;另外,还取决于从什么角度看待操作系统。

在这里插入图片描述

从这个定义可以引申出操作系统的功能:
替用户及其应用管理计算机上的软硬件资源。保证计算机资源的公平竞争和使用。防止对计算机资源的非法侵占和使用。保证操作系统自身正常运转。

CPU管理,即如何分配CPU给不同应用和用户。
内存管理:即如何分配内存给不同应用和用户。
外存管理:即如何分配外存(磁盘)给不同应用和用户。
I/O管理:即如何分配输入输出设备给应用和用户。

为了区分不同的程序的不同权限,人们发明了内核态和用户态的概念。
内核态就是拥有资源多的状态,或者说访问资源多的状态,称为特权态。相对来说,用户态就是非特权态,在此种状态下访问的资源将受到限制。
内核态的程序可以访问计算机的所有资源

一个程序到底应该运行在内核态还是用户态则取决于其对资源和效率的需求。
内核态是特权态,而用户态是普通态。

一般来说,如果一个程序能够运行于用户态,就应该让它运行在用户态。只在迫不得已的情况下,才让程序运行于内核态。
凡是牵扯到计算机本体根本运行的事情都应该在内核态下执行,只与用户数据和应用相关的东西则放在用户态执行。另外,对时序要求特别高的操作,也应该在内核态完成。

什么样的功能应该在内核态下实现呢?首先,CPU的管理和内存管理都应该在内核态实现。诊断与测试程序也需要在内核态下实现。因为诊断和测试需要访问计算机的所有资源,否则怎么判断计算机是否正常呢?输入输出管理也一样,因为要访问各种设备和底层数据结构,所以也必须在内核态实现。
对于文件系统来说,则可以一部分放在用户态,一部分放在内核态。文件系统本身的管理,即文件系统的宏数据部分的管理必须放在内核态,否则任何人都可能破坏文件系统的结构;用户数据的管理则可以放在用户态。

计算机是如何知道现在正在运转的程序是内核态程序?
做出这种判断需要某种标志。这个标志就是处理器的一个状态位。这个状态位是CPU状态字里面的一个字位。也就是说,所谓的用户态、内核态实际上是处理器的一种状态,而不是程序的状态。我们通过设置该状态字,可以将CPU设置为内核态、用户态或者其他的子态(有的CPU有更多种子态)。一个程序运行时,CPU是什么态,这个程序就运行在什么态。

内核态是特权态,特权态下运行的程序可以访问任何资源,如何实现的呢?
为了赋予内核态程序访问所有资源的权限,当系统处于内核态时,内核程序可以绕过内存地址翻译而直接执行特权指令,如停机指令。这种绕过翻译的做法突破了系统对资源的控制。

用户态下的访问则受到限制。那么这种限制是如何实现的呢?要限制一个程序对资源的访问,需要对程序执行的每一条指令进行检查才能完成。而这种检查就是地址翻译。程序发出的每一条指令都要经过这个地址翻译过程。而通过对翻译的控制,就可以限制程序对资源的访问。

操作系统对进程的管理通过进程表来实现。进程表里存放的是关于进程的一切信息。在任何时候,进程所占有的全部资源,包括分配给该进程的内存、内核数据结构和软资源形成一个进程核(core)。核快照(core image)代表的是进程在某一特定时刻的状态。

如果在Linux或UNIX操作系统下编写程序,在出现分段错误(segmentation fault)时,操作系统会自动进行"进程核"倒出(core dump)。“核倒出”把所有计算机的状态保存在一个文件中,通过阅读这个文件的内容可以得知溢出时的进程状况,从而帮助调试程序。

一个壳shell的具体功能包括如下几项:
显示提示符,如UNIX下的提示符通常为$和%。
接受用户命令并执行。
实现输入输出间接(或间接输入输出)。
启动后台进程。
进行工作控制。
提供伪终端服务。

用户态
软件的一部分运行在用户态下,在用户态下,只使用了机器指令中的一个子集,特别地,那些会影响机器的控制或可进行I/O(输入/输出)操作的指令,在用户态中的程序里是禁止的。

内核态(也称为管态、核心态)
软件中最基础的部分是操作系统,它运行在内核态(也称为管态、核心态),在这个模式中操作系统具有对所有硬件的完全访问权,可以执行机器能够运行的任何指令。

在这里插入图片描述

任何单CPU计算机一次只能执行一条指令。

操作系统的结构

操作系统的结构—先放过,dai细看书1.7

我们将讨论的这六种设计包括单体系统、层次式系统、微内核、客户端-服务器模式、虚拟机和外核等。

微内核结构,即只将操作系统核心中的核心放在内核态运行,其他功能都移到用户态运行。

在这里插入图片描述
一个基本操作系统包含如下四个基本管理模块:进程管理、内存管理、I/O管理以及文件系统,再加上提供给上层应用的系统接口,就构成了操作系统的基本结构。

内存

空闲内存管理

dai看书3.2.3

处理内存超载的方法

有两种处理内存超载的通用方法。最简单的策略是交换(swapping)技术,即把一个进程完整调入内存,使该进程运行一段时间,然后把它存回磁盘,空闲进程主要存储在磁盘上,所以当它们不运行时就不会占用内存(尽管其中的一些进程会周期性地被唤醒以完成相关工作,然后就又进入睡眠状态),另一种策略是虚拟内存(virtual memory),该策略甚至能使程序在只有一部分被调入内存的情况下运行。

处理内存超载的方法—交换技术】见书3.2.2

虚拟内存

操作系统如何在单一的物理内存上为多个运行的进程(所有进程共享内存)构建一个私有的、可能很大的地址空间的抽象?当操作系统这样做时,我们说操作系统在虚拟化内存(virtualizing memory),因为运行的程序认为它被加载到特定地址(例如 0)的内存中,并且具有非常大的地址空间(例如32 位或 64 位),现实很不一样。
在这里插入图片描述
例如,当图 13.2 中的进程 A 尝试在地址 0(我们将称其为虚拟地址,virtual address)执行加载操作时,然而操作系统在硬件的支持下,出于某种原因,必须确保不是加载到物理地址 0,而是物理地址 320KB(这是 A 载入内存的地址),这是内存虚拟化的关键,这是
世界上每一个现代计算机系统的基础。

虚拟内存的三个目标】
①虚拟内存(VM)系统的一个主要目标是透明(transparency)。操作系统实现虚拟内存的方式,应该让运行的程序看不见。因此,程序不应该感知到内存被虚拟化的事实,相反,程序的行为就好像它拥有自己的私有物理内存。在幕后,操作系统(和硬件)完成了所有的工作,让不同的工作复用内存,从而实现这个假象。②虚拟内存的另一个目标是效率(efficiency)。操作系统应该追求虚拟化尽可能高效(efficient),包括时间上(即不会使程序运行得更慢)和空间上(即不需要太多额外的内存来支持虚拟化)。在实现高效率虚拟化时,操作系统将不得不依靠硬件支持,包括 TLB 这样的硬件功能。 ③虚拟内存第三个目标是保护(protection)。操作系统应确保进程受到保护(protect),不会受其他进程影响,操作系统本身也不会受进程影响。当一个进程执行加载、存储或指令提取时,它不应该以任何方式访问或影响任何其他进程或操作系统本身的内存内容(即在它的地址空间之外的任何内容)。因此,保护让我们能够在进程之间提供隔离(isolation)的特性,每个进程都应该在自己的独立环境中运行,避免其他出错或恶意进程的影响。

虚拟内存技术】
通常,每个进程有一些可以使用的地址集合,典型值从0开始直到某个最大值,在最简单的情形下,一个进程可拥有的最大地址空间小于主存,在这种方式下,进程可以用满其地址空间,而且内存中也有足够的空间容纳该进程。如果一个进程有比计算机拥有的主存还大的地址空间,而且该进程希望使用全部的内存,操作系统可以把部分地址空间装入主存,部分留在磁盘上,并且在需要时来回交换它们,在本质上,操作系统创建了一个地址空间的抽象,作为进程可以引用地址的集合,该地址空间与机器的物理内存解耦,可能大于也可能小于该物理空间。

虚拟内存((virtual memory)。虚拟内存的基本思想是:每个程序拥有自己的地址空间,这个空间被分割成多个块,每一块称作一页或页面(page),每一页有连续的地址范围,这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序;当程序引用到一部分在物理内存中的地址空间时,由硬件立刻执行必要的映射,当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的指令。

虚拟内存很适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中,当一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用。

虚拟内存是什么】虚拟内存是对主存和磁盘的抽象。

虚拟地址空间】内存的最小可寻址的内存单元是字节,内存的每一个字节都由一个唯一的数字来标识,称它为地址,所有可能地址的集合就称为虚拟地址空间(virtual address space)。这个虚拟地址空间只是一个展现给机器级程序的概念性映像。

虚拟内存系统负责为程序提供一个巨大的、稀疏的、私有的地址空间的假象,其中保存了程序的所有指令和数据;操作系统在专门硬件的帮助下,通过每一个虚拟内存的索引,将其转换为物理地址,物理内存根据获得的物理地址去获取所需的信息,操作系统会同时对许多进程执行此操作,并且确保程序之间互相不会受到影响,也不会影响操作系统,整个方法需要大量的机制(很多底层
机制)和一些关键的策略。

页面置换算法

进程Process,相关

进程,知识点概述

进程,知识点概述】进程定义/进程是什么。一个进程的组成部分/进程实体的组成/进程结构。进程控制块PCB。进程的特点。进程控制结构。进程的状态及其转换。进程树。创建进程的步骤。进程间通信。进程调度。

什么是进程

进程的简单定义:一个程序的一次运行过程。进程是可并发执行的程序在一个数据集合上的一次运行过程,是系统进行资源分配和调度的一个独立单位。进程是容纳一个程序所需要所有信息的容器。进程是对一个正在运行的程序的抽象,或说进程是处理器、主存和I/O设备的抽象。
进程是对正在运行程序的一个抽象。静态的代码(即程序),当运行起来被加载进内存,就是进程,一种动态概念,一个运行实体,有地址空间有系统资源。进程本质上是正在执行的一个程序,与每个进程相关的有:地址空间、资源集。一个进程是某种类型的一个活动,它有程序、输入、输出以及状态。
注:资源集通常包括寄存器(含有程序计数器和堆栈指针)打开文件的清单、突出的报警、有关进程清单,以及运行该程序所需要的所有其他信息。进程基本上容纳运行一个程序所需要所有信息的容器。
在单个CPU下,实际上在任何时刻只能有一个进程处于执行状态。

进程实体的组成/进程结构

进程实体的组成/进程结构,包括:可执行的程序(段)、相关数据集(变量、工作空间,缓冲区等)、栈(内核栈/用户栈:返址,参数传递,局部变量)、进程控制块PCB
进程的组成:一个进程就是一个正在执行程序的实例,包括程序计数器、寄存器和变量的当前值。

在这里插入图片描述

进程控制块PCB(又叫进程表)

进程控制块,是什么】操作系统管理控制进程运行所用的信息集合。操作系统用PCB来描述进程的基本情况以及运行变化的过程,PCB是进程存在的唯一标志。进程控制块pcb:就是一个数据结构即一个结构体,包含了运行时很多描述状态的信息

PCB的作用/PCB是用来干什么的】

进程的创建:为该进程生成一个PCB。
进程的终止:回收它的PCB。
进程的组织管理:通过对PCB的组织管理来实现。

谁管理进程的资源?是操作系统。那需要什么手段或资源呢?操作系统要管理进程就要维护关于进程的一些信息。当一个进程产生时,操作系统也需要为其创建记录。操作系统用于维护进程记录的结构就是进程表或进程控制块(Process Control Block,PCB)。这个进程表或PCB中存放的就是有关该进程的资料。

PCB内有什么/PCB内具体包含什么信息】 PCB内有什么:进程标识信息,进程调度信息、进程现场信息、进程控制信息。

在这里插入图片描述
在这里插入图片描述

不同的操作系统维护的进程资料不尽相同。但一般来说,维护的资料信息应当包括寄存器、程序计数器、状态字、栈指针、优先级、进程ID、信号、创建时间、所耗CPU时间、当前持有的各种句柄等。

PCB的的数据结构主要是线性表、链表和结构(struct),当然也可能使用树和图(网络)结构。

进程表存放在操作系统所在的内核空间里

在UNIX中的进程将其存储空间划分为三段:正文段(如程序代码)、数据段(如变量)以及堆栈段。数据向上增长而堆栈向下增长,如图120所示。夹在中间的是未使用的地址空间。堆栈在需要时自动地向中间增长,不过数据段的扩展是显式地通过系统调用brk进行的,在数据段扩充后,该系统调用指定一个新地址。但是,这个调用不是POSIX标准中定义的,对于存储器的动态分配,鼓励程序员使用malloc库过程,而malloc的内部实现则不是一个适合标准化的主题,因为几乎没有程序员直接使用它,我们有理由怀疑是否会有人注意到brk实际不是属于POSIX的。

在这里插入图片描述

PCB的组织方式】

在这里插入图片描述

进程树】
若一个进程能够创建一个或多个进程(称为子进程),而且这些进程又可以创建子进程,则很容易得到进程树。

在这里插入图片描述

进程的特征/特点

进程的特征/特点】动态性:程序待在磁盘里,是。并发性:进程的执行是间断性的(一会儿上cpu一会儿停止)。独立性:进程有自己的内存,独立调度操作(dai没有引入线程时这么说)。异步性:进程以各自堵路的、不可预知的速度向前推进,进程的执行需要资源,而资源可能别的部分在占着,是走走停停。共享性:会导致之间的限制。

在这里插入图片描述

进程和程序的关系、区别、联系】
在这里插入图片描述

在这里插入图片描述

进程的生命周期

在这里插入图片描述

进程创建】

创建进程的步骤】
创建进程的步骤如下所示:
1)分配进程控制块。
2)初始化机器寄存器。
3)初始化页表。
4)将程序代码从磁盘读进内存。
5)将处理器状态设置为“用户态”。
6)跳转到程序的起始地址(设置程序计数器)。

在这里插入图片描述

进程运行】
在这里插入图片描述

进程等待】

在这里插入图片描述
进程唤醒】
在这里插入图片描述

进程结束】
在这里插入图片描述

进程的状态及转换

进程的三种基本状态:就绪状态、运行状态、阻塞状态。阻塞状态:正在执行的进程由于等待某事件而无法继续执行而处于的暂停状态,如scanf等待用户输入。运行状态:进程已经分配到cpu,当前正在cpu上执行的状态。就绪状态:进程分配到必要资源,等待获得cpu的状态,组织成一个或多个就绪队列

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

挂起状态是什么,和阻塞状态区别与关系】

Linux中进程状态TASK_…:有很多更加细致的状态,这些状态的概念解释。进程状态转化图】

进程挂起】
资料】《清华OS课程》–

在这里插入图片描述

进程间通信

进程通信Inter Process Communication,IPC,概述】进程对应有操作系统分配的内存,其中存储了该进程的相关信息,有时不同进程之间需要交换信息,但是操作系统规定一个进程无法直接去访问另一个进程对应的内存空间,同时为了进行不同进程间信息交换,由于进程有独立的地址空间,这使得进程间共享状态信息变得困难,为了共享信息,需要显式的进程间通信机制。
合作完成某些作业的相关进程经常需要彼此通信以便同步它们的行为,这种通信称为进程间通信

与进程管理有关的系统调用:进程创建、进程终止、申请更多的内存(或释放不再需要的内存)、等待一个子进程结束、用另一个程序覆盖该程序等。
其中,进程创建和进程终止是与进程管理有关的最重要的系统调用

在这里插入图片描述

学习进程间通信,需要考虑以下几个问题】1.一个进程如何把信息传递给另一个。2.确保两个或更多的进程在关键活动中不会出现交叉,例如,在飞机订票系统中的两个进程为不同的客户试图争夺飞机上的最后一个座位。3.保证正确的顺序(如果该顺序是有关联的话),比如,如果进程A产生数据而进程B打印数据,那么B在打印之前必须等待,直到A已经产生一些数据。

进程间通信问题,同样的问题和解决方法也适于线程。

竞争条件】
两个或多个进程读写某些共享数据,而最后的结果取决于进程运行的精确时序,称为竞争条件(race condition)。

怎样避免竞争条件?实际上凡涉及共享内存、共享文件以及共享任何资源的情况都会引发与前面类似的错误,要避免这种错误,关键是要找出某种途径来阻止多个进程同时读写共享的数据。换言之,我们需要的是互斥(mutual exclusion),即以某种手段确保当一个进程在使用一个共享变量或文件时,其他进程不能做同样的操作。前述问题的症结就在于,在进程A对共享变量的使用未结束之前进程B就使用它。为实现互斥而选择适当的原语是任何操作系统的主要设计内容之一。避免竞争条件的问题也可以用一种抽象的方式进行描述,一个进程的一部分时间做内部计算或另外一些不会引发竞争条件的操作,在某些时候进程可能需要访问共享内存或共享文件,或执行另外一些会导致竞争的操作,我们把对共享内存进行访问的程序片段称作临界区域(critical region)或临界区(criticalsection),如果我们能够适当地安排,使得两个进程不可能同时处于临界区中,就能够避免竞争条件。

对于一个好的避免竞争条件的解决方案,需要满足以下4个条件:
1)任何两个进程不能同时处于其临界区。
2)不应对CPU的速度和数量做任何假设。
3)临界区外运行的进程不得阻塞其他进程。
4)不得使进程无限期等待进入临界区。

讨论几种实现互斥的方案,在这些方案中,当一个进程在临界区中更新共享内存时,其他进程将不会进入其临界区,也不会带来任何麻烦。方案:屏蔽中断;锁变量;严格轮换法;Peterson解法;TSL指令;(每种方案,详细见书)

Peterson解法和TSL或XCHG解法都是正确的,但它们都有忙等待的缺点。这些解法在本质上是这样的:当一个进程想进入临界区时,先检查是否允许进入,若不允许,则该进程将原地等待,直到允许为止。这种方法不仅浪费了CPU时间,而且还可能引起预想不到的结果。考虑一台计算机有两个进程,H优先级较高,L优先级较低。调度规则规定,只要H处于就绪态它就可以运行。在某一时刻,L处于临界区中,此时H变到就绪态,准备运行(例如,一条I/O操作结束)。现在H开始忙等待,但由于当H就绪时L不会被调度,也就无法离开临界区,所以H将永远忙等待下去。这种情况有时被称作优先级反转问题(priority inversion problem)。

介绍两个进程间通信原语,它们在无法进入临界区时将阻塞,而不是忙等待,最简单的是sleep和wakeup,sleep是一个将引起调用进程阻塞的系统调用,即被挂起,直到另外一个进程将其唤醒,wakeup调用有一个参数,即要被唤醒的进程,另一种方法是让sleep和wakeup各有一个参数,即有一个用于匹配sleep和wakeup的内存地址。

生产者消费者问题。—见书72页

进程间通信方式】
管道通信、信号量、消息队列、共享内存

信号量】
在计算机里,信号量实际上就是一个简单整数,一个进程在信号变为0或者1的情况下推进,并且将信号变为1或0来防止别的进程推进;当进程完成任务后,则将信号再改变为0或1,从而允许其他进程执行。
需要注意的是,信号量不只是一种通信机制,更是一种同步机制。

进程调度

进程调度算法
进程调度算法,陈列】先来先服务调度算法FCFS;短作业(进程)优先调度算法HRRF;高响应比优先调度算法HRRF;优先级调度算法;时间片轮转调度算法;多级队列调度算法。
要会计算每个调度算法的周转时间等。根据作业的特性,让你选择应该的调度算法。

先来先服务调度算法FCFS】
如果早就绪的进程排在就绪队列的前面,迟就绪的进程排在就绪队列的后面,那么先来先服务(FCFS: first come first service)总是把当前处于就绪队列之首的那个进程调度到运行状态,也就说,它只考虑进程进入就绪队列的先后,而不考虑它的下一个CPU周期的长短及其他因素。FCFS算法简单易行,是一种非抢占式策略,但性能却不大好。

最短作业优先算法SJF】
SJF算法是以进入系统的作业所要求的CPU时间为标准,是指对短作业或者短进程优先调度的算法,将每个进程与其估计运行时间进行关联选取估计计算时间最短的作业投入运行。
SJF是一种Non-preemptive(非抢占式)调度。

高响应比优先调度算法HRRF】
调度方式:采用非抢占式。
响应比定义公式。

优点:照顾了短作业,同时也考虑了长作业的等待时间,相对公平,不会出现由于短作业的不断到来,长作业一直得不到执行
缺点:每次调度都需要计算那个判断;由于是非抢占,紧迫作业无法立刻得到响应

优先级调度算法】
优先级调度算法,概述:将进程根据某种依据设定优先级

调度算法分类:非抢占式优先级调度算法;抢占式优先级调度算法。

优先级设计方法:静态优先级----根据一些依据,在进程创建时确定它的优先级,在整个生命周期不改变确定依据:进程类型,尊重时间。动态优先级----在进程创建后的运行期间动态调整权值,以改变优先级

时间片轮转调度算法】
时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。

改进版----动态时间片。适用类型----交互式系统

多级反馈队列调度算法】
多级队列调度算法是什么:多个不同优先级队列,每个队列有不同调度算法,按优先级(就绪队列)设置不同的时间片。
多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成,因而它是目前被公认的一种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法。

进程的缺陷】

进程,杂

杂】
在进程模型中,计算机上所有可运行的软件,通常也包括操作系统,被组织成若干顺序进程( sequential process),简称进程(process)。从概念.上说,每个进程拥有它自己的虚拟CPU。当然,实际上真正的CPU在各进程之间来回切换。但为了理解这种系统,考虑在(伪)并行情况下运行的进程集,要比试图跟踪CPU如何在程序间来回切换简单得多,这种快速的切换称作多道程序设计。

在这里插入图片描述
系统管理器授权每个进程使用一个给定的UID(User IDentification)。每个被启动的进程都有启动该进程的用户UID。子进程拥有与父进程一样的UID。用户可以是某个组的成员,每个组也有一个GID (Group IDentification)。在UNIX中,有一个UID称为超级用户(superuser),或者Windows中的管理员(administrator),它具有特殊的权力,可以违背一些保护规则。在大型系统中,只有系统管理员掌握着成为超级用户的密码。

一个(挂起的)进程包括:进程的地址空间(往往称作磁芯映像,core image,纪念过去使用的磁芯存储器),以及对应的进程表项(其中包括寄存器以及稍后重启动该进程所需要的许多其他信息)。

线程

线程,知识点概述

线程,知识点概述】为什么要有线程呢?线程的状态。线程之间通信。

为什么要有线程呢?

人们需要多线程的主要原因是,在许多应用中同时发生着多种活动,其中某些活动随着时间的推移会被阻塞。通过将这些应用程序分解成可以准并行运行的多个顺序线程,程序设计模型会变得更简单。
第二个关于需要多线程的理由是,由于线程比进程更轻量级,所以它们比进程更容易(即更快)创建,也更容易撤销。在许多系统中,创建一个线程较创建一个进程要快10~100倍。在有大量线程需要动态和快速修改时,具有这一特性是很有用的。
需要多线程的第三个原因涉及性能方面的讨论,若多个线程都是CPU密集型的,那么并不能获得性能上的增强,但是如果存在着大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠进行,从而会加快应用程序执行的速度。

进程拥有一个执行的线程,通常简写为线程(thread)。在线程中有一个程序计数器,用来记录接着要执行哪一条指令。线程拥有寄存器,用来保存线程当前的工作变量。

每个线程拥有一个自己的堆栈,用来记录执行历史,其中每一帧保存了一个已调用的但是还没有从中返回的过程,在该栈帧中存放了相应过程的局部变量以及过程调用完成之后使用的返回地址。例如,如果过程X调用过程Y,而Y又调用Z,那么当Z执行时,供X、Y和Z使用的栈帧会全部存在堆栈中。通常每个线程会调用不同的过程,从而有一个各自不同的执行历史,这就是为什么每个线程需要有自己的堆栈的原因。

尽管线程必须在某个进程中执行,但是线程和它的进程是不同的概念,并且可以分别处理。进程用于把资源集中到一起,而线程则是在CPU上被调度执行的实体。

当多线程进程在单CPU系统中运行时,线程轮流运行。多线程的工作方式:CPU在线程之间的快速切换,制造了线程并行运行的假象,好似它们在一个比实际CPU慢一些的CPU上同时运行,在一个有三个计算密集型线程的进程中,线程以并行方式运行,每个线程在一个CPU上得到了真实CPU速度的三分之一。

一个进程中的不同线程,所有的线程都有完全一样的地址空间,这意味着它们也共享同样的全局变量;由于各个线程都可以访问进程地址空间中的每一个内存地址,所以一个线程可以读、写或甚至清除另一个线程的堆栈;

一个进程中的线程之间是没有保护的,原因是:1)不可能,2)也没有必要。这与不同进程是有差别的。不同的进程会来自不同的用户,它们彼此之间可能有敌意,一个进程总是由某个用户所拥有,该用户创建多个线程应该是为了它们之间的合作而不是彼此间争斗。

线程之间通信

线程之间通信,需要考虑:1.确保两个或更多的进程在关键活动中不会出现交叉。2.保证正确的顺序。
进程间通信问题,同样的问题和解决方法也适于线程。

线程的状态

运行、阻塞、就绪、终止。
线程可以处于若干种状态的任何一个

在这里插入图片描述

线程,杂

常见的线程调用】
在多线程的情况下,进程通常会从当前的单个线程开始,这个线程有能力通过调用一个库函数(如thread_create)创建新的线程,thread_create的参数专门指定了新线程要运行的过程名,这里,没有必要对新线程的地址空间加以规定,因为新线程会自动在创建线程的地址空间中运行。通常所有的线程都是平等的。不论有无层次关系,创建线程通常都返回一个线程标识符,该标识符就是新线程的名字。

当一个线程完成工作后,可以通过调用一个库过程(如thread_exit)退出,该线程接着消失,不再可调度。

线程调用thread_join,一个线程可以等待一个(特定)线程退出,这个过程阻塞调用线程直到那个(特定)线程退出,在这种情况下,线程的创建和终止非常类似于进程的创建和终止,并且也有着同样的选项。

线程调用thread_yield,它允许线程自动放弃CPU从而让另一个线程运行,这样一个调用是很重要的,因为不同于进程,(线程库)无法利用时钟中断强制线程让出CPU,所以设法使线程行为“高尚”起来,并且随着时间的推移自动交出CPU,以便让其他线程有机会运行,就变得非常重要,有的调用允许某个线程等待另一个线程完成某些任务,或等待一个线程宣称它已经完成了有关的工作等。


并发

临界区(critical section),解释】临界区(critical section)是访问共享资源的一段代码,资源通常是一个变量或数据结构。由于执行这段代码的多个线程可能导致竞争状态,因此我们将此段代码称为临界区(critical section)。临界区是访问共享变量(或更一般地说,共享资源)的代码片段,一定不能由多个线程同时执行。

“竞态条件”,解释】
竞态条件(race condition)出现在多个执行线程大致同时进入临界区时,它们都试图更新共享的数据结构,导致了令人惊讶的(也许是不希望的)结果。竞态条件(race condition):结果取决于代码的时间执行。

“不确定性”,解释】
在执行过程中发生的上下文切换,我们得到了错误的结果。
事实上,可能每次都会得到不同的结果。因此,我们称这个结果是不确定的(indeterminate),而不是确定的(deterministic)计算(我们习惯于从计算机中得到)。不确定的计算不知道输出是什么,它在不同运行中确实可能是不同的。不确定性(indeterminate)程序由一个或多个竞态条件组成,程序的输出因运行而异,具体取决于哪些线程在何时运行。这导致结果不是确定的(deterministic),而我们通常期望计算机系统给出确定的结果。

并发编程的一个最基本问题:我们希望原子式执行一系列指令,但由于单处理器上的中断(或者多个线程在多处理器上并发执行),我们做不到,锁(lock),直接解决这一问题。程序员在源代码中加锁,放在临界区周围,保证临界区能够像单条原子指令一样执行。锁的基本任务:提供互斥。

举例解释锁,锁的基本思想、工作过程】
在这里插入图片描述
锁就是一个变量,因此我们需要声明一个某种类型的锁变量(lock variable,如上面的mutex),才能使用,这个锁变量(简称锁)保存了锁在某一时刻的状态,它要么是可用的(available,或 unlocked,或 free),表示没有线程持有锁,要么是被占用的(acquired,或 locked, 或 held),表示有一个线程持有锁,正处于临界区,我们也可以保存其他的信息,比如持有锁的线程,或请求获取锁的线程队列,但这些信息会隐藏起来,锁的使用者不会发现。

lock()和 unlock()函数的语义很简单,调用 lock()尝试获取锁,如果没有其他线程持有锁(即它是可用的),该线程会获得锁,进入临界区,这个线程有时被称为锁的持有者(owner),如果另外一个线程对相同的锁变量(本例中的 mutex)调用 lock(),因为锁被另一线程持有,该调用不会返回,这样,当持有锁的线程在临界区时,其他线程就无法进入临界区。

锁的持有者一旦调用 unlock(),锁就变成可用了,如果没有其他等待线程(即没有线程调用过 lock()并卡在那里),锁的状态就变成可用了,如果有等待线程(卡在 lock()里),其中一个会(最终)注意到(或收到通知)锁状态的变化,获取该锁,进入临界区。

锁为程序员提供了最小程度的调度控制。我们把线程视为程序员创建的实体,但是被操作系统调度,具体方式由操作系统选择。锁让程序员获得一些控制权。通过给临界区加锁,可以保证临界区内只有一个线程活跃。锁将原本由操作系统调度的混乱状态变得更为可控。

在这里插入图片描述

评价锁】

在实现锁之前,我们应该首先明确目标,因此我们要问,如何评价一种锁实现的效果。为了评价锁是否能工作(并工作得好),我们应该先设立一些标准。

一:正确性。锁是否能完成它的基本任务,即提供互斥(mutual exclusion),一次只允许一个线程进入临界区。最基本的,锁是否有效,能够阻止多个线程进入临界区?
二:公平性(fairness)。当锁可用时,是否每一个竞争线程有公平的机会抢到锁?用另一个方式来看这个问题是检查更极端的情况:是否有竞争锁的线程会饿死(starve),一直无法获得锁?
三:性能(performance),具体来说,是使用锁之后增加的时间开销。有几种场景需要考虑。一种是没有竞争的情况,即只有一个线程抢锁、释放锁的开支如何?另外一种是一个 CPU 上多个线程竞争,性能如何?最后一种是多个 CPU、多个线程竞争时的性能。通
过比较不同的场景,我们能够更好地理解不同的锁技术对性能的影响,

基于硬件支持实现的锁&&自旋锁,以实现锁

评价自旋锁】
1.自旋锁提供公平性。
2.自旋锁不提供任何公平性保证。实际上,自旋的线程在竞争条件下可能会永远自旋。自旋锁没有公平性,可能会导致饿死。
3.讨论自旋锁的性能。首先,考虑线程在单处理器上竞争锁的情况,在单 CPU 的情况下,性能开销相当大,假设一个线程持有锁进入临界区时被抢占,调度器可能会运行其他每一个线程(假设有 N−1 个这种线程),而其他线程都在竞争锁,都会在放弃 CPU 之前,自旋一个时间片,浪费 CPU 周期。 ||||||考虑这些线程跨多个处理器在多 CPU 上,自旋锁性能不错(如果线程数大致等于 CPU 数),假设线程 A 在CPU 1,线程 B 在 CPU 2 竞争同一个锁,线程 A(CPU 1)占有锁时,线程 B 竞争锁就会自旋(在 CPU 2 上),然而,临界区一般都很短,因此很快锁就可用,然后线程 B 获得锁。自旋等待其他处理器上的锁,并没有浪费很多 CPU 周期,因此效果不错。

对基于硬件的锁的评价】
基于硬件的锁简单(只有几行代码)而且有效,但是,某些场景下,这些解决方案会效率低下。”以两个线程运行在单处理器上为例,当一个线程(线程 0)持有锁时,被中断,第二个线程(线程 1)去获取锁,发现锁已经被持有,因此,它就开始自旋,接着自旋,然后它继续自旋,最后,时钟中断产生,线程 0 重新运行,它释放锁,最后(比如下次它运行时),线程 1 不需要继续自旋了,它获取了锁,因此,类似的场景下,一个线程会一直自旋检查一个不会改变的值,浪费掉整个时间片!如果有 N 个线程去竞争一个锁,情况会更糟糕,同样的场景下,会浪费 N−1 个时间片,只是自旋并等待一个线程释放该锁。

yield()立刻让出CPU,以实现锁

在讨论基于硬件的锁时,发现自旋会很浪费时间,那么如何让锁不会不必要地自旋,浪费 CPU 时间?
只有硬件支持是不够的。我们还需要操作系统支持。
一种简单友好的方法就是,在要自旋的时候,放弃 CPU。
操作系统提供原语 yield(),在线程发现锁被占用从而要自旋的时候,线程可以调用yield()主动放弃 CPU,让其他线程运行。
在单 CPU 上运行两个线程,基于 yield 的方法十分有效。
考虑许多线程(例如 100 个)反复竞争一把锁的情况:
在这种情况下,一个线程持有锁,在释放锁之前被抢占,其他 99 个线程分别调用 lock(),发现锁被抢占,然后让出 CPU,假定采用某种轮转调度程序,这 99 个线程会一直处于运行—让出这种模式,直到持有锁的线程再次运行。虽然比原来的浪费 99 个时间片的自旋方案要好,但这种方法仍然成本很高,上下文切换的成本是实实在在的,因此浪费很大,更糟的是,我们还没有考虑饿死的问题,一个线程可能一直处于让出的循环,而其他线程反复进出临界区。

睡眠队列,以实现锁

YW】休眠睡眠?到底是个什么状态?

睡眠队列,以实现锁】显式地施加某种控制,决定锁释放时,谁能抢到锁,我们需要操作系统的更多支持,并需要一个队列来保存等待锁的线程。调用者在获取不到锁时睡眠,在锁可用时被唤醒。例如 Solaris 的 park()和 unpark()原语,Linux 的 futex)
看书《os导论》28.14

两阶段锁

在这里插入图片描述

基于锁的并发结构

通过锁可以使数据结构线程安全(thread safe)。

讨论以下问题;如何在常见数据结构中使用锁?对于特定数据结构,如何加锁才能让该结构功能正确?进一步,如何对该数据结构加锁,能够保证高性能,让许多线程同时访问该结构,即并发访问(concurrently)?

并发计数器;并发链表;并发队列;并发散列表。
见书《os导论》29章

同步

要保证线程安全,也并非一定要进行阻塞或非阻塞同步,同步与线程安全两者没有必然的联系。 同步只是保障存在共享数据争用时正确性的手段,如果能让一个方法本来就不涉及共享数据,那它自 然就不需要任何同步措施去保证其正确性,因此会有一些代码天生就是线程安全的

Peterson算法
Peterson算法是一种进程互斥的软件实现方法。未遵循:让权等待
在这里插入图片描述

死锁

面试常考点:什么是死锁?产生死锁的原因?死锁产生的必要条件?解决死锁的基本方法?怎么预防死锁?怎么避免死锁?怎么解除死锁?

地址空间

地址空间address space,概述】
要使多个应用程序同时处于内存中并且不互相影响,需要解决两个问题:保护和重定位,我们来看一个原始的对前者的解决办法,它曾被用在IBM 360上:给内存块标记上一个保护键,并且比较执行进程的键和其访问的每个内存字的保护键,然而,这种方法本身并没有解决后一个问题,虽然这个问题可以通过在程序被装载时重定位程序来解决,但这是一个缓慢且复杂的解决方法,一个更好的办法是创造一个新的存储器抽象:地址空间。

地址空间是物理内存的抽象,地址空间为程序创造了一种抽象的内存,地址空间是运行的程序看到的系统中的内存。地址空间是一个进程可用于寻址内存的一套地址集合,每个进程都有一个自己的地址空间,并且这个地址空间独立于其他进程的地址空间(除了在一些特殊情况下进程需要共享它们的地址空间外)。
地址空间是操作系统提供的一种抽象,地址空间与进程相关,这是从某个最小值的存储位置(通常是零)到某个最大值的存储位置的列表,在这个地址空间中,进程可以进行读写,该地址空间中存放有可执行程序、程序的数据以及程序的堆栈。

在32位或64位地址的计算机中,分别有232或264字节的地址空间。

地址空间,就像进程的概念创造了一类抽象的CPU以运行程序一样,

理解这个基本的操作系统内存抽象—地址空间,是了解内存虚拟化的关键。

一个进程的地址空间包含运行的程序的所有内存状态,比如:程序的代码(code,指令)必须在内存中,因此它们在地址空间里,当程序在运行的时候,利用栈(stack)来保存当前的函数调用信息,分配空间给局部变量,传递参数和函数返回值;最后,堆(heap)用于管理动态分配的、用户管理的内存,就像你从 C 语言中调用 malloc()或面向对象语言(如 C ++或 Java)中调用 new 获得内存。当然,还有其他的东西(例如,静态初始化的变量),但现在假设只有这 3 个部分:代码、栈和堆。

在这里插入图片描述
当我们描述地址空间时,所描述的是操作系统提供给运行程序的抽象(abstract),程序不在物理地址 0~16KB 的内存中,而是加载在任意的物理地址。

基于硬件的地址转换(hardware-based address translation),简称为地址转换(address translation)利用地址转换,硬件对每次内存访问进行处理(即指令获取、数据读取或写入),将指令中的虚拟(virtual)地址转换为数据实际存储的物(physical)地址,因此,在每次内存引用时,硬件都会进行地址转换,将应用程序的内存引用重定位到内存中实际的位置。仅仅依靠硬件不足以实现虚拟内存,因为它只是提供了底层机制来提高效率,操作系统必须在关键的位置介入,设置好硬件,以便完成正确的地址转换。
进程中使用的内存引用都是虚拟地址(virtual address),硬件接下来将虚拟地址加上基址寄存器中的内容,得到物理地址(physical address),再发给内存系统。将虚拟地址转换为物理地址,这正是所谓的地址转换(address translation)技术。

动态重定位】

重定位是在运行时发生的,而且我们甚至可以在进程开始运行后改变其地址空间,这种技术一般被称为动态重定位(dynamic relocation)
在动态重定位的过程中,只有很少的硬件参与,但获得了很好的效果。一个基址寄存器将虚拟地址转换为物理地址,一个界限寄存器确保这个地址在进程地址空间的范围内,它们一起提供了既简单又高效的虚拟内存机制。

高速缓存Cache

由于计算机 的存储设备与处理器的运算速度有着几个数量级的差距,所以现代计算机系统都不得不加入一层或多 层读写速度尽可能接近处理器运算速度的高速缓存(Cache)来作为内存与处理器之间的缓冲:将运算 需要使用的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处 理器就无须等待缓慢的内存读写了。
缓存一致性问题,描述】
在多路处理器系统中,每 个处理器都有自己的高速缓存,而它们又共享同一主内存,当多个处理器的运算任务都涉及 同一块主内存区域时,将可能导致各自的缓存数据不一致。
在这里插入图片描述

缓存一致性问题,解决方法】
为了解决一致性的问题,需要各个处理器访问缓存时都遵循一些协 议,在读写时要根据协议来进行操作,这类协议有MSI、MESI(Illinois Protocol)、MOSI、 Synapse、Firefly及Dragon Protocol等

协程

------协程的局限性】需要在应用层面实现的内容(调用栈、调度器这些)特别多

协程】
资料)由于最初多数的用户线程是被设计成协同式调度 (Cooperative Scheduling)的,所以它有了一个别名——“协程”(Coroutine)。又由于这时候的协程会 完整地做调用栈的保护、恢复工作,所以今天也被称为“有栈协程”(Stackfull Coroutine),起这样的 名字是为了便于跟后来的“无栈协程”(Stackless Coroutine)区分开。无栈协程不是本节的主角,不过 还是可以简单提一下它的典型应用,即各种语言中的await、async、yield这类关键字。无栈协程本质上 是一种有限状态机,状态保存在闭包里,自然比有栈协程恢复调用栈要轻量得多,但功能也相对更有 限。
协程的主要优势是轻量,无论是有栈协程还是无栈协程,都要比传统内核线程要轻量得多。

文件系统

文件是进程创建的信息逻辑单元,一个磁盘一般含有几千甚至几百万个文件,每个文件是独立于其他文件的,唯一不同的是文件是对磁盘的建模,而非对RAM的建模,事实上,如果能把每个文件看成一个地址空间,那么读者就能理解文件的本质了。

操作系统中处理文件的部分称为文件系统(file system)。

文件的类型

目录

文件系统的实现

系统调用----先放过,dai细看

操作系统的服务是通过什么方式提供的呢?答案是系统调用(system call)。系统调用就是操作系统提供的应用程序界面(Application Programming Interface,API)。用户程序通过调用这些API获得操作系统的服务。
任何操作系统的核心是它可处理的系统调用集,这些系统调用真实地说明了操作系统所做的工作。

系统调用按照功能可以划分为六大类:
进程控制类。
文件管理类。
设备管理类。
内存管理类。
信息维护类。
通信类。

系统调用的过程。系统调用分为三个阶段,分别是:
参数准备阶段。
系统调用识别阶段。
系统调用执行阶段。

任何单CPU计算机一次只能执行一条指令,如果一个进程正在用户态运行一个用户程序,并且需要一个系统服务,比如从一个文件读数据,那么它就必须执行一个陷阱或系统调用指令,将控制转移到操作系统,操作系统接着通过参数检查找出所需要的调用进程,然后,它执行系统调用,并把控制返回给在系统调用后面跟随着的指令,在某种意义上,进行系统调用就像进行一个特殊的过程调用,但是只有系统调用可以进入内核,而过程调用则不能。

需要与操作系统进行交互的用户来说,又怎么使用操作系统的服务呢?壳shell

每个操作系统都提供某种壳,以便与用户进行交互。这个壳是覆盖在操作系统服务上面的一个用户界面,既可以是图形界面,也可以是文本界面。用户在这个界面上输入命令,操作系统则执行这些命令。当然,用户输入的命令不是直接的操作系统服务,而是所谓的utilities。utilities的功用相当于C语言中的库函数。因为用户不能直接调用系统调用(至于为什么不能留给读者思考),C语言提供了库函数来解决这个问题。

接口中所提供的调用随着操作系统的不同而变化(尽管基于的概念是类似的),多数现代操作系统都有实现相同功能的系统调用,尽管它们在细节上差别很由于引发系统调用的实际机制是非常依赖于机器的,而且必须用汇编代码表达,所以,通过提供过程库使C程序中能够使用系统调用,当然也包括其他语言。

POSIX系统调用】
从广义上看,由这些调用所提供的服务确定了多数操作系统应该具有的功能,而在个人计算机上资源管理功能是较弱的(至少与多用户的大型机相比较是这样)。所包含的服务有创建与终止进程,建、删除、读出和写入文件,目录管理以及完成输入/输出。有必要指出,将POSIX过程映射到系统调用并不是一对一的。POSIX标准定义了构造系统所必须提供的一套过程,但是并没有规定它们是系统调用、库调用还是其他的形式。如果不通过系统调用就可以执行一个过程(即无须陷入内核),那么从性能方面考虑,它通常会在用户空间中完成。不过,多数POSIX过程确实进行系统调用,通常是一个过程直接映射到一个系统调用上。在一些情形下,特别是所需要的过程仅仅是某个调用的变体时,一个系统调用会对应若干个库调用。

在这里插入图片描述

用于进程管理的系统调用

fork、execve、waitpid、exit

用于文件管理的系统调用

用于目录管理的系统调用

其他常用系统调用

并发编程中需要处理的的两个关键问题】
线程之间如何通信及线程之间如何同步(这里的 线程是指并发执行的活动实体)。

考虑线程之间的通信】
通信是指线程之间以何种机制来交换信息。在命令式编程 中,线程之间的通信机制有两种:共享内存和消息传递,
在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态 进行隐式通信。
在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消 息来显式进行通信。

考虑线程之间的同步】
同步是指程序中用于控制不同线程间操作发生相对顺序的机制。在共享内存并发模型 里,同步是显式进行的,程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。
在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。

处理器对内存的读写】
现代的处理器使用写缓冲区临时保存向内存写入的数据。写缓冲区可以保证指令流水线 持续运行,它可以避免由于处理器停顿下来等待向内存写入数据而产生的延迟。同时,通过以 批处理的方式刷新写缓冲区,以及合并写缓冲区中对同一内存地址的多次写,减少对内存总 线的占用。虽然写缓冲区有这么多好处,但每个处理器上的写缓冲区,仅仅对它所在的处理器 可见。这个特性会对内存操作的执行顺序产生重要的影响:处理器对内存的读/写操作的执行 顺序,不一定与内存实际发生的读/写操作顺序一致!
在这里插入图片描述
这里处理器A和处理器B可以同时把共享变量写入自己的写缓冲区(A1,B1),然后从内存 中读取另一个共享变量(A2,B2),最后才把自己写缓存区中保存的脏数据刷新到内存中(A3, B3)。当以这种时序执行时,程序就可以得到x=y=0的结果。
从内存操作实际发生的顺序来看,直到处理器A执行A3来刷新自己的写缓存区,写操作 A1才算真正执行了。虽然处理器A执行内存操作的顺序为:A1→A2,但内存操作实际发生的顺 序却是A2→A1。此时,处理器A的内存操作顺序被重排序了(处理器B的情况和处理器A一样, 这里就不赘述了)。

共享内存多核系统(Shared Memory Multiprocessors System)
在多路处理器系统中,每 个处理器都有自己的高速缓存,而它们又共享同一主内存(Main Memory)


线程切换

为什么内核线程调度切换起来成本就要 更高?】
内核线程的调度成本主要来自于用户态与核心态之间的状态转换,而这两种状态转换的开销主要 来自于响应中断、保护和恢复执行现场的成本。
内核线程的切换开销是来自于保护和恢复现场的成本。具体解释如下:
内核线程的调度成本主要来自于用户态与核心态之间的状态转换,而这两种状态转换的开销主要 来自于响应中断、保护和恢复执行现场的成本。请读者试想以下场景,假设发生了这样一次线程切 换:
线程A -> 系统中断 -> 线程B。处理器要去执行线程A的程序代码时,并不是仅有代码程序就能跑得起来,程序是数据与代码的 组合体,代码执行时还必须要有上下文数据的支撑。而这里说的“上下文”,以程序员的角度来看,是 方法调用过程中的各种局部的变量与资源;以线程的角度来看,是方法的调用栈中存储的各类信息; 而以操作系统和硬件的角度来看,则是存储在内存、缓存和寄存器中的一个个具体数值。物理硬件的 各种存储设备和寄存器是被操作系统内所有线程共享的资源,当中断发生,从线程A切换到线程B去执 行之前,操作系统首先要把线程A的上下文数据妥善保管好,然后把寄存器、内存分页等恢复到线程B 挂起时候的状态,这样线程B被重新激活后才能仿佛从来没有被挂起过。这种保护和恢复现场的工 作,免不了涉及一系列数据在各种寄存器、缓存中的来回拷贝,当然不可能是一种轻量级的操作。

中断

中断是计算机里面的一个最为重要的机制,它也是操作系统获得计算机控制权的根本保证。若没有中断,很难想象操作系统能够完成人们所赋予的任务。中断的基本原理是:设备在完成自己的任务后向CPU发出中断,CPU判断优先级,然后确定是否响应。如果响应,则执行中断服务程序,并在中断服务程序执行完后继续执行原来的程序。中断是一个很复杂的过程,中断处理过程中又可以发生中断,且还可以有所谓的软中断,即软件发出的中断。透彻理解中断对了解计算机操作系统的运行具有重要意义。因此,对中断机制不甚了解的读者请复习在计算机组成与体系结构中所学习的中断内容。
在这里插入图片描述

管道】
管道(pipe)是一种虚文件,它可连接两个进程,所示。如果进程A和B希望通过管道对话,它们必须提前设置该管道。当进程A想对进程B发送数据时,它把数据写到管道上,仿佛管道就是输出文件一样。进程B可以通过读该管道而得到数据,仿佛该管道就是一个输入文件一样。这样,在UNIX中两个进程之间的通信就非常类似于普通文件的读写了。更为强大的是,若进程想发现它所写入的输出文件不是真正的文件而是管道,则需要使用特殊的系统调用。

mine:操作系统的编译过程、开发操作系统的环境------先放过,dai细看书1.8

操作系统通常是由许多程序员写成的,包括很多部分的大型C(有时是C++)程序。用于开发操作系统的环境,与个人(如学生)用于编写小型Java程序的环境是非常不同的。本节简要地介绍编写操作系统的环境。
涉及知识点:C语言和Java的差别;编译C和头文件来构建可执行文件的过程;

单位
在这里插入图片描述

上下文切换

上下文切换指的是内核(操作系统的核心)在CPU上对进程或者线程进行切换。上下文切换过程中的信息被保存在进程控制块(PCB-Process Control Block)中。上下文切换的信息会一直被保存在CPU的内存中,直到被再次使用。

上下文切换 (context switch) , 其实际含义是任务切换, 或者CPU寄存器切换。当多任务内核决定运行另外的任务时, 它保存正在运行任务的当前状态, 也就是CPU寄存器中的全部内容,这些内容被保存在任务自己的堆栈中, 入栈工作完成后就把下一个将要运行的任务的当前状况从该任务的栈中重新装入CPU寄存器, 并开始下一个任务的运行, 这一过程就是context switch。

上下文的切换流程如下
(1)挂起一个进程,将这个进程在CPU中的状态(上下文信息)存储于内存的PCB中。
(2)在PCB中检索下一个进程的上下文并将其在CPU的寄存器中恢复。
(3)跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行)并恢复该进程。

Unix内存操作的API

《操作系统导论》14章

介绍 UNIX 操作系统的内存分配接口。操作系统提供的接口非常简洁。在 UNIX/C 程序中,理解如何分配和管理内存是构建健壮和可靠软件的重要基础。通常使用哪些接
口?哪些错误需要避免?

dai收集,《操作系统导论》14章知识点陈列:两种内存类型:栈和堆。malloc()调用;free()调用;使用malloc()和free()调用时的常见错误。

malloc()和 free()不是系统调用,而是库调用。malloc 库管理虚拟地址空间内的空间,但是它本身是建立在一些系统调用之上的,这些系统调用会进入操作系统,来请求本多内存或者将一些内容释放回系统。

其他

同步、异步、阻塞、非阻塞】

网友-------------------
同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)。同步,就是调用某个东西是,调用方得等待这个调用返回结果才能继续往后执行。异步,和同步相反 调用方不会理解得到结果,而是在调用发出后调用者可用继续执行后续操作,被调用者通过状体来通知调用者,或者通过回掉函数来处理这个调用。
阻塞和非阻塞 强调的是程序在等待调用结果(消息,返回值)时的状态. 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。 对于同步调用来说,很多时候当前线程还是激活的状态,只是从逻辑上当前函数没有返回而已,即同步等待时什么都不干,白白占用着资源。
-----------------------------------------网友

僵尸进程

守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。

孤儿进程:在操作系统领域中,孤儿进程指的是在其父进程执行完成或被终止后仍继续运行的一类进程,这些孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

王道

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述在这里插入图片描述

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Abner_iii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值