操作系统-超20000字的“总结”
 南窗  分类:IT技术  人气:97  回帖:0  发布于1年前 收藏

概述

什么是操作系统

管理计算机硬件和软件资源的系统软件

  • 管理计算机系统的硬软件
  • 分配调度资源的系统软件

操作系统的目标

方便性、有效性、可扩充性、开放性

提高系统资源的利用率,提高系统的吞吐量

基本功能

  • 统一管理计算机资源
    • 处理器资源
    • IO设备资源
    • 存储器资源
    • 文件资源
  • 实现计算机资源的抽象
    • IO设备管理软件提供读写接口、文件管理软件提供操作文件接口
  • 提供用户与计算机之间的接口
    • GUI
    • 命令行事
    • 系统调用形式

特征

最基本的特征,互为存在的条件:并发、共享

  • 并发: 指两个和多个事件可以在同一时间间隔发生(同一时间段,但这个事件段很短),宏观的同时,实际交替执行
  • 并行: 指两个或多个事件可以在同一时刻(同一个时间点)发生,多个CPU可以实现并行,一个CPU同一时刻只有一个程序在运行,同理可以在单个CPU上实现并发
  • 共享: OS中的资源可以共多个并发的程序共同使用 - 互斥共享:当资源被占用时,其他想使用的程序智能等待 - 同时访问:某种资源并发的被多个程序访问
  • 虚拟 把一个物理实体转变为若干个逻辑实体 - 时分复用技术:资源在时间上进行复用,不同程序并发使用,多道程序分时使用计算机的硬件资源,提高资源的利用率。 - 空分复用技术:用来实现虚拟磁盘(物理磁盘虚拟为逻辑磁盘,电脑上的C盘、D盘等)、虚拟内存(在逻辑上扩大程序的存储容量)等,提高资源的利用率,提高编程效率。
  • 异步 在多道程序环境下,允许多个进程并发执行,但由于资源等因素的限制,使进程的执行以“停停走走”的方式运行,而且每个进程执行的情况(运行、暂停、速度、完成)也是未知的

中断处理

中断机制的作用:为了在多道批处理系统中让用户进行交互;

一.单道批处理系统

1.概念

在这里插入图片描述

2.特点

  • 自动:作业自动运行,无需干预
  • 批量:磁带上的各个作业按顺序地进入内存,先调入先完成
  • 单道:内存中仅有一道程序运行,可以看成是串行的

3.CPU的利用情况

在这里插入图片描述

分析:外设和CPU交替空闲和忙碌,CPU和外设利用效率低

4.缺点

从单道批处理系统对CPU的利用情况可看出,作业运行过程中若发生IO请求,高速的CPU要等待低速的I/O操作完成,导致CPU资源利用率和系统吞吐量降低。

二. 多道批处理系统

1.概念

内存中存放多道程序,当某道程序因某种原因如执行I/O操作时而不能继续运行放弃CPU时,操作系统便调度另一程序运行,这样CPU就尽量忙碌,达到提高系统效率的目的。

2.特点

  • 多道:内存同时存放多道程序
  • 宏观上并行:进入系统的多道程序先后开始了自己的运行,但都未运行完毕
  • 微观上串行:内存中多道程序轮流占有CPU,交替执行

3.CPU的利用情况

在这里插入图片描述

分析:程序A要通过操作系统的调度进行磁盘操作,B则进行磁带操作。当程序A执行I/O请求时,A放弃了CPU,操作系统接着调度B,B开始占用CPU(红宽线),此时程序A的磁盘操作也在同时进行。

结论:A,B两道程序相互穿插运行,使CPU和外设都尽量忙碌。

4.缺点

  • 作业处理时间长
  • 交互能力差
  • 运行过程不确定

其他

image-20230222020508690
image-20230222020550785

中断产生:

  • 发生中断时,CPU立马切换到管态,开展管理工作;(管态又叫特权态,系统态或核心态,是操作系统管理的程序执行时,机器所处的状态。)
  • 发生中断后,当前运行的进程回暂停运行,由操作系统内核对中断进行处理;
  • 对于不同的中断信号,会进行不同的处理。

中断的分类:

  • 内中断(也叫“异常”、“例外”、“陷入”)------- 信号来源:CPU内部,与当前执行指令有关;
  • 外中断(中断)----------信号来源:CPU外部,与当前执行指令无关。

