一、进程状态查看

二、Z(zombie)-僵尸进程

1.僵尸进程概念


子进程先于父进程结束,而父进程又没有调用wait或者waitpid获取其退出信息。子进程还需要在其PCB中保存其退出的相关信息,所以,子进程的执行主体已经结束,但是操作系统并没有释放该进程PCB结构,以满足父进程后续对该子进程退出信息的查询(如果父进程还在运行)。在子进程结束运行之后,父进程读取其退出状态之前,我们称该子进程为僵尸进程。

2.僵死状态/进程

  1. 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
  2. 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
  3. 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。

3.僵死状态/进程处理


由父进程调用wait或者waitpid获取其退出信息,但是调用wait或者waitpid的进程会阻塞运行,导致父进程在子进程结束之前不能执行其他事情。在此基础之上可用三种方式解决

  1. 重启操作系统(建议不推荐)。
  2. 利用信号SIGCHLD,子进程状态改变后会向其父进程发送SIGCHLD信号。父进程在接受到该信号后,在信号处理函数中调用wait或者waitpid。
  3. 将僵尸进程的父进程杀掉,将最终使用的子进程变为孤儿进程,从而交由init进程处理其退出信息。

4.僵尸进程的模拟实现


代码如下:

#include<stdio.h>
#include<unistd.h>
int main()
{
    pid_t pid=fork();
    if(pid<0)
    {
        printf("fork error!\n");
    }
    else if(pid==0)
    {
        printf("i am child:pid=%d,ppid=%d\n",getpid(),getppid());
        //子进程
    }
    else 
    {
        while(1)
        {
            printf("i am father:pid=%d,ppid=%d\n",getpid(),getppid());
            sleep(1);
        }
        //父进程
    }
    return 0;
}
12345678910111213141516171819202122232425


子进程的状态:

5.僵尸进程的危害


  1. 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
  2. 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存task_struct(PCB)中,换句话说, Z状态一直不退出, PCB一直都要维护?是的!
  3. 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
  4. 内存泄漏?是的!

6.进程状态总结

至此,值得关注的进程状态全部讲解完成,下面来认识另一种进程。

三、孤儿进程

1.孤儿进程概念



父进程结束或者异常终止,但是子进程继续运行。此时子进程的PPID被设置为1,即init进程。init进程接管了该子进程,并等待它结束,在父进程退出之后,子进程退出之前,该子进程属于孤儿进程(在Linux高性能服务器编程中也被称为僵尸进程)。

2.孤儿进程

  1. 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
  2. 父进程先退出,子进程就称之为“孤儿进程”
  3. 孤儿进程被1号init进程领养,当然要有init进程回收喽。

3.孤儿进程的模拟实现


代码如下:

#include<stdio.h>
#include<unistd.h>
int main()
{
    pid_t pid=fork();
    if(pid<0)
    {
        printf("fork error!\n");
    }
    else if(pid==0)
    {
        //子进程
        while(1)
        {
           printf("i am child:pid=%d,ppid=%d\n",getpid(),getppid());
           sleep(1);
        }
    }
    else
    {
        printf("i am father:pid=%d,ppid=%d\n",getpid(),getppid());
    }
    return 0;
}
123456789101112131415161718192021222324


由于进程号为17398的进程变成后台进程,所以不能ctrl+c退出。

要使用kill -9 强杀命令杀掉17398进程。**

题目:

4.僵尸进程和孤儿进程会造成什么?

无论哪种情况,如果父进程没有正确的处理子进程的返回信息,子进程都将停留在僵尸态或者孤儿态,并占据着内核资源。这是不允许的,毕竟内核资源有限。

四、进程优先级

1.进程优先级基本概念

  1. cpu资源分配、外设IO的先后顺序,就是指进程的优先权(priority)。
  2. 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
  3. 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。

2.查看系统进程

在linux或者unix系统中,用ps –al命令则会类似输出以下几个内容:

代码如下:

#include<stdio.h>
#include<unistd.h>
int main()
{
    while(1)
    {
        printf("Hello Linux!\n");
        sleep(1);
    }
    return 0;
}
1234567891011

我们很容易注意到其中的几个重要信息,有下:

  1. UID : 代表执行者的身份,用户标识符。
  2. PID : 代表这个进程的代号。
  3. PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号。
  4. PRI :prority代表这个进程可被执行的优先级,其值越小越早被执行。
  5. NI :代表这个进程的nice值。

3.PRI and NI

  1. PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高。

    PRI的默认值一般是80。

  2. 那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值。

    NI的默认值一般是0。

  3. PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为PRI(new)=PRI(old)+nice。
  4. 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行。
  5. 所以,调整进程优先级,在Linux下,就是调整进程nice值。
  6. nice其取值范围是-20至19,一共40个级别。

4.PRI vs NI

  1. 需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
  2. 可以理解nice值是进程优先级的修正修正数据。

5.查看进程优先级的命令

  1. top。
