文章目录
前言
本文将详细介绍【进程】相关内容,包括,进程的概念、状态、调度、通信等
一、进程
1.进程的基本概念
进程是计算机系统中资源分配和调度的基本单位。它是一个正在执行的程序的实例,包含了程序执行所需的各种资源,如代码段(存储程序的指令)、数据段(存储程序中的变量等数据)、堆栈段(用于函数调用和局部变量存储)以及操作系统为其分配的其他资源(如打开的文件、网络连接等)。
你可以打开任务管理器,或者活动监视器,就会显示一条条的进程,包含进程名,内存,线程等信息
2.进程的状态及其转换
2.1新建(New)状态:
当一个程序开始执行时,进程首先处于新建状态。在这个状态下,操作系统为进程分配必要的资源,如内存空间,初始化进程控制块(PCB)。PCB 是一个数据结构,用于记录进程的各种信息,包括进程标识符(PID)、进程状态、优先级等。例如,当你双击一个可执行文件图标时,操作系统会开始创建进程,在这个过程中进程处于新建状态。
2.2就绪(Ready)状态:
进程在获得了除 CPU 以外的所有必要资源后,就进入就绪状态。处于就绪状态的进程已经准备好运行,只是在等待 CPU 分配时间片。可以将其想象为一群等待乘坐游乐设施的游客,游乐设施(CPU)每次只能接待一部分游客,其他游客(就绪状态的进程)就在旁边排队等待。例如,在一个多任务操作系统中,有多个进程同时处于就绪状态,它们会根据操作系统的调度算法来竞争 CPU 时间片。
2.3运行(Running)状态:
当进程获得 CPU 时间片后,就进入运行状态,开始执行进程中的指令。在运行过程中,进程可能会因为时间片用完、等待某个事件(如 I/O 操作完成、获取锁等)或者被更高优先级的进程抢占 CPU 而暂停运行。例如,一个正在进行复杂计算的进程,在其时间片用完后,会暂停运行,将 CPU 让给其他就绪状态的进程。
2.4阻塞(Blocked)状态:
当进程因为等待某个事件而暂时无法继续执行时,就会进入阻塞状态。常见的导致阻塞的事件包括等待 I/O 操作完成(如从磁盘读取文件、向网络发送数据等)、等待获取某个共享资源的锁(如多个进程同时访问一个文件时需要获取文件锁)。例如,一个进程需要从硬盘读取一个大型文件,在文件读取操作完成之前,进程会进入阻塞状态,等待数据读取完成后才能继续执行。
2.5终止(Terminated)状态:
当进程完成任务或者由于出错(如发生段错误、收到终止信号等)而结束时,进程就进入终止状态。在这个状态下,操作系统会回收进程所占用的资源,包括释放内存空间、关闭打开的文件和网络连接等。例如,当你关闭一个已经打开的应用程序,操作系统会将该应用程序对应的进程转换到终止状态,并进行资源回收。
3.进程的创建与销毁
创建:
进程的创建通常是由操作系统的系统调用完成的。在大多数操作系统中,当一个程序被执行时,例如用户在命令行中输入一个可执行文件的名称并回车,或者通过图形界面双击一个可执行文件的图标,操作系统会进行一系列的操作来创建一个新的进程。首先,操作系统会为新进程分配内存空间,这个空间包括代码段(存储程序的指令)、数据段(存储程序中的全局变量和静态变量)和堆栈段(用于函数调用和局部变量存储)。然后,操作系统会将可执行文件的内容加载到新分配的内存空间的代码段中。接着,会初始化进程控制块(PCB),PCB 是操作系统用于管理进程的一个数据结构,它包含了进程的各种信息,如进程 ID、进程状态、优先级等。最后,操作系统会将新进程放入就绪队列,等待 CPU 分配时间片来开始运行。
销毁:
进程的销毁也称为进程终止,有多种情况会导致进程终止。一种情况是进程正常执行完所有的任务后,主动调用系统调用(如在 Linux 系统中的exit()函数)来终止自己。在这种情况下,操作系统会回收进程所占用的各种资源,包括释放内存空间、关闭打开的文件描述符等。另一种情况是进程出现异常,例如程序发生了段错误(访问了非法的内存地址)或者被外部信号(如用户通过命令行发送的kill信号)强制终止。当进程异常终止时,操作系统同样会进行资源回收操作,不过可能会根据具体的异常情况生成一些错误信息来帮助开发者排查问题。
4.进程的内存布局
进程的内存空间通常分为几个不同的区域。
代码段:
这是存储程序指令的区域,它是只读的。这意味着在进程运行过程中,代码段中的指令不会被修改。例如,对于一个 C 语言编写的程序,所有的函数定义和执行语句对应的机器指令都存储在代码段中。这种只读的特性有助于保证程序的安全性和稳定性,因为如果代码段可以随意修改,可能会导致程序执行出现不可预测的错误。
数据段:
数据段主要存储程序中的全局变量和静态变量。这个区域的内容是可以修改的。在程序运行过程中,当全局变量或静态变量的值发生变化时,就是在数据段中进行修改。例如,一个程序中有一个全局变量用于记录用户的登录次数,每次用户登录成功后,这个变量的值就会在数据段中被更新。
堆栈段:
堆栈段又分为栈和堆两个部分。栈是用于存储函数调用信息的,包括函数的参数、局部变量和返回地址等。当一个函数被调用时,会在栈上为这个函数创建一个栈帧,栈帧的大小根据函数的参数和局部变量的数量以及类型而定。当函数返回时,对应的栈帧会被销毁。堆则是用于动态内存分配,例如在 C 语言中使用malloc()函数分配的内存空间就在堆中。程序员可以根据程序的需要在堆中申请和释放内存,但如果管理不当,可能会导致内存泄漏(申请的内存没有释放)或者悬空指针(指向已经释放的内存的指针)等问题。
5.进程的优先级与调度
优先级:
进程的优先级是操作系统用于决定哪个进程能够优先获得 CPU 资源的一个重要因素。不同的操作系统对进程优先级的划分方式可能不同。一般来说,优先级可以分为静态优先级和动态优先级。
静态优先级是在进程创建时就确定的,并且在进程的整个生命周期内基本不变。例如,在一些操作系统中,系统服务进程可能被赋予较高的静态优先级,因为它们对于系统的正常运行至关重要。
动态优先级则是根据进程的运行情况动态调整的。例如,如果一个进程长时间等待 CPU 资源,操作系统可能会适当提高它的优先级,以确保它能够尽快得到运行;反之,如果一个进程占用 CPU 资源的时间过长,操作系统可能会降低它的优先级。
调度:
进程调度是操作系统决定哪个进程在什么时间获得 CPU 资源的过程。常见的调度算法有先来先服务(FCFS)、短作业优先(SJF)、时间片轮转(RR)和优先级调度等。
先来先服务(FCFS)调度算法是按照进程到达就绪队列的先后顺序来分配 CPU 资源,先到达的进程先获得 CPU。
短作业优先(SJF)调度算法则是优先选择预计执行时间较短的进程来运行,这种算法可以有效地提高系统的吞吐量,但可能会导致长作业长时间等待。
时间片轮转(RR)调度算法是将 CPU 时间划分成固定大小的时间片,每个进程轮流获得一个时间片来运行,当时间片用完后,即使进程还没有执行完,也会暂停运行,将 CPU 让给下一个进程。
优先级调度算法是根据进程的优先级来分配 CPU 资源,优先级高的进程优先获得 CPU。不同的调度算法适用于不同的应用场景,操作系统会根据具体的系统需求和设计目标来选择合适的调度算法。
6.进程间通信(IPC)
由于进程之间是相互独立的,它们的内存空间是隔离的,所以当进程需要相互交流信息或者共享数据时,就需要通过进程间通信(IPC)机制来实现。
管道(Pipe):管道是一种简单的单向通信机制,它可以用于具有亲缘关系(如父子进程)的进程之间的通信。管道就像一个连接两个进程的管道,一个进程向管道的一端写入数据,另一个进程从管道的另一端读取数据。管道分为无名管道和有名管道。无名管道只能用于有亲缘关系的进程之间,并且在创建管道的进程结束后,管道就会自动消失。有名管道则可以在没有亲缘关系的进程之间使用,它有一个文件名,通过这个文件名,不同的进程可以找到并使用这个管道。
消息队列(Message Queue):消息队列是一种在进程之间传递消息的数据结构。进程可以将消息发送到消息队列中,其他进程可以从消息队列中接收消息。消息队列中的消息具有一定的格式,通常包括消息类型和消息内容。消息队列的优点是可以实现异步通信,即发送消息的进程和接收消息的进程不需要同时运行。
共享内存(Shared Memory):共享内存是一种高效的进程间通信方式,它允许两个或多个进程共享同一块内存区域。通过将数据存储在共享内存区域中,进程可以直接访问这些数据,而不需要像管道和消息队列那样进行数据的复制。但是,共享内存也带来了一些问题,如数据的同步和互斥问题,因为多个进程可能会同时访问共享内存中的数据,所以需要使用同步机制(如信号量)来确保数据的正确访问。
信号量(Semaphore):信号量主要用于控制多个进程对共享资源的访问。它是一个计数器,用于记录可用资源的数量。当一个进程需要访问共享资源时,它会先检查信号量的值。如果信号量的值大于 0,表示有可用的资源,进程可以访问共享资源,并将信号量的值减 1;如果信号量的值等于 0,表示没有可用的资源,进程需要等待,直到信号量的值大于 0。信号量可以有效地解决进程间共享资源时的同步和互斥问题。
套接字(Socket):套接字主要用于不同主机上的进程之间的通信,也可以用于同一主机上的进程之间的通信。它是一种基于网络协议(如 TCP/IP)的通信方式。通过套接字,进程可以建立连接,发送和接收数据,就像在网络上进行通信一样。套接字通信具有很强的通用性和扩展性,是实现分布式系统和网络应用的重要基础。