【Linux】进程学习(2)---理解进程操作

news2024/9/28 11:53:13

文章目录

  • 查看进程
    • 通过系统目录查看
    • 通过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函数的返回值

  1. 如果子进程创建成功,在父进程中返回子进程的PID,在子进程中返回0.
  2. 如果子进程创建失败,则在父进程中返回-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;
}  

测试结果如图:
在这里插入图片描述

在这里插入图片描述
僵尸进程的危害

  1. 僵尸进程的退出状态必须要被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那么子进程就会一直处于僵尸Z状态。
  2. 维护退出状态要用数据维护,这也属于进程基本信息,所以僵尸进程的退出信息保存在task_stuct(PCB)中,如果父进程一直不读取子进程退出结果,那么Z状态一直不退出,PCB就要一直被维护。
  3. 如果一个父进程创建了很多子进程,但是都没有进行回收,那么就会造成内存资源的浪费,因为数据结构对象(task_stuct)本省就要占用内存,如果不进行回收,那当然就会造成内存泄漏这样严重的问题。

死亡状态-X

X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。进程死亡状态立马被它的父进程回收,速度太快了,所以我们看不到。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

操作系统引导(开机过程)

操作系统安装在C盘中&#xff0c;其一步步启动的过程如下&#xff1a; 操作系统要启动&#xff0c;操作系统的数据需要先被放入主存里。 如图所示&#xff0c;计算机的主存由RAM和ROM组成&#xff0c;ROM芯片被集成在电脑主板上&#xff0c;里面存储的是BIOS&#xff08;Basic…

【组合数学算贡献+枚举】CF816div2 C. Monoblock

题解都看了半天才懂 Problem - C - Codeforces 题意&#xff1a; 思路&#xff1a; 一开始的思路是这样的&#xff1a; 只能说&#xff0c;想到了更换枚举对象&#xff0c;然后组合数学算贡献 也想到了修改操作与&#xff08;a[i]和a[i-1]&#xff09;有关 但是我想的是枚…

在Linux上搭建gitlab以及自动化编译部署的完整流程

一、安装gitlab 首先下载gitlab的安装包&#xff0c;地址如下&#xff1a; https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu/pool/bionic/main/g/gitlab-ce/ 然后安装下载的包即可&#xff0c;一般还需要安装openssh-server等依赖包&#xff0c;在安装gitlab包之前可以…

【MongoDB】二、MongoDB数据库的基本操作

【MongoDB】二、MongoDB数据库的基本操作 实验目的实验内容任务一&#xff1a;&#xff08;1&#xff09;创建数据库newdb&#xff08;2&#xff09;在数据库newdb中创建集合mycollection&#xff08;3&#xff09;在集合mycollection中插入以下数据&#xff1a;&#xff08;4&…

如何安装 Auto GPT 4:分步指南

动动发财的小手&#xff0c;点个赞吧&#xff01; 您对尝试最新最好的文本生成技术感到兴奋吗&#xff1f; Auto GPT 4 因其令人印象深刻的功能而广为人知&#xff0c;但启动和运行它似乎令人望而生畏。幸运的是&#xff0c;我们在这里[1]提供安装 Auto GPT 4 的分步指南。 1. …

快手sig3 48位-unidbg

研究某手app的小伙伴都了解sig3有两个版本&#xff0c;低版本的是42位&#xff0c;高版本的48位。 废话不多说&#xff0c;先抓个包&#xff1a; 上一个当前最新版本的48位sig3&#xff0c;我们以搜索接口为例&#xff0c;效果如图&#xff1a; 在上面可以看到使用unidbg的方式…

【深度学习】计算机视觉(11)——Faster RCNN(工具篇)

文章目录 1 gcc编译报错1.1 错误提示“ld: cannot find -lm/-lc/-lpthread”1.2 解决方法&#xff1a;安装glibc工具1.3 解决方法&#xff1a;修改sources.list1.4 解决方法&#xff1a;软连接 2 Permission denied3 运行报错3.1 module tensorflow has no attribute 3.2 No mo…

火山 xl,xa,xg,xk,xh,xm 六神签名参数

火山 xl,xa,xg,xk,xh,xm 六神签名参数 27/100 发布文章 weixin_38819889 未选择任何文件 new 纯属技术研究&#xff0c;如有侵权&#xff0c;请联系删除。 抓个包&#xff0c;在火山最新的15.6版本中&#xff0c;已经新增加了2个参数x-helios&#xff0c;x-medusa 前段时间do…

