【Linux】进程学习(2)---理解进程操作
文章目录
- 查看进程
- 通过系统目录查看
- 通过ps命令查看
- 通过系统调用获取进程标识符
- 通过系统调用创建进程
- 初识fork函数
- fork函数的返回值
- 进程状态
- 阻塞与运行状态
- Linux内核源码中的进程状态
- 运行状态-R
- 浅度睡眠状态-S
- 深度睡眠状态-D
- 暂停状态-T
- 僵尸状态-Z
- 死亡状态-X
查看进程
通过系统目录查看
在根目录下有一个名为proc的系统文件夹(查看结果如下图),这个proc文件夹当中包含大量进程信息,其中有些目录名为数字,这些数字其实是某一进程的PID,对应文件夹当中记录着对应进程的各种信息。我们若想查看PID为1的进程的进程信息,则查看名字为1的文件夹即可/proc/1
。
通过ps命令查看
1.单独使用ps命令,会显示所有进程信息。
[nan@VM-8-10-centos test_23_4_23]$ ps axj
2.ps命令与grep命令搭配使用,即可只显示某一进程的信息。
ps axj | head -1 && ps axj | grep myproc | grep -v grep
//head -1 这个指令可以带上进程的小标题。
//grep -v grep 由于grep本身也是一个进程,加上这句话可以过滤掉grep这个进程的显示
3.中止进程
法一:Ctrl+c
法二:kill -9 [进程PID]
通过系统调用获取进程标识符
- 进程id(PID)
- 父进程id(PPID)
通过使用系统调用函数,getpid和getppid即可分别获取进程的PID和PPID。
需要包含的头文件#include<unistd.h> #include<sys/types.h>
我们通过一段代码来观察以下情况:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{while(1){printf("你好,我已经是一个进程了,我的PID是:%d,我的父进程是:%d\n",getpid(),getppid());sleep(1);}return 0; }
当运行该代码生成的可执行程序后,可循环打印该进程的PID和PPID。我们可以通过ps命令查看该进程的信息,可发现通过ps命令得到的进程的PID和PPID与使用系统调用函数getpid和getppid所获取的值相同。
还有一个现象就是,如果我们在命令行重复运行该程序,每次进程的PID都输不一样的,但是进程的PPID都是相同的,我们通过ps命令查看以下这个父进程的属性信息,可以发现这个进程的父进程就是bash。
结论:命令行启动所有程序,最终都会变成进程,而该进程对应的父进程都是bash,所以bash命令行解释器,本质上也是一个进程。bash通过派生子进程的方式执行程序,如果程序有bug退出了,那只是子进程出问题,对bash没有影响。如果我们用kill命令终止bash这个进程,那么命令行就失效了(有兴趣的同学可以试一试,之后重启就会恢复的)
通过系统调用创建进程
初识fork函数
- fork是一个系统调用级别的函数,其功能是创建一个子进程
- 运行
man fork
可以查看fork系统调用函数的使用手册 - fork有两个返回值
- 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
我们先来看一段测试代码:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{printf("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:PID:%d,PPID:%d\n",getpid(),getppid()); fork();printf("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB:PID:%d,PPID:%d\n",getpid(),getppid());sleep(1);return 0;
}
运行结果:
看到这个运行结果,有人肯定会纳闷,怎么打印了两行B的信息?
解释:并且我们从运行结果可以看到,第一次打印的B,PID是22063(进程PID),第二次打印的B,PPID(父进程ID)也是22063,这个结果说明了,当代码执行到fork函数之后,我们自己创建了一个子进程。而这个子进程的父进程就是我们运行起来myproc进程,myproc进程的父进程20166是base。打印结果有两行B是因为我们当前进程在fork创建子进程之后,进行了分流(如图),一条是当前的myproc进程,另一条是子进程。
父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
fork之前,父进程有它的PCB,以及代码和数据,fork创建子进程之后,并没有把父进程的代码和数据再拷贝一份,而是在内核当中再创建一份进程所对应的PCB,子进程的进程属性大部分会以父进程为模板,还有小部分的属性是子进程私有的,比如子进程的PID,PPID。也就是说,fork之后,父进程和子进程共享一份代码和数据(父进程的)。
fork函数的返回值
- 如果子进程创建成功,在父进程中返回子进程的PID,在子进程中返回0.
- 如果子进程创建失败,则在父进程中返回-1.
上面打印A和B的测试代码,fork函数创建出来的子进程与父进程共享代码,但是如果让父子进程做相同的事是没有意义的,所以,实际上,在fork之后一般使用if-else语句进行分流,父子进程相互独立,可以执行不同的任务。
测试代码如下:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{printf("I am running...\n");//接收fork函数的返回值pid_t id=fork();if(id==0){//子进程while(1){printf("我是子进程...\n");sleep(1);}}else if(id>0){//父进程while(1){printf("我是父进程...\n");sleep(1);}}else{//fork error}return 0;
}
运行结果:父子进程循环打印
从上述代码,可以很清楚的了解到fork创建子进程后,if-else语句居然都被分别执行了。有两个myproc进程在跑诶~
结论:
a.fork之后,会由一个执行流,变成两个执行流。
b.fork之后,两个进程被OS调度的顺序是不确定的,取决于操作系统调度算法的具体实现。
c.fork之后,fork之后的代码共享,通过我们使用if和else进行执行流分流。
注意:父子进程之间相互独立
进程在运行的时候,是具有独立性的!父子进程在运行的时候,也是具有独立性的。kill -9 子进程PID,可以看到父进程正常运行。他们代码共享,数据以写时拷贝的方式各自私有一份。
fork如何看待代码和数据?
代码:代码是只读的,
数据:当有一个执行流尝试修改数据的时候,操作系统会自动给我们当前进程触发写时拷贝。父子进程之间的数据不会相互影响。
进程状态
阻塞与运行状态
想要弄明白进程的各种状态,我们需要先弄明白什么是阻塞,什么是运行。
问?当我们打开一个软件,它就一直处于运行状态吗?
答案是No,CPU不是处理完一个进程,再处理下一个进程的。而是轮流着处理,只是因为处理速度非常快,我们没有感受到那个时间差。
阻塞状态:进程等待某种资源就绪的过程。
进程要通过等待的方式,等具体的资源被别人使用完成后,再被自己使用。task_struct结构体需要在某种被OS管理的资源下排队。所以因为等待某种条件就绪,而导致的一种不推进的状态,即进程卡住了,就被称为阻塞。比如,你去银行柜台办理业务,结果业务员叫你先到一旁去填表,那么你就处于阻塞状态。
进程不仅仅会占用CPU资源,也会占用硬件资源。对于CPU,它可以很快的处理进程的请求;但是对于硬件,速度很慢,例如网卡,可能同时有迅雷、百度网盘、QQ等进程需要获取网卡的资源,所以每一个描述硬件的结构体中也有一个task_struct* queue运行队列指针,指向排队中的PCB对象的头结点。
那么CPU和硬件的速度差异巨大,系统该怎么平衡这种速度?当CPU发现运行状态的进程需要访问硬件资源时,会让该进程去所需访问的硬件的运行队列中排队,CPU继续执行下一个进程。
那么这个被CPU剥离至硬件运行队列中的进程状态被称为阻塞状态。当进程对硬件的访问结束后,进程的状态将会被修改为运行状态,即该进程重新回到CPU的运行队列。
总结:PCB可以被维护在不同的队列中。
阻塞挂起状态:硬件的速度较慢,但是大量的进程需要访问硬件,势必会产生较多的阻塞进程,这些阻塞进程的代码和数据在短期内不会被执行,如果全部存在于内存中将会导致内存占用。
对于这个问题,如果内存中有过多的阻塞状态的进程导致内存不足,操作系统会将其的代码和数据先挪动至磁盘,仅留PCB结构体,以节省内存空间,这种进程状态被称为挂起状态。将进程相关数据,加载或保存至磁盘的过程,称为内存数据的换入和换出。
进程的阻塞状态不一定是挂起状态,部分操作系统可能会存在新建状态挂起或运行状态挂起等。
Linux内核源码中的进程状态
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在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 *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*/
};
ps:进程的当前状态是保存到自己的进程控制块(PCB)当中的,在Linux操作系统当中也就是保存在task_struct当中的。
task_struct进程控制块的内容分类
接下来,开始详细解析每种状态的定义。
进程状态查看命令
ps axj | head -1 && ps axj | grep 进程PID | grep -v grep
运行状态-R
测试代码:
#include<stdio.h>
int main
{while(1){}return 0;
}
查询进程状态:
R运行状态(running):一个进程处于运行状态,并不意味着进程一定在运行中,这个进程有可能是在运行,也有可能是在运行队列里(排队中)。所有处于运行状态的进程,都被放到运行队列中,当操作系统切换进程进行运行时,就从运行队列中选取进程运行。
浅度睡眠状态-S
睡眠状态的本质就是阻塞状态。
测试代码:
#include <stdio.h>
int main()
{ int a=0; while(1) { printf("%d\n",a++); } return 0;
}
查看进程状态:
浅度睡眠状态S:一个进程处于浅度睡眠状态(sleeping),表面该进程正在等待某件事情完成,处于浅度睡眠状态的进程随时可以被唤醒,也可以被杀掉(浅度睡眠也可叫做可中断睡眠)。
有人可能会疑问,明明代码是在运行的呀,为什么是处于阻塞状态呢?
那是因为这段测试代码相比上段测试运行状态的代码多了一个打印函数printf,既然是打印函数,当然就需要访问到外设(显示屏),所以啊,这个时候我们维护mytest进程的PCB就到外设的运行队列去等待外设了(阻塞状态),由于CPU的处理速度远大于外设的速度,所以我们只有小小小概率,可能可以看到进程状态是R,大部分查询到的进程状态还是S。
ps:状态后面有+号表示前台进程,没有+号表示后台进程。
前台进程通过Ctrl+c可以终止进程,后台进程不受终端控制,Ctrl+c无法终止进程运行。
深度睡眠状态-D
深度睡眠状态-D:也叫作不可中断睡眠状态,表示该进程不会被杀掉,即便是操作系统也不行,不然你的系统可能就会宕机了,只有该进程自动唤醒才可以恢复,在这个状态下,进程通常会等待IO的结束。
暂停状态-T
暂停状态的本质也是一个阻塞状态。
暂停状态-T(stopped):在Linux中,我们可以通过发送SIGSTOP(kill -19 进程PID)使一个进程进入暂停状态,发送SIGCONT信号(kill -18 PID)可以使处于暂停状态的进程继续运行。
追踪暂停状态t:在我们使用gdb对可执行文件进行调试,利用b设置断点,并run(运行)后,程序会在断点处停下,此时程序就会进入t追踪暂停状态(tracing stop),表示该进程正在被追踪。
僵尸状态-Z
僵尸状态-Z(zombie):当一个进程将要退出的时候,操作系统OS不会立即释放该进程的资源,会等一段时间,让父进程或者操作系统读取子进程的返回结果(即退出码),没有读取到子进程退出的返回代码就会产生僵尸进程。僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就进入僵尸状态-Z。
例如,我们在写C/C++代码的时候,在最后都会return 0,这个0实际上就是退出码,也是父进程在等待子进程时,需要拿到的结果, 其中退出码是暂时被保存在其进程控制块当中的。
父进程是派生子进程去完成某项任务,那么子进程的任务完成情况也需要在最后上报给父进程。
在Linux操作系统当中,我们可以通过使用echo $?命令获取最近一次进程退出时的退出码。
[nan@VM-8-10-centos test_23_4_27]$ echo $?
模拟僵尸状态,测试代码:下列代码子进程打印完一次,执行到exit(1)的时候就退出了,而父进程会一直打印信息,也就是说子进程退出了,父进程还在运行,但是父进程并没有读取子进程的退出结果,那么子进程就会陷入僵尸状态。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{ pid_t id=fork(); if(id==0) { //子进程 while(1) { printf("子进程,PID=%d,PPID=%d\n",getpid(),getppid()); sleep(1); exit(1); } } else if(id>0) { //父进程 while(1) { printf("父进程,PID=%d,PPID=%d\n",getpid(),getppid());sleep(1); }}else {perror("fork error\n");exit(-1); }return 0;
}
测试结果如图:
僵尸进程的危害:
- 僵尸进程的退出状态必须要被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那么子进程就会一直处于僵尸Z状态。
- 维护退出状态要用数据维护,这也属于进程基本信息,所以僵尸进程的退出信息保存在task_stuct(PCB)中,如果父进程一直不读取子进程退出结果,那么Z状态一直不退出,PCB就要一直被维护。
- 如果一个父进程创建了很多子进程,但是都没有进行回收,那么就会造成内存资源的浪费,因为数据结构对象(task_stuct)本省就要占用内存,如果不进行回收,那当然就会造成内存泄漏这样严重的问题。
死亡状态-X
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。进程死亡状态立马被它的父进程回收,速度太快了,所以我们看不到。
相关文章:

【Linux】进程学习(2)---理解进程操作
文章目录 查看进程通过系统目录查看通过ps命令查看 通过系统调用获取进程标识符通过系统调用创建进程初识fork函数fork函数的返回值 进程状态阻塞与运行状态Linux内核源码中的进程状态运行状态-R浅度睡眠状态-S深度睡眠状态-D暂停状态-T僵尸状态-Z死亡状态-X 查看进程 通过系统…...

基于springcloud实现的医院信息系统
访问【WRITE-BUG数字空间】_[内附完整源码和文档] 医疗信息就诊系统,系统主要功能按照数据流量、流向及处理过程分为临床诊疗、药品管理、财务管理、患者管理。诊疗活动由各工作站配合完成,并将临床信息进行整理、处理、汇总、统计、分析等。本系统包括以…...

设计模式-创建型模式-(工厂、简单工厂、抽象工厂)
一、简单工厂模式 上代码 public class FoodFactory {public static Food makeFood(String name) {if (name.equals("noodle")) {Food noodle new LanZhouNoodle();noodle.addSpicy("more");return noodle;} else if (name.equals("chicken")…...
JAVA12新特性
JAVA12新特性 概述 2019年3月19日,java12正式发布了,总共有8个新的JEP(JDK Enhancement Proposals) JDK 12 is the open-source reference implementation of version 12 of the Java SE12 Platform as specified by by JSR 386 in the Java Community Process. JDK 12 reac…...

Nginx 静态文件、反向代理、负载均衡、缓存、SSL/TLS 加密、gzip 压缩 等等
Nginx的功能 1. 静态文件服务器2. 反向代理服务器3. 负载均衡4. 缓存5. SSL/TLS 加密6. URL 重写7. HTTP/28. WebSocket9. 反向代理缓存10. 安全限制11. gzip 压缩12. 请求限速13. 日志记录14. SSL 证书续订 Nginx 是一个高性能的开源 Web 服务器和反向代理服务器,它…...

Linux设备驱动模型(一)
一、sysfs文件系统 sysfs是一个虚拟文件系统,将内核总的设备对象的链接关系,以文件目录的方式表示出来,并提对设备提供读写接口。 二、kobject kobject是内核中对象表示的基类,可以认为所有的内核对象都是一个kobject kobject单…...

【Python入门篇】——Python基础语法(标识符与运算符)
作者简介: 辭七七,目前大一,正在学习C/C,Java,Python等 作者主页: 七七的个人主页 文章收录专栏: Python入门,本专栏主要内容为Python的基础语法,Python中的选择循环语句…...

扩展 VirtualBox 已分配磁盘的方法
扩展 VirtualBox 已分配磁盘的方法 第一步:用VirtualBox命令行调整已分配磁盘的大小第二步:用windows磁盘管理工具扩展磁盘空间其他无关配置如何选择虚拟机的芯片组 注意:扩展操作只支持 vdi 格式的磁盘,就是VirtualBox自己的磁盘…...

【LeetCode】646. 最长数对链
646. 最长数对链(中等) 思路 这道题和 300. 最长递增子序列 类似,我们可以定义 dp 数组,其中 dp[i] 表示以 i 结尾的子序列的性质。在处理好每个位置后,统计一遍各个位置的结果即可得到题目要求的结果。 但是题目中强…...

Makefile教程(Makefile的结构)
文章目录 前言一、Makefile的结构二、深入案例三、Makefile中的一些技巧总结 前言 一、Makefile的结构 Makefile 通常由一系列规则组成,每条规则定义了如何从源文件生成目标文件。每个规则又由目标、依赖和命令三部分组成。 下面是 Makefile 规则的基本结构&…...

SpringMVC(后)SSM整合
10、文件上传和下载 10.1、文件下载 ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文 使用ResponseEntity实现下载文件的功能 RequestMapping("/testDown") public ResponseEntity<byte[]> testResp…...

【博弈论】【第一章】博弈论导论
博弈论导论 【例题】选择数字【例题】巴什博弈【例题】射手博弈博弈论的基本概念:参与人战略行动信息支付函数【例题】分100元 课程概述: 【例题】选择数字 两个参与人A和B,轮流选择[3,4,5,6,7,8,9]中的一个整数(可重复)。当累计…...

keil移植linux(makefile)
文章目录 运行环境:1.1 freeRTOS_LED工程移植1)修改cubeMX配置2)setting设置3)launch设置4)修改makefile5)修改代码6)实验效果 运行环境: ubuntu18.04.melodic 宏基暗影骑士笔记本 stm32f427IIH6 stlink 9-24v可调电源 robomaster A 板 1.1 freeRTOS_L…...

