Linux进程概念-详细版(一)
目录
进程概念
描述进程-PCB
task_struct-PCB的一种
task_struct内容分类
查看进程
通过系统目录查看
通过ps命令查看
通过系统调用获取进程的PID和PPID
通过系统调用创建进程
fork的认识
使用if进行分流
最后的总结
Linux进程状态
运行状态-R
浅度睡眠状态-S
深度睡眠状态-D
暂停状态-T
暂停状态-t
死亡状态-X
僵尸状态-Z (子退出)
僵尸进程
僵尸进程的危害
孤儿进程
重要知识补充:阻塞和挂起
阻塞
挂起
二者区别
进程概念
课本定义:进程 是程序的一个执行实例,是正在执行的程序。
但是实际上,这种说法不全面。
举个例子: 如果有一个社会人士,想要成为你们学校的学生,那么他需要满足什么样的条件呢?只需要他本人进入到你们的学校,在学校内活动就可以了吗?这显然是不行的,判断一个人是不是这个学校的学生,依据的是这个人有没有被学校所管理,学校会不会给他排课,给他计学分、发毕业证。
所以同样的,把一个可执行程序变为进程,不仅仅要把该可执行程序加载到内存中,还要让这个可执行程序被操作系统所管理。
所以在内核观点:担当分配系统资源(CPU时间,内存)的实体。 这样描叙比较合适点。
在我们日常写代码的时候,其中有一部分内容为:编译原理。其对于一个c/c++代码,要想运行起来,要经过预处理,编译,汇编,链接。四步走,才会生成一个.exe的可执行程序,对于这个生成的.exe的可执行程序,其实就存放在磁盘上(现在的笔记本电脑仍然有磁盘作为存储设备,不过磁盘的类型和配置有所变化。根据搜索结果,笔记本电脑的磁盘主要分为固态硬盘(SSD)和机械硬盘(HDD)两种。)当我们双击这个可执行程序将其运行起来时,本质上是将这个程序加载到内存当中了,因为冯诺依曼体系的原因,只有加载道内存的数据才可以被CPU进行接下来的操作。而一旦将这个.exe程序加载到内存后,我们就不应该将这个.exe程序再叫做程序了,严格意义上将应该将其称之为进程。
在我们Windows下,我们也可以通过打开任务管理器,查看当前运行的所有进程。
不难理解的是,原本存放在磁盘上的任务管理器,经过我们打开运行后,他也会变为一个进程,这也侧面证明了我们上面说的是对的。
描述进程-PCB
系统当中可以同时存在大量进程,刚才我们在Windows下查看了当前运行的所有进程,光后台运行都有142个,可见之多。当然在Linux下也可以查看。,使用命令ps aux便可以显示系统当中存在的进程。
ps aux
这里就截取一部分,其实还是蛮多的。
而当你开机的时候启动的第一个程序就是我们的操作系统(即操作系统是第一个加载到内存的),我们都知道操作系统是做管理工作的,而其中就包括了进程管理。而系统内是存在大量进程的,那么操作系统是如何对进程进行管理的呢?
遵循我们之前文章中提到的六个字原则:先描述,再组织。
操作系统管理进程也是一样的,操作系统作为管理者是不需要直接和被管理者(进程)直接进行沟通的,当一个进程出现时,操作系统就立马对其进行描述,之后对该进程的管理实际上就是对其描述信息的管理。
在Windows下这些操作其实都是在c盘下进行的,这也就是为什么,如果c盘空间满的话,电脑会特别卡。
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合,课本上称之为PCB(process control block)。
这样一来,操作系统只要拿到这个双链表的头指针,便可以访问到所有的PCB。此后,操作系统对各个进程的管理就变成了对这条双链表的一系列操作。
例如创建一个进程实际上就是先将该进程的代码和数据加载到内存,紧接着操作系统对该进程进行描述形成对应的PCB,并将这个PCB插入到该双链表当中。而退出一个进程实际上就是先将该进程的PCB从该双链表当中删除,然后操作系统再将内存当中属于该进程的代码和数据进行释放或是置为无效。
总的来说,操作系统对进程的管理实际上就变成了对该双链表的增、删、查、改等操作。
所以总的来说,根据上面描叙,对于一个进程,不仅仅是由我们想象的代码与数据组成的,还要由内核PCB数据结构对象(描述你所有的属性值,同样还存在指针指向下一个PCB)。
task_struct-PCB的一种
进程控制块(PCB)在描述进程的过程中,从c++这类高级编程语言来看,就是面向对象,但是Linux是用c语言来写的,所以对于PCB,他只能使用结构体。
- 对于task_struct,Linux中的PCB就是task_struct(在其他操作系统中的PCB就不一定叫task_struct)。
- task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含进程的信息。
task_struct内容分类
task_struct就是Linux当中的进程控制块,task_struct当中主要包含以下信息:
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器(pc): 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。\
- 上下文数据: 进程执行时处理器的寄存器中的数据。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟总和,时间限制,记账号等。
- 其他信息。
此后所有对于进程的管理都被转换成了对数据结构PCB的增删查改,这是一个对进程的管理建模的过程。
所以进程的正确定义:进程是内核关于进程的相关数据结构与当前进程的代码和数据的结合。
查看进程
通过系统目录查看
在根目录下有一个名为proc的系统文件夹。
文件夹当中包含大量进程信息,其中有些子目录的目录名为数字。
ls /proc
文件夹当中包含大量进程信息,其中有些子目录的目录名为数字。
这些数字其实是某一进程的PID,对应文件夹当中记录着对应进程的各种信息。我们若想查看PID为1的进程的进程信息,则查看名字为1的文件夹即可。
通过ps命令查看
在一开始我们就是用ps aux 指令查看所有进程
那么这里我们就不单单显示效果了,就说简单解释一下这些是什么吧。此部分只是了解内容,重要的部分后面会详细说明。
1. USER
- 含义:进程的所有者用户名,即哪个用户在系统中启动了这个进程。
- 示例:如果进程由
root
用户启动,显示为root
;如果是普通用户启动的进程,则会显示该普通用户的用户名。2. PID
- 含义:进程的 进程标识符(Process ID),是系统用来唯一标识每个运行中的进程的数字。俗称编号
- 示例:
1234
表示进程的唯一 ID,是操作系统用来管理该进程的标识符。3. %CPU
- 含义:进程当前占用的 CPU 百分比,表示进程在系统中使用 CPU 时间的百分比。这个值是根据进程在一定时间内消耗的 CPU 时间和系统总 CPU 时间的比例计算的。
- 示例:如果显示为
5.0
,意味着该进程在过去的 1 秒钟里,占用了系统 CPU 总量的 5%。- 注意:如果是多核 CPU 系统,该百分比可能会超过 100%。例如,4 核 CPU 下,一个进程可能占用 400% 的 CPU,意味着该进程使用了 4 核的全部或多个核心的 CPU 时间。
4. %MEM
- 含义:进程当前占用的 内存百分比,表示进程使用的物理内存(RAM)大小与系统总内存的比例。
- 示例:如果显示为
2.1
,意味着该进程占用了系统总内存的 2.1%。- 注意:这是基于物理内存的百分比,并不考虑交换空间(swap)。
5. VSZ
- 含义:进程的 虚拟内存大小(Virtual Memory Size),单位为 KB。这个值包括进程使用的所有内存(包括共享库、映射文件等),以及可能存在的虚拟内存空间(比如被交换到硬盘上的内存)。
- 示例:
123456
表示该进程的虚拟内存占用了 123456 KB。6. RSS
- 含义:进程的 常驻内存集(Resident Set Size),单位为 KB。RSS 表示进程当前在物理内存中占用的实际内存(不包括交换到磁盘的部分),也就是进程占用的非虚拟内存部分。
- 示例:
23456
表示该进程的 RSS 为 23456 KB。7. TTY
- 含义:进程所关联的 终端(Terminal),表示该进程是在哪个终端下启动的。如果进程没有终端(如后台进程或系统进程),这个字段显示为
?
。- 示例:
tty1
表示该进程是在tty1
终端上运行;?
表示该进程没有关联终端。8. STAT
这部分后面细讲,再次省略。9. START
- 含义:进程的 启动时间,表示该进程开始运行的时间。格式通常是 小时:分钟(对于启动时间较短的进程)或 日期(对于启动时间较长的进程)。
- 示例:
12:34
表示该进程在12:34
启动;Nov 28
表示该进程在 11 月 28 日启动。10. TIME
- 含义:进程已经使用的 CPU 时间,即该进程自启动以来占用的总 CPU 时间,通常以 分钟:秒 的形式显示。
- 示例:
00:02
表示该进程已占用了 2 秒钟的 CPU 时间。11. COMMAND
- 含义:启动进程的 命令,即运行该进程的完整命令行(包括路径和参数)。对于较长的命令行,可能只会显示部分内容,如果需要查看完整命令行,可以使用
ps auxww
来显示完整内容。- 示例:
/usr/bin/python3 /path/to/script.py
表示该进程是运行 Python 脚本。
在Windows下我们也通过实践得知,我们使用任务管理器查看当前运行的所有进程的时候,此时被打开的任务管理器也会变为进程,那么,同样我们在使用ps命令查看当前的进程时候,也会将ps作为一个进程运行起来。
此时我们就可以ps命令与grep -V grep命令搭配使用,即可只显示某一进程的信息,从而过滤ps命令。
ps aux | head -1 && ps aux | grep proc | grep -v grep
通过系统调用获取进程的PID和PPID
通过使用系统调用函数,getpid和getppid即可分别获取进程的PID和PPID。
这里我们就不man 来查看原文的介绍了,这里直接解释getpid与getppid是什么,
- getpid是返回其当前进程的pid
- getppid是返回当前进程的ppid,ppid也就是其当前进程的父进程的pid
- 需要引用头文件#include <unistd.h>
当运行该代码生成的可执行程序后,便可循环打印该进程的PID和PPID。
我们可以通过ps命令查看该进程的信息,即可发现通过ps命令得到的进程的PID和PPID与使用系统调用函数getpid和getppid所获取的值相同。
注意:aux是显示详细信息,ajx显示的稍微少一点。我这里用的是ajx,不是aux,其实用aux也行,只是这里不需要显示详细信息,所有用ajx就可以。
通过系统调用创建进程
fork的认识
在以前,我们熟悉的创建进程的方式有两种,第一种是在Windows系统下,我们双击一个 .exe 文件,就创建了一个进程,还有一种是在Linux系统下,我们通过在命令行输入 ./ 来将程序变成进程去运行
现在我们再来学习一种创建进程的方式,通过系统调用:fork
同样不man查看官方的解释,直接用案例来演示
我们写一个这样的代码,然后运行查看效果
若是代码当中没有fork函数,我们都知道代码的运行结果就是循环打印该进程的PID和PPID。而加入了fork函数后,代码运行结果如下:
可以看到也是打印了独两份,二者不会相互干扰,其中蓝色标识的ppid为红色标识的pid,拿着也在侧面说明了,其蓝色指向的进程的父进程为红色指向的进程。也就是说test1进程与fork函数创建的进程之间是父子关系。
每出现一个进程,操作系统就会为其创建PCB,fork函数创建的进程也不例外。
我们知道加载到内存当中的代码和数据是属于父进程的,那么fork函数创建的子进程的代码和数据又从何而来呢?
我们看看以下代码的运行结果:
运行结果:
实际上,使用fork函数创建子进程,在fork函数被调用之前的代码被父进程执行,而fork函数之后的代码,则默认情况下父子进程都可以执行。需要注意的是,父子进程虽然代码共享,但是父子进程的数据各自开辟空间(采用写时拷贝,也就是说如果不做任何修改的情况下还是共用)。
特别提醒: 使用fork函数创建子进程后就有了两个进程,这两个进程被操作系统调度的顺序是不确定的,这取决于操作系统调度算法的具体实现。
使用if进行分流
上面说到,fork函数创建出来的子进程与其父进程共同使用一份代码,但是如果让父进程创建的子进程与父进程做着完全相同的事情,那么这就显得子进程没有存在的意义。
所以实际上,我们创建子进程的目的就是为了让其与父进程做着完全不同的事,想要达到这点要求,还是需要先了解其fork函数的返回值。
fork函数的返回值:
- 如果子进程创建成功,在父进程中返回子进程的PID,而在子进程中返回0。
- 如果子进程创建失败,则在父进程中返回 -1。
既然父进程和子进程获取到fork函数的返回值不同,那么我们就可以据此来让父子进程执行不同的代码,从而做不同的事。
fork创建出子进程后,子进程会进入到else if 语句的循环打印当中,而父进程会进入到 else语句的循环打印当中。
最后的总结
- 子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。
- 为什么要创建子进程:为了让其父子进程执行不同的代码块。
- 子进程的数据相对于父进程是会进行写时拷贝(COW)。
Linux进程状态
一个进程从创建到撤销的整个生命周期中,体现了进程与程序之间的区别。尽管在同一台机器上可以同时运行多个进程,有些进程可能正在占用处理器执行,而另一些进程则处于等待状态,无法获得处理器资源。即使有空闲的处理器,由于某些条件尚未满足,这些进程仍然无法执行。这一切都表明,进程是动态的,并且具有状态变化的特性,因此引入了“进程状态”这一概念。
这里我们具体谈一下Linux操作系统中的进程状态,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*/
};
小提醒: 进程的当前状态是保存到自己的进程控制块(PCB)当中的,在Linux操作系统当中也就是保存在task_struct当中的。
在Linux操作系统当中我们可以通过 ps aux 或 ps axj 命令查看进程的状态。
ps aux
ps ajx
运行状态-R
运行状态根据名字也不难理解,但是在实际上,一个进程处于运行状态(running),并不意味着进程一定处于运行当中,运行状态表明一个进程要么在运行中,要么在运行队列里。也就是说,可以同时存在多个R状态的进程。
但是如果自己去实验,ps一个当前运行的进程,可能我们查看到的并不是R状态。
这是因为:进程在CPU上运行的时候,并不是一直在运行的,而是一个进程先在CPU上运行一会(大概时10ms),再切换另一个进程在CPU上运行一会,不断的切换进程周而复始重复运作的。这叫做基于进程切换的分时操作系统,由于CPU的运行速度非常快,切换速度使人类感觉不到,从而使人们有种进程一直在运行的感觉。所以在实际上可能在那1ms内该进程时运行的,但是在下一ms又不运行了,又可能有过了几ms又运行了,然后实际算下来不运行的时间比运行的长,所以在查看的时候,不是R状态。
特别提醒: 所有处于运行状态,即可被调度的进程,都被放到运行队列当中,当操作系统需要切换进程运行时,就直接在运行队列中选取进程运行。
浅度睡眠状态-S
一个进程处于浅度睡眠状态(sleeping),意味着该进程正在等待某件事情的完成,处于浅度睡眠状态的进程随时可以被唤醒,也可以被杀掉(这里的睡眠有时候也可叫做可中断睡眠(interruptible sleep))。(杀死进程可以使用kill指令然后指定杀死对应的进程)
代码当中调用sleep函数进行休眠20秒,在这期间我们若是查看该进程的状态,则会看到该进程处于浅度睡眠状态。
ps aux | head -1 && ps aux | grep test2 | grep -v grep
而处于浅度睡眠状态的进程是可以被杀掉的,我们可以使用kill命令将该进程杀掉。
这里的-9是以编号为9的杀死方式去kill。
深度睡眠状态-D
一个进程处于深度睡眠状态(disk sleep),表示该进程不会被杀掉,即便是操作系统也不行,只有该进程自动唤醒才可以恢复。该状态有时候也叫不可中断睡眠状态(uninterruptible sleep),处于这个状态的进程通常会等待IO的结束。
例如,某一进程要求对磁盘进行写入操作,那么在磁盘进行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。(磁盘休眠状态)。
暂停状态-T
在Linux当中,我们可以通过发送SIGSTOP信号使进程进入暂停状态(stopped),发送SIGCONT信号可以让处于暂停状态的进程继续运行。
例如,我们对一个进程发送SIGSTOP信号,该进程就进入到了暂停状态。
补充:
S
状态表示 中断睡眠(Interruptible Sleep),即进程正在等待某个事件,且可以被信号中断。S+
状态表示 中断睡眠,并且该进程是 前台进程组的一部分,通常与终端操作相关。
补充kill:
指令: kill -l
对应的名称就是其缩写。
比如12,SIGUSR2
SIG代表 "Signal"(信号)
USR:代表 "User"(用户)
2:表示第二个用户定义的信号
暂停状态-t
这个没什么好解释的,这个就是其在调试代码的时候,遇见断点,就会变为此状态,但是实际上在vim中很少调试,能直接观察出代码问题的话,都是不建议调试。
死亡状态-X
死亡状态只是一个返回状态,当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也就不存在了,所以你不会在任务列表当中看到死亡状态(dead)。
僵尸状态-Z (子退出)
当一个进程将要退出的时候,在系统层面,该进程曾经申请的资源并不是立即被释放,而是要暂时存储一段时间,以供操作系统或是其父进程进行读取,如果退出信息一直未被读取,则相关数据是不会被释放掉的,一个进程若是正在等待其退出信息被读取,那么我们称该进程处于僵尸状态(zombie)。(关于读取退出信息的工作是要有父进程来做,读取方式是调用函数wait()函数(后面介绍))
首先,僵尸状态的存在是必要的,因为进程被创建的目的就是完成某项任务,那么当任务完成的时候,调用方是应该知道任务的完成情况的,所以必须存在僵尸状态,使得调用方得知任务的完成情况,以便进行相应的后续操作。
例如,我们写c语言代码时都在主函数最后返回0。
你是否思考过为什么我们要写0,不能写1,2,3么?
答案是可以的,只是一般来说,在 C 语言程序中,我们习惯性地返回
0
,表示程序的成功执行。这并不是因为语法要求,而是因为操作系统、脚本以及工具通常通过这个返回值来判断程序是否成功运行。对于别的都是存在问题的。或与等哪天你可以创造一个习惯返回1的。
那么我再这里提到了c语言的main函数,那么肯定是想靠拢僵尸进程的。
虽然在 C 语言代码中,main
函数是程序的入口点,但从操作系统的角度来看,main
函数并不是程序的“最外层”。实际上,main
函数是操作系统加载并运行程序后,交给程序的一部分,操作系统在背后执行了许多准备工作,确保程序能够顺利启动和执行(也就是去调用main函数,这也就是为什么一个项目只能有一个main函数)。
那么如果main函数的父进程没有处理main函数的退出信息,在理论上main函数同样会变为僵尸进程,但是在实际情况上,这种事情根本不会发生。
以上说了那么多,其实都在解释其实main函数也是一个进程,其也有存在变为僵尸进程的风险。只不过这种风险根本不可能发生。
那么,这个0就是返回给操作系统的,告诉操作系统代码顺利执行结束。在Linux操作系统当中,我们可以通过使用echo $?命令获取最近一次进程退出时的退出码。
echo $?
所以总的来说如果一个子进程结束时,立刻退出,父进程是没有机会拿到退出结果的。所以在Linux中,进程退出时,一般不会立即彻底退出,而是要维持一个 Z 状态,也叫僵尸状态,方便后续父进程读取该子进程的退出结果。
补充: 进程退出的信息(例如退出码),是暂时被保存在其进程控制块当中的,在Linux操作系统中也就是保存在该进程的task_struct当中。
僵尸进程
前面说到,一个进程若是正在等待其退出信息被读取,那么我们称该进程处于僵尸状态。而处于僵尸状态的进程,我们就称之为僵尸进程。
俗来说,僵尸状态 是给 父进程 准备的,当 子进程 被终止后,会先维持一个 僵尸 状态,方便 父进程 来读取到 子进程 的退出结果,然后再将 子进程 回收。
例如,对于以下代码,创建一个维持30s的僵尸进程的例子
运行该代码后,我们可以通过以下监控脚本,每隔一秒对该进程的信息进行检测
while :; do ps aux | head -1; ps aux | grep test4 | grep -v grep; sleep 1; done
僵尸进程的危害
- 僵尸进程的退出状态必须一直维持下去,因为它要告诉其父进程相应的退出信息。可是父进程一直不读取,那么子进程也就一直处于僵尸状态。
- 僵尸进程的退出信息被保存在task_struct(PCB)中,僵尸状态一直不退出,那么PCB就一直需要进行维护。
- 若是一个父进程创建了很多子进程,但都不进行回收,那么就会造成资源浪费,因为数据结构对象本身就要占用内存。
- 僵尸进程申请的资源无法进行回收,那么僵尸进程越多,实际可用的资源就越少,也就是说,僵尸进程会导致内存泄漏。
到这里值得被关注的进程状态已经讲解完毕,那么下补充知识,说一些别的状态(这些知识虽没有放在前面说,但是也是十分的重要,不是了解内容,是掌握的内容,非常重要!!!)
孤儿进程
在Linux当中的进程关系大多数是父子关系,若子进程先退出而父进程没有对子进程的退出信息进行读取,那么我们称该进程为僵尸进程。但若是父进程先退出,那么将来子进程进入僵尸状态时就没有父进程对其进行处理,此时该子进程就称之为孤儿进程。
若是一直不处理孤儿进程的退出信息,那么孤儿进程就会一直占用资源,此时就会造成内存泄漏。因此,当出现孤儿进程的时候,孤儿进程会被1号init进程领养,此后当孤儿进程进入僵尸状态时就由int进程进行处理回收。
例如,对于以下代码,fork函数创建的子进程会一直打印信息,而父进程在打印5次信息后会退出,此时该子进程就变成了孤儿进程。
重要知识补充:阻塞和挂起
一个进程可以有多个状态,我们先来说明两个最为核心的状态:阻塞和挂起。
以下解释来源于别人写的文章。
阻塞
进程因为等待某种条件就绪,而导致的一种不推进的状态叫做阻塞状态,给人们最直观的感受就是程序卡住了。换句话说,一个进程阻塞,一定是在等待某种所需要的资源就绪的过程。
想象这样一个场景,我们在下载一些资料的时候,如果网断了,CPU还有必要继续调度这个下载进程吗?肯定是没必要了,因为没有意义,此时就会把该进程设置为阻塞状态。那么这个进程是如何等待网络资源就绪的呢?
首先再在这里提及一个知识点,在Linux操作系统下,一切设备皆文件,那么比如我们日常所说的显示器,其实他也在操作系统面前也是文件,他是文件,他就一定会被管理。
我们之前讲过,操作系统需要管理网卡、磁盘等外设,这个过程通常是一个“先描述再组织”的过程。操作系统会创建多个结构体类型(如 struct dev
),然后将各个外设的属性信息填充到这些结构体中,接着通过数据结构将它们组织在一起。同样地,操作系统管理大量的进程时,也是通过“先描述再组织”的方式进行的。具体而言,操作系统会定义多个结构体来描述进程的各种属性,并将这些进程通过链表、队列等数据结构进行组织和调度。
当网络断开时, 需要等待网络资源的进程就会把自己的PCB从CPU的某些特定队列中拿取出来,连接到网卡设备结构体队列的尾部来排队等待网络资源:
此时,再获取到等待的资源之前,该进程不会再被CPU调度。
PCB是可以被维护在不同的队列中的。进程在等待哪种资源,就会被排列到哪种资源的队列中去。再举个例子,当我们在C语言中使用scanf 函数时,运行程序,如果我们不在键盘上输入内容,进程就会处于阻塞状态,并在键盘的结构体中排队等待资源,只有拿到数据时,进程才会再次被CPU调度。
总结:阻塞就是不被CPU调度——一定是因为当前进程需要等待某种资源就绪——一定是进程tesk_struct结构体需要在某种被OS管理的资源下排队。
挂起
如果有时候出现了内存资源紧张的情况,而且阻塞进程的PCB被接入到了所需要等待资源的结构体队列中,不被调度。这时,操作系统就会把阻塞进程的代码和数据交换到磁盘中,同时释放其所在内存中占据的空间,从而起到节省内存空间的目的。等到进程所需要的资源就绪的时候,再把该进程的代码和数据加载到内存中,交由CPU调度。
其中把进程的代码和数据由OS暂时性的交换到磁盘中时,称该进程处于挂起状态。全称为阻塞挂起状态。挂起可以看作一种特殊的阻塞状态。
比如在我们生活中,一边走路一边玩手机很危险,所以此时我们会将玩手机这个 进程挂起
,即把手机揣进兜里,然后 专心执行走路这个 进程。
二者区别
- 是否释放CPU:阻塞(pend)就是任务释放CPU,其他任务可以运行,一般在等待某种资源或信号量的时候出现。挂起(suspend)不释放CPU,如果任务优先级高就永远轮不到其他任务运行。一般挂起用于程序调试中的条件中断,当出现某个条件的情况下挂起,然后进行单步调试。
- 是否主动:显然阻塞是一种被动行为,其发生在磁盘,网络IO,wait,lock等要等待某种事件的发生的操作之后。因为拿不到IO资源,所以阻塞时会放弃 CPU的占用。而挂起是主动的,因为挂起后还要受到CPU的监督(等待着激活),所以挂起不释放CPU,比如sleep函数,站着CPU不使用。
相关文章:

Linux进程概念-详细版(一)
目录 进程概念 描述进程-PCB task_struct-PCB的一种 task_struct内容分类 查看进程 通过系统目录查看 通过ps命令查看 通过系统调用获取进程的PID和PPID 通过系统调用创建进程 fork的认识 使用if进行分流 最后的总结 Linux进程状态 运行状态-R 浅度睡眠状态-S 深度睡…...

K8S网络系列--Flannel网络下UDP、VXLAN模式的通信流程机制分析
文章目录 前言一、了解overlay、underlay容器网络二、网络通信1.分类2.网络虚拟设备对2.1、什么是网络虚拟设备对veth pair?2.2、如何查看容器的网卡与主机的哪个veth设备对是成对的关系? 3、vxlan和vtep3.1、vtep3.2、vxlan相关概念 三、Flannel网络模式剖析0、flannel的作用…...

ThreadLocal的设计思考
问题的提出 在Java多线程中,共享变量的读写非常容易出现不可预测的行为,因此对共享变量的访问控制非常重要。因此在多线程编程时,为了保证线程安全,需要进行额外的同步措施。比如典型的操作就是加锁。除了加锁外,另一…...

shell脚本练习(2)
1. 使用case实现成绩优良差的判断 2. for创建20用户 用户前缀由用户输入 用户初始密码由用户输入 例如:test01,test10 3. for ping测试指网段的主机 网段由用户输入,例如用户输入192.168.2 ,则ping 192.168.2.10 --- 192.168.2.2…...

