MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

进程就绪态的形成与意义

2021-02-066.7k 阅读

进程就绪态的形成

进程状态概述

在操作系统中,进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。进程有着不同的状态,常见的进程状态包括就绪态(Ready)、运行态(Running)和阻塞态(Blocked)。运行态是指进程正在处理机上运行;阻塞态是指进程因为等待某一事件(如I/O操作完成、信号量等)而暂时不能运行;就绪态则是指进程已经具备了运行条件,只等待处理机资源,一旦获得处理机,进程就可以立即投入运行。

就绪态形成的原因

  1. 新进程创建 当一个新的进程被创建时,系统会为其分配必要的资源,如内存空间、文件描述符等。在完成这些资源分配后,进程就进入就绪态。以C语言为例,使用fork()函数创建子进程的过程可以很好地说明这一点。在父进程中调用fork(),系统会为子进程分配内核数据结构和用户空间的副本。当fork()返回时,子进程就进入了就绪态,等待被调度执行。以下是简单的代码示例:
#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid;
    pid = fork();
    if (pid == 0) {
        // 子进程代码
        printf("I am the child process, ready to run.\n");
    } else if (pid > 0) {
        // 父进程代码
        printf("I am the parent process, child created.\n");
    } else {
        // fork失败
        perror("fork");
        return 1;
    }
    return 0;
}

在上述代码中,子进程创建后,就进入了就绪态,等待系统调度执行其printf语句。

  1. 进程从运行态切换 运行态的进程在某些情况下会主动放弃处理机资源,从而进入就绪态。一种常见的情况是时间片用完。现代操作系统大多采用分时调度算法,为每个进程分配一个时间片。当进程运行完其时间片后,调度程序会暂停该进程的执行,将其状态改为就绪态,然后从就绪队列中选择另一个进程投入运行。例如,在Linux系统中,基于CFS(完全公平调度器)的调度算法,每个进程都有一个虚拟运行时间,当进程的虚拟运行时间超过当前调度周期中其他进程的虚拟运行时间时,该进程就会被调度程序暂停,进入就绪态。

另一种情况是进程执行了某些系统调用,例如yield()函数(在一些操作系统中支持),该函数会使当前运行的进程主动放弃处理机,进入就绪态,以便其他进程有机会运行。以下是一个简单模拟yield效果的代码示例(假设系统支持yield函数):

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("Process starts running.\n");
    // 模拟一些计算
    for (int i = 0; i < 100000000; i++);
    // 主动放弃处理机
    yield();
    printf("Process is back in running state after yield.\n");
    return 0;
}

在上述代码中,当执行到yield()时,进程从运行态进入就绪态,等待再次被调度执行后续的printf语句。

  1. 进程等待的事件完成 处于阻塞态的进程,当它所等待的事件发生时,进程就会从阻塞态转变为就绪态。例如,一个进程发起了一个I/O读操作后进入阻塞态,当I/O设备完成数据读取并将数据返回给进程时,该进程就会从阻塞态进入就绪态。以Python的文件读取操作为例:
import time

def read_file():
    try:
        with open('example.txt', 'r') as file:
            data = file.read()
            print("Data read successfully: ", data)
    except FileNotFoundError:
        print("File not found.")

print("Process starts, about to read file.")
# 模拟进程进入阻塞态等待I/O完成
time.sleep(2)  
print("I/O operation completed, process enters ready state.")
read_file()

在上述代码中,time.sleep(2)模拟了进程因为I/O操作而进入阻塞态的过程。当模拟的I/O操作完成(时间到),进程就进入了就绪态,准备执行read_file函数。

就绪态形成的系统机制

  1. 内核数据结构更新 当进程进入就绪态时,操作系统内核需要更新该进程对应的内核数据结构。以Linux内核为例,每个进程都有一个task_struct结构体来描述其状态和属性。当进程进入就绪态时,task_struct中的状态字段会被设置为就绪态标识。同时,内核还会更新与调度相关的字段,如进程的优先级、虚拟运行时间(在CFS调度算法中)等,以便调度程序能够根据这些信息进行合理的调度。

  2. 就绪队列管理 操作系统通常会维护一个或多个就绪队列,用于存放处于就绪态的进程。当进程进入就绪态时,它会被插入到相应的就绪队列中。不同的操作系统可能采用不同的数据结构来实现就绪队列,例如链表、堆等。以简单的双向链表实现的就绪队列为例,当一个新的进程进入就绪态时,内核会为其创建一个链表节点,并将该节点插入到就绪队列的尾部(也可能根据优先级等因素插入到不同位置)。以下是一个简单的C语言代码示例来模拟就绪队列的插入操作:

#include <stdio.h>
#include <stdlib.h>

// 定义进程结构体
typedef struct Process {
    int pid;
    struct Process *next;
    struct Process *prev;
} Process;

// 定义就绪队列结构体
typedef struct ReadyQueue {
    Process *head;
    Process *tail;
} ReadyQueue;

// 初始化就绪队列
void initQueue(ReadyQueue *queue) {
    queue->head = NULL;
    queue->tail = NULL;
}

// 将进程插入就绪队列
void enqueue(ReadyQueue *queue, Process *process) {
    if (queue->tail == NULL) {
        queue->head = process;
        queue->tail = process;
        process->next = NULL;
        process->prev = NULL;
    } else {
        queue->tail->next = process;
        process->prev = queue->tail;
        queue->tail = process;
        process->next = NULL;
    }
}

int main() {
    ReadyQueue queue;
    initQueue(&queue);

    // 创建新进程
    Process *newProcess = (Process *)malloc(sizeof(Process));
    newProcess->pid = 1;

    enqueue(&queue, newProcess);
    printf("Process with PID %d added to ready queue.\n", newProcess->pid);

    return 0;
}

在上述代码中,enqueue函数模拟了将一个新进程(代表进入就绪态的进程)插入到就绪队列的过程。

进程就绪态的意义

提高系统资源利用率

  1. CPU资源的高效利用 就绪态的存在使得CPU资源能够得到充分利用。在单处理机系统中,如果没有就绪态,当一个进程因为I/O操作或其他原因阻塞时,CPU就会处于空闲状态。而有了就绪态,当一个进程进入阻塞态时,调度程序可以立即从就绪队列中选择另一个就绪态的进程投入运行,从而避免CPU空闲。在多处理机系统中,就绪队列中的进程可以被分配到不同的处理机上并行运行,进一步提高了CPU资源的利用率。例如,在一个服务器系统中,可能同时有多个客户端请求需要处理,每个请求对应的进程在创建后进入就绪态。当一个进程在处理I/O操作(如读取数据库数据)时,其他就绪态的进程可以利用CPU资源继续处理其他客户端请求,使得整个系统的处理能力得到提升。

  2. 其他资源的协同利用 进程就绪态不仅与CPU资源利用相关,还能促进其他系统资源的协同利用。例如,当一个进程因为等待I/O设备完成操作而进入阻塞态时,I/O设备在进行操作的同时,CPU可以调度其他就绪态的进程运行。当I/O操作完成,原阻塞进程进入就绪态,又可以与其他进程竞争CPU资源。这种协同机制使得系统中的各种资源(如CPU、I/O设备、内存等)能够得到更合理的分配和利用,提高了整个系统的资源利用率。

实现多任务并发执行

  1. 分时复用实现并发 在现代操作系统中,通过对就绪态进程的调度,采用分时复用的方式实现多任务并发执行。调度程序按照一定的调度算法(如时间片轮转算法),从就绪队列中依次选择进程投入运行,每个进程运行一个时间片后被暂停,重新回到就绪队列,等待下一次调度。这样,多个进程看似在同时运行,给用户一种多任务并发执行的体验。例如,在个人计算机上,用户可以同时打开浏览器浏览网页、使用音乐播放器播放音乐、运行文字处理软件进行文档编辑等。这些应用程序对应的进程都处于就绪态或运行态,通过调度程序的分时复用,实现了多任务的并发执行。

  2. 提高系统响应速度 就绪态对于提高系统的响应速度有着重要意义。当用户发起一个新的任务(如启动一个应用程序)时,新创建的进程进入就绪态。调度程序能够快速地将其调度执行,使得用户能够尽快看到任务的执行结果。同时,对于那些对响应时间要求较高的进程(如实时进程),操作系统可以通过设置较高的优先级,使其在就绪队列中优先被调度,从而保证系统对关键任务的快速响应。例如,在一个工业控制系统中,实时监测进程需要及时处理传感器数据,其优先级较高。当该进程处于就绪态时,调度程序会优先调度它运行,以确保系统能够快速响应传感器数据的变化。

优化系统调度策略

  1. 基于就绪队列的调度算法设计 就绪队列是操作系统调度算法的重要基础。不同的调度算法根据就绪队列中进程的特点和需求来选择下一个执行的进程。例如,先来先服务(FCFS)调度算法按照进程进入就绪队列的先后顺序进行调度;最短作业优先(SJF)调度算法则选择预计运行时间最短的就绪态进程进行调度;优先级调度算法根据进程的优先级,从就绪队列中选择优先级最高的进程运行。通过合理设计调度算法,可以优化系统的性能,满足不同应用场景的需求。以下是一个简单的基于优先级调度算法的C语言模拟代码:
#include <stdio.h>
#include <stdlib.h>

// 定义进程结构体
typedef struct Process {
    int pid;
    int priority;
    struct Process *next;
} Process;

// 定义就绪队列结构体
typedef struct ReadyQueue {
    Process *head;
} ReadyQueue;

// 初始化就绪队列
void initQueue(ReadyQueue *queue) {
    queue->head = NULL;
}

// 将进程插入就绪队列(按优先级插入)
void enqueue(ReadyQueue *queue, Process *process) {
    if (queue->head == NULL || process->priority < queue->head->priority) {
        process->next = queue->head;
        queue->head = process;
    } else {
        Process *current = queue->head;
        while (current->next != NULL && current->next->priority <= process->priority) {
            current = current->next;
        }
        process->next = current->next;
        current->next = process;
    }
}

// 从就绪队列中取出优先级最高的进程
Process* dequeue(ReadyQueue *queue) {
    if (queue->head == NULL) {
        return NULL;
    }
    Process *temp = queue->head;
    queue->head = queue->head->next;
    return temp;
}

int main() {
    ReadyQueue queue;
    initQueue(&queue);

    // 创建进程
    Process *p1 = (Process *)malloc(sizeof(Process));
    p1->pid = 1;
    p1->priority = 3;

    Process *p2 = (Process *)malloc(sizeof(Process));
    p2->pid = 2;
    p2->priority = 1;

    Process *p3 = (Process *)malloc(sizeof(Process));
    p3->pid = 3;
    p3->priority = 2;

    enqueue(&queue, p1);
    enqueue(&queue, p2);
    enqueue(&queue, p3);

    printf("Processes dequeued in priority order:\n");
    Process *runningProcess;
    while ((runningProcess = dequeue(&queue)) != NULL) {
        printf("PID: %d, Priority: %d\n", runningProcess->pid, runningProcess->priority);
        free(runningProcess);
    }

    return 0;
}

在上述代码中,enqueue函数根据进程的优先级将进程插入到就绪队列的合适位置,dequeue函数从就绪队列中取出优先级最高的进程,模拟了基于优先级的调度过程。

  1. 动态调整调度策略 操作系统可以根据系统的运行状态和就绪队列中进程的情况动态调整调度策略。例如,当系统负载较轻时,可以采用较为简单的调度算法,如FCFS,以减少调度开销;当系统负载较重时,为了提高系统的整体性能,可以采用更复杂的调度算法,如多级反馈队列调度算法。多级反馈队列调度算法将就绪队列分为多个级别,每个级别有不同的时间片和优先级。新进程首先进入最高优先级队列,运行一个时间片后,如果未完成,则进入下一级队列。通过这种方式,既能保证短作业快速完成,又能兼顾长作业的执行。这种动态调整调度策略的能力依赖于对就绪态进程的有效管理和监控,从而进一步优化系统的整体性能。

保障系统的稳定性和可靠性

  1. 进程故障隔离 就绪态有助于实现进程故障隔离。当一个进程出现故障(如程序崩溃、内存访问越界等)时,操作系统可以暂停该进程,将其从运行态转换为就绪态(如果可能的话)或终止该进程。同时,其他处于就绪态的进程不受影响,仍然可以正常被调度运行。这样,一个进程的故障不会导致整个系统崩溃,保障了系统的稳定性。例如,在一个多进程的服务器程序中,如果某个处理客户端请求的进程因为代码错误而崩溃,操作系统可以将其终止,然后继续调度其他就绪态的进程处理新的客户端请求,使得服务器仍然能够正常运行。

  2. 系统资源保护 通过对就绪态进程的合理调度和资源分配,可以保护系统资源不被恶意或错误的进程过度占用。操作系统可以根据进程的优先级和资源需求,从就绪队列中选择合适的进程运行,并为其分配适量的资源。如果一个进程试图过度占用资源(如无限申请内存),操作系统可以限制其资源分配,甚至将其暂停或终止,从而保证其他进程能够获得必要的资源,维护系统的可靠性。例如,在一个共享主机环境中,可能存在多个用户的进程,操作系统通过对就绪态进程的资源管理,可以防止某个用户的进程耗尽系统资源,影响其他用户的正常使用。

综上所述,进程就绪态的形成是操作系统进程管理中的一个重要环节,其形成机制涉及到进程创建、状态转换以及内核数据结构和就绪队列的管理等方面。而进程就绪态对于提高系统资源利用率、实现多任务并发执行、优化系统调度策略以及保障系统的稳定性和可靠性都具有至关重要的意义,是操作系统能够高效、稳定运行的关键因素之一。