操作系统校招面试复习知识详述

校招面试操作系统知识详述

一. 进程与线程

1.1 进程

1.概念

  1. 进程是资源分配的基本单位

  2. 进程控制块 (Process Control Block, PCB) 描述进程的基本信息和运行状态,所谓的创建进程和撤销进程,都是指对 PCB 的操作。、

  3. 进程可以并发运行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K4IEDelX-1662458352750)(C:\Users\Administrator.DESKTOP-G8VK4LM\Desktop\markdown\基础科目\图片资源\进程并行.png)]

  1. 进程状态转换

在这里插入图片描述

2. 进程调度

  1. 批处理系统

    调度算法目标是保证吞吐量和周转时间(从提交到终止的时间)

    1. 先来先服务 first-come first-serverd(FCFS)

      非抢占式的调度算法,按照请求的顺序进行调度。(后面的进程容易等待时间过长)

    2. 短作业优先 shortest job first(SJF)

      长作业有可能会饿死,处于一直等待短作业执行完毕的状态。

    3. 最短剩余时间优先 shortest remaining time next(SRTN)

    短作业优先升级,可以基本保证进程调度。

  2. 交互式系统

    该系统中调度算法的目标是快速地进行响应

    1. 时间片轮转

      就绪进程按先来先服务的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。

      通俗的讲,就是进程都排好队,每次过来处理它们一段时间,不管有没有结束,都放到末尾,下一个进程再来处理相同(时间片)

      时间片大小的选择很重要:片过小,频繁切换导致效率下降;

      ​ 片过大。降级成先来先服务算法

    2. 优先级调度

      为每个进程分配一个优先级,按优先级进行调度。

      为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级

    3. 多级反馈队列

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0pc7eEYz-1662458352754)(C:\Users\Administrator.DESKTOP-G8VK4LM\Desktop\markdown\基础科目\图片资源\多级队列算法.png)]

  3. 实时系统

    实时系统要求一个请求在一个确定时间内得到响应。

    分为硬实时和软实时,前者必须满足绝对的截止时间,后者可以容忍一定的超时。

3. 进程同步

  1. 临界值

    临界资源进行访问的那段代码称为临界区。

    为了互斥访问临界资源,每个进程在进入临界区之前,需要先进行检查

  2. 经典同步问题

    1. 哲学家进餐

      为了防止死锁的发生,可以设置两个条件:

      • 必须同时拿起左右两根筷子;
      • 只有在两个邻居都没有进餐的情况下才允许进餐。
    2. 读者-写者问题

      允许多个进程同时对数据进行读操作,但是不允许读和写以及写和写操作同时发生。

      一个整型变量 count 记录在对数据进行读操作的进程数量,一个互斥量 count_mutex 用于对 count 加锁,一个互斥量 data_mutex 用于对读写的数据加锁。

4.进程通信

​ 进程同步:控制多个进程按一定顺序执行
​ 进程通信:进程间传输信息
​ 进程通信是一种手段,而进程同步是一种目的。也可以说,为了能够达到进程同步的目的,需要让 进程进行通信,传输一些进程同步所需要的信息。

  1. 管道

    它具有以下限制:

    • 只支持半双工通信(单向交替传输)
    • 只能在父子进程或者兄弟进程中使用。
  2. FIFO(命名管道)

    也称为命名管道,去除了管道只能在父子进程中使用的限制。

  3. 消息队列

    相比于 FIFO,消息队列具有以下优点:

    • 消息队列可以独立于读写进程存在,从而避免了 FIFO 中同步管道的打开和关闭时可能产生的困难;
    • 避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法;
    • 读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收。
  4. 信号量

    它是一个计数器,用于为多个进程提供对共享数据对象的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。一些经典进程同步问题就是利用信号量进行解决的

  5. 共享存储

    允许多个进程共享一个给定的存储区。因为数据不需要在进程之间复制,所以这是最快的一种 IPC。

    需要使用信号量用来同步对共享存储的访问。

    多个进程可以将同一个文件映射到它们的地址空间从而实现共享内存。另外 XSI 共享内存不是使用文件,而是使用内存的匿名段

  6. 套接字(socket)

    与其它通信机制不同的是,它可用于不同机器间的进程通信。

5. 僵尸进程和孤儿进程

在unix/linux中,正常情况下子进程是通过父进程创建的,子进程再创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。 当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。

孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进
程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid
获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。

1.2 线程

  1. 线程是独立调度的基本单位。
  2. 一个进程中可以有多个线程,它们共享进程资源。

1.2.1 线程调度

  • 调度策略:
    时间片:线程的调度采用时间片轮转的方式
    抢占式:高优先级的线程抢占CPU

  • Java的调度方法:

    1. 对于同优先级的线程组成先进先出队列(先到先服务),使用时间片策略
    2. 对高优先级,使用优先调度的抢占式策略(高优先级的线程要抢占低优先级的线程的cpu的执行权。但是仅是从概率上来说的,高优先级的线程更有可能被执行。并不意味着只有高优先级的线程执行完以后,低优先级的线程才执行。)低优先级线程会随着时间推移,优先级逐渐增大。
  • 线程池

    背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
    思路:提前创建好多个线程,放入线程池之,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。(数据库连接池)
    好处:提高响应速度(减少了创建新线程的时间)
    降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    便于线程管理
    corePoolSize:核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没有任务时最多保持多长时间后会终止

1.2.2 线程通信方式

线程通信方法:
wait()/ notify()/ notifayAll():此三个方法定义在Object类中的,因为这三个方法需要用到锁,而锁是任意对象都能充当的,所以这三个方法定义在Object类中。

