【Linux】基础:进程概念

摘要:本文的主要目的是理解进程的概念。文章通过介绍进程的管理方式来引入进程的概念,从而提出进程的理解——PCB与程序。然后对PCB内容进行详细介绍,并同时介绍了关于进程的基本操作,如进程的创建等。最后还补充了关于进程相关的概念,为了后面的学习打下基础。


文章目录

  • 【Linux】基础:进程概念
    • 一. 进程概念
    • 二. task_struct
      • 2.1 概述
      • 2.2 具体内容
    • 三. 进程查看
    • 四. 进程创建
      • 4.1 fork()示例
      • 4.2 fork()理解
      • 4.3 fork()返回值
    • 五. 进程状态
      • 5.1 概述
      • 5.2 具体状态
      • 5.3 小结
    • 六. 进程优先级
      • 6.1概述
      • 6.2 查看优先级
      • 6.3 修改优先级
    • 七. 概念补充

一. 进程概念

所谓进程,通俗而言可以说是程序的一个执行实例,正在执行的程序等,在部分教材中也会对其简单称为加载到内存中的程序,在内核观点上,是担当分配系统资源(CPU时间,内存)的实体,这些表述是没问题的,但如果需要详细的了解进程,可以从进程的创建到管理上来理解进程这一概念。本文将由Linux进行举例,从进程管理的方式上来理解进程概念。

在编写程序的过程中,以往对数据处理和使用的一般经验是首先对数据进行描述,再对数据通过描述来管理,进程也一样,任何进程形成时,操作系统都会为该进程创建PCB,也叫做进程控制块,在Linux上使用结构体task_struct来描述。PCB就是描述进程的数据结构,其中包括了关于该进程的所有属性,而操作系统通过控制这些进程控制块来控制进程。也就是说 ,在操作系统上,进程除了是加载内存中的程序外,还包括了自身的进程控制块PCB。而关于PCB与Linux上的task_struct关系或者区别,可以说是PCB是一个统称,而task_struct是PCB的一个实体,是在Linux上对PCB的具体实现,关于task_struct的具体内容,将会在后文提及。

在此还会通过指令ps ajx | head -1 && ps axj | grep "程序名字"来查看进程,从而更进一步的理解如何管理进程,与进程这一概念。

Makefile文件

proc:proc.cgcc -o $@ $^
.PHONY:cleanrm -f proc
#include<stdio.h>
#include<unistd.h>int main(){while(1){printf("hello world!\n");sleep(2);}return 0;
}

通过编写上述C语言程序,编译并运行,可以看到,在输入指令后,除了进程在运行程序的过程,还有关于许多关于该正在运行的程序的信息,这些信息就是储存在task_struct中。

[lht@VM-12-7-centos Blogs_process]$ ps ajx | head -1 && ps ajx | grep "proc"PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
1395513 1395743 1395743 1395513 pts/7    1395743 S+    1002   0:00 ./proc
1395202 1395780 1395779 1395202 pts/6    1395779 S+    1002   0:00 grep --color=auto proc

也就是说,可以确定进程就是程序文件内容与相关进程相关的数据结构,这些数据结构有操作系统自己创建,包含了进程内部的所有属性信息。图示如下:

将其结合操作系统与CPU进行理解总结,在进程创建时,操作系统会创建对应的PCB,通过这些PCB来记录该进程的信息,并且还会通过某种方式来表示自身程序,这些内容一般是存在内存中。在CPU工作过程中,会并发处理许多进程,而这些进程的控制会通过某种数据结构进行管理,存储在缓冲区或内存中。对于操作系统与CPU在调度或者运行这些进程过程中,是不管程序是什么内容的,一切管理方式都是通过相应的进程控制块完成的,总的来说是与程序无关,与进程控制块强相关

补充:程序与进程的区别

通过上述对进程的理解,可以很好的区分进程与程序的区别,实际上进程除了包括自身程序外,还需要将程序通过操作系统拷贝到内存中,并创建相应的数据结构,给予CPU与操作系统进行管理与运行。

二. task_struct

2.1 概述

在介绍task_struct之前,先对PCB进行介绍,并说明两者间的关系,PCB是进程控制块,其作用是描述进程,可以理解为进程信息的集合,而task_struct就是PCB的一种

