【Linux】-- 进程概念
创始人
2025-06-01 14:14:20

基本概念

进程(Process):是操作系统进行资源分配的最小单位。一个进程是一个程序的一次执行过程。

进程和程序的区别

程序首先以文件的形式保存在磁盘当中 (双击之后)加载到了内存中 由cpu加载开始运行

这个进程被加载到内存之后除了原本的代码之外还多了一堆的数据

多出来的数据就是操作系统对于进程的描述 而我们将这一堆数据称为PCB(Process Control Block) 进程控制块

进程等于程序加上PCB

PCB

当我们使用ps指令的时候 我们可以看到系统中存在着大量的进程

每个进程都对应着一个PCB

每个PCB对应着一个进程的数据 它们之间使用双链表组织起来 我们可以通过PCB来找到并管理每个进程

这样子我们就把操作系统对于进程的管理转化为了对于双链表的增删查改

Linux下的PCB是test_struct结构体

task_struct是什么

进程控制块(PCB)的作用是描述进程 而Linux系统是使用c语言写出来的

我们在c语言中一般是使用一个结构体去描述一个对象 这个在linux描述进程的结构体我们就把它叫做task_struct

task_struct是PCB

task_struct一般会被储存在内存中

task_struct里面有什么

  • 标示符: 描述本进程的唯一标示符 用来区别其他进程

  • 状态: 任务状态 退出代码 退出信号等

  • 优先级: 相对于其他进程的优先级

  • 程序计数器(pc): 程序中即将被执行的下一条指令的地址

  • 内存指针: 包括程序代码和进程相关数据的指针 还有和其他进程共享的内存块的指针

  • 上下文数据: 进程执行时处理器的寄存器中的数据

  • I/O状态信息: 包括显示的I/O请求 分配给进程的I/O设备和被进程使用的文件列表

  • 记账信息: 可能包括处理器时间总和 使用的时钟总和 时间限制 记账号等

  • 其他信息

查看进程

我们可以通过两种方式来查看进程

  • 通过系统目录查看

  • 通过ps指令查看

通过系统目录查看进程

在根目录下有一个proc目录

这个目录中含有大量的进程信息

这些文件中有些是用数字表示的 而这些数字实际上就是程序的pid

通过ps指令查看进程

但是我们一般查看进程的时候使用的是这样子的语句

ps axj | head -1 && ps axj | grep xxx

这段命令分为两部分&& 连接的两个命令 如果前面执行成功了就会执行后面的语句

ps axj | head -1

这段命令的意思打出 ps axj的第一行

ps axj | grep xxx

这段命令的意思是打出所有包含xxx的进程

获取程序的pid和ppid

  • pid : pid是程序的标识符

  • ppid: ppid是当前进程的父进程的标识符

getpid() 在 unistd.h 头文件里面
int main()    
{    while(1)    {    printf("I am a process, pid: %d, ppid: %d\n", getpid(), getppid());    sleep(1);    }    return 0;
}

通过fork函数创建进程

fork是一个系统调用级别的函数 其功能就是创建一个子进程

我们可以通过如下的代码来验证它

int main()    
{    int ret = fork();                                                                                 while(1)    {    printf("I am a process, pid: %d, ppid: %d\n", getpid(), getppid());    sleep(1);    }    return 0;
}

如何理解fork创建进程

  1. 从创建层面 不论是使用指令 跑代码还是使用fork创建进程 在操作系统眼中都没有区别

  1. 从继承层面 fork出来的子进程它本身没有代码和数据 所以说它是拷贝的父进程的代码和数据(写时拷贝)

代码和数据是全部拷贝过来吗?

对于代码来说 是的 但是一般创建进程之前的代码是用不到的 因为已经运行到创建进程(上下文)
对于数据来说父子进程的数据也就是PCB大部分是共享的 但是我们也要考虑修改的情况
因为进程具有独立性 这里就要用到一个写时拷贝的技术

关于父子进程

可以通过pid的返回值来区分父子进程 从而达到同时进行两个任务的目的

fork函数的返回值:

  1. 如果子进程创建失败返回-1

  1. 如果子进程创建成功 在父进程中返回子进程的pid 在子进程中返回0

