【Linux】进程的基础概念 进程的相关操作 进程的状态

进程

  • 一、进程的基本知识
    • 1、基本概念
    • 2、进程的描述 —— PCB
    • 3、task_ struct内容分类
  • 二、进程的相关操作
    • 1、在Linux下查看进程
    • 2、通过系统调用在代码中获取进程标示符
    • 3、如何创建子进程
    • 4、关于fork()的一些深度理解
  • 三、进程的状态
    • Linux中的进程的状态
  • 四、僵尸进程与孤儿进程
    • 僵尸进程
    • 孤儿进程


一、进程的基本知识

1、基本概念

在通常的课本中进程的概念:程序的一个执行实例,正在执行的程序等。
如果以内核的观点来看应该是:内核关于进程的数据结构(PCB) + 当前进程的代码与数据。

2、进程的描述 —— PCB

PCB(process control block)进程的相关信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合,Linux操作系统下的PCB是: task_struct一个结构体类型。
task_struct是PCB的一种,也是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
进程的组织:所有运行在Linux系统里的进程都以task_struct链表的形式存在内核里,可以在内核源代码里找到它。
在这里插入图片描述

3、task_ struct内容分类

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

二、进程的相关操作

1、在Linux下查看进程

在Linux下查看当前进程的命令是ps命令。

ps命令来自于英文词组“process status”的缩写,其功能是用于显示当前系统的进程状态。
使用ps命令可以查看到进程的所有信息,例如进程的号码、发起者、系统资源使用占比(处理器与内存)、运行状态等等。帮助我们及时的发现哪些进程出现”僵死“或”不可中断“等异常情况。

常用参数:

  • a  显示现行终端机下的所有程序,包括其他用户的程序
  • x  显示所有程序,不以终端机来区分
  • j  采用工作控制的格式显示程序状况
ps -axj

查看当前正在运行的进程

在这里插入图片描述
可以看到每个进程都有自己的pid,每个进程的pid都是不一样的,通过pid操作系统和我们可以区别不同的进程。

当然在Linux中还有其他方式查看当前运行的进程,进程的信息可以通过/proc 系统文件夹查看,这是一个特殊的目录,里面有许多以pid来命名的目录,以pid命名的目录存放的是当前pid进程的相关信息,当当前进程结束后pid会消失,同时以pid命名的目录也会被删除。所以这个目录是一个动态的目录。

在这里插入图片描述

2、通过系统调用在代码中获取进程标示符

在Linux中进程id是(PID),进程的父进程id是(PPID)那么我们怎么在代码中获取一个进程的pid呢?
答案是:Linux系统为我们提供了两个系统调用接口getpid() getppid()
通过这两个函数我们可以拿到当前进程的pid与当前进程的父进程的pid

在这里插入图片描述
注意:getpid() 与 getppid()的返回值是 pid_t ,此类型是int 的一个 typedef

那么我们来看一下下面程序运行起来后的结果:

在这里插入图片描述

运行结果

在这里插入图片描述

可以看到getpid()与getppid()为我们返回了相应进程的pid与其父进程的pid,我们再用查看进程的相关指令查看一下当前进程。

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

在这里插入图片描述

我们查看到的进程pid与我们程序返回的pid相同,再次证明结果没有错。
那么我们还有一个疑问,我们当前进程的父进程又是谁呢?我们再次查看一下

 ps 22927

在这里插入图片描述
我们可以看到我们程序的父进程是bash,也就是shell外壳,为什么会是这样呢?原来在Linux中我们让bash去执行命令./processC让bash帮我们运行我们所写的程序,但是我们所写的程序可能有问题,如果bash自己帮我们运行了有问题程序,那么bash就无法进行正常的命令解释了,那么我们整个Linux接下来就无法进行任何操作了,所以bash为了保护自己同时为了能让Linux的其他程序能正常运行不受干扰,bash采用了创建子进程的方式来帮助我们运行程序,换句话说,在shell下直接运行的程序其父进程都是shell,shell是通过创建子进程的方式完成我们所交给shell的命令。