外中断的处理过程:

  • 每执行完一个指令后,CPU都需要检查当前是否有外部中断信号;
  • 如果检查到外部中断信号,则需要保护被中断进程的CPU环境(如程序状态字PSW,程序计数器PC、各种通用寄存器)把他们存储在PCB(进程控制块中);
  • 根据中断信号类型转入相应的中断处理程序;
  • 恢复原进程的CPU环境并退出中断,返回原进程继续执行。

其他概念

指令

  • 特权指令:不允许用户程序使用(只允许操作系统使用)。如IO指令、置中断指令
  • 非特权指令:普通的运算指令

程序

  • 内核程序:系统的管理者,可执行—切指令、运行在核心态
  • 应用程序:普通用户程序只能执行非特权指令,运行在用户态

处理机状态

  • 用户态(目态):CPU只能执行非特权指令
  • 核心态(又称管态、内核态):可以执行所有指令用户态到核心态:通过中断(是硬件完成的)
  • 核心态到用户态:特权指令psw的标志位 0用户态 1核心态

原语

  • 处于操作系统的最低层,是最接近硬件的部分。
  • 这些程序的运行具有原子性,其操作只能一气呵成
  • 这些程序的运行时间都较短,而且调用频繁。

中断和异常

  • 内中断(异常,信号来自内部)
    • 自愿中断
      • 指令中断
    • 强迫中断
      • 硬件中断
      • 软件中断
  • 外中断(中断,信号来着外部)
    • 外设请求
    • 人工干预

系统调用

系统给程序员(应用程序)提供的唯一接口,可获得OS的服务。在用户态发生,核心态处理

体系结构

  • 大内核
  • 微内核

进程管理

进程实体

引入进程目的

为了更好地描述和控制程序并发执行,实现操作系统的并发性和共享性(进程是动态的,程序是静态的)

进程的定义

是计算机中的程序关于某数据集合上的一次运行活动是系统进行资源分配和调度的基本单位(没有引入线程时 )

为什么需要进程:

  1. 进程是系统进行资源分配和调度的基本单位
  2. 进程作为程序独立运行的载体保障程序正常执行;
  3. 进程的存在使得操作系统资源的利用率大幅提升。

进程控制块(PCB)

用于描述和控制进程运行的通用数据结构,记录进程当前状态和控制进程运行的全部信息,是进程存在的唯一标识

进程(Process)与线程(Thread)

  • 线程:操作系统进行运行调度的最小单位
  • 进程:系统进行资源分配和调度的基本单位

区别与联系

  • 一个进程可以有一个或多个线程
  • 线程包含在进程之中,是进程中实际运行工作的单位
  • 进程的线程共享进程资源
  • 一个进程可以并发多个线程,每个线程执行不同的任务
image-20210826153718544

进程管理五状态模型

  • 创建状态:创建进程时拥有PCB但其它资源尚未就绪
  • 就绪状态:其它资源(进程控制块、内存、栈空间、堆空间等)都准备好、只差CPU的状态
  • 执行状态:进程获得CPU,其程序正在执行
  • 阻塞状态:进程因某种原因放弃CPU的状态,阻塞进程以队列的形式放置。
  • 终止状态:进程结束由系统清理或者归还PCB的状态。
image-20210830134139425

经典问题

生产者-消费者问题

有一群生产者进程在生产产品,并将这些产品提供给消费者进程进行消费,生产者进程和消费者进程可以并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程需要将所生产的产品放到缓冲区中(+1操作),消费者进程可以从缓冲区取走产品消费(-1操作)。

(感觉有点微服务的意思了)
image-20210826155239580
image-20210826155306444

产生问题:当两者并发执行时可能出差错,导致预期的结果与真实的结果不相符:当执行生产者+1和消费者-1操作之后,缓冲区的值从10变为了11

image-20210826155457272

问题解决

  • 在缓冲区为空时,消费者不能再进行消费
  • 在缓冲区为满时,生产者不能再进行生产
  • 在一个线程进行生产或消费时,其余线程不能再进行生产或消费等操作,即保持线程间的同步
  • 注意条件变量与互斥锁的顺序
这里写图片描述

由于前两点原因,因此需要保持线程间的同步,即一个线程消费(或生产)完,其他线程才能进行竞争CPU,获得消费(或生产)的机会。对于这一点,可以使用条件变量进行线程间的同步:生产者线程在product之前,需要wait直至获取自己所需的信号量之后,才会进行product的操作;同样,对于消费者线程,在consume之前需要wait直到没有线程在访问共享区(缓冲区),再进行consume的操作,之后再解锁并唤醒其他可用阻塞线程。

image-20230222023013803
image-20230222022910958

