进程概述
程序和进程是计算机科学中的基本概念,它们经常被提到,尤其是在操作系统的上下文中。这两个概念虽然紧密相关,但有明显的区别:
程序(Program)
程序是指存储在磁盘上的一组指令和数据,它是静态的。程序本质上是一个文件,包含了执行特定任务所需要的代码。程序在被执行之前并不进行任何操作;它只是一组可能被执行的指令。简单地说,程序就是编写好的代码,存储在某种存储介质上,如硬盘、USB驱动器或其他形式的存储设备。
程序是包含一系列信息的文件,这些信息描述了如何在运行时创建一个进程:
◼ 二进制格式标识:每个程序文件都包含用于描述可执行文件格式的元信息。内核利用此信息来解
释文件中的其他信息。(ELF可执行连接格式)
◼ 机器语言指令:对程序算法进行编码。
◼ 程序入口地址:标识程序开始执行时的起始指令位置。
◼ 数据:程序文件包含的变量初始值和程序使用的字面量值(比如字符串)。
◼ 符号表及重定位表:描述程序中函数和变量的位置及名称。这些表格有多重用途,其中包括调试
和运行时的符号解析(动态链接)。
◼ 共享库和动态链接信息:程序文件所包含的一些字段,列出了程序运行时需要使用的共享库,以
及加载共享库的动态连接器的路径名。
◼ 其他信息:程序文件还包含许多其他信息,用以描述如何创建进程。
进程(Process)
进程则是程序的一个实例,它是在内存中执行的。当你双击一个程序或通过命令行启动一个程序时,你实际上是在创建一个或多个进程。进程包含程序代码和它的当前活动,包括程序计数器、寄存器和变量的当前值。进程是系统资源分配和调度的基本单位。
进程是正在运行的程序的实例。是一个具有一定独立功能的程序关于某个数据集合的
一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是
基本的分配单元,也是基本的执行单元。
◼ 可以用一个程序来创建多个进程,进程是由内核定义的抽象实体,并为该实体分配用
以执行程序的各项系统资源。从内核的角度看,进程由用户内存空间和一系列内核数
据结构组成,其中用户内存空间包含了程序代码及代码所使用的变量,而内核数据结构则用于维护进程状态信息。记录在内核数据结构中的信息包括许多与进程相关的标识号(IDs)、虚拟内存表、打开文件的描述符表、信号传递及处理的有关信息、进
程资源使用及限制、当前工作目录和大量的其他信息
关键区别
- 存储:程序存储在磁盘上,而进程存在于内存中。
- 状态:程序是静态的,仅仅是代码和数据的集合;进程是动态的,表示程序的执行状态。
- 资源:进程需要系统资源(如CPU时间、内存空间、文件句柄等)来执行;程序本身不消耗资源,除了占用存储空间。
- 生命周期:程序的生命周期可以非常长,因为它是被保存在磁盘上的文件。进程的生命周期则通常较短,从程序启动到执行完毕。
总结
程序是指令和数据的集合,存储为文件,等待被执行;而进程是程序执行时的动态表现形式,包括程序代码的执行和资源的使用。进程是操作系统进行资源管理和调度的基本单位,每个进程至少有一个执行线程。
单道多道程序设计
单道和多道程序设计是操作系统中管理和运行程序的两种不同策略。这些策略基本上描述了系统如何利用资源(如CPU和内存)来增强计算效率、提高资源利用率和减少系统的空闲时间。
单道程序设计(Single-Programming)
在单道程序设计中,计算机一次只能加载和执行一个程序。直到这个程序运行完毕或者释放控制权(例如进行I/O操作),系统才能加载和执行另一个程序。这种方法的特点是实现简单,但效率较低,因为它无法充分利用计算机的所有资源。例如,在进行I/O操作时,CPU通常会闲置,因为它没有其他任务可以执行。
- 优点:简单易于管理,调试程序也相对容易。
- 缺点:资源利用率低,系统响应时间可能长。
多道程序设计(Multiprogramming)
多道程序设计允许多个程序同时驻留在内存中,并由操作系统管理这些程序的执行。当一个程序等待I/O操作完成时,操作系统可以切换到另一个程序,继续使用CPU进行计算,从而提高资源的利用率和系统的吞吐量。
在多道程序设计中,操作系统需要有效的管理和调度策略来决定哪个程序应当获得CPU时间,以及如何在程序之间公平地分享资源。这就引入了一些复杂的概念,如进程调度算法、内存管理和虚拟内存等。
- 优点:提高CPU和其他资源的利用率,增加系统吞吐量。
- 缺点:更复杂的操作系统设计,可能引发如死锁等同步与并发问题。
总结
单道程序设计和多道程序设计反映了操作系统在资源利用和程序运行效率之间的权衡。随着计算机技术的发展,多道程序设计成为了主流,现代操作系统几乎都是基于多道程序设计的概念构建的,如Windows、Linux和Mac OS等。这些系统通过复杂的管理策略实现了高效的资源利用和用户响应时间,适应了现代多任务和高性能计算的需求。
时间片
在计算机操作系统的上下文中,时间片(Time Slice),也被称为时间量子(Time Quantum),是指分配给每个正在运行的进程的固定时间段,在这段时间内进程可以运行其任务。时间片是多道程序设计和多任务操作系统中使用的概念,特别是在基于时间共享的系统中,如轮转调度(Round Robin Scheduling)算法中非常关键。
时间片的作用
时间片的主要目的是实现公平的CPU资源分配,使得每个进程都能获得执行的机会,从而增加系统的响应性和交互性。操作系统调度器负责管理各个进程的时间片,确保每个进程在它的时间片内运行,并在时间片用完时进行进程切换。
时间片的调度
在轮转调度算法中,所有的进程被组织在一个队列中。调度器按顺序将CPU分配给每个进程一个时间片。如果进程在其时间片结束前完成了任务或被阻塞(如等待I/O操作),它会提前释放CPU。如果时间片耗尽,进程尚未完成,调度器会强制从该进程中夺回CPU,并将其移到队列的末尾,接着分配CPU给下一个进程。
时间片长度的影响
时间片的长度对系统的性能有显著影响:
- 时间片过长:如果时间片设置得太长,系统的响应性会降低,因为I/O密集型进程可能需要等待很长时间才能执行,导致类似于单道程序设计的情况。
- 时间片过短:如果时间片过短,虽然系统的响应性很高,但频繁的进程切换会增加系统的开销(上下文切换成本),从而降低系统的整体效率。
优化时间片
选择合适的时间片长度是优化系统性能的关键。操作系统设计者需要在响应时间和系统开销之间找到平衡点。有些现代操作系统实现了动态时间片,即根据系统的负载情况和进程类型动态调整时间片的长度,以达到更优的调度效果。
时间片的设定和管理是操作系统设计中的一个重要方面,直接关系到多任务操作系统的效率和公平性。通过适当的调度策略和时间片长度,可以显著提高多用户和多任务环境中的计算机系统性能。
并行和并发
在计算机科学和软件工程中,"并行"和"并发"这两个术语常常被提及,尤其是在谈论多任务处理和系统设计时。虽然这两个词在日常使用中可能被交替使用,但它们在技术上有明确的区别,并且指代了不同的概念。
并发(Concurrency)
并发是指系统或应用程序能够同时处理多个任务的能力。在并发模型中,一个处理器可以在单个时间点内交替执行多个任务的不同部分,通过任务之间的快速切换给用户一种多任务同时进行的错觉。并发不一定意味着这些任务实际上是在同一时刻执行的,而是任务在单位时间内都有向前推进的进展。
- 关键点:并发更多地关注的是任务管理和调度的效率,使多个任务可以在有限的资源上高效地运行。
- 示例:在单核CPU上,操作系统可以通过时间片轮转等技术使多个进程看似同时运行。
并行(Parallelism)
并行处理指的是多个处理任务真正同时发生,通常需要多个处理器或多核处理器。在并行计算中,多个处理单元将同时工作,每个单元处理分配给它的任务的一部分。并行可以显著加快处理速度,因为它利用了物理上的多个核心或处理器。
- 关键点:并行性侧重于同时执行多个任务,以缩短总体处理时间。
- 示例:一个具有四个核心的处理器可以同时运行四个独立的进程。
并行与并发的关系和区别
- 资源利用:并发强调的是资源使用的最大化,通过任务交替执行达到多任务处理的目的。并行则是通过物理上的多个计算单元来同时执行任务,实现速度的最大化。
- 设计复杂性:并行通常需要更复杂的硬件支持和编程模型,因为它涉及到真正的同时执行,可能会遇到数据同步和资源共享的问题。并发虽然在单核处理器上就可以实现,但管理多个同时运行的任务也需要复杂的调度算法。
- 适用场景:并行处理适合计算密集型的任务,如科学计算和大数据处理,其中任务可以被划分为可以同时进行的多个子任务。并发处理适合于I/O密集型任务,如服务器和交互式应用,它们需要高效地管理多个同时到达的请求。
理解并行与并发的区别对于设计能够有效利用现代多核处理器的系统和应用至关重要。正确的应用并行和并发原则可以极大地提高软件的性能和响应速度。
进程控制块(PCB)
进程控制块(PCB, Process Control Block)是操作系统中一个非常关键的数据结构,用于存储有关进程的重要信息。PCB作为进程的元数据,使得操作系统能够管理所有的进程。每个进程都有一个与之对应的PCB,它包含了操作系统需要的所有信息,以便在适当的时候对进程进行调度和控制。
PCB包含的主要信息:
-
进程标识符(Process ID, PID):
- 唯一标识一个进程。通常用于跟踪进程状态或在进程间通信中识别进程。
-
进程状态:
- 描述进程的当前状态,如就绪(ready)、运行(running)、等待(waiting)、挂起(suspended)等。
-
程序计数器(Program Counter, PC):
- 存储下一条要执行的指令的地址。程序计数器使得CPU在完成当前任务后,能够知道从哪里恢复执行。
-
寄存器集的信息:
- 包括所有寄存器的当前值。这对于进程在被暂停时保存其状态,以及恢复执行时重新加载状态非常重要。
-
进程优先级:
- 决定进程调度的优先级,高优先级的进程比低优先级的进程更有可能被CPU先执行。
-
内存管理信息:
- 包括进程的内存分配情况、页表、内存限制、段表等,这些信息帮助操作系统管理进程的内存需求。
-
账户信息:
- 跟踪进程使用的处理器时间、实时时钟、使用的内存等,这些信息通常用于系统监控和计费。
-
I/O状态信息:
- 包括分配给进程的I/O设备列表和打开的文件描述符列表等,这些信息帮助操作系统管理进程的输入输出需求。
-
指向进程队列中下一个进程控制块的指针:
- 这使得操作系统能够组织多个PCB为队列,便于进程的调度和管理。
进程控制块的作用:
-
进程调度:
- 操作系统使用PCB来选择哪个进程接下来获得CPU时间,通过查看PCB中的状态和优先级信息来做决策。
-
进程管理:
- 操作系统需要PCB来挂起或恢复进程,以及在多任务环境中管理进程间的切换。
-
资源管理:
- PCB帮助操作系统监控和控制进程的资源使用,如CPU使用时间、内存使用等。
PCB是操作系统设计的基石之一,是进行有效进程管理和资源分配的关键。理解PCB及其在操作系统中的功能有助于更好地理解多任务操作系统的工作原理和性能优化。
daic@daic:~$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15400
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 15400
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
进程状态转换
进程的状态
进程状态反映进程执行过程的变化。这些状态随着进程的执行和外界条件的变化而转换。
在三态模型中,进程状态分为三个基本状态,即就绪态,运行态,阻塞态。在五态模型
中,进程分为新建态、就绪态,运行态,阻塞态,终止态。
◼ 运行态:进程占有处理器正在运行
◼ 就绪态:进程具备运行条件,等待系统分配处理器以便运
行。当进程已分配到除CPU以外的所有必要资源后,只要再
获得CPU,便可立即执行。在一个系统中处于就绪状态的进
程可能有多个,通常将它们排成一个队列,称为就绪队列
◼ 阻塞态:又称为等待(wait)态或睡眠(sleep)态,指进程
不具备运行条件,正在等待某个事件的完成
◼新建态:进程刚被创建时的状态,尚未进入就绪队列
◼ 终止态:进程完成任务到达正常结束点,或出现无法克服的错误而异常终止,或被操作系统及
有终止权的进程所终止时所处的状态。进入终止态的进程以后不再执行,但依然保留在操作系
统中等待善后。一旦其他进程完成了对终止态进程的信息抽取之后,操作系统将删除该进程。
进程相关命令
在不同的操作系统中,用于管理和查看进程的命令各有不同。下面,我将介绍几种常见操作系统(Linux/Unix, Windows, macOS)中用于进程管理的常用命令。
Linux/Unix
-
ps - 显示当前运行的进程的快照。
ps aux
显示所有正在运行的进程的详细信息。ps -ef
用全格式列出所有进程。
-
top - 实时显示系统中各个进程的资源占用情况。
-
运行
top
命令可以看到 CPU、内存等资源的实时使用情况,以及各个进程的详细信息。 -
可以在使用 top 命令时加上 -d 来指定显示信息更新的时间间隔,在 top 命令
执行后,可以按以下按键对显示的结果进行排序:
⚫ M 根据内存使用量排序
⚫ P 根据 CPU 占有率排序
⚫ T 根据进程运行时间长短排序
⚫ U 根据用户名来筛选进程
⚫ K 输入指定的 PID 杀死进程
-
-
kill - 发送信号到一个或多个进程。
-
kill -9 [PID]
强制终止具有指定PID的进程。 -
kill -SIGTERM [PID]
发送终止信号到指定的进程。 -
kill [-signal] pid
kill –l 列出所有信号
kill –SIGKILL 进程ID
kill -9 进程ID
killall name 根据进程名杀死进程
-
-
pkill / killall - 根据进程名称发送信号。
pkill firefox
终止所有名为firefox的进程。killall firefox
与pkill
类似,也是终止所有名为firefox的进程。
-
htop (需要安装) - 一个比
top
更友好的交互式进程查看器。htop
提供一个彩色的界面,支持进程管理的更多功能,如直接终止、调整优先级等。
进程号和相关函数
每个进程都由进程号来标识,其类型为 pid_t(整型),进程号的范围:0~32767。
进程号总是唯一的,但可以重用。当一个进程终止后,其进程号就可以再次使用。
◼ 任何进程(除 init 进程)都是由另一个进程创建,该进程称为被创建进程的父进程,
对应的进程号称为父进程号(PPID)。
◼ 进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各
种信号,关联的进程有一个进程组号(PGID)。默认情况下,当前的进程号会当做当
前的进程组号。
◼ 进程号和进程组相关函数:
⚫ pid_t getpid(void);
⚫ pid_t getppid(void);
⚫ pid_t getpgid(pid_t pid);
进程创建
系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成
进程树结构模型。
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回值:
⚫ 成功:子进程中返回 0,父进程中返回子进程 ID
⚫ 失败:返回 -1
失败的两个主要原因:
- 当前系统的进程数已经达到了系统规定的上限,这时 errno 的值被设置
为 EAGAIN
- 系统内存不足,这时 errno 的值被设置为 ENOMEM
父子进程虚拟地址空间情况
/*
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
函数的作用:用于创建子进程。
返回值:
fork()的返回值会返回两次。一次是在父进程中,一次是在子进程中。
在父进程中返回创建的子进程的ID,
在子进程中返回0
如何区分父进程和子进程:通过fork的返回值。
在父进程中返回-1,表示创建子进程失败,并且设置errno
父子进程之间的关系:
区别:
1.fork()函数的返回值不同
父进程中: >0 返回的子进程的ID
子进程中: =0
2.pcb中的一些数据
当前的进程的id pid
当前的进程的父进程的id ppid
信号集
共同点:
某些状态下:子进程刚被创建出来,还没有执行任何的写数据的操作
- 用户区的数据
- 文件描述符表
父子进程对变量是不是共享的?
- 刚开始的时候,是一样的,共享的。如果修改了数据,不共享了。
- 读时共享(子进程被创建,两个进程没有做任何的写的操作),写时拷贝。
*/
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int num = 10;
// 创建子进程
pid_t pid = fork();
// 判断是父进程还是子进程
if(pid > 0) {
// printf("pid : %d\n", pid);
// 如果大于0,返回的是创建的子进程的进程号,当前是父进程
printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
printf("parent num : %d\n", num);
num += 10;
printf("parent num += 10 : %d\n", num);
} else if(pid == 0) {
// 当前是子进程
printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());
printf("child num : %d\n", num);
num += 100;
printf("child num += 100 : %d\n", num);
}
// for循环(父子进程交替执行)
for(int i = 0; i < 3; i++) {
printf("i : %d , pid : %d\n", i , getpid());
sleep(1);
}
return 0;
}
/*
实际上,更准确来说,Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。
写时拷贝是一种可以推迟甚至避免拷贝数据的技术。
内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。
只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。
也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。
注意:fork之后父子进程共享文件,
fork产生的子进程与父进程相同的文件文件描述符指向相同的文件表,引用计数增加,共享文件偏移指针。
*/
输出
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson18$ ./a.out
i am parent process, pid : 14679, ppid : 13613
parent num : 10
parent num += 10 : 20
i : 0 , pid : 14679
i am child process, pid : 14680, ppid : 14679
child num : 10
child num += 100 : 110
i : 0 , pid : 14680
i : 1 , pid : 14679
i : 1 , pid : 14680
i : 2 , pid : 14680
i : 2 , pid : 14679