通过这个现象我们可以知道:

  • bash命令行解释器,本质上它也是一个进程!
  • 命令行启动的所有的程序,最终都会变成进程,而该进程对应的父进程都是bash

3、如何创建子进程

shell可以通过创建子进程的方式来完成我们交给shell的命令,那我们可不可以在我们所运行的进程里面再创建一个子进程来帮我们完成相应的任务呢?答案是可以的,Linux系统给我们提供了一个系统调用接口fork()函数,通过这个函数我们能够将创建一个新的子进程,我们先来看看fork()man手册吧。

在这里插入图片描述
简单的了解完了fork(),我们先动手用一下fork()吧。我们看以下代码,如果创建成功了那么下面两条printf函数都应该会被执行!

在这里插入图片描述

实验结果:

在这里插入图片描述
这说明我们的fork()函数,确实能帮我们创建一个新的子进程,fork()通过返回给父进程子进程不同的值让父进程与子进程运行了不同的代码。

fork()的执行过程:

  • fork之后,执行流会变成2个执行流.
  • fork之后,两个执行流谁先运行由调度器决定。
  • fork之后的代码共享,通常我们通过 if 和 elseif 来进行执行流分流完成不同的任务!

4、关于fork()的一些深度理解

  1. fork()做了什么
    fork()函数创建子进程并不是在内存中重新拷贝一份代码与数据,而是在内存中以父进程为模板创建一个新的PCB结构,子进程的PCB与父进程大部分属性都是一致的但是也有一部分不一样,如:pid,ppid。
    这个子进程的PCB与父进程PCB一样指向同一份代码与数据!
    在这里插入图片描述
  2. fork创建的子进程与父进程是怎么保证相互独立性的
    我们都知道,进程在运行时是具有独立性的,关闭A进程不会影响B进程,同样我们杀死父进程也不会影响子进程,但是我们知道fork()创建子进程时并没有复制父进程的代码,那么父子进程之间又是怎么保持进程之间的相互独立性的呢?

  首先,进程 = 内核数据结构(PCB) + 当前进程的代码与数据。

  子进程与父进程有不同的PCB ,可以保证内核数据结构的独立性,然后就是讨论代码与数据是怎么保持独立性的呢?
  我们知道对于代码,当我们编译完成以后代码便变成了是只读的常量,常量无法修改,于是父子进程执行同一份代码就不会影响父子进程彼此之间的代码逻辑了。

  那么对于数据呢?我们看上面的代码中有一个变量 pid 明明是同一个pid,为什么一个变量里面存放了两个不同的值呢?答案是写时拷贝,当我们父子进程尝试去修改数据的值时,便会触发写时拷贝,写时拷贝会给我们复制一份当前数据的值,让我们的父子进程去其他位置修改数据,而不是真正在原数据的基础是修改数据。

通过上面的解释我们知道,父子进程拥有自己独立的PCB,父子进程的代码是只读的,所以父子进程都无法修改代码,也就无法相互影响,父子进程的数据是以写时拷贝的方式各自私有一份,通过这些便让我们的父子进程保持了相互的独立性。

  1. fork为什么有两个返回值

我们知道,当一个函数准备执行 return 语句的时候,该函数的主体功能就已经完成了,return 语句不影响函数的功能,仅仅起到返回结果的作用。因此, fork 系统调用函数在执行 return 语句之前,子进程就已经创建完成甚至已经被操作系统调度了,所以当执行 return 语句返回结果的时候,由于在复制时复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。就要给父进程与子进程各自返回一份结果,即执行了两次。最终返回结果被赋值给变量 pid 的时候,OS自动触发了写时拷贝,分别把结果存入两者的备份空间中。

三、进程的状态

在了解进程的状态之前我们先来了解两个概念:

阻塞:进程因为等待某种条件就绪,而处于一种不推进的状态。
为什么会有阻塞状态呢?因为计算机是资源是有限的,当某种资源被其他进程占据时,一些也要使用此资源的进程便要进入阻塞状态,等待资源处于就绪状态后才能继续运行。