哲学家进餐问题

有5个哲学家,他们的生活方式是交替的思考和进餐,哲学家们共同使用一张圆桌,分别坐在5张椅子上,圆桌上有5只碗和5只筷子。平时哲学家们只进行思考,饥饿时则试图取靠近他们的左右两只筷子,只有两只筷子都被拿到的时候才能进餐,否则等待,进餐完毕后,放下左右筷子进行思考。

image-20210826155708446

这会导致以下的问题,筷子就相当于临界资源:

临界资源指的是一些虽作为共享资源却又无法同时被多个线程共同访问的共享资源。当有进程在使用临界资源时,其他进程必须依据操作系统的同步机制等待占用进程释放该共享资源才可重新竞争使用共享资源。

image-20210826155818721

问题解决

方法一:当两边的叉子都可用时才拿

当某一个哲学家能够同时拿起左右两只叉子时,才让他拿,这样就能够保证不会因为每个科学家都只拿了一只叉子而导致死锁。

为了保证能够同时拿起,我们需要对拿叉子这一步骤进行加锁,保证哲学家能够同时拿起一双叉子,而不会拿了一边后另一边被人抢走

class DiningPhilosophers {
public:
    DiningPhilosophers() 
    {}

    void wantsToEat(int philosopher,
                    function<void()> pickLeftFork,
                    function<void()> pickRightFork,
                    function<void()> eat,
                    function<void()> putLeftFork,
                    function<void()> putRightFork) 
    {
        //对拿叉子进行这一流程进行加锁,保证其能同时拿起一双,而不会被其他人抢走
        _lock.lock();
        _fork[philosopher].lock();
        _fork[(philosopher + 1) % 5].lock();
		_lock.unlock();
		
		//拿起左右叉子
        pickLeftFork();	
        pickRightFork();

        eat();	//吃饭
        
		//放下左右叉子
        putLeftFork();
        putRightFork();
        
        //解锁,让其他人获取叉子
        _fork[philosopher].unlock();
        _fork[(philosopher + 1) % 5].unlock();
    }

private:
    mutex _lock;	
    mutex _fork[5];
};
方法二:限制就餐的哲学家数量(或者说,多加一支筷子)

如果要保证至少有一个哲学家能够进餐,那么我们可以采用最简单粗暴的方法,限制人数,只要同时进餐的哲学家不超过四人时,即使在最坏情况下,也至少有一个哲学家能够拿到多出来的那一个叉子。

我们需要用到一个计数器来表示当前就餐的人数,为了保证线程安全我们需要用到一个互斥锁和一个条件变量对其进行保护

class DiningPhilosophers {
public:
    DiningPhilosophers()
        :_count(0)
    {}

    void wantsToEat(int philosopher,
                    function<void()> pickLeftFork,
                    function<void()> pickRightFork,
                    function<void()> eat,
                    function<void()> putLeftFork,
                    function<void()> putRightFork) 
    {
        
        unique_lock<mutex> lock(_mtx);
        _cond.wait(lock, [this]()->bool{
            return _count < 4;
        });    //当就餐人数不超过四人的时候允许拿叉子
        ++_count;

        _fork[philosopher].lock();
        _fork[(philosopher + 1) % 5].lock();
        pickLeftFork();
        pickRightFork();

        eat();

        putLeftFork();
        putRightFork();
        _fork[philosopher].unlock();
        _fork[(philosopher + 1) % 5].unlock();

        --_count;
        _cond.notify_one();	//就餐完成,让下一个人进来就餐
    }

private:
    mutex _fork[5];
    mutex _mtx;
    condition_variable _cond;
    int _count;
};
方法三:奇数先左后右,偶数先右后左

由于餐桌是一个如下图的圆环,如果我们此时规定奇数位的哲学家先拿左边的叉子,再拿右边的叉子。而偶数位的哲学家先拿右边的再拿左边的,此时竞争情况如下图所示

在这里插入图片描述

此时2号和3号哲学家争抢3号叉子,4号和5号哲学家争抢5号叉子,1号没有竞争对手,直接获取叉子1。

可以看到,在第一轮中所有哲学家先去争抢奇数叉子,抢到偶数叉子后再去争抢偶数叉子,这样就能够保证至少有一个科学家能够获得两只叉子

class DiningPhilosophers {
public:
    DiningPhilosophers()
    {}

