深入浅出:进程管理的艺术

目录

进程的定义

进程的特征

进程的状态

进程与程序的区别

进程的控制和管理

进程的特点

1. 虚拟内存空间的分配

2. 时间片轮转调度

图解:

进程段

数据段(Data Segment)

正文段(Text Segment)

堆栈段(Stack Segment)

进程分类

交互进程

批处理进程

守护进程

进程状态

进程状态切换

C语言中进程函数

 创建进程fork

fork() 调用总结

注意事项

 

回收进程wait waitpid

参数说明

返回值

退出进程exit _exit

获取进程号 getpid getppid

总结:


进程的定义

进程可以被视为一个执行中的程序实例,它包含了程序代码、数据集以及一个描述其状态和控制信息的进程控制块(Process Control Block, PCB)。操作系统通过PCB来跟踪和控制进程的执行状态,包括进程的ID、内存映射、打开的文件、状态信息等。

进程的特征

  1. 动态性:进程是程序的一次执行过程,有创建、执行和消亡的生命周期。
  2. 并发性:多个进程可以并发执行,共享处理器和其他系统资源。
  3. 独立性:每个进程都有自己独立的内存空间和系统资源,相互之间不受影响。
  4. 异步性:进程的执行是异步的,即进程的执行速度不可预知,受到多种因素的影响。

进程的状态

进程在其生命周期中会处于不同的状态,主要包括:

  1. 就绪状态:进程已经准备好执行,等待CPU资源。
  2. 执行状态:进程正在使用CPU执行指令。
  3. 阻塞状态:进程因为等待某种事件(如I/O操作完成)而暂时不能执行。

进程与程序的区别

  • 程序是一组指令的集合,是静态的;而进程是程序在某个数据集上的一次执行过程,是动态的。
  • 程序是永久的,存储在磁盘上;进程是暂时的,存在于内存中,随程序的启动而创建,随程序的结束而消亡。

进程的控制和管理

操作系统负责管理和控制进程,主要包括:

  • 进程创建:操作系统为新进程分配资源,初始化PCB。
  • 进程调度:决定下一个执行的进程,将CPU分配给进程。
  • 进程同步:协调多个进程对共享资源的访问,防止冲突。
  • 进程通信:允许进程间交换信息,支持进程协同工作。
  • 进程终止:回收进程占用的资源,结束进程的生命周期。

进程的特点

1. 虚拟内存空间的分配
  • 虚拟地址空间:在典型的32位系统中,进程拥有一个4GB的虚拟地址空间,这个空间被分为两部分:
    • 用户空间(0-3GB):这部分空间专属于每个进程,用于存放代码、数据、堆和栈等,是进程的私有区域,其他进程无法直接访问。
    • 内核空间(3-4GB):这部分空间是所有进程共享的,主要存放操作系统内核代码和数据结构。当进程需要与内核交互时,如进行系统调用,会进入内核空间。
2. 时间片轮转调度
  • 时间片调度:操作系统采用时间片轮转(Round Robin, RR)调度策略来管理多个进程。每个进程在获得CPU执行权时,会被分配一个固定的时间片(通常几毫秒到几十毫秒不等),在此期间,进程独占CPU资源执行。
  • 多任务处理:通过快速地在多个进程之间切换执行(时间片轮转),操作系统可以给用户创造出多任务并行执行的错觉,即使在单核CPU上也能实现高效的多任务处理。
图解:

进程段

数据段(Data Segment)

  • 存储内容:数据段主要用于存储进程中的全局变量、静态变量以及动态分配的内存。全局变量是指在程序的多个函数间共享的数据,而静态变量则是在函数外部声明但在函数内部使用的变量,它们在程序的整个生命周期中都保持有效。

  • 动态数据分配:使用如 malloccallocreallocfree 等函数动态分配的内存也位于数据段中,这部分内存通常被称为“堆”(Heap),它提供了灵活的内存管理能力,允许程序在运行时根据需要动态地分配和释放内存空间。

正文段(Text Segment)

  • 存储内容:正文段,也称作代码段或文本段,主要用于存储程序的可执行代码。这些代码是预先编译好的机器指令,由操作系统加载到内存中供 CPU 执行。正文段通常具有只读属性,以防止程序意外修改自己的代码,从而提高程序的稳定性和安全性。