在Linux中描述进程的结构体叫做task_struct,是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。其主要信息包括:

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

当然task_struct不止这些内容,还有许多内容,还有更多信息就在此不再赘述了,在该段内容中,会先对几个点进行简单介绍,对部分内容会在后文具体讲解。

2.2 具体内容

标识符——pid查看

标识符是描述本进程的唯一标示符,用来区别其他进程,可以通过系统提供的库函数进行查看,其主要函数getpid与获取父节点标识符getppid,具体内容可以通过man指令来查看,示例如下:

#include<stdio.h>
#include<unistd.h>int main(){while(1){printf("hello world! --> pid: %d  --> ppid: %d \n",getpid(),getppid());sleep(2);}return 0;
}

在运行过程中如果想结束进程可以输入指令kill -9 pid,来杀死进程,其输出结果与进程查看结果如下:

[lht@VM-12-7-centos Blogs_process]$ ./proc
hello world! --> pid: 1399606  --> ppid: 1395513
hello world! --> pid: 1399606  --> ppid: 1395513
Killed
[lht@VM-12-7-centos Blogs_process]$ ps ajx | head -1 && ps ajx | grep "proc"PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
1395513 1399606 1399606 1395513 pts/7    1399606 S+    1002   0:00 ./proc
1395202 1399841 1399840 1395202 pts/6    1399840 S+    1002   0:00 grep --color=auto proc

可以看出,输出结果的标识符与父节点标识符与命令查看的结果是一致的。

状态码与退出码

状态码可以表示任务的状态,退出码将会保留关于退出信息等内容,状态码的主要内容会在后文主要介绍。在此对退出码进行实验示例,如何查看相应程序的退出码。对以下程序进行编写并编译运行。

#include<stdio.h>
#include<unistd.h>int main(){printf("hello world! --> pid: %d  --> ppid: %d \n",getpid(),getppid());return 123;
}

输入指令echo $?来查看最近执行的命令的退出码,同时输入多几遍,发现以下结果:

[lht@VM-12-7-centos Blogs_process]$ ./proc
hello world! --> pid: 1401403  --> ppid: 1395513
[lht@VM-12-7-centos Blogs_process]$ echo $?
123
[lht@VM-12-7-centos Blogs_process]$ echo $?
0
[lht@VM-12-7-centos Blogs_process]$ echo $?
0

可以发现当proc程序退出时,返回的退出码为123,而对于echo的命令退出码为0,因此连续打印两次0。

优先级

由于系统资源是有限的,因此不能每个进程同时并行运行,为了提高效率与防止饥饿问题,采取并发的方法,既然是并发就需要一定的顺序运行程序,因此需要优先级来进行排。该内容将会在后文详细介绍,在此区别优先级与权限的关系。优先级与权限:优先级决定的是进程执行的顺序,而权限决定的是进程能不能执行,两者具有本质的区别。

上下文数据

在上文提及,CPU在运行进程的过程中,一般是并发进行的,因此在多进程并发的情况下,需要有数据结构管理在运行状态的进程PCB,并有操作系统完成调度。由于需要进行调度操作,就需要对进程进行切换。在进程运行过程中,会产生许多中间数据,这些数据可能暂时保存在寄存器中,但寄存器资源有限,因此需要不断保护与恢复。下面将会其中过程进行具体分析。

更具体来说,进程的代码并不是所有的都可以在较短时间内运行完毕的,但对于CPU而言,为了让用户感受到多个进程同时运行,会让CPU对进程进行来回切换,一般情况下切换的方式是规定每个进程单次运行的时间片。在进程运行过程中,会有大量的临时数据,这些数据存放到寄存器中,暂时由CPU寄存器保存。可以通过VS调试窗口,进行简单的查看。

可CPU只有一套,寄存器的资源是有限的,而且寄存器是离CPU最近的存储设备,造价很高,因此无法多造来解决问题。可是虽然寄存器资源有限,但是对于临时数据来说,是针对于每个进程的,是相对于进程本身的,因此可以通过保存临时数据到对应的进程自身,来完成进程切换临时数据的存储,总的来说就是保护上下文与恢复上下文,可以暂时理解为存到了PCB中(实际上并不准确,只是简略一下)。

程序计数器、内存指针、I/O状态信息、记账信息:略

三. 进程查看