    void wantsToEat(int philosopher,
                    function<void()> pickLeftFork,
                    function<void()> pickRightFork,
                    function<void()> eat,
                    function<void()> putLeftFork,
                    function<void()> putRightFork) 
    {
        //如果是奇数则先抢左后抢右
        if(philosopher & 1)
        {
            _fork[philosopher].lock();
            _fork[(philosopher + 1) % 5].lock();
            pickLeftFork();
            pickRightFork();
        }
        //如果是偶数则先抢右后抢左
        else
        {
            _fork[(philosopher + 1) % 5].lock();
            _fork[philosopher].lock();
            pickRightFork();
            pickLeftFork();
            
        }
        eat();  //吃饭

        putLeftFork();  //放下叉子
        putRightFork();
        _fork[philosopher].unlock();
        _fork[(philosopher + 1) % 5].unlock();
    }
private:
    mutex _fork[5];
};

进程通信

管道/匿名管道(Pipes) :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。

有名管道(Named Pipes) : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出(first in first out)。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。

信号(Signal) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;

消息队列(Message Queuing) :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显式地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺点。

信号量(Semaphores) :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。

共享内存(Shared memory) :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。

套接字(Sockets) : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

进程同步

进程同步的作用

对竞争资源在多进程间进行使用次序的协调,使得并发执行的多个进程之间可以有效使用资源和相互合作

进程间同步的四原则

  • 空闲让进:资源无占用,允许使用;
  • 忙则等待:资源被占用,请求进程等待;
  • 有限等待:保证有限等待时间能够使用资源;
  • 让权等待:等待时,进程需要让出CPU。

进程同步的方法

image-20210826215825209

使用fork系统调用创建进程

使用fork系统调用无参数,fork会返回两次,分别返回子进程id和0,返回子进程id的是父进程,返回0的是子进程。

如果创建失败,返回-1

image-20210830162044792
  • fork系统调用是用于创建进程的;
  • fork创建的进程初始化状态与父进程一样;
  • 系统会为fork的进程分配新的资源

子进程一般继承父进程:用户信息、权限、目录信息、信号信息、环境表、共享存储段和资源限制。 (86条消息) 子进程和父进程的关系和示例_xujiali5172923的博客-CSDN博客 【Linux 进程】fork父子进程间共享数据分析 - 我得去图书馆了 - 博客园 (cnblogs.com) 进程——父子进程共享 - _程序兔 - 博客园 (cnblogs.com) (1)父子进程 子进程通过父进程创建,子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程什么时候结束。 当子进程退出的时候,内核会释放子进程所有资源,包括打开的文件,占用的内存等。但是依然会保留部分信息(进程id,退出状态,运行时间),直到父进程通过wait/waitpid来调用获取子进程状态信息后才释放 (86条消息) 面试中常被问到的(18)父子进程,孤儿进程及僵尸进程_HT . WANG的博客-CSDN博客

孤儿进程

一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程,孤儿进程将被init进程(1号进程)托管,由init进程负责完成状态收集工作

  • IDEA启动SpringBoot项目,关掉IDEA后,SpringBoot(未终止没有断开连接),仍在后台
僵尸进程

通常情况下,子进程退出后,父进程会使用 waitwaitpid 函数进行回收子进程的资源,并获得子进程的终止状态。(如果父进程在子进程结束之前退出,则子进程由init接管。init将会以父进程身份对僵尸状态的子进程进行处理)

但是,如果父进程先于子进程结束,则子进程成为孤儿进程。孤儿进程将被 init 进程(进程号为1)领养,并由 init 进程对孤儿进程完成状态收集工作。

而如果子进程先于父进程退出,同时父进程太忙了,无瑕回收子进程的资源,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程,如下图所示:

img

Linux系统僵尸进程详解 - 良许Linux - 博客园 (cnblogs.com)

共享内存

在某种程度上,多进程是共同使用物理内存的,但是由于操作系统的进程管理,进程间的内存空间是独立的,因此进程默认是不能访问进程空间之外的内存空间的。

  • 共享存储允许不相关的进程访问同一片物理内存;
  • 共享内存是两个进程之间共享和传递数据最快的方式
  • 共享内存未提供同步机制,需要借助其他机制管理访问;
image-20210826223244411

Unix域套接字

域套接字是一种高级的进程间通信的方法,可以用于同一机器进程间通信。

套接字(socket):为网络通信中使用的术语。

Unix系统提供的域套接字提供了网络套接字类似的功能,如Nfinx、uWSGI等。

服务端和客户端分别使用Unix域套接字的过程:

image-20210826223709480

线程同步

线程同步的方法

  • 互斥锁