通讯专题4.1——CAN通信之计算机网络与现场总线
从通讯专题4开始,来学习CAN总线的内容。 为了更好的学习CAN,先从计算机网络与现场总线开始了解。 1 计算机网络体系的结构 在我们生活当中,有许多的网络,如交通网(铁路、公路等)、通信网(电信、…...

Harmony NEXT-越过相机读写权限上传图片至项目云存储中
问题成因 在制作用户注册登录界面时想要实现用户头像上传共能,查询API文档,发现有picker和PhotoAccessHelper两个包可以选择使用,但是在使用PhotoAccessHelper包拉起相册并读入所选的照片后将该照片传入云存储中产生报错,需要相册…...

MATLAB基础应用精讲-【数模应用】Retinex图像去雾算法(附MATLAB和python代码实现)
目录 前言 算法原理 图像去雾 数学模型 算法步骤 算法拓展 多尺度Retinex (MSR) 算法 MSR算法的实现细节 McCann Retinex 算法 McCann99 Retinex算法 基于暗通道先验的图像去雾算法 暴力解法——直方图均衡化去雾 基于Retinex理论的图像去雾 基于暗通道先验的单…...

点击A组件跳转到B页面的tab的某一列
1、使用vuex存储点击的数据; 点击A组件里面的button按钮: <div><button click"banli(first)">已办理</button><button click"banli(second)">未办理</button><button click"banli(third)&quo…...

HarmonyOS xml转换JavaScript 常用的几个方法
HarmonyOS 使用 xml转换JavaScript 的好处 易用性: 提供了简洁的API接口,使得XML到JavaScript对象的转换变得简单直接。转换选项的灵活性允许开发者根据实际需求自定义转换结果。 高效性: HarmonyOS对底层运行时环境进行了优化,使…...

Linux笔记---进程:进程等待
1. 进程等待的概念 进程等待是指父进程通过系统调用wait或waitpid来对子进程进行状态检测与回收的功能。 当子进程退出时,如果父进程不读取子进程的退出状态,子进程就会成为僵尸进程,造成内存泄漏的问题。因此,父进程需要调用wa…...

【Linux】匿名管道通信场景——进程池
🔥 个人主页:大耳朵土土垚 🔥 所属专栏:Linux系统编程 这里将会不定期更新有关Linux的内容,欢迎大家点赞,收藏,评论🥳🥳🎉🎉🎉 文章目…...

算法妙妙屋-------1.递归的深邃回响:全排列的奇妙组合
全排列的简要总结 全排列(Permutation)是数学中一个经典的问题,指的是从一组元素中,将所有元素按任意顺序排列形成的所有可能序列。 特点 输入条件: 给定一组互异的元素(通常为数组或字符串)。…...

【maven-6】Maven 生命周期相关命令演示
Maven 是一个广泛使用的项目管理工具,尤其在 Java 项目中。它通过定义一系列的生命周期阶段(Phases)来管理项目的构建过程。理解这些生命周期阶段及其相关命令,对于高效地构建和管理项目至关重要。本文将通过实际演示,…...

黑马程序员Java笔记整理(day06)
1.继承的特点 2.继承的权限 3. 4.小结 5.方法重写 6.子类构造器 7.兄弟构造器 8.多态 9.小结...

LeetCode【代码随想录】刷题(动态规划篇)
509. 斐波那契数 力扣题目链接 题目:斐波那契数(通常用F(n)表示)形成的序列称为斐波那契数列 。该数列由0和1开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) 0,F(1) 1 F(n) F(n - 1) F(n…...

【看海的算法日记✨优选篇✨】第三回:二分之妙,寻径中道
🎬 个人主页:谁在夜里看海. 📖 个人专栏:《C系列》《Linux系列》《算法系列》 ⛰️ 一念既出,万山无阻 目录 📖一、算法思想 细节问题 📚左右临界 📚中点选择 📚…...

基于yolov8、yolov5的铝材缺陷检测识别系统(含UI界面、训练好的模型、Python代码、数据集)
摘要:铝材缺陷检测在现代工业生产和质量管理中具有重要意义,不仅能帮助企业实时监控铝材质量,还为智能化生产系统提供了可靠的数据支撑。本文介绍了一款基于YOLOv8、YOLOv5等深度学习框架的铝材缺陷检测模型,该模型使用了大量包含…...

计算机光电成像理论基础
一、透过散射介质成像 1.1 光在散射介质中传输 光子携带物体信息并进行成像的过程是一个涉及光与物质相互作用的物理现象。这个过程可以分为几个步骤来理解: 1. **光的发射或反射**: - 自然界中的物体可以发射光(如太阳)&am…...

【QNX+Android虚拟化方案】125 - 如何创建android-spare镜像
【QNX+Android虚拟化方案】125 - 如何创建android-spare镜像 1. Android侧创建 (ext4 / sparse) test_img.img 镜像 方法一2. Android侧创建 (ext4 / sparse) test_img.img 镜像 方法二3. qnx 侧 分区透传Android 配置3.1 配置分区透传3.2 Android 侧分区 rename3.3 创建挂载目…...

深度学习基础小结_项目实战:手机价格预测
目录 库函数导入 一、构建数据集 二、构建分类网络模型 三、编写训练函数 四、编写评估函数 五、网络性能调优 鲍勃开了自己的手机公司。他想与苹果、三星等大公司展开硬仗。 他不知道如何估算自己公司生产的手机的价格。在这个竞争激烈的手机市场,你不能简单地…...

EMall实践DDD模拟电商系统总结
目录 一、事件风暴 二、系统用例 三、领域上下文 四、架构设计 (一)六边形架构 (二)系统分层 五、系统实现 (一)项目结构 (二)提交订单功能实现 (三࿰…...

【随笔】AI技术在电商中的应用
这几年,伴随着ChatGPT开始的AI浪潮席卷全球,从聊天场景逐步向多场景扩散,形成了广泛开花的现象。至今,虽然在部分场景的进展已经略显疲态,但当前的这种趋势仍然还在不断的扩展。不少公司,甚至有不少大型电商…...

序列式容器详细攻略(vector、list)C++
vector std::vector 是 STL 提供的 内存连续的、可变长度 的数组(亦称列表)数据结构。能够提供线性复杂度的插入和删除,以及常数复杂度的随机访问。 为什么要使用 vector 作为 OIer,对程序效率的追求远比对工程级别的稳定性要高得多,而 vector 由于其对内存的动态处理,…...

快速启动项目
1 后端项目 https://gitee.com/liuyunkai666/gungun-boot.git 分支: mini 是 springboot3 jdk17 的基础版本,后续其他功能模块陆续在其基础上追加即可。 1.1 必备环境 1.1.1 mysql 创建一个 自定义名称 数据库,【只要】 执行对应数据库…...

springboot347基于web的铁路订票管理系统(论文+源码)_kaic
摘 要 当今社会进入了科技进步、经济社会快速发展的新时代。计算机技术对经济社会发展和人民生活改善的影响也日益突出,人类的生存和思考方式也产生了变化。传统铁路订票管理采取了人工的管理方法,但这种管理方法存在着许多弊端,比如效率低…...

使用API管理Dynadot域名,在账户中添加域名服务器(Name Server)
前言 Dynadot是通过ICANN认证的域名注册商,自2002年成立以来,服务于全球108个国家和地区的客户,为数以万计的客户提供简洁,优惠,安全的域名注册以及管理服务。 Dynadot平台操作教程索引(包括域名邮箱&…...

【Linux | 计网】TCP协议深度解析:从连接管理到流量控制与滑动窗口
目录 前言: 1、三次握手和四次挥手的联系: 为什么挥手必须要将ACK和FIN分开呢? 2.理解 CLOSE_WAIT 状态 CLOSE_WAIT状态的特点 3.FIN_WAIT状态讲解 3.1、FIN_WAIT_1状态 3.2、FIN_WAIT_2状态 3.3、FIN_WAIT状态的作用与意义 4.理解…...

go语言的成神之路-筑基篇-对文件的操作
目录 一、对文件的读写 Reader 接口 Writer接口 copy接口 bufio的使用 ioutil库 二、cat命令 三、包 1. 包的声明 2. 导入包 3. 包的可见性 4. 包的初始化 5. 标准库包 6. 第三方包 7. 包的组织 8. 包的别名 9. 包的路径 10. 包的版本管理 四、go mod 1. 初始…...

两道数据结构编程题
1.写出在顺序存储结构下将线性表逆转的算法,要求使用最少的附加空间。 解:输入:长度为n的线性表数组A(1:n) 输出:逆转后的长度为n的线性表数组A(1:n)。 C语言描述如下(其中ET为数据元素的类型):…...

【Qt】QDateTimeEdit控件实现清空(不保留默认时间/最小时间)
一、QDateTimeEdit控件 QDateTimeEdit 提供了一个用于编辑日期和时间的控件。用户可以通过键盘或使用上下箭头键来增加或减少日期和时间值。日期和时间的显示格式根据设置的格式显示,可以通过 setDisplayFormat() 方法来设置。 二、如何清空 我在使用的时候&#…...