由于wait,notify,以及notifyAll都涉及到与锁相关的操作

  • wait(在进入锁住的区域以后阻塞等待,释放锁让别的线程先进来操作)---- Obj.wait 进入Obj这个锁住的区域的线程把锁交出来原地等待通知;一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
  • notify(由于有很多锁住的区域,所以需要将区域用锁来标识,也涉及到锁) ----- Obj.notify 新线程进入Obj这个区域进行操作并唤醒wait的线程;一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程,就唤醒优先级高的线程
  • notifyAll一旦执行此方法,就会唤醒所有被wait()的线程

所以wait,notify需要使用在有锁的地方,也就是需要用synchronize关键字来标识的区域,即使用在同步代码块或者同步方法中,且为了保证wait和notify的区域是同一个锁住的区域,需要用锁来标识,也就是锁要相同的对象来充当。这三个方法均只能使用在同步代码块或者同步方法中。

1.2.3 线程分类(java)

线程的分类:
java中的线程分为两类:

1.守护线程(如垃圾回收线程,异常处理线程)

2.用户线程(如主线程)

若JVM中都是守护线程,当前JVM将退出。

1.2.4 线程同步问题

  1. Synchronized与lock的异同?
    相同:二者都可以解决线程安全问题
    不同:synchronized机制在执行完相应的代码逻辑以后,自动的释放同步监视器
    lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())(lock的方式更为灵活)

    优先使用顺序:
    LOCK->同步代码块->同步方法

  2. sleep和wait的异同?

    相同点:一旦执行方法以后,都会使得当前的进程进入阻塞状态
    不同点:
    1.两个方法声明的位置不同,Thread类中声明sleep,Object类中声明wait。
    2.调用的要求不同,sleep可以在任何需要的场景下调用,wait必须使用在同步代码块或者同步方法中
    3.关于是否释放同步监视器,如果两个方法都使用在同步代码块或同步方法中,sleep不会释放,wait会释放

1.2.5 IO多路复用

  1. IO多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;没有文件句柄就绪时会阻塞应用程序,交出cpu。多路是指网络连接,复用指的是同一个线程。

  2. IO多路复用有三种实现方式: select, poll, epoll
    (1)select:时间复杂度O(n),它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。
    select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述副就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。
    select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。
    (2)poll:时间复杂度O(n),poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的。pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。
    (3)epoll:时间复杂度O(1),epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以说epoll实际上是事件驱动(每个事件关联上fd) 的,此时对这些流的操作都是有意义的。效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即Epoll最大的优点就在于它只管你**“活跃”的连接**,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。

1.3 协程

协程:协程是微线程,在子程序内部执行,可在子程序内部中断,转而执行别的子程序,在适当的时候再返回来接着执行。

  1. 协程执行效率极高。协程直接操作栈基本没有内核切换的开销,所以上下文的切换非常快,切换开销比线程更小。
  2. 协程不需要多线程的锁机制,因为多个协程从属于一个线程,不存在同时写变量冲突,效率比线程高,避免了加锁解锁的开销。
  3. 协程占用内存少:执行协程只需要极少的栈内存(大概是4~5KB),而默认情况下,线程栈的大小为1MB。