互斥锁是最简单的线程同步的方法,也称为互斥量,处于两态之一的变量:解锁和加锁,两个状态可以保证资源访问的串行。 原子性:指一系列操作不可被中断的特性,要么全部执行完成,要么全部没有执行。
image-20210826220013572
  • 自旋锁

自旋锁是一种多线程同步的变量,使用自旋锁的线程会反复检查锁变量是否可用,自旋锁不会让出CPU,是一种忙等待状态,即死循环等待锁被释放自旋锁的效率远高于互斥锁。特点:避免了进程或者线程上下文切换的开销,但是不适合在单核CPU使用

常见的例子:分布式锁设计

  • 读写锁

是一种特殊的自旋锁,允许多个读操作同时访问资源以提高读性能,但是对写操作是互斥的,即对多读少写的操作效率提升很显著。

  • 条件变量

是一种相对比较复杂的线程同步方法,条件变量允许线程睡眠,直到满足某种条件,当满足条件时,可以给该线程信号通知唤醒

线程同步方法的对比

image-20210826222325975

image-20210826222346498

image-20210826222400048

Linux的进程管理

进程类型

  • 前台进程:具有终端,可以和用户交互;
  • 后台进程:没有占用终端,基本不和用户交互,优先级比前台进程低(将需要执行的命令以“&”符号结束);
  • 守护进程:特殊的后台进程,在系统引导时启动,一直运行直到系统关闭(进程名字以“d”结尾的一般都是守护进程),如crond、sshd、httpd、mysqld…

(86条消息) 带你了解Docker背后的守护进程_董哥的黑板报的博客-CSDN博客

进程标记

  • 进程ID:非负整数,进程的唯一标记,每个进程拥有不同的ID;
  • 进程的状态标记:R表示进程处于运行状态,S表示进程处于睡眠状态…
img

操作Linux进程的相关命令:

  1. ps命令:列出当前的进程,结合-aux可以打印进程的详细信息(ps -aux);
  2. top命令:查看所有进程的状态;
  3. kill命令:给进程发送信号。

作业管理(处理机调度)

处理机是什么?

简单来说,处理机指的是硬件,它包含cpu在内(早期CPU由运算器和控制器组成,称为中央处理机),而内核是操作系统中的概念,是操作系统的核心,是属于软件部分。

  • 处理机包括中央处理器,主存储器,输入-输出接口,加接外围设备就构成完整的计算机系统。
  • 处理机是处理计算机系统中存储程序和数据,并按照程序规定的步骤执行指令的部件。
  • 程序是描述处理机完成某项任务的指令序列。 指令则是处理机能直接解释、执行的信息单位。

概念

是对处理机进行分配,即从就绪队列中按照定的算法(公平、高效)选择一个进程并将处理机分配给它运行,以实现进程并发地执行。

分类

  • 高级调度(作业调度)

按一定的原则从外存上处于后备队列的作业中挑选一个(或多个)作业,给他们分配内存等必要资源,并建立相应的进程(建立PCB),以使它(们)获得竟争处理机的权利。

高级调度是辅存(外存)与内存之间的调度。每个作业只调入一次,调出一次。作业调入时会建立相应的PCB,作业调出时才撤销PCB。高级调度主要是指调入的问题,因为只有调入的时机需要操作系统来确定,但调出的时机必然是作业运行结束才调出。

  • 中级调度(内存调度)

为了使内存中的内存不至于太多,有时需要把某些进程从内存中调到外存。在内存使用情况紧张时,将一些暂时不能运行的进程从内存中对换到外存中等待。当内存有足够的空闲空间时,再将合适的进程重新换入内存。

  • 低级调度(进程调度)

主要任务是按照某种方法和策略从就绪队列中选取一个进程,将处理机分配给它。进程调度是操作系统中最基本的一种调度,在一般的操作系统中都必须配置进程调度。进程调度的频率很高,一般几十毫秒一次。

(86条消息) 三级调度: 高级调度、中级调度、低级调度彭嘭嘭的博客-CSDN博客高级调度中级调度低级调度高级调度(作业调度)、低级调度(进程调度)、中级调度 - 简书 (jianshu.com)

调度方式

  • 剥夺式(抢占式):进程1在运行,进程2优先级比进程1高,进程2直接上处理器
    • 原则1:优先级原则,允许优先级高的并且是新到的进程可以抢占当前进程的处理机
    • 原则2:短进程原则
    • 原则3:时间片原则
  • 非剥夺式(非抢占式):进程1在运行,即使进程2优先级比进程1高,进程2也得等待进程1执行完上处理器

