计算机操作系统

本文深入探讨了计算机的基本结构,包括CPU、内存对齐和冯诺依曼体系。详细阐述了操作系统的概念、运行机制、中断和异常处理。重点讲解了Linux内存管理的段页式存储和虚拟内存机制,以及各种页面置换算法。此外,还介绍了进程的状态、通信方式和调度算法,以及Java中的IO管理和LRU缓存实现。
摘要由CSDN通过智能技术生成

计算机基本结构

冯诺依曼结构

在这里插入图片描述
计算机执行指令的过程

取指令、分析指令、执行指令、取下一条指令

CPU

CPU在冯诺依曼体系结构中包括两个部分:控制器和运算器,这两部分都包含有寄存器、总线

PC: 程序计数器,记录程序执行到的位置

寄存器:暂时存储计算需要用到的数据。相当于高速存储区域

ALU:逻辑运算单元,例如将寄存器中两个数字进行加法运算。。一个ALU可以对应两个寄存器,一个寄存器对应一个线程(超线程2核4线程)需要线程上下文切换

CU:控制单元。获取指令、分析指令、执行指令、存储结果

MMU:内存管理单元

Cache:缓存

总线:总线是CPU内部及在CPU和主板间传输信息的电子数据线路。可以通过总线访问各种输入输出设备

计算机运算过程

取指令、分析指令、执行指令、取下一条指令

CPU 只能通过针脚识别高低电平,也就是只能识别0101这样的机器语言,64位就一次能读取64个数字,通过时钟发生器一直通电断点推进CPU的运算(CPU主频相关),所以编写代码就通过输入01代码,非常麻烦

诞生出助记符,就是把01010010 —> add sub 加法运算,这就是汇编语言

对于寄存器的计算:例如计算a+b,先将a,b先后放入寄存器,ALU执行加法运算,然后返回

内存对齐

因为CPU是通过针脚读入高低电平 来读入内存的,所以一次能读64个01数字或32个,也就是8个或4个字节。内存对齐就可以加快CPU的读写,提高程序性能。

什么是操作系统

用户和计算机之间的桥梁,计算机软硬件交互的中间软件,管理计算机的资源、进程和软硬件

计算机上很多程序都要对内存cpu交互,操作系统就要保证这些访问和交互能够无误的进行

主要目的

  1. 管理计算机资源(cpu、内存、磁盘、打印机)
  2. 提供图形界面,提供用户和计算机之间的桥梁
  3. 为其他软件提供服务,分配资源

特征:并发、共享、虚拟、异步

操作系统运行机制

时钟管理

由计算机硬件提供

  1. 计时:提供系统时间
  2. 时钟中断:提供时间片轮转调度机制 来完成 进程/线程切换。(提高CPU利用率)

中断和异常

中断就是CPU停止执行现在的程序,在保留现场后转去执行别的程序,再返回到原程序的断点处继续执行

外中断是被迫的,是由于系统中某个事件引起的,例如IO中断、时钟中断;与程序指令无关;通过中断控制器发送信号给CPU,CPU发送给内核,由内核处理中断

内中断是自愿的,是由于执行了某个指令引起的,例如系统调用、程序出现异常;与程序相关;是由CPU产生的
在这里插入图片描述

中断处理过程

  1. 保护现场: 在中断服务程序的起始部分安排若干条入栈指令,将各寄存器的内容压入堆栈保存。
  2. 开中断:在中断服务程序执行期间允许级别更高的中断请求中断现 行的中断服务程序,实现中断嵌套。
  3. 中断服务:完成中断源的具体要求。
  4. 恢复现场:中断服务程序结束前,必须恢复主程序的中断现场。通常是将保存在堆栈中的现场信息弹出到原来的寄存器中。
  5. 中断返回:返回到原程序的断点处,继续执行原程序。

linux内存管理

段页式存储

分段式

以段为单位进行存储,每个段在内存中连续;用于划分逻辑上独立的程序

参数:段号+段长度;(段大小不固定)

通过段号查找

分页式

虚拟地址空间物理内存分成大小相同的页,linux为4k;用于实现虚拟内存

通过页号+页内偏移地址查找

缺页中断:内存中不存在该页表,要进行页面置换,加载到内存

段页式

先按照程序分段,在分页

通过段号+页号+页内偏移量查找

虚拟内存机制

物理内存:实际电脑上的内存,安装的内存条大小

虚拟内存:linux 32位操作系统,会给每个进程分配4GB虚拟内存;64位为2的48次方=256TB

让程序能够使用更多的内存,虚拟内存要映射到物理内存才能使用,如果没有映射到,就会发生缺页中断,使用相应的页面置换算法进行页替换。