1.4 进程,线程与协程的区别

  1. 调度

    线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换

  2. 系统开销

    在操作进程时,系统要为之分配资源,如内存空间、I/O 设备等;系统开销较大

    在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置;

    而线程切换时只需保存和设置少量寄存器内容,开销很小

  3. JVM中两者的关系
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uay3JDfK-1662458352755)(C:\Users\Administrator.DESKTOP-G8VK4LM\Desktop\markdown\基础科目\图片资源\进程与线程.png)]
    从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器虚拟机栈本地方法栈

  • 堆和方法区
    堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象,方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 程序计数器为什么是私有的?
    程序计数器主要有下面两个作用:1.字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
    2.在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。(记录当前运行的位置
    需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置
  • 虚拟机栈和本地方法栈为什么是私有的?
    虚拟机栈:每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
    本地方法栈:和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
    所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。
  • java中的native方法
    1.native方法是java与其他语言协作的时候使用的,底层实现不是java(c,c++)
    2.native并不是java实现的,所以直接调用就好,不需要管他底层实现
    3.native方法实际上是java用于调用底层操作系统的方法,java只能调用,不能查看和修改
    4.java是跨平台语言,自然而然会失去对底层的控制,于是想要调用底层方法,就必须使用native方法间接调用底层操作系统的方法(c,c++实现)
    5.随着java的不断发展,native方法越来越少
    6.native是一个关键字,其修饰的方法只说明不实现
    7.native方法加载到 本地栈中
  1. 线程与协程的区别:
    1. 协程执行效率极高。协程直接操作栈基本没有内核切换的开销,所以上下文的切换非常快,切换开销比线程更小。
    2. 协程不需要多线程的锁机制,因为多个协程从属于一个线程,不存在同时写变量冲突,效率比线程高。
    3. 一个线程可以有多个协程。

原文链接:https://blog.csdn.net/ThinkWon/article/details/102021274

原文链接:https://blog.csdn.net/weixin_44797490/article/details/91006241

链接:https://leetcode.cn/leetbook/read/tech-interview-cookbook/ootri6/

原文链接:https://blog.csdn.net/TheWayForDream/article/details/122202601

1.5 面试题,Java中线程同步方式有哪些?

  1. 同步方法
    即有synchronized关键字修饰的方法,由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。需要注意, synchronized关键字也可以修饰静态方法,但不能修饰静态代码块。此时如果调用该静态方法,将会锁住整个类
  2. 同步代码块
    即有synchronized关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。需值得注意的是,同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
  3. ReentrantLock
    Java 5新增了一个java.util.concurrent包来支持同步,其中ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其
    能力。需要注意的是,ReentrantLock还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,因此不推荐使用
  4. volatile
    volatile关键字为域变量的访问提供了一种免锁机制,使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,因此每次使用该域就要重新计算,而不是使用寄存器中的值。需要注意的是,volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
  5. 原子变量
    在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。例如AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer。可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。

二. 基础概念

  1. 并发
    并发:是指宏观上在一段时间内能同时运行多个程序,而并行则指同一时刻能运行多个指令。

    并行:需要硬件支持,如多流水线、多核处理器或者分布式计算系统。

    操作系统通过引入进程和线程,使得程序能够并发运行。

  2. 共享
    共享是指系统中的资源可以被多个并发进程共同使用。

    有两种共享方式:互斥共享和同时共享。

    互斥共享的资源称为临界资源,例如打印机等,在同一时刻只允许一个进程访问,需要用同步机制来实现互斥访问。

  3. 虚拟
    虚拟技术把一个物理实体转换为多个逻辑实体

    主要有两种虚拟技术**:时分复用技术和空分复用技术**。

    多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占用处理器,每次只执行一小个时间片并快速切换。

    虚拟内存使用了空分复用技术,它将物理内存抽象为地址空间,每个进程都有各自的地址空间。地址空间的页被映射到物理内存,地址空间的页并不需要全部在物理内存中,当使用到一个没有在物理内存的页时,执行页面置换算法,将该页置换到内存中。

  4. 异步
    异步指进程不是一次性执行完毕,而是走走停停,以不可知的速度向前推进。

  5. 系统调用
    在这里插入图片描述

  6. 宏内核和微内核

    宏内核
    宏内核是将操作系统功能作为一个紧密结合的整体放到内核。由于各模块共享信息,因此有很高的性能。

    微内核
    由于操作系统不断复杂,因此将一部分操作系统功能移出内核,从而降低内核的复杂性。移出的部分根据分层的原则划分成若干服务,相互独立。在微内核结构下,操作系统被划分成小的、定义良好的模块,只有微内核这一个模块运行在内核态,其余模块运行在用户态。因为需要频繁地在用户态和核心态之间进行切换,所以会有一定的性能损失。

三.锁的知识

3.1锁分类

  1. 悲观锁
    悲观锁并不是某一个锁,是一个锁类型,无论是否并发竞争资源,都会锁住资源,并等待资源释放下一个线程才能获取到锁。 这明显很悲观,所以就叫悲观锁。这明显可以归纳为一种策略,只要符合这种策略的锁的具体实现,都是悲观锁的范畴。

  2. 乐观锁
    与悲观锁相对的,乐观锁也是一个锁类型。当线程开始竞争资源时,不是立马给资源上锁,而是进行一些前后值比对,以此来操作资源。例如常见的CAS操作,就是典型的乐观锁。

    CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:
    内存地址V,旧的预期值A,即将要更新的目标值B。CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。高并发环境下,对同一个数据的并发读(两边都读出余额是100)与并发写(一个写回28,一个写回38)导致的数据一致性问题。解决方案是在set写回的时候,加上初始状态的条件compare,只有初始状态不变时,才允许set写回成功,这是一种常见的降低读写锁冲突,保证数据一致性的方法。

  3. 自旋锁
    自旋锁是一种基础的同步原语,用于保障对共享数据的互斥访问。与互斥锁的相比,在获取锁失败的时候不会使得线程阻塞而是一直自旋尝试获取锁。当线程等待自旋锁的时候,CPU不能做其他事情,而是一直处于轮询忙等的状态。自旋锁主要适用于被持有时间短,线程不希望在重新调度上花过多时间的情况。实际上许多其他类型的锁在底层使用了自旋锁实现,例如多数互斥锁在试图获取锁的时候会先自旋一小段时间,然后才会休眠。如果在持锁时间很长的场景下使用自旋锁,则会导致CPU在这个线程的时间片用尽之前一直消耗在无意义的忙等上,造成计算资源的浪费。

  4. 公平锁
    多个线程竞争同一把锁,如果依照先来先得的原则,那么就是一把公平锁。

  5. 非公平锁
    多个线程竞争锁资源,抢占锁的所有权。

  6. 共享锁
    多个线程可以共享这个锁的拥有权。一般用于数据的读操作,防止数据被写修改。

3.2 死锁

  1. 必要条件

    • 互斥:每个资源要么已经分配给了一个进程,要么就是可用的。
    • 占有和等待:已经得到了某个资源的进程可以再请求新的资源。
    • 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。
    • 环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。
  2. 处理方法

    • 鸵鸟策略

      当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略。

      大多数操作系统,包括 Unix,Linux 和 Windows,处理死锁问题的办法仅仅是忽略它。

    • 死锁检测与死锁恢复

      死锁检测:每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。

      死锁回复:利用抢占恢复;利用回滚恢复;通过杀死进程恢复

    • 死锁预防

      1. 破坏互斥条件
        例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。

      2. 破坏占有和等待条件
        一种实现方式是规定所有进程在开始执行前请求所需要的全部资源

      3. 破坏不可抢占条件

      4. 破坏环路等待
        给资源统一编号,进程只能按编号顺序来请求资源。

    • 死锁避免
      在这里插入图片描述

四. 内存相关

  1. 虚拟内存

    虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。

    为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。

    虚拟内存分成五大区,分别为栈区、堆区、全局区(静态区)、文字常量区(常量存储区)、程序代
    码区。五大区特性如下:

    1. 栈区(stack): 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
    2. 堆区(heap): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
    3. 全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
    4. 文字常量区(常量存储区) :常量字符串就是放在这里的。 程序结束后由系统释放。这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改
    5. 程序代码区:存放函数体的二进制代码。
  2. 页面置换算法

    在程序运行过程中,如果要访问的页面不在内存中,就发生缺页中断从而将该页调入内存中。此时如果内存已无空闲空间,系统必须从内存中调出一个页面到磁盘对换区中来腾出空间。

    页面置换算法和缓存淘汰策略类似,可以将内存看成磁盘的缓存。在缓存系统中,缓存的大小有限,当有新的缓存到达时,需要淘汰一部分已经存在的缓存,这样才有空间存放新的缓存数据。

    页面置换算法的主要目标是使页面置换频率最低(也可以说缺页率最低

    1. 最佳算法

      所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。

      是一种理论上的算法,因为无法知道一个页面多长时间不再被访问。

    2. 最近最久未使用算法(LRU, Least Recently Used)

      为了实现 LRU,需要在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的。

      因为每次访问都需要更新链表,因此这种方式实现的 LRU 代价很高。

    在这里插入图片描述

    1. 最近未使用算法(NRU, Not Recently Used)

      每个页面都有两个状态位:R 与 M,当页面被访问时设置页面的 R=1,当页面被修改时设置 M=1。其中 R 位会定时被清零。可以将页面分成以下四类:

      R=0,M=0
      R=0,M=1
      R=1,M=0
      R=1,M=1
      当发生缺页中断时,NRU 算法随机地从类编号最小的非空类中挑选一个页面将它换出。

      NRU 优先换出已经被修改的脏页面(R=0,M=1),而不是被频繁使用的干净页面(R=1,M=0)

    2. 先进先出算法(FIFO, First In First Out)

      选择换出的页面是最先进入的页面。

      该算法会将那些经常被访问的页面换出,导致缺页率升高

    3. 第二次机会算法

      当页面被访问 (读或写) 时设置该页面的 R 位为 1。需要替换的时候,检查最老页面的 R 位。如果 R 位是 0,那么这个页面既老又没有被使用,可以立刻置换掉;如果是 1,就将 R 位清 0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的一样,然后继续从链表的头部开始搜索。

    4. 时钟算法

      第二次机会算法需要在链表中移动页面,降低了效率。时钟算法使用环形链表将页面连接起来,再使用一个指针指向最老的页面

  3. 分页与分段

    1. 虚拟内存采用的是分页技术,也就是将地址空间划分成固定大小的页,每一页再与内存进行映射。

    2. 分段的做法是把每个表分成段,一个段构成一个独立的地址空间。每个段的长度可以不同,并且可以动态增长。

    3. 两者区别

      分页和分段有许多相似之处,比如两者都不要求作业连续存放.但在概念上两者完全不同,主要表现在以下几个方面:

      (1)页是信息的物理单位,分页是为了实现非连续分配,以便解决内存碎片问题,或者说分页是由于系统管理的需要.段是信息的逻辑单位,它含有一组意义相对完整的信息,分段的目的是为了更好地实现共享,满足用户的需要.

      (2)页的大小固定,由系统确定,将逻辑地址划分为页号和页内地址是由机器硬件实现的.而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时根据信息的性质来划分.

      (3)分页的作业地址空间是一维的.分段的地址空间是二维的.

    4. 段页式

      在段页式存储管理系统中,作业的地址空间首先被分成若干个逻辑分段,每段都有自己的段号,然后再将每段分成若干个大小相等的页。对于主存空间也分成大小相等的页,主存的分配以页为单位。

      段页式系统中,作业的地址结构包含三部分的内容:段号 页号 页内位移量

      程序员按照分段系统的地址结构将地址分为段号与段内位移量,地址变换机构将段内位移量分解为页号和页内位移量。

      为实现段页式存储管理,系统应为每个进程设置一个段表,包括每段的段号,该段的页表始址和页表长度。每个段有自己的页表,记录段中的每一页的页号和存放在主存中的物理块号。

五.磁盘调度算法

读写一个磁盘块的时间的影响因素有:

  • 旋转时间(主轴转动盘面,使得磁头移动到适当的扇区上)
  • 寻道时间(制动手臂移动,使得磁头移动到适当的磁道上)
  • 实际的数据传输时间

其中,寻道时间最长,因此磁盘调度的主要目标是使磁盘的平均寻道时间最短

  1. 先来先服务(FCFS)

  2. 最短寻道时间优先(SSTF)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d31vJx2K-1662458352759)(C:\Users\Administrator.DESKTOP-G8VK4LM\Desktop\markdown\基础科目\图片资源\最短寻道算法.png)]

  3. 电梯算法(SCAN)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fYdJQJTN-1662458352760)(C:\Users\Administrator.DESKTOP-G8VK4LM\Desktop\markdown\基础科目\图片资源\电梯算法.png)]

  4. C-SCAN 循环扫描算法
    会像SCAN算法一样从磁盘的一端到另一端,并且处理请求。而当到达另一端时会立即返回到开头,并不会处理回程上的请求,然后开始新一轮的一端到另一端。