非抢占式调度:只能由当前运行的进程主动放弃CPU;处理器一旦分配给某个进程,就让该进程一直使用下去; 调度程序不以任何原因抢占正在被使用的处理器; 调度程序不以任何原因抢占正在被使用的处理器; 抢占式调度:可由操作系统剥夺当前进程的CPU使用权。允许调度程序以一定的策略暂停当前运行的进程; 保存好旧进程的上下文信息,分配处理器给新进程;

image-20210826162907842

进程调度的三大机制

就绪队列排队

为了提高进程调度的效率,将就绪进程按照一定的方式排成队列,以便调度程序可以最快找到就绪进程。

image-20210830141937877

选择运行进程委派

调度程序以一定的策略,选择就绪进程,将CPU资源分配给它。

新老进程上下文切换

存当前进程的上下文信息,装入被委派执行进程的运行上下文

image-20210830141949702

调度准则

  • CPU利用率
  • 系统吞吐量

单位时间内cpu完成作业的数目

  • 周转时间

作业的完成时间-提交时间

  • 等待时间

进程与等待处理机的时间之和

  • 响应时间

从提交到第一次开始运行的时间

算法

先来先服务(FCFS):

算法原理:按照作业(进程)到达的先后次序来进行调度,谁先来,谁就先被调度。 缺点:忽略了作业的运行时间

短作业优先(SJF):

算法原理:以作业的长短来计算优先级,作业越短优先级越高,作业长短以所要求的运行时间来衡量。 缺点:必须预先知道作业的运行时间、对长作业不利,未考虑作业的紧迫程度。

例题

img

解:“作业被调度进入运行后不再退出"意为非抢占式调用,job2到来时也得等待job1执行完

job1最先达到,运行60分钟,此时job2-6已经全部提交,此时从job2-6中挑选运行时间最短的,那么顺序依次为1→5→6→3→4→2

标准流程如图(要写出作业号、提交时间、运行时间、开始时刻、完成时刻、周转时间):

img

②周转时间=完成时间-提交时间

平均周转时间=1/n *(N1+N2+……+Nn)

(n为作业过程总数,N1、N2为周转时间)

优先级调度算法:

算法原理:FCFS、SJF两种算法都不能反映进程的紧迫程度。而优先级调度算法是外部赋予进程相应的优先级,来体现出进程的紧迫程度,紧迫性进程优先运行

(如何确定优先级:

1、利用某一范围内的一个整数,优先数

2、响应比的大小,谁响应比大,谁优先级就大——高响应比优先调度算法)

高响应比优先调度算法

响应比=作业周转时间/作业处理时间=(作业处理时间+作业等待时间)/作业处理时间=1+(作业等待时间/作业处理时间)

时间片轮转

适合系统:分时系统

算法原理:基于时间片的轮转,非常公平,就绪队列中的每一个进程每次仅仅运行一个时间片,并且每个进程是轮流运行。

首先按照FCFS策略把就绪进程排成一个就绪队列,设置时间片,从第一个进程开始分配处理机,第一个进程的时间片执行完后,再从就绪队列中新的队首进程开始。若进程已经运行完,注意此时第一个进程就已经不在就绪队列的队首,而是从就绪队列中删除。若未执行完只是时间片完了,则是调度程序把它送往末尾去了。

多级反馈队列调度算法

算法原理(调度机制):

设置多个就绪队列,每个队列赋予不同的优先级,第一个队列优先级最高,并且首先调度最高优先级,也就是第一个队列里面的所有进程,仅当第一个队列空闲时,才开始调度第二个队列中的进程运行。优先级越高的队列,时间片越小。

总结

先来先服务算法:按照在就绪队列中的先后顺序执行。 短进程优先调度算法:优先选择就绪队列中估计运行时间最短的进程,不利于长作业进程的执行。 高优先权优先调度算法:进程附带优先权,优先选择权重高的进程,可以使得紧迫的任务优先处理。 时间片轮转调度算法:按照FIFO的原则排列就绪进程,每次从队列头部取出待执行进程,分配一个时间片执行,是相对公平的调度算法,但是不能保证就是响应用户。

死锁

进程死锁、饥饿、死循环的区别

  • 死锁两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。永远在互相等待的进程称为死锁进程。
  • 饥饿:由于长期得不到资源导致进程无法推进;
  • 死循环:代码逻辑BUG。

死锁的产生