针对进程的查看在主要有两种方式,第一种在上文已经提及,可以采取指令ps ajx | head -1 && ps axj | grep "程序名字" | grep -v grep来查看,对于head -1其作用显示标题,grep "程序文字"表示过滤程序名,grep -v grep由于grep指令也是一个进程,因此我们将其也过滤掉。示例如下:

[lht@VM-12-7-centos Blogs_process]$ ./proc
hello world! --> pid: 1494052  --> ppid: 1493840
hello world! --> pid: 1494052  --> ppid: 1493840
hello world! --> pid: 1494052  --> ppid: 1493840
hello world! --> pid: 1494052  --> ppid: 1493840
......
[lht@VM-12-7-centos Blogs_process]$ ps axj | head -1 && ps axj | grep proc | grep -v grepPPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
1493840 1494052 1494052 1493840 pts/6    1494052 S+    1002   0:00 ./proc

第二种方式是通过 /proc系统文件夹查看,示例如下:

[lht@VM-12-7-centos Blogs_process]$ ls /proc
1        1489696  1726     23      38   737        cmdline      key-users     self
10       1489734  1747     24      39   738        consoles     kmsg          slabinfo
11       1489770  178      25      4    742        cpuinfo      kpagecgroup   softirqs
1161     1489781  179      26      40   743        crypto       kpagecount    stat
1170     1489822  18       260086  41   744        devices      kpageflags    swaps
1189     1491775  180      27      43   746        diskstats    loadavg       sys
12       1491982  181      28      498  748        dma          locks         sysrq-trigger
1216     1492472  1812     29      499  76         driver       mdstat        sysvipc
1217     1493197  1818     3       502  767        execdomains  meminfo       thread-self
13       1493203  1819     30      503  768        fb           misc          timer_list
14       1493709  182      31      504  819        filesystems  modules       tty
1486516  1493840  184      32      531  823        fs           mounts        uptime
1489404  1494181  185      33      532  9          interrupts   mtrr          version
1489599  1495101  1856     330     6    982        iomem        net           vmallocinfo
1489619  1495442  186      331     642  987        ioports      pagetypeinfo  vmstat
1489622  1495819  2        34      673  acpi       irq          partitions    zoneinfo
1489628  15       20       35      696  buddyinfo  kallsyms     sched_debug
1489629  16       2205052  36      698  bus        kcore        schedstat
1489684  17       2205176  37      735  cgroups    keys         scsi

如果需要查看相应PID的进程可以在后续添加PID,示例如下:

[lht@VM-12-7-centos Blogs_process]$ ./proc
hello world! --> pid: 1496170  --> ppid: 1493840
......
[lht@VM-12-7-centos Blogs_process]$ ls /proc/1496170 -al
total 0
......
-rw-r--r--   1 lht  lht  0 Nov 10 10:46 coredump_filter
-r--r--r--   1 lht  lht  0 Nov 10 10:46 cpu_resctrl_groups
-r--r--r--   1 lht  lht  0 Nov 10 10:46 cpuset
lrwxrwxrwx   1 lht  lht  0 Nov 10 10:46 cwd -> /home/lht/code/Blogs_process
-r--------   1 lht  lht  0 Nov 10 10:46 environ
lrwxrwxrwx   1 lht  lht  0 Nov 10 10:46 exe -> /home/lht/code/Blogs_process/proc
dr-x------   2 lht  lht  0 Nov 10 10:46 fd
......

其中exe表示和可执行程序的对应关系 ,cwd当前工作目录。

四. 进程创建

在程序中,我们可以使用fork()来创建子进程,以pid_t作为返回值,其通过man指令查看结果如下:

4.1 fork()示例

编写以下C语言程序,编译并运行:

#include<stdio.h>
#include<unistd.h>int main(){fork();printf("hello world! --> pid: %d  --> ppid: %d \n",getpid(),getppid());return 0;
}
[lht@VM-12-7-centos Blogs_process]$ ./proc
hello world! --> pid: 1500710  --> ppid: 1493840
hello world! --> pid: 1500711  --> ppid: 1500710

可以发现,对于子程序拥有着属于自己PID,并且PPID为其父进程PID,根据实验结果是相对应的,而对于父进程的PPID,可以通过指令进行查看,可以发现其进程为bash进程