堆栈段(Stack Segment)

  • 存储内容:堆栈段是用于存储函数调用过程中产生的临时数据的区域,主要包括:
    • 函数的局部变量:这些变量在函数调用时创建,在函数退出时销毁。
    • 函数调用的参数:当一个函数被调用时,其参数会按照一定的顺序压入堆栈。
    • 返回地址:记录调用者的位置,以便函数执行完毕后返回到正确的指令处继续执行。
  • 工作原理:堆栈段遵循“先进后出”(LIFO, Last In First Out)的原则,通过压栈(PUSH)和弹栈(POP)操作来管理数据的进出,这种机制简化了函数调用的管理,使得函数调用和返回的控制流更加清晰和高效。

进程分类

交互进程

  • 特性与用途:交互进程通常由 shell 启动,直接与用户交互,响应用户输入并展示输出结果。这类进程既可以运行在前台,占据用户的终端界面,也可以运行在后台,不干扰用户的其他操作。典型的交互式进程包括 shell 命令、文本编辑器(如 vi 或 nano)、图形界面应用程序等。

批处理进程

  • 特性与用途:批处理进程不直接与用户交互,它们通常被提交到一个队列中,按顺序执行。这类进程主要用于处理大量数据或执行长时间运行的任务,如批量数据处理、备份作业、系统维护任务等。由于不需要用户实时干预,批处理进程可以高效利用系统资源,避免因用户交互而引起的中断和延迟。

守护进程

  • 特性与用途:守护进程是长期运行在后台的服务进程,它们在系统启动时被激活,持续运行直到系统关闭。守护进程不依附于任何终端,主要负责提供系统级的服务,如网络服务(HTTP、FTP)、系

进程状态

  1. 新建状态(New): 当一个进程被创建时,它首先进入新建状态。此时,操作系统为其分配必要的资源,如内存、文件描述符等,并初始化进程控制块(PCB)。进程尚未被调度执行。

  2. 就绪状态(Ready): 进程已经准备好执行,但由于当前CPU正忙于其他进程,它需要等待调度器的调度。在就绪状态下的进程位于就绪队列中,等待CPU时间片。一旦被调度,进程将从就绪状态转变为执行状态。

  3. 执行状态(Running): 进程正在使用CPU执行指令。在多任务操作系统中,执行状态的进程可能很快就会因为时间片到期、更高优先级的进程就绪或进程自身请求I/O操作等原因而转变为其他状态。

  4. 阻塞状态(Blocked): 也称为等待状态或睡眠状态。当进程在执行过程中遇到I/O操作或其他需要等待的事件时,它会主动或被动地进入阻塞状态。此时,进程放弃CPU使用权,等待相应的事件发生,如I/O操作完成、信号量变为可用等。阻塞状态下的进程不参与CPU调度,直到阻塞原因解除。

  5. 僵尸状态(Zombie): 当一个子进程结束执行但其父进程尚未对其进行善后处理(如调用 wait()waitpid() 函数)时,子进程会进入僵尸状态。僵尸进程仍然占用一部分系统资源,如进程表中的一个条目,但不再占用CPU时间,也不会执行任何指令。

  6. 终止状态(Terminated): 进程执行完毕或因异常而终止后,会进入终止状态。在终止状态下,进程实际上已经停止运行,但其状态信息仍保留在系统中,直到父进程清理这些信息(通过调用 wait()waitpid())。清理后,进程完全消失,释放所有资源。

进程状态切换

进程创建后,进程进入就绪态,当CPU调度到此进程时进入运行态,当时间片用完时,此进程会进入就绪态,如果此进程正在执行一些IO操作(阻塞操作)会进入阻塞态,完成IO操作(阻塞结束)后又可进入就绪态,等待CPU的调度,当进程运行结束即进入结束态。
 

C语言中进程函数

 创建进程fork