挂起:进程在处于阻塞状态时,内存里面既有task_struct结构体又有代码和数据,为了缓解内存的压力,暂时将代码和数据存放进磁盘中,将内存中的代码和数据进行删除,此时内存里面只有该进程的task_struct结构体而没有此进程的代码与数据的状态被称为挂起状态

在这里插入图片描述

Linux中的进程的状态

  • R运行状态 (running):运行状态并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
    在这里插入图片描述
  • S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠( interruptible sleep)
    我们来看看下面的代码运行起来后进程的状态:
    在这里插入图片描述

要查看进程的状态我们可以用:

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

在这里插入图片描述

我们明明可以看到右边的程序一直在运行,但是我们的Linux却告诉我们,我们的进程处于睡眠状态,(+号表示正在台前运行而不是后台运行)为什么会这样呢?

这是因为我们的代码要使用显示器设备,在我们执行printf函数时由于我们的进程需要使用显示器资源,但是显示器资源没有处于就绪状态,于是我们的CPU就无法继续执行此进程,此时进程便不在CPU队列里排队了,而跑去显示器队列里排队,所以此进程便被设置为了睡眠状态,当显示器资源就绪后我们的进程才会重新进入CPU队列里变为运行状态,而外设的运行速度是远远低于CPU的,所以运行此代码我们几乎99%时间看到的都是处于睡眠状态。

之所以S状态又被称为可中断睡眠是因为我们可以使用ctrl+C 或kill命令去杀掉此进程。

kill -9 对应进程的pid # 杀死一个进程

在这里插入图片描述

当我们对代码进行修改,不去访问外设时我们再看看此时进程的运行状态:

在这里插入图片描述

通过观察我们发现我们的进程一直处于R状态,这是因为我们的代码中没有去访问外设降低代码的执行速度。

在这里插入图片描述

  1. D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
    当我们的处于R状态进程需要向磁盘内部写入一些数据时,我们的进程便会从R状态切换为D状态,这是因为CPU的执行速度很快,但是向磁盘写入数据很慢,同时呢,为了防止进程在向磁盘中写数据时被杀掉(Linux操作系统会在内存不足时进行杀后台)造成数据丢失,处于此状态的进程不能被中断,那怕是操作系统,kill命令,CTRL+C,都无法使此状态的进程中止。

  2. T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程,这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
    通过kill -l命令我们能列出kill命令下的所有信号。
    在这里插入图片描述
    通过观察我们可以知道当我们想要暂停一个进程时可以发送

kill -19 对应进程的pid

当我们想要让一个暂停的进程继续运行时可以发送

kill -18 对应进程的pid

我们来看下面一段代码运行后,我们发送 kill -19号信号

在这里插入图片描述

我们发现进程确实暂停了,但现在我们又想要让进程运行起来,我们需要发送 kill -18号信号

在这里插入图片描述

  1. t (tracing stop)追踪状态,此状态也是暂停状态的一种,当我们用gdb调试我们C语言代码时我们运行至断点时便处于此种状态。
    在这里插入图片描述

  2. X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

  3. Z(zombie)-僵尸状态
    僵尸状态(Zombies)是一个比较特殊的状态。在子进程退出过程中,进程占有的所有资源将被回收,但是如果子进程立即退出,那么父进程就会拿不到子进程中的一些关键信息,于是,系统为了方便父进程拿到子进程的相关信息,在子进程退出时,子进程并不会立即退出,而是维持一段时间的僵尸状态。等到父进程拿到子进程中的关键信息后,子进程才能退出进程,变为X死亡状态。

    在僵尸状态时操作系统会去除除了task_struct结构(以及少数资源)以外的所有资源。于是进程就只剩下task_struct这么个空壳,故称为僵尸。之所以保留task_struct,是因为task_struct里面保存了进程的退出码、以及一些统计信息。而其父进程很可能会关心这些信息。比如在shell中,$?变量就保存了最后一个退出的前台进程的退出码,而这个退出码往往被作为if语句的判断条件。