int main()      
{      int ret = fork();              if(ret > 0)                                                                                                                                                                                                  {        printf("you can see me!!!\n");        }        else        {        printf("oops! you can see me!!!\n");        }        sleep(1);        printf("hello world\n");        sleep(1); 
}
#include
#includeusing namespace std;int main()
{pid_t id = fork();   // int                                                                                                                                                                                  if(id == 0)        {                  //child        while(true)    {              cout << "I am child, pid: " << getpid() << ", ppid:" << getppid() <<  endl;      sleep(1);      }              }                  else if(id > 0)      {                  //father       while(true)      {      cout << "I am parent, pid: " << getpid() << ", ppid:" << getppid() <<  endl;      sleep(2);      }      }      else       {      //TODU      }      //cout << "hello proc: " << getpid() << " hello parent: " << getppid() << "ret: " << id << endl;sleep(1);return 0;
}

这就是两个进程同时运行的结果

在做业务的时候我们可以将里面的代码换成业务逻辑就可以了

进程的状态

在进程从创建到消亡的这段时间会存在不同的状态 这些状态值储存在PCB当中 操作系统会根据PCB中的状态值来决定这个进程是否运行 结束

常见的进程状态有以下几点

在我们的Linux源码中对于状态有着如下的定义

/*
* 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 *task_state_array[] = 
{"R (running)",       /*  0*/"S (sleeping)",      /*  1*/"D (disk sleep)",    /*  2*/"T (stopped)",       /*  4*/"T (tracing stop)",  /*  8*/"Z (zombie)",        /* 16*/"X (dead)"           /* 32*/
};

运行状态 R (run)

一个进程处于运行状态(running) 并不意味着进程一定处于运行当中,运行状态表明一个进程要么在运行中 要么在运行队列里 可以同时存在多个R状态的进程

浅度睡眠状态 S (sleep)

我们让这个进程休眠30秒

此时我们可以使用 kill -9 + pid 结束进程

深度睡眠状态 D (disk sleep)

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

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

暂停状态-T (stop)

在Linux当中 可以通过发送SIGSTOP信号使进程进入暂停状态(stopped)

发送SIGCONT信号 该进程就继续运行

僵尸状态 Z(zombie)

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

僵尸状态的存在是必要的

因为如果该进程的申请的资源在其运行结束之后立即释放那么我们便不知道该进程完成的如何

要知道它完成的如何我们必须要它的调用者来接受它的返回值 在了解完毕之后才可以让这个进程终止

我们在写c语言代码的时候通常在最后加上一个返回值

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

这个return的值实际上是返回给操作系统的

我们可以使用 echo $? 命令来查询上次命令的返回值

死亡状态 X (dead)

我们一般不能查询到进程的死亡状态 因为在进程死亡的那一瞬间该进程的所有资源将会被释放 该进程也不存在了

僵尸进程

处于僵尸状态的进程就叫做僵尸进程

我们可以制造一个僵尸进程 具体操作就是让父进程一直运行 然后子进程退出 这样子的话子进程就一直无法将返回值传递给父进程 那么子进程就会一直处于僵尸状态

代码如下

int main()
{pid_t id = fork();   // int     if(id == 0)    {    //child     cout << "I am child, pid: " << getpid() << ", ppid:" << getppid() <<  endl;                                                                                           }                          else if(id > 0)            {                          //parent               while(true)            {                      cout << "I am parent, pid: " << getpid() << ", ppid:" << getppid() <<  endl;    sleep(2);            }                      }                          else                       {                          //TODU                 } return 0;
}

可以看到子进程的状态就变成了Z 僵尸状态

僵尸进程的危害

  1. 僵尸进程的退出状态必须一直维持下去 因为它要告诉其父进程相应的退出信息 可是父进程一直不读取 那么子进程也就一直处于僵尸状态

  1. 僵尸进程的退出信息被保存在task_struct(PCB)中 僵尸状态一直不退出 那么PCB就一直需要进行维护

  1. 若是一个父进程创建了很多子进程 但都不进行回收 那么就会造成资源浪费 因为数据结构对象本身就要占用内存

  1. 僵尸进程申请的资源无法进行回收 那么僵尸进程越多 实际可用的资源就越少 也就是说 僵尸进程会导致内存泄漏

孤儿进程

在Linux中的进程大多是父子关系 万一 一个进程的子进程还没有结束但是他的父进程结束了 那么就称这个进程为孤儿进程

