当前位置: 首页 > news >正文

Linux进程概念(上):进程基本概念和进程状态

文章目录

    • 一、进程的基本概念
      • 描述进程(PCB)
      • Linux的PCB——task_struct
      • task_ struct内容分类
      • 查看进程
        • 系统目录查看
        • ps命令查看
      • 通过系统调用获取进程标识符
      • 通过系统调用创建子进程(fork的初识)
        • fork函数创建子进程
        • 用if分流父子进程
    • 二、进程状态
      • R运行状态(running)
      • S浅度睡眠随眠状态(sleeping)
      • D磁盘休眠(深度随眠)状态(Disk sleep)
      • T暂停状态
      • X死亡状态
      • Z僵尸状态
      • 僵尸进程
        • 僵尸进程的危害
      • 孤儿进程


一、进程的基本概念

课本概念:一个已经加载到内存中的程序或者是正在运行的程序,叫做进程。

在现实生活中,我们通常称作进程为任务。比如Windows的任务管理器,实际上就是进程的管理器。

然而,我们以课本概念理解进程是肤浅的,我们需要站在操作系统的角度,才能深入理解进程。

我们知道,操作系统是做管理的,那操作系统是如何管理进程的呢???

先描述,再组织!

描述进程(PCB)

常识告诉我们:一个操作系统不仅仅只能运行一个进程,而是可以同时运行多个进程。

因此,操作系统必须将所有进程管理起来。

任何一个进程,在加载到内存的时候(形成进程时),操作系统会先创建该进程的一个结构体对象(描述进程),这个结构体对象包含进程的所有重要属性。

这个结构体对象叫做进程控制块(PCB)(process control block)

PCB属性中含有指向下一个PCB的指针成员变量(组织进程),这样一来,多个进程就组成了一个以PCB为节点的单链表的数据结构对象。此时在操作系统中,对进程的管理就变成了对单链表的增删查改。

一个程序,是包含代码和数据的,所以一个进程也应该包含代码和数据。那么,如果代码量和数据量非常庞大呢?而内存的资源又是十分有限的!

事实上,代码和数据根本不会加载到内存操作系统中,而是在磁盘上!在内存的PCB中,存在指向代码和数据的指针,在需要时,寻址获取即可。

在这里插入图片描述
总结:
进程 = 内核PCB数据结构对象(描述该进程的所有属性值) + 代码和数据

进程内核观点:担当分配系统资源(CPU时间,内存)的实体。

Linux的PCB——task_struct

那么在Linux系统中,具体是怎么做的呢?

在Linux中描述进程的结构体叫做task_struct。

task_struct是Linux内核的一种数据结构(采用双向链表组织的),它会被装载到RAM(内存)里并且包含着进程的信息。

task_ struct内容分类

task_ struct具体包含哪些属性呢?
其实非常多,翻阅Linux内核源码即可窥见全貌,此处列举部分属性:

  • 标识符:描述本进程的唯一标识符,用来区别其他进程。
  • 状态:任务状态,退出代码,退出信号等。
  • 优先级:相对于其他进程的优先级
  • 程序计数器:程序中即将被执行的下一条指令地址
  • 内存指针:包括程序代码和进程相关数据的指针,还有其他进程共享的内存块的指针
  • 上下文数据:进程执行时处理器的寄存器中的数据
  • I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息:包括处理时间总和,使用的时钟数总和,时间限制记账号等。
  • 其他……

查看进程

系统目录查看

在我们的根目录下,有一个名为proc的系统目录:
在这里插入图片描述
目录中包含系统所有的进程信息,其中有很多的名为数字的目录:
在这里插入图片描述
这些数字其实就是进程的标识符PID,每个PID是唯一的,一个PID对应一个进程,打开这个进程目录,就可以查看该进程的所有信息。
例如,查看PID为1的进程,打开这个名为1的目录即可:

在这里插入图片描述

ps命令查看

查看所有进程:

ps axj

在这里插入图片描述
配合grep使用可查看我们需要查看的进程信息:

ps axj |grep myproc|grep -v grep

使用grep过滤时,也会产生进程(会被筛出来),我们可以用grep -v grep将其过滤掉:
在这里插入图片描述
还可以将所有信息属性显示出来,便于我们查看:

ps axj | head -1 && ps axj | grep myproc | grep -v grep

在这里插入图片描述

通过系统调用获取进程标识符

  • PID:进程id
  • PPID:父进程id

通过系统调用接口getpid()getppid(),可以分别获取进程的PID和PPID。

测试:
在这里插入图片描述
我们运行这个程序,并查看该进程信息,观察到运行打印提示和我们预期结果一致,如图:
在这里插入图片描述

通过系统调用创建子进程(fork的初识)

fork函数创建子进程

fork是一个系统调用级别的接口,其功能就是创建一个子进程。
空口无凭,我们来测试一下:
在这里插入图片描述
运行结果:
在这里插入图片描述
运行结果是循环打印两行数据,第一行数据是该进程的PID和PPID,第二行数据是代码中fork函数创建的子进程的PID和PPID。我们可以发现fork函数创建的进程的PPID就是proc进程的PID,也就是说proc进程与fork函数创建的进程之间是父子关系。

可以看到,父进程也有一个父进程2052,这个进程是bash,这里的父进程2455也是bash通过创建子进程(fork)完成的。
bash其实就是执行解释命名的程序。

我们知道,每创建一个进程,操作系统会为其创建一个PCB,fork创建的进程也亦如此。

那么,fork函数创建的子进程的代码和数据从何而来呢?与父进程共用吗?

事实上:

  • 父子进程的代码是共享的,因为代码是不可修改的,所以共享是合理的。但是一般而言,fork之后的代码才是父子共享的(因为fork之前子进程还未被创建)(顺序编译)。
  • 父子进程的数据是各自开辟空间的,私有一份(采用写时拷贝),数据是可能被修改的,所以不能共享数据。在默认情况下,父子进程的数据是相同的,但并非同一块数据
用if分流父子进程

如果父子进程执行的代码是一样的,也就是让父子进程做相同的事情,那么这个子进程就没有意义了。
因此,fork之后通常需要用if进行分流,让父子进程做不同的事情

那么如何分流呢?

fork函数的返回值:

  • 如果子进程创建成功,则在父进程中返回子进程的PID,在子进程中返回0
  • 如果子进程创建失败,则在父进程中返回-1

既然fork函数在父子进程中的返回值不同,那么我们就可以利用它们的返回值进行分流:
在这里插入图片描述
运行结果:
在这里插入图片描述
在这里,相信大家还是存疑的,这里解答几个问题:

  1. 为什么fork要给子进程返回0?给父进程返回子进程的PID呢?
    返回不同的返回值,是为了区分,让不同的执行流执行不同的代码块!

  2. 一个函数是如何做到返回两次的?如何理解呢?
    以fork函数底层逻辑理解,fork也只是一个函数,那么fork究竟干了什么呢?
    在这里插入图片描述

  3. 一个变量怎么会有两个不同的内容呢?如何理解?
    该问题与进程地址空间相关,后文我会交代清楚。

  4. 父子进程创建好,也就是fork之后,谁先运行呢?
    由调度器的调度算法决定的,不确定!


二、进程状态

在操作系统中,一个进程从创建至消亡的整个生命期间,有时占有处理器执行,有时虽可运行但分不到处理器,有时虽有空闲处理器但因等待某个时间而无法执行。因此,进程是活动且有状态的。

在这里插入图片描述
我们来看看Linux具体有哪些状态
下面的状态在kernel源代码里定义:

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};

注意:进程的当前状态是保存到自己的进程控制块(PCB)当中的,在Linux操作系统当中也就是保存在task_struct当中的。

在Linux操作系统当中我们可以通过 ps aux 或 ps axj 命令查看进程的状态:
在这里插入图片描述

R运行状态(running)

一个进程处于运行状态,并不意味着进程一定在运行中,它表明进程要么在运行中,要么在运行队列里。也就是说,可以同时存在多个R状态的进程。

那为何要运行队列呢?
在现实中,几乎所有的计算机都只有一个CPU,但我们的计算机又是如何同时运行多个进程的呢?
靠的就是并发执行!

并行:多个进程在多个CPU下分别同时运行
并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进

因此,每一个进程都有一个叫做时间片的概念!
每个进程只会在CPU中运行一次时间片,然后就被切换,这就是进程切换
在一个时间段,大量的进程会从CPU上放上去又拿下来,因为时间片是非常小的(ms级别),所以我们是感知不到进程切换的,最终给用户呈现的效果就是多个进程在同时运行。