页面置换算法

  • FIFO先进先出:淘汰最先到达的页面,可能会删除存在时间长但正在使用的页面
  • 第二次机会:对FIFO改进,判断是否正在使用
  • 时钟:第二次机会的另一实现方式
  • LRU:淘汰最久未使用的页面
  • OPT:最佳置换算法,淘汰将来最久不使用
  • NRU
  • 工作集算法
  • 工作集时钟算法

写时复制

当父进程 fock 出一个子进程是时,子进程不会复制父进程的占用的内存页,而是和父进程共用一个内存页(两个进程的虚拟内存映射到一段物理内存),当父进程或子进程对内存页表进行修改时,才会复制

进程

进程的内存模型

操作系统会给进程分配独立的资源空间,总的分为内核区和用户区;

内核区是Linux自身使用的内存空换件,主要提供程序调度、内存分配、连接硬件资源、程序逻辑使用;

内核区包含PCB,进程控制块,里边有描述这个进程的基本信息,例如进程ID、当前状态等等。系统通过PCB对进程调度和管理,而对PCB的操作就需要进入内核区,通过中断和异常陷入内核态,所以一些对系统资源的操作自然而然就需要切换到内核态执行。

用户区包含 堆区、栈区、共享数据区、代码段(应用程序的机器代码,只读且大小固定)、空闲区

进程内存和JVM内存:JVM本质上就是一个线程,所以它的内存模型和进程是类似的。但是将许多由操作系统管理的东西移至到了JVM内部,减少了系统调用的次数;对于堆进行分区管理,分代收集
在这里插入图片描述

进程状态

在这里插入图片描述

进程间通信方式

每个进程在内存中都会有独立的空间,一般不能相互访问,但是内核是共享的,所以一般通过内核通信。

管道、消息队列、共享内存、信号量、信号、套接字

管道

本质上就是内存中的一个缓存;当进程创建一个管道(mkfifo),Linux就会返回两个文件描述符,写入端和输出端,就可以通过这两个文件描述符往管道输入输出数据。

两个进程通过管道通信,必须fork出一个子进程,这样子进程才能获取到父进程的文件描述符,父子进程才能都对管道操作,实现通信。

A进程写完,B进程才能读,等到B进程读完了,A进程才能继续写;不适合频繁交互数据; 效率低

消息队列

本质是保存在内核中的消息链表;写入数据的时候,会将数据分割成消息体,消息体是用户自定义的,所以双方要约定好数据格式;写入不需要像管道一样等待接收方接收后才能继续写入,而是可以一直写入;读取是内核就将链表中的消息体删除。

存在用户态和内核态之间数据拷贝开销

共享内存

操作系统对于内存管理采用虚拟内存技术,每个进程有自己独立的虚拟内存空间,然后映射到物理内存上;所有只需要将两个进程的虚拟内存映射到相同的物理内存,就能实现数据共享。

如果两个进程同时对共享内存修改,就会出现冲突(数据覆盖)

信号量

为了保证进程对共享内存的资源竞争,使用信号量实现互斥;信号量本质就是一个计数器,主要用于实现进程中的互斥和同步,而不是传递数据。

信号量记录一个资源的数量,通过PV原语对信号量操作;

P操作就是将信号量 -1,V操作将信号量+1;如果P操作信号量小于零,这个进程就需要阻塞;如果V操作信号量<=0,就证明有进程正在阻塞,就将进程唤醒。

信号量初始为1,A进入一个共享资源前要进行P操作将信号量 -1,减完后信号量是0,没有小于0,就可以执行;B进程要访问这个资源,进行P操作后发现信号量是-1,就得等待;等到A进程离开执行V操作+1后,这时B才能访问。实现互斥

信号

Linux为我们提供了一些信号,对应着各种各样的时间,通过这些信号就可以对资源进行操作;信号的来源可以分成硬件和命令;

硬件:使用Ctrl C产生sigint信号表示终止该进程;
软件事件驱动:例如kill命令产生sigkill信号表示立即结束该进程

进程会对这些信号使用对应的监听处理,在收到这些信号就进行对应的操作,所以信号是异步通信机制(唯一一种)

Socket

用于跨网络在不同主机进程上通信

双方需要建立连接,告知对方自己的IP、端口、文件描述符,传输协议可以选择TCP或UDP,通过write()写入和监听接收的方式完成数据交互。

线程间通信方式

  1. wait() + notify() / Reentrantlock的 condition / join()
  2. volatile、synchronized
  3. countDownLatch、CyclicBarrier、Semaphore

进程调度算法

作业调度和进程调度

作业调度:从磁盘上将作业装入内存
进程调度:一个作业在什么时间拿到CPU

以下算法既可以用作作业调度,也可以用作线程调度

  • FCFS(先来先服务):选择最先进入该队列的进程
  • SPF(短进程优先):估计运行时间最短的进程
  • SRTN(最短剩余时间优先):
  • 时间片轮转:适用于分时系统,先来先服务但只能运行一个时间片
  • 优先级调度:选择优先级最高的进程
  • 高响应比优先:FCFS+SPF,计算响应比=(等待时间+要求服务时间)/要求服务时间