六 链接

  1. 编译系统
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e7ss6mPL-1662458352762)(C:\Users\Administrator.DESKTOP-G8VK4LM\Desktop\markdown\基础科目\图片资源\编译系统.png)]

  2. 目标文件

    • 可执行目标文件:可以直接在内存中执行;
    • 可重定位目标文件:可与其它可重定位目标文件在链接阶段合并,创建一个可执行目标文件;
    • 共享目标文件:这是一种特殊的可重定位目标文件,可以在运行时被动态加载进内存并链接;
  3. 静态链接与动态链接

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TuNBz8kE-1662458352762)(C:\Users\Administrator.DESKTOP-G8VK4LM\Desktop\markdown\基础科目\图片资源\静态链接.png)]

    • 静态库有以下两个问题:

      当静态库更新时那么整个程序都要重新进行链接;
      对于 printf 这种标准函数库,如果每个程序都要有代码,这会极大浪费资源。

    • 共享库是为了解决静态库的这两个问题而设计的,在 Linux 系统中通常用 .so 后缀来表示,Windows 系统上它们被称为 DLL。它具有以下特点:

      在给定的文件系统中一个库只有一个文件,所有引用该库的可执行目标文件都共享这个文件,它不会被复制到引用它的可执行文件中;
      在内存中,一个共享库的 .text 节(已编译程序的机器代码)的一个副本可以被不同的正在运行的进程共享。