竞争资源(共享资源数量不满足各进程需求)、进程调度顺序不当,当调度顺序为A->B->C->D时会产生死锁,但改为A->D->B->C则不会产生。
image-20210826163418015

死锁的必要条件

  • 互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
  • 不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。
  • 请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
  • 循环等待条件:在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所申请地资源。

以上给出了导致死锁的四个必要条件,只要系统发生死锁则以上四个条件至少有一个成立。事实上循环等待的成立蕴含了前三个条件的成立,似乎没有必要列出然而考虑这些条件对死锁的预防是有利的,因为可以通过破坏四个条件中的任何一个来预防死锁的发生。

死锁的处理策略

预防死锁的方法

破坏四个必要条件的中一个或多个。

  • 破坏互斥条件:将临界资源改造成共享资源(Spooling池化技术);(可行性不高,很多时候无法破坏互斥条件)
  • 破坏请求保持条件:系统规定进程运行之前,一次性申请所有需要的资源;(资源利用率低,可能导致别的线程饥饿)

第一种方法静态分配即每个进程在开始执行时就申请他所需要的全部资源。 第二种是动态分配即每个进程在申请所需要的资源时他本身不占用系统资源。

  • 破坏不可剥夺条件:当一个进程请求新的资源得不到满足时,必须释放占有的资源;(实现复杂,剥夺资源可能导致部分工作失效,反复申请和释放造成额外的系统开销)

一个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加入到 系统的资源列表中,可以被其他的进程使用,而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动,执行。

  • 破坏环路等待条件:可用资源线性排序,申请必须按照需要递增申请;(进程实际使用资源顺序和编号顺序不同,会导致资源浪费)

采用资源有序分配其基本思想是将系统中的所有资源顺序编号,将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程

银行家算法

检查当前资源剩余是否可以满足某个进程的最大需求;如果可以,就把该进程加入安全序列,等待进程允许完成,回收所有资源;重复1,2,直到当前没有线程等待资源;

(85条消息) 银行家算法详解(C语言)Sparky*的博客-CSDN博客银行家算法数据结构

死锁的检测和解除

死锁检测算法,资源剥夺法,撤销进程法(终止进程法),进程回退法;

存储管理

存储管理为了确保计算机有足够的内存处理数据;确保程序可以从可用内存中获取一部分内存使用;确保程序可以归还使用后的内存以供其他程序使用。

img

内存分配的过程

  • 单一连续分配(已经过时)
  • 固定分区分配
  • 动态分区分配(根据实际需要,动态的分配内存)

动态分配算法

  • 首次适应算法(First Fit):该算法从空闲分区链首开始查找,直至找到一个能满足其大小要求的空闲分区为止。然后再按照作业的大小,从该分区中划出一块内存分配给请求者,余下的空闲分区仍留在空闲分区链中。
  • 特点: 该算法倾向于使用内存中低地址部分的空闲区,在高地址部分的空闲区很少被利用,从而保留了高地址部分的大空闲区。显然为以后到达的大作业分配大的内存空间创造了条件。