所有处于运行状态可被调度的进程,被放到运行队列当中,当操作系统需要切换进程运行时,就直接在运行队列中选取进程运行即可。

S浅度睡眠随眠状态(sleeping)

一个进程处于浅度睡眠状态,意味着该进程正在等待某件事情的完成,处于浅度睡眠状态的进程随时可以被唤醒,也可以被杀掉(这里的睡眠有时候也可叫做可中断睡眠(interruptible sleep))。

  • 在平时我们用的sleep()函数,等待的过程中进程其实就是S状态,它等待的事情就是我们设置的时间倒计时
  • 我们程序等待我们键盘输入时(I流函数),等待的过程中也是S状态,它等待的事情就是我们从键盘输入数据。

D磁盘休眠(深度随眠)状态(Disk sleep)

一个进程处于深度睡眠状态,表示该进程不会被杀掉,即便是操作系统也不行,只有该进程自动唤醒才可以恢复。该状态有时候也叫不可中断睡眠状态(uninterruptible sleep),处于这个状态的进程通常会等待IO的结束。

例如,某一进程要求对磁盘进行写入操作,那么在磁盘进行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。

T暂停状态

我们可以通过发送SIGSTOP信号使进程进入暂停状态(stopped),发送SIGCONT信号可以让处于暂停状态的进程继续运行。

使用kill -l 命令可以列出当前系统所支持的信号集。

kill -l

在这里插入图片描述

X死亡状态

死亡状态只是一个返回状态,当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也就不存在了,所以你不会在任务列表当中看到死亡状态(dead)。

Z僵尸状态

当一个进程将要退出的时候,在系统层面,该进程曾经申请的资源并不是立即被释放,而是要暂时存储一段时间,以供操作系统或是其父进程进行读取,如果退出信息一直未被读取,则相关数据是不会被释放掉的,一个进程若是正在等待其退出信息被读取,那么我们称该进程处于僵尸状态(zombie)。

僵尸状态的意义:因为进程被创建的目的就是完成某项任务,那么当任务完成的时候,调用方是应该知道任务的完成情况的,所以必须存在僵尸状态。

我们在写C/C++代码时,都会在main函数的最后return 0。

int main()
{//...return 0;
}

其实这个0就是返回给操作系统的退出码,告诉操作系统代码已经执行结束了,可以回收释放这个进程了。

在Linux操作系统当中,我们可以通过使用echo $?命令获取最近一次进程退出时的退出码。
在这里插入图片描述

echo $?

注意:进程退出的信息(例如退出码),是暂时被保存在其进程控制块(PCB)当中的

僵尸进程

处于僵尸状态的进程,我们就称之为僵尸进程