top命令用于显示系统运行的进程信息,作用类似于windows中的任务管理器,只不过top不是图形化的,而是显示实时文本信息。
统计信息区域的下方显示了各个进程的详细信息。首先来认识一下各列的含义。PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
序号列名含义
PID 进程id
PPID 父进程id
RUSER Realusername
UID 进程所有者的用户id
USER 进程所有者的用户名
GROUP 进程所有者的组名
TTY 启动进程的终端名。不是从终端启动的进程则显示为?
PR 优先级
NInice 值。负值表示高优先级,正值表示低优先级
P 最后使用的CPU,仅在多CPU环境下有意义
%CPU 上次更新到现在的CPU时间占用百分比
TIME 进程使用的CPU时间总计,单位秒
TIME+ 进程使用的CPU时间总计,单位1/100秒
%MEM 进程使用的物理内存百分比
VIRT 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
SWAP 进程使用的虚拟内存中,被换出的大小,单位kb。
RES 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA
CODE 可执行代码占用的物理内存大小,单位kb
DATA 可执行代码以外的部分(数据段+栈)占用的物理内存大小,单位kb
SHR 共享内存大小,单位kb
nFLT 页面错误次数
nDRT 最后一次写入到现在,被修改过的页面数。
S 进程状态。
D= 不可中断的睡眠状态
R= 运行
S= 睡眠
T= 跟踪/停止
Z= 僵尸进程
COMMAND 命令名/命令行
WCHAN 若该进程在睡眠,则显示睡眠中的系统函数名
Flags 任务标志,参考sched.h
  1. 进入top后按“r”–>输入进程PID–>输入nice值。

1.用top命令更改已存在进程的nice:

进入top后按“r”–>输入进程PID–>输入nice值。

  1. 输入top命令,记得要加sudo提升权限。
  2. 按r显示这个界面。
  3. 输入进程PID的值。
  4. 输入nice值。

这里就显示了修改以后的PRI和NI。

重要:

6.其他概念

  1. 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
  2. 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。
  3. 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行。
  4. 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。

五、环境变量

1.环境变量基本概念

  1. 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。
  2. 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
  3. 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。

2.为什么需要环境变量

环境变量(envirnment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。

3.常见的环境变量

  1. PATH : 指定命令的搜索路径。
  2. HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)。
  3. SHELL : 当前Shell,它的值通常是/bin/bash。

4.查看环境变量的方法

echo $NAME //NAME:你的环境变量名称

5.测试PATH


代码如下:

#include<iostream>
int main()
{
    std::cout<<"Hello world"<<std::endl;
    return 0;
}
123456
  1. 创建hello.cpp文件
  2. 对比./hello执行和之间hello执行

./hello运行

./目的是为了在当前路径下找到hello的可执行程序。

而如果直接运行hello那么将会发生错误:

  1. 为什么有些指令可以直接执行,不需要带路径,而我们的二进制程序需要带路径才能执行?
  2. 将我们的程序所在路径加入环境变量PATH当中, export PATH=$PATH:hello程序所在路。

    这种方法只是在当前终端有效,离开当前终端就失效了。

  3. 对比测试使环境变量永久生效。

    原来的.bash_profile是这样的。

    经过修改后变成了:

  4. 还有什么方法可以不用带路径,直接就可以运行呢?

    扩展知识:

6.测试HOME

用root和普通用户,分别执行 echo $HOME ,对比差异. 执行 cd ~; pwd ,对应 ~ 和 HOME 的关系。
root用户:

普通用户:

7.和环境变量相关的命令

  1. echo: 显示某个环境变量值。
  2. export: 设置一个新的环境变量。
  3. env: 显示所有环境变量。
  4. unset: 清除环境变量。
  5. set: 显示本地定义的shell变量和环境变量。

8.环境变量的组织方式

在C语言中我们学到了指针数组,就是存放指针的数组,那么存放的就是各种环境变量的首地址,通过找到环境变量的首地址就可以找到对于的环境变量。

每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串。

9.通过代码如何获取环境变量

  1. 命令行第三个参数

代码如下:

#include<stdio.h>
int main(int argc,char* argv[],char* env[])
{
    //argc : 命令行参数的个数, 本质上就是argv数组的元素个数
    //argv :具体的命令行参数
    // envp : 环境变量的值
    for(int i=0;i<argc;i++)
    {
        printf("%s\n",argv[i]);
    }
    for(int i=0;env[i]!=NULL;i++)
    {
        printf("%s\n",env[i]);
    }
    return 0;
}
12345678910111213141516

  1. 通过第三方变量environ获取

代码如下:

#include<stdio.h>
int main(int argc,char* argv[])
{
    extern char** environ;
    for(int i=0;environ[i]!=NULL;i++)
    {
        printf("%s\n",environ[i]);
    }
    return 0;
}
12345678910


libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。

10.通过代码如何获取环境变量

常用getenv和putenv函数来访问特定的环境变量。
代码如下:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
    printf("%s\n",getenv("PATH"));
    return 0;
}
12345678

11.环境变量通常是具有全局属性

直接查看,发现没有结果,说明该环境变量根本不存在。

导出环境变量 export MYENV=“hello world”。
再次运行程序,发现结果有了!说明:环境变量是可以被子进程继承下去的!

想象为什么?

代码如下:

#include<iostream>
#include<stdlib.h>
int main()
{
    char* env=getenv("MYENV");
    if(env)
    {
        std::cout<<env<<std::endl;
    }
    return 0;
}
1234567891011

总结

以上就是今天要讲的内容,本文仅仅进程概念后面的一些知识,而进程提供了大量能使我们快速便捷地处理数据的函数和方法。到这里,进程概念就结束了,后序将会有更重要的文章陆续更新,希望大家多多支持!另外如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。

————————————————
版权声明:本文为CSDN博主「森明帮大于黑虎帮」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44918090/article/details/117826771

最后修改:2021 年 08 月 06 日
如果觉得我的文章对你有用,请随意赞赏