[lht@VM-12-7-centos Blogs_process]$ ps axj | head -1 && ps axj | grep 1493840PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
1489734 1493840 1493840 1493840 pts/6    1500710 Ss    1002   0:00 /usr/bin/bash --init-file
......
1493840 1496170 1496170 1493840 pts/6    1500710 T     1002   0:00 ./proc

4.2 fork()理解

创建进程无论是通过执行可执行文件,执行指令,或者通过fork创建,对于操作系统来说是没有差别的,当进程创建时,会创建出进程相关的数据结构以及进程的代码与数据。可是对于fork来说,创建了进程,那对应的代码与数据来源于哪里呢?实际上在默认的情况下会继承父进程的代码与数据,而内核数据结构task_struct也会以父进程作为模板,初始化子进程的task_struct

对于父子进程的代码来说,子进程与父进程的代码是共享的,这些代码是不可以修改,并且有且只有一份。

可是数据却不一样,在默认情况下,对数据不修改的情况下,数据也是共享的,可是当需要修改数据时,就会发生写时拷贝,这样就可以保持每个进程的数据的独立性,同时使进程也保持独立性。其示意简图如下:

需要补充的是,关于进程的独立性,在后续章节会介绍关于进程的控制,只有进程的独立性,相互之间互不干扰,才可以完成各个进程的职能工作。

4.3 fork()返回值

在man手册中,可以查看到返回类型为pid_tfork()的返回值对于父进程与子进程是不一样的,如果创建子进程失败,会返回一个负数,如果成功,返回父进程子进程的pid,返回子进程0。通过对于返回值的控制,就可以完成父子进程进行不一样的事物。查看父子进程返回值示例如下:

[lht@VM-12-7-centos Blogs_process]$ cat proc.c
#include<stdio.h>
#include<unistd.h>int main(){pid_t re = fork();printf("hello world! --> pid: %d  --> ppid: %d || retrun: %d \n",getpid(),getppid(),re);return 0;
}
[lht@VM-12-7-centos Blogs_process]$ ./proc
hello world! --> pid: 1517845  --> ppid: 1500608 || retrun: 1517846
hello world! --> pid: 1517846  --> ppid: 1 || retrun: 0

有人可能会疑问为什么会出现两个返回值?对于fork()为一个库函数,在return之前,已经进行完成了函数创建子进程的核心功能,在进行return时,由于返回值是数据,在上文提到,对于父子进程发生数据改变修改时,会进行写时拷贝,在返回返回值时,就是进行了写时拷贝

对于返回值内容设计问题,由于父进程可以对应多个子进程,对于子进程只能有一个父进程,因此在对于返回值来说,要告诉父进程自身是哪一个子进程,才会有父进程返回子进程pid,子进程返回0的设计。

在程序中,可以通过不同的返回值,让父子进程进行不一样的事务,示例如下:

#include<stdio.h>
#include<unistd.h>int main(){pid_t id = fork();if(id == 0){printf("I am child. pid = %d ,ppid = %d \n",getpid(),getppid());}else if(id>0){printf("I am parent. pid = %d ,ppid = %d \n",getpid(),getppid());}else{perror("failed!");}return 0;
}
[lht@VM-12-7-centos Blogs_process]$ ./proc
I am parent. pid = 1520148 ,ppid = 1500608
I am child. pid = 1520149 ,ppid = 1520148

关于子进程与父进程的运行顺序,是不确定的,这是需要调度器进行调度。由于上面程序较短,因此可能会呈现一定的顺序,可是可能进程是需要长时间进行的,需要不断切换,完成不同事务,其运行顺序将会由优先级等属性决定,有调度器调度。

五. 进程状态

5.1 概述

