操作系统笔记
一. 进程
1. 主存中的进程形态
- 标识符:唯一标记一个进程,用于区别不同进程;
- 状态:标记进程的进程状态;
2.1 创建状态:创建进程时拥有PCB但其他资源尚未就绪的状态称为创建状态;
2.2 就绪状态:当进程被分配到除CPU以外所有必要的资源后即为就绪状态,只要获得CPU的使用权,就可以立即执行。在一个系统中多个处于就绪状态的进程通常排成一个队列,即就绪队列;
2.3 执行状态:当进程获得CPU资源时,即为执行状态。在单处理机中,在某个时刻只能有一个进程处于执行状态;
2.4 阻塞状态:进程因某种原因,程序未执行完毕,而放弃CPU资源,则称为阻塞状态。在一个系统中多个处于阻塞状态的进程通常排成一个队列,即阻塞队列;
2.5 终止状态:进程结束由系统清理或归还PCB的状态称为终止状态。
- 程序计数器:进程即将被执行的下一条指令的地址;
- 内存指针:程序代码、进程数据相关指针;
- 上下文数据:进程执行时处理器存储的数据;
- IO状态信息:被进程IO操作所占用的文件列表;
- 记账信息:使用处理器时间、时钟数总和等;
2. 进程控制块
进程控制块PCB
:用于描述和控制进程运行的通用数据结构,记录进程当前状态和控制进程运行的全部信息。PCB
进程被操作系统读取,PCB常驻内存。
二、进程与线程
一个进程Process
可以有一个或多个线程Thread
。
进程是操作系统进行资源分配和调度的基本单位,线程是操作系统进行运行调度的最小单位。
一个进程可以并发多个线程,每个线程执行不同的任务。
进程的线程共享进程的资源。
- | 进程 | 线程 |
---|---|---|
资源 | 资源分配的基本单位 | 不拥有资源 |
调度 | 独立调度的基本单位 | 独立调度的最小单位 |
系统开销 | 进程系统开销大 | 线程系统开销小 |
开销 | 进程IPC | 读写同一进程数据通信 |
三、进程同步
1. 临界资源
临界资源指的是一些虽作为共享资源却又无法同时被多个线程共同访问的共享资源。当有进程在使用临界资源时,其他进程必须依据操作系统的同步机制等待占用进程释放该共享资源才可重新竞争使用共享资源。
2. 为什么要进行进程间通讯?
对竞争资源在多进程间进行使用次序的协调,使得并发执行的多个进程之间可以有效使用资源和相互合作。
3. 进程间同步原则
- 空闲让进:资源无占用,允许使用;
- 忙则等待:资源有占用,请求进程等待;
- 有限等待:保证有限等待时间能够使用资源。防止进程长时间未响应;
- 让权等待:等待时,进程需要让出CPU资源。进程由执行状态,进入阻塞状态。
4. 进程同步的方法
消息队列、共享内存、信号量
4.1 共享内存
所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享内存段“连接到”他们自己的地址空间里去。所有进程都能访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会即时被有访问同一段共享内存的其他进程看到。共享内存的使用大大降低了在大规模数据处理过程中内存的消耗,但是共享内存的使用中有很多的陷阱,一不注意就很容易导致程序崩溃。
四、线程同步
1. 线程同步的方法
- 互斥量/互斥锁
在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
原子性:指一系列操作不可被中断的特性。这系列操作要么全部被执行,要么均为执行。
import threading,time
def add(i):
lock.acquire()
global num
num += 1
time.sleep(0.1)
print('add: ', i, num)
lock.release()
def sub(i):
lock.acquire()
global num
num -= 1
time.sleep(0.1)
print('sub: ', i, num)
lock.release()
if __name__ == "__main__":
lock = threading.Lock()
num = 0
for i in range(10):
t = threading.Thread(target=add, args=(i,))
t.start()
t = threading.Thread(target=sub, args=(i,))
t.start()
- 读写锁
读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。在读写锁保持期间也是抢占失效的。如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁。 - 自旋锁
自旋锁避免了进程或线程上下文切换的开销。操作系统内部很多地方使用的是自旋锁。自旋锁不适合在单核CPU使用,因为自旋锁在等待过程中,不会释放CPU资源。
何谓自旋锁:它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。 - 条件变量
转载 三者区别
条件变量是线程的另外一种同步机制,这些同步对象为线程提供了会合的场所,理解起来就是两个(或者多个)线程需要碰头(或者说进行交互-一个线程给另外的一个或者多个线程发送消息),我们指定在条件变量这个地方发生,一个线程用于修改这个变量使其满足其它线程继续往下执行的条件,其它线程则接收条件已经发生改变的信号。条件变量同锁一起使用使得线程可以以一种无竞争的方式等待任意条件的发生。所谓无竞争就是,条件改变这个信号会发送到所有等待这个信号的线程。而不是说一个线程接受到这个消息而其它线程就接收不到了。
五、Linux
1. 进程的类型
- 前台进程:具有终端,可以和用户交互的进程;
- 后台进程:不占用终端的就是后台进程。将需要执行的命令以
&
结尾,来启动一个后端进程; - 守护进程:是特殊的后台进程。进程名字以
d
结尾的一般都是守护进程。当系统启动时开始执行,一直到系统关闭。
2. 特殊进程
当在进程A中,创建一个进程B,则A为B的祖先进程,B为A的子进程。
ID为0
的进程为idle
进程,是系统创建的第一个进程。
ID为1
的进程为init
进程,是0
号进程的子进程,完成系统初始化。init
进程是所有用户进程的祖先进程。
3. 进程标记
状态符号 | - | 状态说明 |
---|---|---|
R | TASK_RUNNING | 进程整处于允许状态 |
S | TASK_INTERRUPTIBLE | 进程正处于睡眠状态 |
D | TASK_UNINTERRUPTIBLE | 进程正在处于IO等待的睡眠状态 |
T | TASK_STOPPED | 进程处于暂停状态 |
Z | TASK_DEAD or EXIT_ZOMBIE | 进程正处于退出状态,或僵尸进程 |
4. 进程相关命令
4.1 ps命令
ps // 当前终端shell所运行的进程
ps -aux // 打印进程的具体信息
ps -u root // 查看root(或其他用户)的进程
ps -aux | grep 'python' // 查看python(或其他程序)的进程
ps -ef // 显示所有进程信息,连同命令行
ps -ef --forest // 查看进程树,显示进程的父子关系
ps -aux --sort=-pcpu // 根据进程的 CPU 使用状态进行排序
ps -aux --sort=-pmem // 根据进程的 内存 使用状态进行排序
4.2 top命令
top // 显示进程信息
top -p 139 // 显示进程号为139的进程信息,CPU、内存占用率等
4.3 kill命令
kill -KILL [PID] // 强制杀死进程
kill -9 [PID] // 彻底杀死进程
kill -l // 查看操作系统支持的信号
kill -u root // 杀死root(或其他用户)的所有进程
5. 死锁
死锁是指两个或多个进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,他们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
5.1 死锁的四个充分必要条件
- 互斥条件:进行请求的资源只能由一个进程使用,其他程序需要只能进行等待;
- 请求保持条件:进程至少占用一个资源,又提出新的资源请求。新资源被占用,请求被阻塞;
破坏请求保持条件:系统规定进程运行之前,一次性申请所有需要的资源,在执行期间,不会提出资源请求。 - 不可剥夺条件:进程获得的资源在未完成前不能被剥夺;
破坏不可剥夺条件:当一个进程请求新的资源得不到满足时,必须释放占有的资源。 - 环路等待条件:必然存在资源环形链。进程A需要进程B的占用资源才能继续执行,进程B需要进程A的占用资源才能继续执行。
破坏环路等待条件:可用资源线性排序,申请必须按照需要递增申请,线性申请不再形成环路。
5.2 银行家算法
银行家算法(Banker’s Algorithm)是一个避免死锁(Deadlock)的著名算法,是由艾兹格·迪杰斯特拉在1965年为T.H.E系统设计的一种避免死锁产生的算法。它以银行借贷系统的分配策略为基础,判断并保证系统的安全运行。
六、存储管理
1. 虚拟内存
虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。目前,大多数操作系统都使用了虚拟内存,如Windows家族的“虚拟内存”;Linux的“交换空间”等。虚拟内存也被称为页面文件,因为其文件名称为PageFile.Sys
。
2. Linux的存储管理
2.1 Buddy内存管理算法
页内碎片:是已经被分配出去(能明确指出属于哪个进程)的内存空间大于请求所需的内存空间,不能被利用的内存空间就是内部碎片。
页外碎片是指还没有分配出去(不属于任何进程),但是由于大小而无法分配给申请内存空间的新进程的内存空闲块。
算法的内存分配原则:向上取整为2的幂大小。当进程申请90k时,系统向其分配128k内存;当进程申请145k时,系统向其分配256k内存。
伙伴系统的伙伴:一片连续内存的“伙伴”是相邻的另一片大小一样的连续内存。
原理:进程申请120k内存,算法先从1024k开始拆分,1024 -> 512 -> 256 -> 128,128k满足需要,将内存分配给进程。拆分过程中,1024 -> 512 -> 256 -> 128组成链表,当回收128k时,将其伙伴进行合并,一直合并到1024。合并一次,链表对应节点弹出一次,链表为空时,回收完成。
算法是基于计算机处理二进制的优势,具有极高的效率。算法主要是为了解决内存外碎片问题,是将内存外碎片问题转换成了内存内碎片问题。
3. Linux的交换空间
交换空间swap
是磁盘的一个分区。Linux物理内存满时,会把一些内存文件交换至swap
空间。
作用:1. 冷启动内存依赖:某些文件在程序启动时会被使用,当程序开始运行时则不会使用,就可以把这些文件存储在交换空间中;2. 系统睡眠依赖:当系统睡眠时,会把内存中文件存储在交换空间中;3. 大进程空间依赖:当内存不能满足进程的运行要求时,则会使用交换空间。
3.1 交换空间与虚拟内存
交换空间 | 虚拟内存 |
---|---|
Swap空间存在于磁盘 | 虚拟内存存在于磁盘 |
Swap空间与主存发生置换 | 虚拟内存与主存发生置换 |
Swap空间是操作系统概念 | 虚拟内存是进程概念 |
Swap空间解决系统物理内存不足问题 | 虚拟内存解决进程物理内存不足问题 |
4. Linux的文件
5. Linux的文件系统
- FAT(File Allocation Table):微软Dos/Windows使用的文件系统。使用一张表保存盘块的信息;
- NTFS(New Technology File System):WindowsNT环境的文件系统。NTFS对FAT进行了改进,取代了旧的文件系统;
- EXT2/3/4(Extended File System):扩展文件系统。Linux的文件系统。EXT4表示第四代的EXT系统;
Linux支持以上三种文件系统。Windows不支持EXT系统。