参考原文:

链接:https://leetcode.cn/leetbook/read/tech-interview-cookbook/ora40s/

七.Linux操作系统相关

7.1 Linux内核

  1. 内核是操作系统的核心,具有很多最基本功能,它负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。
    Linux 内核有 4 项工作:

    1. 内存管理: 追踪记录有多少内存存储了什么以及存储在哪里
    2. 进程管理: 确定哪些进程可以使用中央处理器(CPU)、何时使用以及持续多长时间
    3. 设备驱动程序: 充当硬件与进程之间的调解程序/解释程序
    4. 系统调用和安全防护: 从流程接受服务请求
  2. 在正确实施的情况下,内核对于用户是不可见的,它在自己的小世界(称为内核空间)中工作,并从中分配内存和跟踪所有内容的存储位置。用户所看到的内容(例如 Web 浏览器和文件则被称为用户空间。这些应用通过==系统调用接口==(SCI)与内核进行交互。

  3. 为了更具象地理解内核,不妨将 Linux 计算机想象成有三层结构:
    硬件:物理机(这是系统的底层结构或基础)是由内存(RAM)、处理器(或 CPU)以及输入/输出(I/O)设备(例如存储、网络和图形)组成的。其中,CPU 负责执行计算和内存的读写操作。
    Linux 内核:操作系统的核心。它是驻留在内存中的软件,用于告诉 CPU 要执行哪些操作。
    用户进程:这些是内核所管理的运行程序。用户进程共同构成了用户空间。用户进程有时也简称为进程。内核还允许这些进程和服务器彼此进行通信(称为进程间通信或 IPC)。

  4. 系统执行的代码通过以下两种模式之一在 CPU 上运行:内核模式或用户模式。在内核模式下运行的代码可以不受限制地访问硬件,而用户模式则会限制 SCI 对 CPU 和内存的访问。内存也存在类似的分隔情况(内核空间和用户空间)。这两个小细节构成了一些复杂操作的基础,例如安全防护、构建容器和虚拟机的权限分隔。

7.2 内核态和用户态

  1. 内核态其实从本质上说就是内核,它是一种特殊的软件程序,控制计算机的硬件资源,例如协调CPU资源,分配内存资源,并且提供稳定的环境供应用程序运行。
  2. 用户态就是提供应用程序运行的空间,为了使应用程序访问到内核管理的资源例如CPU,内存,I/O。内核必须提供一组通用的访问接口,这些接口就叫系统调用
    1. 系统调用是操作系统的最小功能单位。根据不同的应用场景,不同的Linux发行版本提供的系统调用数量也不尽相同,大致在240-350之间。这些系统调用组成了用户态跟内核态交互的基本接口。
    2. 从用户态到内核态切换可以通过三种方式:
      1. 系统调用:系统调用本身就是中断,但是是软件中断,跟硬中断不同。
      2. 异常:如果当前进程运行在用户态,如果这个时候发生了异常事件,就会触发切换。例如:
        缺页异常。
      3. 外设中断:当外设完成用户的请求时,会向CPU发送中断信号。

7.3 负载

负载(load)是linux机器的一个重要指标,直观了反应了机器当前的状态。
在UNIX系统中,系统负载是对当前CPU工作量的度量,被定义为特定时间间隔内运行队列中的平均线程数。load average 表示机器一段时间内的平均load。这个值越低越好。负载过高会导致机器无法处理其他请求及操作,甚至导致死机。
top 或 uptime 等命令会输出系统的平均负载 (Load Average),一般会有三个值,分别代表 1 分钟,5 分钟和 15 分钟的平均负载。
负载记录的是 CPU 的负荷,能对 CPU 造成负荷的是进程(包括线程)的执行。负载的数值代表的是CPU 还没处理完的进程的数目。单核满载是 1 ,有 n 核满载是 n 。一般说线上运行的系统大于 0.7 的时候就要注意了。

7.4 Linux内存

常见的计算机存储层次如下:

  • 寄存器:CPU提供的,读写ns级别,容量字节级别。
  • CPU缓存:CPU和CPU间的缓存,读写10ns级别,容量较大一些,百到千节。
  • 主存:动态内存,读写100ns级别,容量GB级别。
  • 外部存储介质:磁盘、SSD,读写ms级别,容量可扩展到TB级别。
  1. 虚拟内存
    物理内存是有限的(即使支持了热插拔)、非连续的,不同的CPU架构对物理内存的组织都不同。这使得直接使用物理内存非常复杂,为了降低使用内存的复杂度,引入了虚拟内存机制。
    虚拟内存抽象了应用程序物理内存的细节,只允许物理内存保存所需的信息(按需分页),并提供了一种保护和控制进程间数据共享数据的机制。有了虚拟内存机制之后,每次访问可以使用更易理解的虚拟地址,让CPU转换成实际的物理地址访问内存,降低了直接使用、管理物理内存的门槛。
  2. 大页机制
    虚拟地址转换的过程中,需要好几个内存访问,由于内存访问相对CPU较慢,为了提高性能,CPU维护了一个TLB地址转换的cache,TLB是比较重要且珍稀的缓存,对于大内存工作集的应用程序,会因TLB命中率低大大影响到性能。
    为了减少TLB的压力,增加TLB缓存的命中率,有些系统会把页的大小设为MB或者GB,这样页的数目少了,需要转换的页表项也小了,足以把虚拟地址和物理地址的映射关系,全部保存于TLB中。
  3. 区域概念
    通常硬件会对访问不同的物理内存的范围做出限制,在某些情况下设备无法对所有的内存区域做DMA。在其他情况下,物理内存的大小也会超过了虚拟内存的最大可寻址大小,需要执行特殊操作,才能访问这些区域。这些情况下,Linux对内存页的可能使用情况将其分组到各自的区域中(方便管理和限制)。比如ZONE_DMA用于指明哪些可以用于DMA的区域,ZONE_HIGHMEM包含未永久映射到内核地址空间的内存,ZONE_NORMAL标识正常的内存区域。
  4. 节点
    多核CPU的系统中,通常是NUMA系统(非统一内存访问系统)。在这种系统中,内存被安排成具有不同访问延迟的存储组,这取决于与处理器的距离。每一个库,被称为一个节点,每个节点Linux构建了一个独立的内存管理子系统。一个节点有自己的区域集、可用页和已用页表和各种统计计数器。
  5. page cache
    从外部存储介质中加载数据到内存中,这个过程是比较耗时的,因为外部存储介质读写性能毫秒级。为了减少外部存储设备的读写,Linux内核提供了Page cache。最常见的操作,每次读取文件时,数据都会被放入页面缓存中,以避免后续读取时所进行昂贵的磁盘访问。同样,当写入文件时,数据被重新放置在缓存中,被标记为脏页,定期的更新到存储设备上,以提高读写性能。
  6. 匿名内存
    匿名内存或者匿名映射表示不受文件系统支持的内存,比如程序的堆栈隐式创立的,或者显示通过mmap创立的。
  7. 内存回收
    贯穿系统的生命周期,一个物理页可存储不同类型的数据,可以是内核的数据结构,或是DMA访问的buffer,或是从文件系统读取的数据,或是用户程序分配的内存等。
    根据页面的使用情况,Linux内存管理对其进行了不同的处理,可以随时释放的页面,称之为可回收页面,这类页面为:页面缓存或者是匿名内存(被再次交换到硬盘上)
    大多数情况下,保存内部内核数据并用DMA缓冲区的页面是不能重新被回收的,但是某些情况下,可以回收使用内核数据结构的页面。例如:文件系统元数据的内存缓存,当系统处于内存压力情况下,可以从主存中丢弃它们。
    释放可回收的物理内存页的过程,被称之为回收,可以同步或者异步的回收操作。当系统负载增加到一定程序时,kswapd守护进程会异步的扫描物理页,可回收的物理页被释放,并逐出备份到存储设备。
  8. compaction
    系统运行一段时间,内存就会变得支离破碎。虽然使用虚拟内存可以将分散的物理页显示为连续的物理页,但有时需要分配较大的物理连续内存区域。比如设备驱动程序需要一个用于DMA的大缓冲区时,或者大页内存机制分页时。内存compact可以解决了内存碎片的问题,这个机制将被占用的页面,从内存区域合适的移动,以换取大块的空闲物理页的过程,由kcompactd守护进程完成。
  9. OOM killer
    机器上的内存可能会被耗尽,并且内核将无法回收足够的内存用于运行新的程序,为了保存系统的其余部分,内核会调用OOM killer杀掉一些进程,以释放内存。
  10. 段页机制
    段页机制是操作系统管理内存的一种方式,简单的来说,就是如何管理、组织系统中的内存。要理解这种机制,需要了解一下内存寻址的发展历程。
    直接寻址:早期的内存很小,通过硬编码的形式,直接定位到内存地址。这种方式有着明显的缺点:可控性弱、难以重定位、难以维护
    分段机制:8086处理器,寻址空间达到1MB,即地址线扩展了20位,由于制作20位的寄存器较为困难,为了能在16位的寄存器的基础上,寻址20位的地址空间,引入了 段 的概念,即 内存地址=段基址左移4位+偏移
    分页机制:随着寻址空间的进一步扩大、虚拟内存技术的引入,操作系统引入了分页机制。引入分页机制后,逻辑地址经过段机制转换得到的地址仅是中间地址,还需要通过页机制转换,才能得到
    实际的物理地址。 逻辑地址 -->(分段机制) 线性地址 -->(分页机制)

7.5 内存映射文件

  1. 内存映射(mmap) 是一种内存映射文件的方法,即将一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和应用程序进程虚拟地址空间中一段虚拟地址的一一映射关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上。应用程序处理映射部分如同访问主存。

  2. mmap内存映射原理
    (1)线程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域。先在用户空间调用库函数mmap,并在进程当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续虚拟地址作为内存虚拟映射区域,对此区域初始化并插入进程的虚拟地址区域链表或树中。
    (2)系统在内核空间调用内核函数mmap,实现文件物理地址和进程虚拟地址之间的一一映射关系。
    (3)进程发起对这片映射空间的访问
    进程读写操作访问虚拟地址,查询页表,发现这一段地址并不在内存的物理页面上,因为虽然建立了映射关系,但是还没有将文件从磁盘移到内存中。由此发生缺页中断,内核请求从磁盘调入页面。调页过程先在交换缓存空间(swap cache)中查找,若没有则通过nopage函数把缺失页从磁盘调入内存。之后进程会对其做读写操作,若写操作改变了页面内容,一段时间后系统会自动回写脏页面到磁盘中。(修改过的脏页面不会立即更新到文件中,可以调用msync来强制同步,写入文件)

  3. mmap和分页文件操作的区别
    区别在于
    1.分页文件操作在进程访存时是需要先查询页面缓存 (page cache) 的,若发生缺页中断,需要通过inode定位文件磁盘地址,先把缺失文件复制到page cache再从page cache复制到内存中,才能进行访问。这样访存需要经过两次文件复制,写操作也是一样。总结来说,常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。
    2.但mmap的优势在于,把磁盘文件与进程虚拟地址做了映射,这样可以跳过pagecache,只使用一次数据拷贝

7.6 五大I/O模型

Linux有五大I/O模型,分别为阻塞IO、同步非阻塞IO、IO多路复用、信号驱动IO、异步IO五种。

  1. 阻塞IO(blocking IO)
    最传统的一种IO模型,即在读写数据过程中会发生阻塞现象。
    当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除block状态。
    典型的阻塞IO模型的例子为:
    data = socket.read();
    如果数据没有就绪,就会一直阻塞在read方法。

  2. 同步非阻塞IO(nonblocking IO)
    当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。
    所以事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。
    在这里插入图片描述

  3. IO多路复用(IO multiplexing)
    多路复用IO模型是目前使用得比较多的模型。Java NIO实际上就是多路复用IO。
    在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。
    在Java NIO中,是通过selector.select()去查询每个通道是否有到达事件,如果没有事件,则一直阻塞在那里,因此这种方式会导致用户线程的阻塞。
    多路复用IO模式,通过一个线程就可以管理多个socket,只有当socket真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO比较适合连接数比较多的情况。
    另外多路复用IO为何比非阻塞IO模型的效率高是因为:在非阻塞IO中不断地询问socket状态时通过用户线程去进行的,而在多路复用IO中,轮询每个socket状态是内核在进行的,这个效率要比用户线程要高的多。
    注意:多路复用IO模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件逐一进行响应。
    因此对于多路复用IO模型来说,一旦事件响应体很大,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询。

  4. 信号驱动IO(signal driven IO)
    在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。这个一般用于UDP中,对TCP套接口几乎是没用的,原因是该信号产生得过于频繁,并且该信号的出现并没有说明发生了什么事情。

  5. 异步IO(asynchronous IO)
    异步IO模型才是最理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它收到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何阻塞。然后,内核会等待数据准备完成,再将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要关心实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。也就说在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号表示IO操作已经完成,不需要再在用户线程中调用IO函数进行实际的读写操作。
    注意:异步IO是需要操作系统的底层支持,在Java 7中,提供了Asynchronous IO(简称AIO)

7.7 软连接和硬链接

软链接相当于建立了一个新的快捷方式文件,该文件有自己的名称和inode以及物理存储的文件数据,文件数据里记录着如何跳转的设置数据,访问该快捷文件会被重新定向到原始文件,删除原始文件,软链文件失效

硬链接相当于为当前文件名对应的文件再建立了一个文件别名,别名对应的inode以及物理数据都是一样的,一旦建立,我们甚至根本无法区分谁是原始文件的原始名称,删除文件的其中一个名称,文件不会丢失,除非把所有的名称都删除。

软连接和硬链接的区别
(1) 软链接可以为文件和目录(哪怕是不存在的)创建链接;硬链接只能为文件创建链接。
(2) 软链接可以跨文件系统;硬链接必须是同一个文件系统
(3) 硬链接因为只是文件的一个别名,所以不重复占用内存;软链接因为只是一个访问文件的快捷
方式文件,文件内只包含快捷指向信息,所以占用很小的内存
(4) 软链接的文件权限和源文件可以不一样;硬链接文件权限肯定是一样的,因为他们本来就是一
个文件的不同名称而已。

使用场景

一般比较重要的文件我们担心文件被误删除且传统复制备份方式占用double数量的空间会造成浪费,可以使用硬链做备份来解决;软链接一般被用来设置可执行文件的快捷方式的路径。

7.8 写时拷贝技术(CopyOnWrite)

Linux的fork()使用写时拷贝页来实现新进程的创建,它是一种可推迟甚至避免数据拷贝的技术,开始时内核并不会复制整个地址空间,而是让父子进程共享地址空间,只有在写时才复制地址空间,使得父子进程都拥有独立的地址空间,即资源的复制是在只有需要写入时才会发生。在此之前都是以读的方式去和父进程共享资源,这样,在页根本不会被写入的场景下,fork()立即执行exec(),无需对地址空间进行复制,fork()的实际开销就是复制父进程的一个页表和为子进程创建一个进程描述符,也就是说只有当进程空间中各段的内存内容发生变化时,父进程才将其内容复制一份传给子进程,大大提高了效率。

  1. 现在P1用fork()函数为进程创建一个子进程P2

    内核:
    (1)复制P1的正文段,数据段,堆,栈这四个部分,注意是其内容相同。
    (2)为这四个部分分配物理块,P2的:正文段->PI的正文段的物理块,其实就是不为P2分配正
    文段块,让P2的正文段指向P1的正文段块,数据段->P2自己的数据段块(为其分配对应的块),堆->P2自己的堆块,栈->P2自己的栈块。如下图所示:从左到右大的方向箭头表示复制内容。

  2. 写时拷贝技术:内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟究竟结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。

  3. vfork():这个做法更加火爆,内核连子进程的虚拟地址空间结构也不创建了,直接共享了父进程的
    虚拟空间,当然了,这种做法就顺水推舟的共享了父进程的物理空间。

八. 缓存

  1. 寄存器,缓存和内存之间的关系

    1. 寄存器(register)是CPU(中央处理器)的组成部分,是一种直接整合到cpu中的有限的高速访问速度的存储器,它是有一些与非门组合组成的,分为通用寄存器和特殊寄存器。cpu访问寄存器的速度是最快的。那为什么我们不把数据都存储到寄存器中呢,因为寄存器是一种容量有限的存储器,并且非常小。因此只把一些计算机的指令等一些计算机频繁用到的数据存储在其中,来提高计算机的运行速度。
    2. 缓存就是数据交换的缓冲区(称作Cache),当某一硬件要读取数据时,会首先从缓存中查找需要的数据,如果找到了则直接执行,找不到的话则从内存中找。由于缓存的运行速度比内存快得多,故缓存的作用就是帮助硬件更快地运行。
    3. 内存也被称作内存储器,其作用是用于暂时存放CPU的运算数据,以及与硬盘等外部存储交换的数据。只要计算机在运行中,CPU就会把需要进行运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来,内存的运行也决定了计算机的稳定运行。
    4. CPU读取方式:寄存器—>缓存---->内存
  2. 缓存溢出

    1. 缓存溢出及其危害
      缓存溢出是指输入到一个缓冲区或者数据保存区域的数据量超过了其容量,从而导致覆盖了其它区域数据的状况。攻击者造成并利用这种状况使系统崩溃或者通过插入特制的代码来控制系统。被覆盖的区域可能存有其它程序的变量、参数、类似于返回地址或指向前一个栈帧的指针等程序控制流数据。缓冲区可以位于堆、栈或进程的数据段。这种错误可能产生如下后果:
      (1)破坏程序的数据;
      (2)改变程序的控制流,因此可能访问特权代码。
      最终很有可能造成程序终止。当攻击者成功地攻击了一个系统之后,作为攻击的一部分,程序的控制流可能会跳转到攻击者选择的代码处,造成的结果是被攻击的进程可以执行任意的特权代码(比如通过判断输入是否和密码匹配来访问特权代码,如果存在缓冲区漏洞,非法输入导致存放“密码”的内存区被覆盖,从而使得“密码”被改写,因此判断为匹配进而获得了特权代码的访问权)缓冲区溢出攻击是最普遍和最具危害性的计算机安全攻击类型之一。
    2. 如何预防缓存溢出
      广义上分为两类:
      (1)编译时防御系统,目的是强化系统以抵御潜伏于新程序中的恶意攻击
      (2)运行时预防系统,目的是检测并终止现有程序中的恶意攻击

九.中断

  1. 缺页中断
    在请求分页系统中,可以通过查询页表中的状态位来确定所要访问的页面是否存在于内存中。
    当所要访问的页面不在内存时,会产生一次缺页中断,此时操作系统会根据页表中的外存地址在外存中找到所缺的一页,将其调入内存。

  2. 缺页中断与一般的中断存在区别
    (1)范围不同
    一般中断只需要保护现场然后就直接跳到需及时处理的地方;
    缺页中断除了保护现场之外,还要判断内存中是否有足够的空间存储所需的页或段,然后再把所需页调进来再使用。
    (2)结果不同
    一般中断在处理完之后返回时,执行下一条指令;缺页中断返回时,执行产生中断的那一条指令

    (3)次数不同
    一般中断只产生一次,发生中断指令后转入相应处理程序进行处理,恢复被中断程序现场;在指令执行期间产生和处理缺页中断信号,一条指令在执行期间,可能产生多次缺页中断

  3. 产生缺页中断情况

    1. 当内存管理单元(MMU)中确实没有创建虚拟物理页映射关系,并且在该虚拟地址之后再没有当前进程的线性区(vma)的时候,这将杀掉该进程;
    2. 当MMU中确实没有创建虚拟页物理页映射关系,并且在该虚拟地址之后存在当前进程的线性区vma的时候,这很可能是缺页中断,并且可能是栈溢出导致的缺页中断;
    3. 当使用malloc/mmap等希望访问物理空间的库函数/系统调用后,由于linux并未真正给新创建的vma映射物理页,此时若先进行写操作,将和2产生缺页中断的情况一样;若先进行读操作虽然也会产生缺页异常,将被映射给默认的零页,等再进行写操作时,仍会产生缺页中断,这次必须分配1物理页了,进入写时复制的流程;
    4. 当使用fork等系统调用创建子进程时,子进程不论有无自己的vma,它的vma都有对于物理页的映射,但它们共同映射的这些物理页属性为只读,即linux并未给子进程真正分配物理页,当父子进程任何一方要写相应物理页时,导致缺页中断的写时复制
  4. 软中断和硬中断

    1. 硬中断
      由与系统相连的外设(比如网卡、硬盘)自动产生的。主要是用来通知操作系统外设状态的变化。比如当网卡收到数据包的时候,就会发出一个中断。我们通常所说的中断指的是硬中断(hardirq)。
    2. 软中断
      为了满足实时系统的要求,中断处理应该是越快越好。Linux为了实现这个特点,当中断发生的时候,硬中断处理那些短时间就可以完成的工作,而将那些处理事件比较长的工作,放到中断之后来完成,也就是软中断(softirq)来完成。
    3. 中断嵌套
      Linux下硬中断是可以嵌套的,但是没有优先级的概念,也就是说任何一个新的中断都可以打断正在执行的中断,但同种中断除外。软中断不能嵌套,但相同类型的软中断可以在不同CPU上并行执行。
    4. 软中断与硬中断之间的区别
      (1)硬中断是由外部事件引起的因此具有随机性和突发性;软中断是执行中断指令产生的,无外面事件中断请求信号,因此软中断的发生不是随机的而是由程序安排好的;
      (2)硬中断的中断号是由中断控制器提供的;软中断的中断号是由指令直接给出的,无需使用中断控制器。
      (3)硬中断的中断响应周期,CPU需要发中断回合信号;软中断的中断响应周期,CPU不需要发中断回合信号。
      (4)硬中断是可屏蔽的;软中断是不可屏蔽的。

整理不易,如有问题欢迎指出,觉得文章可以,请大佬们动手点个赞哈撒花~~~~

猜你喜欢

转载自blog.csdn.net/zhangkai__/article/details/126730463