进程状态储存在进程的PCB中,其意义方便于操作系统快速判断进程,完成特定功能,比如调度,其本质上是一种分类。一般情况下会分为以下几种状态,由于进程状态是适用于各个操作系统的,因此先通过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 * const task_state_array[] = {"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
  • R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
  • S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
  • D磁盘休眠状态(Disk sleep):时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
  • T停止状态(stopped):可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
  • X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

5.2 具体状态

R:R状态表示运行状态,运行状态并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。在CPU运行过程中,一般情况下是并发进行的,由于CPU进行有限,一般无法进行并行处理,因此使用数据结构,比如运行队列,组织各个进程的PCB,通过调度器调度。这些在运行队列中的进程,就是出于运行状态的进程。在此,对proc程序代码进行修改,并观察相应的运行状态,需要注意的是不可以增添休眠或者输出指令,因为这会导致使用其他资源,使进程出于其他状态,示例如下:

#include<stdio.h>
#include<unistd.h>int main(){while(1){}return 0;
}
[lht@VM-12-7-centos Blogs_process]$ ps axj | head -1 && ps axj | grep procPPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
1535422 1540127 1540127 1535422 pts/5    1540127 R+    1002   0:00 ./proc
1535341 1540134 1540133 1535341 pts/4    1540133 S+    1002   0:00 grep --color=auto proc

S 、D为休眠状态,可以概述为当完成某种任务时,由于任务条件不具备,需要进程进行某种等待。这里的任务条件,指的不是CPU而是其他外设或者其他系统资源,比如网卡、显卡等设备,实际上进程不仅仅会等待CPU资源,还会等待其他资源,进程在运行过程中,有可能因为运行的需要,可能会出现在不同的队列里,在不同队列中就会处于不同的状态。

#include<stdio.h>
#include<unistd.h>int main(){while(1){printf("pid = %d ,ppid = %d \n",getpid(),getppid());sleep(2);}return 0;
}
[lht@VM-12-7-centos Blogs_process]$ ps axj | head -1 && ps axj | grep  procPPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
1500608 1532886 1532886 1500608 pts/8    1532886 S+    1002   0:00 ./proc
1493840 1532958 1532957 1493840 pts/6    1532957 S+    1002   0:00 grep --color=auto proc

通过运行之前的proc程序,可以查看进程状态,为休眠状态,实际上这个程序中包含了打印操作,由于io操作所需要的时间比CPU多很多,因此进程需要不断等待,进入休眠状态,因此展示出来的是等待状态。

而S与D的区别,在于是否可以终止,S状态可以称为浅睡眠,为可中断睡眠,对应的是D称为不可中断睡眠,深度睡眠,如果进程出于D状态,不可被杀死

最后,补充一下挂起与唤醒的概念,在运行状态中的PCB放入到等待队列中,可以将该状态称为挂起等待(阻塞),从等待队列放到运行队列中,被CPU调度,可以称为唤醒进程。

T:表示暂停状态,即彻底的暂停,可以通过发送 SIGSTOP 信号给进程来停止(T)进程,这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。这里的信号可以通过kill指令实现,分别为kill -19 id为暂停,kill -18 pid表示继续,以下通过运行文章开头编写好的程序proc,使用kill发送信号来进行展示,注意观察状态的变化,示例如下:

@VM-12-7-centos Blogs_process]$ ps axj | head -1 && ps axj | grep procPPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
1535422 1535855 1535855 1535422 pts/5    1535855 S+    1002   0:00 ./proc
1535341 1535929 1535928 1535341 pts/4    1535928 S+    1002   0:00 grep --color=auto proc
[lht@VM-12-7-centos Blogs_process]$ kill -19 1535855
[lht@VM-12-7-centos Blogs_process]$ ps axj | head -1 && ps axj | grep procPPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
1535422 1535855 1535855 1535422 pts/5    1535422 T     1002   0:00 ./proc
1535341 1536032 1536031 1535341 pts/4    1536031 S+    1002   0:00 grep --color=auto proc
[lht@VM-12-7-centos Blogs_process]$ kill -18 1535855
[lht@VM-12-7-centos Blogs_process]$ ps axj | head -1 && ps axj | grep procPPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
1535422 1535855 1535855 1535422 pts/5    1535422 S     1002   0:00 ./proc
1535341 1536160 1536159 1535341 pts/4    1536159 S+    1002   0:00 grep --color=auto proc

补充:kill在上述实验中,提到用kill指令传递信号,在此列举查询信号的方法使用kill -l指令,示例如下:

[lht@VM-12-7-centos Blogs_process]$ kill -l1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX
[lht@VM-12-7-centos Blogs_proce

t:表示追踪状态,平时打的断点就是处于这种状态,在该状态可以临时查看各种数据,在此不再赘述。

X:死亡状态,这个状态只是一个返回状态,不会在任务列表里看到这个状态,其作用为回收进程资源,包括相关的数据结构,以及相应的代码和数据。

Z:僵尸状态,在释放资源前需要先进入僵尸状态,将信息写入PCB提供给父节点进行识别,因此都是先进入僵尸状态再进入死亡状态。出现僵尸状态的原因在于需要辨别进程死亡原因,并将进程退出的信心进行返回,给父进程。当然相应进程如果没有对应检查进行检测或回收,该进程就会进入僵尸态,将事态是需要数据结构维护的,本身也是占用内存的,如果长期不回收,会出现系统内存泄露的问题。而如何要避免将会在其他章节上进行讲解,本文不再赘述。

以下通过实验的形式,对Z状态进行观察,首先编写以下代码,编译运行并书写监控命令行脚本,进行观察,示例如下:

脚本命令

 while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep; sleep 1; echo "===========================================================" ;  done

C语言代码

#include<stdio.h>
#include<unistd.h>int main(){pid_t id = fork();if(id == 0){while(1){printf("I am child,running!\n");sleep(2);}}else{printf("I am father,nothing!\n");sleep(10);}return 0;
}

在运行程序的过程中,通过指令将子进程杀死,观察程序的状态变化。

[lht@VM-12-7-centos Blogs_process]$ while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep; sleep 5; echo "===========================================================" ;  donePPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
1555612 1560405 1560405 1555612 pts/5    1560405 S+    1002   0:00 ./proc
1560405 1560406 1560405 1555612 pts/5    1560405 S+    1002   0:00 ./proc
===========================================================PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
1555612 1560405 1560405 1555612 pts/5    1560405 S+    1002   0:00 ./proc
1560405 1560406 1560405 1555612 pts/5    1560405 Z+    1002   0:00 [proc] <defunct>

补充:孤儿进程

所谓孤儿进程,就是父进程提前退出。子进程退出,进入Z状态,父进程先退出,子进程就称之为”孤儿进程”,孤儿进程被1号init进程领养,由init进程回收。在此重新编写程序,使得父进程提前退出,示例如下:

#include<stdio.h>
#include<unistd.h>int main(){pid_t id = fork();if(id == 0){while(1){printf("I am child,running!\n");sleep(2);}}else{printf("I am father,nothing!\n");sleep(10);exit(1);}return 0;
}

查看进程并观察进程状态变化

[lht@VM-12-7-centos Blogs_process]$ while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep; sleep 5; echo "===========================================================" ;  donePPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
1555612 1563371 1563371 1555612 pts/5    1563371 S+    1002   0:00 ./proc
1563371 1563372 1563371 1555612 pts/5    1563371 S+    1002   0:00 ./proc
===========================================================PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND1 1563372 1563371 1555612 pts/5    1555612 S     1002   0:00 ./proc

补充:前台与后台

在进程状态时,细心的可以发现有些状态会有+号有些没有,+号的表示了前台运行,没有+号的表示后台运行如果在前台运行,我们可以使用各种快捷键进行操作,比如Ctrl + c来是进程结束,而后台运行不行。后台运行是通过后面添加符号&来操作,当需要结束时,需要使用命令用kill命令 ,于前文所言kill -9 pid 杀死进程,kill -19 pid 暂停,进程kill -18 pid 继续进程。

5.3 小结

了解在Linux的优先级后,不妨可以返回看操作系统一般情况下的状态转移,运行态就是在Linux系统中的R状态,不一定CPU在运行,但出于运行队列中,阻塞态为S或者D状态或者T状态,可能处于等待系统资源状态,或者处于等待被唤醒的状态,而终止态表示了X或者Z状态,从僵尸状态转换为终止状态,图示如下:

六. 进程优先级

6.1概述

cpu资源分配的先后顺序,就是指进程的优先权(priority),优先权高的进程有优先执行权利。配置进程优先权对多任务环境的Linux很有用,可以改善系统性能,还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。而设置进程优先级的原因,就是因为资源太少了,无法做到每个进程同时运行,因此会对其重要性进行先后判断,并进行特定的调度规则

6.2 查看优先级

查看进程优先级可以使用指令ps -l或者ps -al来查看,书写实验代码,并执行指令,示例如下:

#include<stdio.h>
#include<unistd.h>int main(){pid_t id = fork();if(id == 0){while(1){printf("I am child,running!\n");sleep(2);}}else{while(1){printf("I am father,nothing!\n");sleep(10);}}return 0;
}
[lht@VM-12-7-centos Blogs_process]$ ps -al
F S   UID     PID    PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1002 1579168 1579054  0  80   0 -  1091 hrtime pts/5    00:00:00 proc
1 S  1002 1579169 1579168  0  80   0 -  1091 hrtime pts/5    00:00:00 proc
0 R  1002 1579233 1579110  0  80   0 - 11368 -      pts/0    00:00:00 ps

可以观察到几个重要信心:

  • UID : 代表执行者的身份
  • PID : 代表这个进程的代号
  • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI :代表这个进程可被执行的优先级,其值越小越早被执行
  • NI :代表这个进程的nice值

其中PRI与NI是我们理解优先级的重要概念。对于PRI来说,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小,进程的优先级别越高。而NI其表示进程可被执行的优先级的修正数值,它会根据实际情况对进程优先级进行一些修正,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行。关系为PRI(New) = PRI(old) + NI,而在Linux下调整进程优先级,就是调整进程nice值。nice其取值范围是-20至19,一共40个级别。需要强调一点的是,进程的nice值不是进程的优先级,不是一个概念,但是进程nice值会影响到进程的优先级变化。

6.3 修改优先级

修改优先级可以通过系统接口或者通过nice指令来修改,例如:

[lht@VM-12-7-centos Blogs_process]$ man sched_get_priority_max
[lht@VM-12-7-centos Blogs_process]$ man nice
[lht@VM-12-7-centos Blogs_process]$ man renice

可是由于优先级的特殊性,是不建议使用修改的,因为只有操作系统才是最懂优先级的,但是在此进行一个简单的优先级修改的实验:首先我们查看各个进程的优先级,可以发现一般情况下进程的优先级为80。通过top指令进入修改,输入r,再输入pidnice值完成修改,输入q可以退出。例如此处输入了10的nice值进行修改,在此查看优先级后发现,进程的优先级变成了90。

[lht@VM-12-7-centos Blogs_process]$ ps -al
F S   UID     PID    PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 T  1002 1579168 1579054  0  80   0 -  1091 -      pts/5    00:00:00 proc
1 T  1002 1579169 1579168  0  80   0 -  1091 -      pts/5    00:00:00 proc
0 R  1002 1582782 1579110  0  80   0 - 11368 -      pts/0    00:00:00 ps
......
[lht@VM-12-7-centos Blogs_process]$ ps -al
F S   UID     PID    PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 T  1002 1579168 1579054  0  80   0 -  1091 -      pts/5    00:00:00 proc
1 T  1002 1579169 1579168  0  90  10 -  1091 -      pts/5    00:00:00 proc
......

不过当输入超过nice值范围的值时,会默认为最大/最小的nice值,例如修改为100或-100,nice值最后表示都为20或-20。

补充:nice为什么设置在较小的范围

nice较小范围内,可以使得优先级顺序不出现绝对顺序的问题。因为优先级再怎么设置也是一种相对的优先顺序,不会出现绝对的顺序,否则会出现“饥饿问题”,关键是使用调度器使得每个进程较为均匀的使用到CPU的资源。

七. 概念补充

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

补充:

  1. 代码将会放到:Linux_Review: Linux博客与代码 (gitee.com) ,欢迎查看!
  2. 欢迎各位点赞、评论、收藏与关注,大家的支持是我更新的动力,我会继续不断地分享更多的知识!