制造一个僵尸进程:
我们让子进程退出,让父进程继续运行,父进程没有读取子进程的退出信息,那么此时子进程就进入了僵尸状态。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{printf("I am running...\n");pid_t id = fork();if(id == 0){ //childint count = 5;while(count){printf("I am child...PID:%d, PPID:%d, count:%d\n", getpid(), getppid(), count);sleep(1);count--;}printf("child quit...\n");exit(1);}else if(id > 0){ //fatherwhile(1){printf("I am father...PID:%d, PPID:%d\n", getpid(), getppid());sleep(1);}}else{ //fork error}return 0;
} 
僵尸进程的危害
  • 僵尸进程的退出信息被保存在task_struct(PCB)中,僵尸状态一直不退出,那么PCB就一直需要进行维护
  • 若是一个父进程创建了很多子进程,但都不进行回收,那么就会造成资源浪费,因为数据结构对象本身就要占用内存。
  • 僵尸进程申请的资源无法进行回收,那么僵尸进程越多,实际可用的资源就越少,也就是说,僵尸进程会导致内存泄漏

孤儿进程

父子进程,如果父进程先退出,子进程的父进程会被改为1号init进程(操作系统的进程)。此时,子进程就是孤儿进程。
定义:父进程是1号init的进程称为孤儿进程

其实,子进程是被系统领养了,这里系统好比现实中的孤儿院。

那么系统为什么要领养呢?

因为进程生命周期结束会退出,也需要被释放,而释放是交给父进程来做的。

那为什么要将父进程修改为1号呢?
因为除了1号进程和其原父进程,其他进程不具备释放该进程的能力。

制造一个孤儿进程:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{printf("I am running...\n");pid_t id = fork();if(id == 0){ //childint count = 5;while(1){printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid(), count);sleep(1);}}else if(id > 0){ //fatherint count = 5;while(count){printf("I am father...PID:%d, PPID:%d, count:%d\n", getpid(), getppid(), count);sleep(1);count--;}printf("father quit...\n");exit(0);}else{ //fork error}return 0;
} 

下篇预告:进程优先级和环境变量
有错误欢迎指出,万分感谢
创作不易,三连支持一下吧~
不见不散!


文章转载自:

http://UtU1896B.fxwkL.cn
http://b9mcPTq2.fxwkL.cn
http://yWliMz4x.fxwkL.cn
http://W1e1ppbB.fxwkL.cn
http://3aiouWrm.fxwkL.cn
http://hs87uJwJ.fxwkL.cn
http://i3yaHQC9.fxwkL.cn
http://ah9vmOKu.fxwkL.cn
http://g57lLkMQ.fxwkL.cn
http://DcUZXhUU.fxwkL.cn
http://OET1851z.fxwkL.cn
http://BtGYlOT9.fxwkL.cn
http://zjoRngAI.fxwkL.cn
http://qFqxwum9.fxwkL.cn
http://9Ty1oFEf.fxwkL.cn
http://tcasx59E.fxwkL.cn
http://dPpH1Bvk.fxwkL.cn
http://vXmjTTCD.fxwkL.cn
http://17m49bBr.fxwkL.cn
http://yag0CggK.fxwkL.cn
http://4B8A45GU.fxwkL.cn
http://nfLIOZAU.fxwkL.cn
http://ycszMHf2.fxwkL.cn
http://Yoelj2Ae.fxwkL.cn
http://T6pJqY6f.fxwkL.cn
http://33A9a9V5.fxwkL.cn
http://fEyrWYG9.fxwkL.cn
http://7KiVsrAU.fxwkL.cn
http://pMg0D8Bf.fxwkL.cn
http://opkYdR3y.fxwkL.cn
http://www.dtcms.com/a/377414.html

相关文章:

  • 汽车EPAS ECU功能安全建模分析:Gamma框架+深度概率编程落地ISO 26262(含寿命预测案例)
  • 深入解析:ES6 中 class 与普通构造器的区别
  • 华清远见25072班网络编程学习day3
  • QT(3)
  • 具有区域引导参考和基础的大型语言模型,用于生成 CT 报告
  • 【QT】-怎么实现瀑布图
  • 【Leetcode hot 100】94.二叉树的中序遍历
  • 渗透测试真的能发现系统漏洞吗
  • 【芯片设计-信号完整性 SI 学习 1.2 -- loopback 回环测试】
  • Android App瘦身方法介绍
  • MySQL修改字段类型避坑指南:如何应对数据截断与转换错误?
  • Linux权限以及常用热键集合
  • 成品油加油站综合监管迈入 “云时代”!智慧物联网涉税数据采集平台推行工作全面推进
  • c primer plus 第五章复习题和练习题
  • C++设计模式,高级开发,算法原理实战,系统设计与实战(视频教程)
  • Spring 统一功能处理
  • ES6基础入门教程(80问答)
  • 第3讲 机器学习入门指南
  • InnoDB 逻辑存储结构:好似 “小区管理” 得层级结构
  • copyparty 是一款使用单个 Python 文件实现的内网文件共享工具,具有跨平台、低资源占用等特点,适合需要本地化文件管理的场景
  • C# 哈希查找算法实操
  • 一个C#开发的Windows驱动程序管理工具!
  • 环境变量
  • Codeforces Round 1049 (Div. 2)
  • Eclipse下载安装图文教程(非常详细,适合新手)
  • vue2迁移到vite[保姆级教程]
  • 基于webpack的场景解决
  • Vite 中的 import.meta.env 与通用 process.env.NODE_ENV 的区别与最佳实践
  • 除了Webpack,还有哪些构建工具可以实现不同环境使用不同API地址?
  • sklearn聚类