如果没有父进程来管理这个子进程那么他就会一直占用资源 所以说为了防止这种情况 我们设计了让1号init进程来领养这个进程

代码如下

int main()
{  pid_t id = fork();      if(id == 0)                  {                            //child            while(true)        {                  cout << "I am child, pid: " << getpid() << ", ppid:" << getppid() <<  endl;    sleep(3);        }                  }                      else if(id > 0)        {                      //parent                          cout << "I am parent, pid: " << getpid() << ", ppid:" << getppid() <<  endl;                   }                      else                   {                                                                                                 //TODU             } return 0;
}

子进程的父进程变为了 1 (init进程)

相关内容

热门资讯

【实验报告】实验一 图像的... 实验目的熟悉Matlab图像运算的基础——矩阵运算;熟悉图像矩阵的显示方法࿰...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
大模型落地比趋势更重要,NLP... 全球很多人都开始相信,以ChatGPT为代表的大模型,将带来一场NLP领...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
kuernetes 资源对象分... 文章目录1. pod 状态1.1 容器启动错误类型1.2 ImagePullBackOff 错误1....
STM32实战项目-数码管 程序实现功能: 1、上电后,数码管间隔50ms计数; 2、...
TM1638和TM1639差异... TM1638和TM1639差异说明 ✨本文不涉及具体的单片机代码驱动内容,值针对芯...
Qt+MySql开发笔记:Qt... 若该文为原创文章,转载请注明原文出处 本文章博客地址:https://h...
Java内存模型中的happe... 第29讲 | Java内存模型中的happen-before是什么? Java 语言...
《扬帆优配》算力概念股大爆发,... 3月22日,9股封单金额超亿元,工业富联、鸿博股份、鹏鼎控股分别为3.0...
CF1763D Valid B... CF1763D Valid Bitonic Permutations 题目大意 拱形排列࿰...
SQL语法 DDL、DML、D... 文章目录1 SQL通用语法2 SQL分类3 DDL 数据定义语言3.1 数据库操作3.2 表操作3....
文心一言 VS ChatGPT... 3月16号,百度正式发布了『文心一言』,这是国内公司第一次发布类Chat...
CentOS8提高篇5:磁盘分...        首先需要在虚拟机中模拟添加一块新的硬盘设备,然后进行分区、格式化、挂载等...
Linux防火墙——SNAT、... 目录 NAT 一、SNAT策略及作用 1、概述 SNAT应用环境 SNAT原理 SNAT转换前提条...
部署+使用集群的算力跑CPU密... 我先在开头做一个总结,表达我最终要做的事情和最终环境是如何的,然后我会一...
Uploadifive 批量文... Uploadifive 批量文件上传_uploadifive 多个上传按钮_asing1elife的...
C++入门语法基础 文章目录:1. 什么是C++2. 命名空间2.1 域的概念2.2 命名...
2023年全国DAMA-CDG... DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义...
php实现助记词转TRX,ET... TRX助记词转地址网上都是Java,js或其他语言开发的示例,一个简单的...
【分割数据集操作集锦】毕设记录 1. 按要求将CSV文件转成json文件 有时候一些网络模型的源码会有data.json这样的文件里...
Postman接口测试之断言 如果你看文字部分还是不太理解的话,可以看看这个视频,详细介绍postma...
前端学习第三阶段-第4章 jQ... 4-1 jQuery介绍及常用API导读 01-jQuery入门导读 02-JavaScri...
4、linux初级——Linu... 目录 一、用CRT连接开发板 1、安装CRT调试工具 2、连接开发板 3、开机后ctrl+c...
Urban Radiance ... Urban Radiance Fields:城市辐射场 摘要:这项工作的目标是根据扫描...
天干地支(Java) 题目描述 古代中国使用天干地支来记录当前的年份。 天干一共有十个,分别为:...
SpringBoot雪花ID长... Long类型精度丢失 最近项目中使用雪花ID作为主键,雪花ID是19位Long类型数...
对JSP文件的理解 JSP是java程序。(JSP本质还是一个Servlet) JSP是&#...
【03173】2021年4月高... 一、单向填空题1、大量应用软件开发工具,开始于A、20世纪70年代B、20世纪 80年...
LeetCode5.最长回文子... 目录题目链接题目分析解题思路暴力中心向两边拓展搜索 题目链接 链接 题目分析 简单来说࿰...