pid_t fork(void);
功能:创建子进程
返回值:
    成功:在父进程中:返回子进程的进程号 >0
         在子进程中:返回值为0
    失败:-1并设置errno

  1. 资源与状态的继承与分离

    • 子进程几乎完全复制了父进程的执行上下文,包括代码段、数据段、栈段、文件描述符等,但它们拥有独立的进程ID(PID)和父进程ID(PPID)。这意味着子进程继承了父进程的运行环境,但二者在系统层面是完全独立的实体。
  2. 独立的地址空间与数据独立性

    • 尽管子进程继承了父进程的内存映像,但它拥有自己独立的地址空间。因此,对全局变量、静态变量的修改在各自进程中是独立的,互不影响。这种隔离性确保了进程间的数据安全。
  3. 孤儿与僵尸进程的处理

    • 若父进程在子进程之前终止,子进程将被系统中的初始化进程(init)收养,成为孤儿进程。孤儿进程会继续执行,直到自然结束或被其他进程显式终止。
    • 当子进程结束,但父进程未能及时回收其资源时,子进程会变成僵尸进程。僵尸进程虽已结束,但仍占用系统资源,直到父进程通过 wait() 或 waitpid() 系统调用来回收为止。应避免产生僵尸进程,以维持系统资源的高效利用。
  4. 文件描述符的共享与独立操作

    • 子进程继承了父进程打开的文件描述符,这意味着同一文件在父进程和子进程中可通过相同的描述符访问。尽管如此,文件操作(如读写位置)在各自的进程中是独立的,不会互相干扰。

fork() 调用总结

  • 执行流程与返回值

    • fork() 调用在父进程中返回子进程的PID,在子进程中返回0。若返回负数,则表示创建子进程失败。
    • fork() 调用仅执行一次,但它的效果是使得代码在父进程和子进程中分别执行一次,形成两个独立的执行流。
  • 资源复制与独立执行

    • fork() 创建子进程时,子进程获得了父进程的资源副本,包括代码段、数据段和栈段,但拥有独立的地址空间和资源管理。
    • 父子进程在 fork() 后的代码段是独立执行的,即使它们执行相同的指令序列,也不会相互影响。

注意事项

在使用 fork() 创建子进程时,应注意以下几点:

  • 资源管理:确保父进程和子进程妥善管理共享资源,如文件描述符和信号量,以避免资源泄露或竞争条件。
  • 进程清理:父进程应负责回收子进程的资源,避免产生僵尸进程,保持系统资源的健康状态。
  • 信号处理:考虑进程间的信号传递机制,以实现进程间的协调和控制,特别是在异常处理和进程终止时。

 

回收进程wait waitpid

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);// 函数原型

pid_t wait(int *status);
功能:回收子进程资源(阻塞)
参数:status:子进程退出状态,不接受子进程状态设为NULL
返回值:成功:回收的子进程的进程号
        失败:-1
        
参数说明
  • pid:用于指定要等待的子进程的 PID(进程ID)或一组进程的选择标准:

    • >0:等待特定 PID 的子进程。
    • -1:等待任意一个子进程,这是最常见的用法。
    • =0:等待与调用进程具有相同进程组 ID 的任意子进程。
    • <-1:等待与 pid 绝对值对应的进程组 ID 相同的任意子进程。
  • status:指向一个整型变量的指针,用于存储子进程的退出状态。如果子进程正常结束,status 中包含的值可以被 WEXITSTATUS() 宏解析得到子进程的退出码;如果是信号导致子进程终止,status 中的值可以被 WTERMSIG() 宏解析得到终止信号。

  • options:指定 waitpid() 的行为模式:

    • 0:默认阻塞模式,父进程将暂停执行,直到子进程结束。
    • WNOHANG:非阻塞模式,立即返回,无论是否有子进程结束。如果无子进程结束,返回 0;否则返回结束子进程的 PID。
返回值
  • 正常情况:返回结束子进程的 PID。
  • 非阻塞模式且没有子进程结束:返回 0
  • 出错:返回 -1,并设置 errno 变量以指示错误原因。

退出进程exit _exit

void exit(int status);
功能:结束进程,刷新缓存
参数:退出的状态
不返回。
void _exit(int status);
功能:结束进程,不刷新缓存
参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。
    通常0表示正常结束;
其他的数值表示出现了错误,进程非正常结束


获取进程号 getpid getppid

pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程号

return: 关键字,在子函数中返回到函数调用的位置,并不结束进程(函数的退出)

exit :函数,不管在子函数还是主函数都会结束进程(进程的退出)

总结:

        在计算中进程是无处不在的,更好的了解进程也可以让我们更好的了解计算机以及编程的原理,文章至此结束,希望可以帮到大家。万分感谢看到这里。

  • 31
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

笨笨小乌龟11

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

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

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

打赏作者

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

抵扣说明:

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

余额充值