C++——类和对象(3)
作者:几冬雪来 时间:2023年5月6日 内容:C类和对象内容讲解 目录 前言: 1.运算符重载(续): 2.赋值重载: 结尾: 前言: 在上一篇博客中我们再一次讲解了…...

itop-3568开发板驱动学习笔记(24)设备树(三)时钟实例分析
《【北京迅为】itop-3568开发板驱动开发指南.pdf》 学习笔记 文章目录 生产者属性#clock-cells 属性clock-output-namesclock-frequencyassigned-clockclock-indicesassigned-clock-parents 消费者属性 设备树中的时钟信息以时钟树形式体现,时钟树包括时钟的属性和结…...

linux中使用docker部署微服务
目录 一、制作jar包(如果看一眼很简单,可以直接使用结尾的jar) 1.首先创建一个微服务 demo2 2.启动微服务(在DemoApplication上右键执行启动就行) 注意:其他操作导致的 可能遇到的报错 3.修改端口 4.新…...

操作系统考试复习—第三章 优先级倒置 死锁问题
当前OS广泛采用优先级调度算法和抢占方式,然而在系统中存在着影响进程运行的资源从而可能产生"优先级倒置"现象 具体解释为:在原本的调度算法设计中,高优先级进程可以抢占低优先级的CPU资源,先执行高优先级任务。但是存…...
OpenHarmony送显流程分析
OpenHarmony送显流程分析 引言 本文档主要记录OpenHarmony在渲染完成之后如何进行合成和送显流程的。这个过程牵涉的代码很多,而且流程也是比较繁琐的。所以我一定要坚持下来。千万不能半途而废,也不要想着一口气吃出一个胖子,路漫漫其修远兮…...
Java面试题字节流字符流
String 编码UTF-8 和GBK的区别 GBK编码:是指中国的中文字符,其实它包含了简体中文与繁体中文字符,另外还有一种字符 “gb2312”,这种字符仅能存储简体中文字符。 UTF-8编码:它是一种全国家通过的一种编码&#x…...

Self-Attention结构细节及计算过程
一、结构 上面那个图其实不是那么重要,只要知道将输入的x矩阵转换成三个矩阵进行计算即可。自注意力结构的输入为 输入矩阵的三个变形 Q(query矩阵)、K(key矩阵)、V(value矩阵)构成,…...

C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...

10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...