四、僵尸进程与孤儿进程

僵尸进程

处于僵尸状态的进程被称为僵尸进程,僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就会一直处于Z状态。

我们先来看下面一段代码
在这里插入图片描述

我们先运行起来

在这里插入图片描述

我们可以利用kill命令去杀死子进程,然后观察子进程的状态是直接死亡状态还是僵尸状态。

在这里插入图片描述

通过观察我们发现子进程处于僵尸状态,因为父进程一直没有去读取子进程的退出状态代码,于是子进程要一直维持僵尸状态。此时子进程就是一个僵尸进程。

僵尸进程危害
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,Z状态一直不退出,PCB一直都要维护那个子进程的僵尸状态,如果父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费!

孤儿进程

在僵尸进程中,如果子进程先退出,父进程后退出,子进程要进入僵尸状态一段时间直到父进程读取到子进程的退出状态代码。那如果父进程先退出,子进程后退出呢?

父进程先退出,子进程就被称之为“孤儿进程”,孤儿进程会被1号进程(操作系统)领养,当然要有操作系统进程回收子进程喽。如果操作系统不进行领养会造成子进程一直处于僵尸状态,从而造成内存泄漏。

注意:操作系统领养后,子进程会由前台进程转换为后台进程。

我们还是运行上面的代码,然后先用kill命令杀死父进程(父进程死亡后会由bash回收)然后观察子进程。

kill命令前
在这里插入图片描述
kill命令后
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/1620.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

L2-014 列车调度 L1-082 种钻石 L1-083 谁能进图书馆

输入格式: 输入第一行给出一个整数N (2 ≤ N ≤105 ),下一行给出从1到N的整数序号的一个重排列。数字间以空格分隔。 输出格式: 在一行中输出可以将输入的列车按序号递减的顺序调离所需要的最少的铁轨条数。 输入样例: 9 8 4 2 …

STM32开发(九)STM32F103 通信 —— I2C通信编程详解

文章目录一、基础知识点二、开发环境三、STM32CubeMX相关配置四、Vscode代码讲解GPIO模拟I2C代码SHT30相关代码main函数中循环代码五、结果演示方式一、示波器分析I2C数据方式2、通过Modbus将获取到的数据传到PC上一、基础知识点 本实验通过I2C通信获取SHT30温湿度值&#xff…

一文带你看透前端世界里的日期时间,对就是Date

很高兴我们能够通过不同空间,不同时间,通过这篇博客相识,那一定是一种缘分,一种你和狗哥的缘分。今天我希望通过这篇博客对我所熟知的前端世界里的日期时间做一个汇总,不止是代码上的汇总哦! 目录 一、时区…

flex布局优化(两端对齐,从左至右)

文章目录前言方式一 nth-child方式二 gap属性方式三 设置margin左右两边为负值总结前言 flex布局是前端常用的布局方式之一,但在使用过程中,我们总是感觉不太方便,因为日常开发中,大多数时候,我们想要的效果是这样的 …

C++数据结构 —— 哈希表、unordered_map/set封装