【Linux】基础:进程的概念相关推荐

  1. 【Linux】进程的概念

    一.进程的概念 程序:代码+数据 进程:代码+数据+堆栈+PCB 程序:为了完成特定功能的一系列指令的有序集合 进程:一个具有一定独立功能的程序在数据集合上的一次动态执行过程 每个进程都有自己的状态 ...

  2. linux中544进程,Linux基础--进程管理及其基本命令

    本文主要讲解Linux中进程管理的基本命令使用方法. 1. top命令 作用: 动态显示进程状态 格式:top [options] 常用选项: -d: 后面可以接秒数,就是整个程序画面更新的秒数, 默 ...

  3. 【Linux】进程的概念(1)

    冯诺依曼体系结构 我们常见的计算机,如笔记本.我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系. 关于冯诺依曼,必须强调几点: 这里的存储器指的是内存 不考虑缓存情况,这里的CPU能且只能对内存 ...

  4. linux的进程命令,Linux基础进程命令详解

    进程有关基础命令 一.进程定义 1.进程就是CPU未完成的工作,而且它是其中运行着一个或多个线程的地址空间和这些线程所需要的系统资源. 二.Linux系统进程和一些有关进程的命令 1.ps命令ps   ...

  5. linux的进程和作业控制实验报告,Linux基础--进程管理和作业控制

    用于进程管理和作业控制的主要命令如下图所示. 大部分命令都是对进程运行状态进行查询和监控的,而工作中与进程相关的也基本就是监控进程运行状态,检查影响系统运行瓶颈在哪里,然后进行调忧工作.因此本文只着重 ...

  6. Linux基础操作-分区概念

    开启Linux系统前添加一块大小为20G的SCSI硬盘 开启系统,右击桌面,打开终端 为新加的硬盘分区,一个主分区大小为10G,剩余空间给扩展分区,在扩展分区上划分两个逻辑分区,大小各5G 进入分区工 ...

  7. linux基础——进程的退出及资源回收

    文章目录 进程的退出 returen 和 exit 代码示例 注册进程结束调用函数 代码示例(on_exit): atexit 代码示例(atexit) 进程资源的回收 代码示例 wait回收进程资源 ...

  8. 【看表情包学Linux】进程的概念 | 进程控制块 PCB | 父进程与子进程 | 进程 ID | task_struct

  9. 从零了解进程(操作系统定位,进程的概念,特征,虚拟地址)

    目录 操作系统的定位 进程的概念 如何描述进程? 如何组织进程? 为什么要引入进程? 进程的特征 1.pid 2.内存指针 3.文件描述符 4.进程调度的相关属性 (1)进程的状态 (2)优先级 (3 ...

  10. linux基础知识大纲

    1 . Linux 操作系统概述 Linux操作系统的发展过程.创始人.GNU计划等 源于UNIX: 得益于GNU计划: 借助internet得以壮大: 推动了自由软件.开源软件的发展 1991年底, ...

最新文章

  1. 带无线驱动的linux版本,怎么在Linux里查询无线网卡的驱动程序版本
  2. Kubernetes — 基于层级命名空间的多租户隔离
  3. 数组array的一些用法
  4. Codeforces 681C:Heap Operations
  5. CentOS7.5 使用二进制程序部署Kubernetes1.12.2(三)
  6. mysql远程连接错误10038--navicat for mysql (10038)
  7. Python案例:GUI用户注册信息管理系统
  8. centos7 nginx php5.4,详解CentOS7.0下Nginx+PHP5.4+MySQL5.5+Memcached+Redis的架构部署
  9. POJ3178 计算几何+DP
  10. Animation中的scale、rotate、translate、alpha
  11. BZOJ4477: [Jsoi2015]字符串树
  12. smartplant license manager issue
  13. go技术文章梳理(2018)
  14. GB/T2659-2000,ISO 3166-1:1997,ISO 3166-1:2006国家和地区代码列表(已整理)
  15. 【宝藏系列】推荐几款免费的视频转文字字幕的软件
  16. 有道翻译js逆向解析
  17. 万网域名注册、域名解析与备案流程
  18. 电荷耦合器件架构及工作原理
  19. JAVA工具_PinyinConv
  20. 【整理】MFC单文档程序窗口大小的设置

热门文章

  1. 批量转换用户的共存模式--Skype for Business to Teams
  2. 调试技巧:如何以数组的方式查看一个指针
  3. 鸽哒im即时通讯源码加教程
  4. 解决matplotlib绘制图片时plt.savefig()后图片全黑的问题
  5. 修改const指针所指向的值
  6. Microbiome:西农韦革宏团队简化合成菌群通过激活ISR防治黄芪根腐病
  7. 2019腾讯游戏客户端面试
  8. 基本函数依赖和候选键_[总结]关系数据库设计基础(函数依赖、无损连接性、保持函数依赖、范式、……)...
  9. Excel中制作目录的3种方法,你了解几种?
  10. java基于微信小程序的校园二手闲置商品交易平台 uinapp 计算机毕业设计