IDEA Java 第一个mybatis入门程序

文章目录 准备mysql 开始新建maven项目maven添加引用mybatis配置文件工具类创建实例类添加mappermappermapper.xml 测试类 发现问题org.apache.ibatis.binding.BindingException: Type interface com.cpyy.mapper.UserMapper is not known to the MapperRegistry.The error may…

[计算机图形学]动画与模拟:欧拉方法、刚体与流体(前瞻预习/复习回顾)

一、前言 这是本专栏的倒数第二篇文章了&#xff0c;为什么不是最后一篇&#xff1f;因为我要单独写一篇总结哈哈&#xff0c;不管怎么说&#xff0c;从今年的3.13的MVP变换开始写&#xff0c;写到现在&#xff0c;也是一个很大的工程了&#xff0c;我很高兴能在大二下学期的期…

使用ffmpeg拼接两张图片

最近在工作中遇到了一个需求&#xff0c;就是需要将两张图片拼接在一起&#xff0c;作为一个封面图。如果只是临时拼接一张&#xff0c;我们可以只用photoshop之类的图片编辑工具&#xff0c;将两张图片拼接在一起。而我们的需要是需要实现自动化&#xff0c;由于之前使用过ffm…

KALI入门到高级【第六章】

预计更新第一章 入门 1.1 什么是Kali Linux&#xff1f; 1.2 安装Kali Linux 1.3 Kali Linux桌面环境介绍 1.4 基本命令和工具 第二章 信息收集 1.1 网络扫描 1.2 端口扫描 1.3 漏洞扫描 1.4 社交工程学 第三章 攻击和渗透测试 1.1 密码破解 1.2 暴力破解 1.3 漏洞利用 1.4 特…

Linux网络编程:基础知识

1. MAC地址和IP地址 IPV4&#xff1a;32位&#xff1b;8B 4 32bit IPV6:128位&#xff1b;4B 32 128bit&#xff0c;图中IPV6补全为&#xff1a;fe80:0000:0000:0000:6e3f:77c3:ceca:b5a7 MAC&#xff1a;48位; 4B 12 48bit (图中IPV6和MAC地址使用的16进制表示法&a…

QTDAY4

定时闹钟 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTimer> //定时器类 #include <QTime> //时间类 #include <QTimerEvent> //定时器事件类 #include <QDateTime> //日期时间类 #include <QText…

052、牛客网算法面试必刷TOP101--二分查找/排序(230503)

文章目录 前言二分查找/排序1、BM17 二分查找-I2、BM18 二维数组中的查找3、BM19 寻找峰值4、BM20 数组中的逆序对5、BM21 旋转数组的最小数字6、BM22 比较版本号 总结 前言 本文记录自己刷&#xff0c;牛客网的面试必刷TOP101&#xff0c;地址&#xff1a;面试必刷TOP101–二…

【GAMES101】05 Rasterization(Triangles)

光栅化过程&#xff1a;将一系列变换后的三角形转换为像素的过程。 三角形在图形学中得到很多的应用。 最基础的多边形&#xff08;边数最少&#xff09;。任何多边形都可以拆成三角形。性质&#xff1a;三角形内部一定是平面的。三角形内外部定义非常清楚。定义三个顶点后&a…

libfacedetection 人脸检测库 检测速度慢的问题

目录 一、libfacedetection 性能介绍 英特尔CPU 使用AVX2指令集 使用AVX512指令集 嵌入式设备 二、加速检测速度 libfacedetetion的前向推理速度很快的原因 使用axv2加速指令 一、libfacedetection 性能介绍 在上一篇文章中&#xff0c;我发现使用摄像头检测&#xff0c;构…

C++入门——内联函数的介绍

目录 前言 内联函数 1. 概念 2.特性 前言 今天小编给大家带来的是内联函数的介绍&#xff0c;大家可能之前没有听过内联函数这个名词&#xff0c;那么今天就和小编一起认识一下这个朋友吧。 内联函数 我们每次在调用函数时都会开辟一个函数栈帧&#xff0c;那么过度的函数…

上海联影面试(部分)(未完全解析)

一面 Spring Boot为什么可以自启动&#xff0c;且变成一个web项目&#xff1f;本地连不上网&#xff0c;Maven缺一个jar包&#xff0c;怎么解决&#xff1f;linux用什么命令找到占用指定端口的进程&#xff0c;并杀掉&#xff1f;Answer by new bing: 查找被占用端口的PID&am…