> [(85条消息) 什么是高地址,什么是低地址,举举例说明?_高地址和低地址_方园几里的博客-CSDN博客](https://blog.csdn.net/CSDNmilu/article/details/123095988)
  • 缺点:低地址部分不断被划分,留下许多难以利用、很小的空闲区,而每次查找又都从低地址部分开始,会增加查找的开销。
  • 最佳适应算法(Best Fit):该算法总是把既能满足要求,又是最小的空闲分区分配给作业。为了加速查找,该算法要求将所有的空闲区按其大小排序后,以递增顺序形成一个空白链。这样每次找到的第一个满足要求的空闲区,必然是最优的。孤立地看,该算法似乎是最优的,但事实上并不一定。因为每次分配后剩余的空间一定是最小的,在存储器中将留下许多难以利用的小空闲区。同时每次分配后必须重新排序,这也带来了一定的开销。
  • 特点:每次分配给文件的都是最合适该文件大小的分区。
  • 缺点:内存中留下许多难以利用的小的空闲区。
  • 最坏适应算法(Worst Fit):最坏适应算法是将输入的作业放置到主存中与它所需大小差距最大的空闲区中。空闲区大小由大到小排序。
  • 特点:尽可能地利用存储器中大的空闲区。
  • 缺点:绝大多数时候都会造成资源的严重浪费甚至是完全无法实现分配。
img

内存回收的过程

  • 回收区在空闲区下方:不需要新建空闲链表节点;只需要把空闲区1的容量增大即可;
  • 回收区在空闲区上方:将回收区与空闲区合并;新的空闲区使用回收区的地址;
  • 回收区在空闲区中间方:将空闲区1、空闲区2和回收区合并;新的空闲区使用空闲区1的地址;
  • 仅仅剩余回收区:为回收区创建新的空闲节点;插入到相应的空闲区链表中去;

分区存储管理

固定分区存储管理

这一部分是内存分配过程的详细介绍,可以简单看一下

把主存中可分配的用户区域预先划分成若干个连续的分区,每个连续区的大小可以相同,也可以不同。但是,一旦划分好分区之后,主存中分区的个数就固定了,且每个分区的大小也固定不变。这是一种静态分区法。

在固定分区方式管理下,每个分区用来装入一个作业。由于主存中有多个分区,就可同时在每个分区中装入一个作业。所以,这种存储管理方式适用于多道程序系统。

img

主存空间的分配与释放

了管理主存空间的使用,必须设置一张“主存分配表”(分区说明表),以说明各分区的分配情况。主存分配表中应指出各分区的起始地址和长度,并为每个分区设一个标志位。当标志位为“0”时,表示对应的分区是空闲分区,当标志位为非“0”时,表示对应的分区已被某作业占用。空闲分区可以用来装作业。

img

当作业队列中有作业要装入主存时,存储管理可采用“顺序分配算法”进行主存空间的分配。

顺序查看主存分配表,找到一个标志为“0”的并且长度大于或等于欲装入作业的地址空间长度的分区,则把此分区分配给该作业,相应表目的标志位改成作业名的标识;若找不到一个这样的空闲分区,则该作业暂时不能装入主存。

主存空间的释放很简单。某作业执行结束后必须归还所占的分区,这时存储管理根据作业名查看主存分配表,找到相应的表目后,把其中的标志位重新置成“0”即可。

地址转换

固定分区管理方式下作业的地址转换常采用静态重定位技术。

(74条消息) 静态重定位和动态重定位阿肆_Maggie的博客-CSDN博客静态重定位

存储保护

固定分区管理方式下只考虑判断其物理地址即可。常采用“界限寄存器对”法。

If 下限地址<=物理地址<=上限地址

	Then   继续

Else 产生“越界中断” ,转越界中断的处理子程序

内存扩充

采用覆盖技术

覆盖技术是指一个程序的若干程序段和几个程序的某些部分共享一个存储空间

固定分区的优缺点

优点:实现简单,无外部碎片

缺点:

  • 当用户程序太大时,可能所有的分区都不能满足需求,此时不得不采用覆盖技术解决,但这又会降低性能
  • 会产生内部碎片,碎片大,存在小分区占用大作业的情况,内存利用率低。

解决办法:采用可变分区存储管理

可变分区存储管理

内存管理的可变分区模式,又称变长分区模式、动态分区分配模式。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数目是可变的

与固定分区的区别就是:动态的划分分区。

克服固定分区管理的“内碎片”问题。

  • 可变分区模式下,刚开始,OS就绪,但任何用户程序未进入内存前整个用户内存区是一大空间。已占用区和空闲分区并不是绝对的
  • 必须有表来记录分区的情况。
  • 程序进入内存时的例行工作就是分配空闲区和装入程序,并修改相应的空闲表和已分配区表。
  • 一旦一个内存分区被分配给一个进程,该进程可以被装入该块中执行,装入时需重定位。

可变分区分配的数据结构

img

可变分区分配算法

把一个新作业装入内存时,需要按照一定的可变分区分配算法,从空闲分区表(或空闲分区链)中选出一个分区分配给该作业。

在可变分区分配方式中,当有很多空闲分区都满足需求时,应该使用哪个分区进行分配?

这里介绍三种可变分区分配算法

image-20230223071608231

算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区。

实现步骤:

空闲区地址由低到高排序

=>1.顺序查找各个空闲区,把第一个找到能容纳申请要求的内存区分配给申请者.(若空闲区比作业长度大,则分割该空闲区。一部分分配给作业一部分空闲。)

=>2.调整相应的空闲分区表和已分配分区表。

评价:性能一般但实现比较简单直接,易于释放时合并相邻空间分区。比较容易的满足大作业的需要。完成一次分配平均需要的搜索次数较大,影响了工作效率。

img

尽可能地利用存储器中低地址的空闲区,而尽量保存高地址的空闲区。

最佳适应算法

算法思想:由于可变分区分配是一种连

 标签: 暂无标签

讨论这个帖子(0)垃圾回帖将一律封号处理……