IO

操作系统IO

例如内存磁盘、磁盘和磁盘间的文件读写。我们应用程序只是IO操作的发起者,实际执行会交给操作系统内核,操作系统负责计算机的资源管理。

IO模型

阻塞式BIO

当应用程序发起获取数据请求时,如果操作系统内核还没有准备好,那应用程序就一直处于等待状态,知道操作系统把数据准备好了交给应用

非阻塞式NIO

当应用程序发起获取数据请求时,如果操作系统内核还没有准备好会立即告诉程序,不会让其等待,而是通过轮询的方式继续请求

IO多路复用

就是通过select函数可以同时监视多个FD(文件描述符,windows中的文件句柄),select监控的FD中只要有一个数据准备就绪,就返回可读状态,,这时应用程序再去请求读数据

select缺点:有最大连接数;通过遍历找到对应的FD。poll可以没有最大连接数,但还是要遍历

epoll:使用事件驱动实现,采用事件监听回调避免了遍历FD

epoll工作流程

  1. 调用epoll_create()会陷入内核态 创建并初始化一个 eventpoll对象
  2. 把被监听的FD封装成一个 epitem对象,并插入到一个红黑树中
  3. 等待被监听的 FD状态改变
  4. 当状态发生改变时,就把对应的epitem对象加入到一个就绪队列中,把对应的文件作为参数调用函数唤醒进程
信号驱动IO

不使用主动询问的方式来看内核是否准备好数据,而是发送一个信号,然后就可以做别的事,内核准备好之后,通知应用程序进行系统调用获取数据。

异步AIO

当应用程序发起请求后,操作系统会立即向程序返回一个类似申请成功的信息,然后应用程序就可以干自己的事情,当操作系统准备好数据(从内核复制到用户空间)后向程序发送信号通知IO操作执行完成

Java中的IO

LRU实现

淘汰最近未使用的

双向链表+hashmap

双向链表完成数据顺序的修改,使用hashmap可以提升查询效率
在这里插入图片描述

/**
 * @author Deevan
 */
public class LruCache {
    /**
     * 节点类
     */
    private static class Entry {
        int key;
        int value;
        Entry pre;
        Entry next;

        public Entry() {
        }

        public Entry(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    /**
     * 头、尾结点
     * 缓存容量
     * 元素个数
     * 缓存映射map(提升读效率)
     */
    Entry head, tail;
    int capacity;
    int size;
    Map<Integer, Entry> cache;

    /**
     * 初始化缓存
     *
     * @param capacity 缓存最大容量
     */
    public LruCache(int capacity) {
        this.capacity = capacity;
        this.size = 0;
        this.cache = new HashMap<>(capacity);
        //初始化链表
        head = new Entry();
        tail = new Entry();
        head.next = tail;
        tail.pre = head;
    }

    /**
     * 添加操作
     */
    public void put(int key, int value) {
        Entry node = cache.get(key);
        //节点存在
        if (node != null) {
            //修改value
            node.value = value;
            //将该节点移至链表头部
            moveToHead(node);
            return;
        }
        //节点不存在,判断缓存是否已满?删除尾结点。添加该节点至头结点
        if (size == capacity) {
            //删除尾结点
            cache.remove(tail.pre.key);
            tail.pre = tail.pre.pre;
            tail.pre.next = tail;
            size--;
        }
        //添加新节点到头部
        Entry newNode = new Entry(key, value);
        addNode(newNode);
        //添加到hashmap
        cache.put(key, newNode);
        size++;
    }

    /**
     * 取缓存
     */
    public Integer get(int key) {
        Entry node = cache.get(key);
        if (node == null) {
            return -1;
        }
        //被访问一次,要将该节点移动到头部
        moveToHead(node);
        return node.value;
    }

    /**
     * 将该节点添加至头部(head后面)
     */
    public void moveToHead(Entry node) {
        deleteNode(node);
        addNode(node);
    }

    /**
     * 将当前节点从链表中删除
     */
    private void deleteNode(Entry node) {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    /**
     * 添加该节点到头部
     */
    private void addNode(Entry node) {
        head.next.pre = node;
        node.next = head.next;
        node.pre = head;
        head.next = node;
    }

    public static void main(String[] args) {
        LruCache lruCache = new LruCache(2);
        lruCache.put(1, 10);
        lruCache.put(2, 20); //cache 21
        System.out.println(lruCache.get(1)); //10
        lruCache.put(3, 30);//cache 31    2
        System.out.println(lruCache.get(2)); //-1
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

EnndmeRedis

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

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

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

打赏作者

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

抵扣说明:

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

余额充值