目录 1.哈希概念 1.1哈希函数 1.2哈希冲突 2.闭散列实现 3.开散列实现 4.容器的封装 4.1unordered_map 4.2unordered_set 4.3封装过程中遇到的问题 1.哈希概念 顺序结构以及平衡二叉搜索树结构中,在查找一个元素时需要经过比较。顺序查找时间复杂度为O(N…

顺序栈的实现

目录 一、数据结构中的栈 二、接口函数 三、栈的初始化 四、入栈 五、判断栈是否为空 六、出栈 七、栈顶元素及元素总数 八、顺序栈的销毁 一、数据结构中的栈 首先,栈(Stack)这个词在数据结构和操作系统两个学科中都有出现。 操作系…

图像分割系列(一)

图像分割分类 语义分割 把每个像素都打上标签(这个像素点是人,树,背景等) (语义分割只区分类别,不区分类别中具体单位) 实例分割 实例分割不光要区别类别,还要区分类别中每一个…

面向切面编程AOP

1.Spring的AOP简介 1.1什么是AOP AOP为Aspect Oriented Programming的缩写,意思是面向切面编程,是通过预编译和运行期动态代理实现程序功能维护的一种技术 AOP是OOP(面向对象)的延续,利用AOP可以对业务逻辑的各部分…

5个代码技巧,加速你的Python

5个代码技巧,加速你的Python 人生苦短,快学Python! Python作为一种功能强大的编程语言,因其简单易学而受到很多初学者的青睐。它的应用领域又非常广泛:科学计算、游戏开发、爬虫、人工智能、自动化办公、Web应用开发…

蓝桥杯C++组怒刷50道真题(填空题)

🌼深夜伤感网抑云 - 南辰Music/御小兮 - 单曲 - 网易云音乐 🌼多年后再见你 - 乔洋/周林枫 - 单曲 - 网易云音乐 18~22年真题,50题才停更,课业繁忙,有空就更,2023/3/18/23:01写下 目录 👊填…

【C++】智能指针

文章目录📖 前言1. 智能指针的引入1.1 内存泄露的危害:1.2 异常安全中的内存泄露:1.3 RAII思想:1.3 拦截异常解决不了的内存泄漏:1.4 智能指针解决:2. 智能指针的拷贝2.1 直接拷贝的问题:2.2 au…

STM32实战项目-触摸按键

前言: 通过触摸按键控制LED灯以及继电器,具体实现功能如下: 1、触摸按键1单击与长按,控制LED1; 2、触摸按键2单击与长按,控制LED2; 3、触摸按键3单击与长按,控制LED3; 4、触摸按键4单击与长…

详解Spring、SpringBoot、SpringCloud三者的联系与区别

一、Spring Spring 是一个轻量级的Java 开发框架,主要依存于SSM 框架,即Spring MVC Spring Mybatis,定位很明确,Spring MVC主要负责view 层的显示,Spring 利用IOC 和AOP 来处理业务,Mybatis则是数据的持…

跨域解决方案

跨域解决方案 1.跨域基本介绍 文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS 跨域问题是什么? 一句话:跨域指的是浏览器不能执行其他网站的脚本,它是由浏览器的同源策略造成的,是浏览器对 javascr…

数据结构 | 栈的中缀表达式求值

目录 什么是栈? 栈的基本操作 入栈操作 出栈操作 取栈顶元素 中缀表达式求值 实现思路 具体代码 什么是栈? 栈是一种线性数据结构,具有“先进后出”(Last In First Out, LIFO)的特点。它可以看作是一种受限的…

“国产版ChatGPT”文心一言发布会现场Demo硬核复现

文章目录前言实验结果一、文学创作问题1 :《三体》的作者是哪里人?问题2:可以总结下三体的核心内容吗?如果要续写的话,可以从哪些角度出发?问题3:如何从哲学角度来进行续写?问题4:电…

学习28个案例总结

学习前 对于之前遇到的问题没有及时总结,导致做什么事情都是新的一样。没有把之前学习到接触到的内容应用上。通过这次对28个案例的学习。把之前遇到的问题总结成自己的经验,在以后的开发过程中避免踩重复性的坑。多看帮助少走弯路。 学习中 对28个案例…

2023年安徽省中职网络安全跨站脚本攻击

B-4:跨站脚本攻击 任务环境说明: √ 服务器场景:Server2125(关闭链接) √ 服务器场景操作系统:未知 √ 用户名:未知 密码:未知 1.访问服务器网站目录1,根据页面信息完成条件&am…

Shader基础

参考文章:Unity着色器介绍 Shader基础 Properties 声明格式 [optional: attribute] name(“display text in Inspector”, type name) default value 属性类型 Color:颜色属性,表示 RGBA 颜色值。Range:范围属性,表示一个在…

基于微信小程序的校园二手交易平台小程序

文末联系获取源码 开发语言:Java 框架:ssm JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7/8.0 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Maven包:Maven3.3.9 浏览器…