首页 > 系统相关 >【Linux】Linux进程状态与进程优先级

【Linux】Linux进程状态与进程优先级

时间:2024-10-15 19:18:24浏览次数:7  
标签:状态 优先级 操作系统 队列 Linux 进程 CPU

1.前置知识

1.1.并行与并发

并发:表示CPU在同⼀个时间内执⾏多个任务

并⾏:表示多个CPU在同⼀个时间内执⾏各⾃的任务

示意图如下:

1.2.时间片

时间⽚(timeslice),⼜称为“量⼦(quantum)”或“处理器⽚(processor slice)”,是分时操作系统分配给每个正在运⾏的进程微观上的⼀段CPU时间(在 抢占内核中是:从进程开始运⾏直到被抢占的时间)

现代操作系统(如:Windows、Linux、Mac OS X等)允许同时运⾏多个进程。例 如,在打开⾳乐播放器的同时⽤浏览器浏览⽹⻚并下载⽂件。由于⼀台计算机通常只 有⼀个CPU,所以不可能真正地同时运⾏多个任务。这些进程「看起来像」同时运 ⾏,实则是轮番运⾏,由于时间⽚通常很短(在Linux上为5ms-800ms),⽤户基 本不会感觉到。

时间⽚由操作系统内核的调度程序分配给每个进程。⾸先,内核会给每个进程分配相 等的初始时间⽚,然后每个进程轮番地执⾏相应的时间,当所有进程都处于时间⽚耗 尽的状态时,内核会重新为每个进程计算并分配时间⽚,如此往复。

在现代操作系统中,⼤部分的⺠⽤级操作系统均是分时操作系统,这类操作系统的最 ⼤特点就是可以通过多道程序和多任务处理的⽅式让⽤户感觉到「尽管只有⼀个 CPU,但是应⽤可以同时执⾏」

1.2.1多道程序:

多道程序:表示操作系统能够同时管理多个运⾏中的程序。在早期的计算机系统 中,⼀次只能运⾏⼀个程序。当这个程序结束或者因为某种原因暂停时,需要⼿ ⼯⼲预来加载下⼀个程序。⽽多道程序技术允许系统同时保持多个程序在内存 中,并且这些程序可以交替执⾏,这样就提⾼了系统的利⽤率和效率

1.2.2 多任务处理:

多任务处理:表示操作系统能够在同⼀时刻处理多个任务的能⼒。在多任务环境下,操作系统通过快速地切换上下⽂(即保存当前任务的状态并加载新任务的状 态),此处切换的时间依据就是时间⽚,使得多个任务看起来像是同时进⾏的⼀ 样

多道程序强调的是在⼀个系统中同时存在多个程序的能⼒,⽽多任务处理则进⼀步强 调了这些程序能够以⼀种看似同时的⽅式执⾏

与分时操作系统类似的,就是实时操作系统,该类操作系统最⼤的特点就是如果有⼀ 个任务需要执⾏,实时操作系统会⻢上(在较短时间内)执⾏该任务,不会有较⻓的 延时。

2.进程状态

2.1基本介绍

在操作系统中,⼀般会存在⼀个进程状态转换图,例如下图:

整个过程中涉及到五个基本进程状态:

1.创建(new):表示进程创建

2. 运⾏(running):表示进程正在被执⾏

3. 等待(waiting):表示进程正在等待具体事件发⽣,也被称为阻塞状态

4. 就绪(ready):等待被调度器调度执⾏

5. 终⽌(terminated):进程完成执⾏

执⾏过程如下:

当进程创建成功后(new),其状态转化为就绪(ready),等待调度器调度 (scheduler dispatch),调度到当前进程后开始运⾏(running),程序 正常结束退出(exit)向操作系统返回数据,最后终⽌(terminated)

整个过程中涉及到等待和中断,例如:当程序需要进⾏类似于IO或者其他事件 (I/O or event wait)时就会进⼊等待状态,等待IO结束或者其他事件结 束(I/O or event completion)再从等待转换为就绪状态(ready)等待 调度器调度重新进⼊运⾏状态(running)

2.2.等待状态的本质

下⾯重点考虑等待(waiting)状态

进程在被创建之后,此时根据操作系统「先描述,再组织」的管理⽅式,在创建进程 时,会形成对应进程的PCB(例如Linux下的 task_struct ),此时「描述」已经 完成

接着程序进⼊就绪状态,此时操作系统会将进程对应的PCB加载到就绪队列中,在 Linux下是⼀般是使⽤双向链表结构对每⼀个PCB进⾏连接,示意图如下,其中 current 指针表示当前正在被执⾏的进程:

每⼀个CPU需要执⾏进程,就需要⼀个与就绪队列有关的结构,这个结构在Linux下 被称为运⾏队列(具体⻅调度算法部分),⼀般该结构中会存在⼀个指针,该指针指 向正在运⾏的进程,例如下⾯的简化图中的 head 指针指向的就是当前进程,⽽ tail 表示当前闲置(idle)的进程

如果此时程序需要进⾏I/O操作,因为I/O操作速度远⼩于CPU的执⾏速度,在分时 操作系统中,会尽可能提⾼CPU的利⽤率,所以此时当前进程就会被操作系统切换到 指定设备的等待队列(例如键盘),⽽CPU继续执⾏其他存在于就绪队列中的进程。 等待队列与就绪队列结构基本⼀致,也是⼀个双向链表结构。进程进⼊等待队列中链 接后,对应进程状态更改为等待状态,等待I/O操作完成。

等待队列和就绪队列示意图如下:

当I/O操作完成,继续进⼊就绪队列等待被调度执⾏进⼊运⾏状态

综上所述:等待的本质就是进⼊对应设备的等待队列进⾏执⾏,只是不会执⾏对应的 代码,⽽等待和运⾏的切换就是进程PCB在不同的双向链表结构中连接

2.3.swap分区

swap 分区从字⾯意思上来看就是交换分区,该分区⼀般存在于硬盘中,主要⽤于内 存和硬盘之间的资源交换,但是这种交换并不是常规性的,⼀般出现于内存空间严重 不⾜的情况

当内存空间严重不⾜时,操作系统为了保证⾃身的运⾏正常,会将当前正在等待队列 的进程对应的代码和数据放到硬盘的 swap 分区,尽可能减少内存空间的占⽤,这个 过程也被称为「换出」,此时进程的状态也被称为阻塞挂起状态

整个过程中的「换⼊」和「换出」实际上就是利⽤「时间换空间」的思想,因为 swap 分区在硬盘上,所以避免不了交换速度慢,如果出现⼤量的交换,整机的效率 就被⼤⼤拉低

部分操作系统也会存在⼀个属于就绪队列的 swap 分区,同样内存空间严重不 ⾜时,会将处于就绪队列中的部分进程的代码和数据进⾏换⼊和换出

3.Linux进程状态

前⾯操作系统的进程状态只是⼀个⼴泛的状态,每⼀种操作系统的进程状态可能不尽 相同,下⾯主要谈Linux下的进程状态

3.1.Linux进程状态分类

在Linux下,进程状态被分为下⾯的7种:

在Linux下,就绪状态和运⾏状态⼀般不作区分,所以就绪队列也可以认为就 是调度队列,处于调度队列的进程对应的状态即为运⾏状态

3.2.运行状态(Running)与等待状态(Sleeping)

运⾏状态:表示程序正在就绪队列或者正在被CPU执⾏,包括前台运⾏和后台运⾏

在Linux下,通过 ps ajx查看到的状态代号后的+代表正在前台运⾏,可以 使⽤ ctrl+c 终⽌,没有+则表示后台运⾏,不可以使⽤ ctrl+c 终 ⽌,只能使⽤ kill 命令

例如下面一个C语言程序:

#include <stdio.h>
 
int main()
{
    while(1) {
 
    }
    return 0;
}

对应的 Makefile 如下:

TARGET=status
SRC=status.c
 
$(TARGET):$(SRC)
    gcc $^ -o $@
 
.PHONY:clean
clean:
    rm -f $(TARGET)

查看进程效果:

但是,需要注意,如果上⾯程序写为:

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

此时尽管程序在前台执⾏,查看进程时会显示S+,表示在前台等待:

之所以会出现这种情况,是因为 printf 函数本质是在做I/O,⽽因为I/O的速度远 ⼩于CPU的执⾏速度,所以为了保证CPU利⽤率,在做I/O的过程中,当前进程会被 操作系统列⼊到等待队列,⽽CPU继续执⾏其他处于就绪队列的进程

3.3.硬盘等待状态(Disk Sleeping)

硬盘等待状态是Linux系统特有的进程状态,前⾯提到当内存空间严重不⾜时,操作 系统为了保证⾃身在内存中的空间安全,会将部分处于等待队列的进程对应的代码和 数据换⼊ swap 分区

假设在「内存空间严重不⾜」的背景下,内存中的某⼀个进程需要向硬盘写⼊⾮常多 的数据,此时就会进⾏I/O操作,⽽正在做I/O的进程就处于等待队列中,⽽操作系 统此时因为要保证⾃身安全,就会换出⼀部分进程的代码和数据到 swap 分区

假设这个⾏为刚好将正在等待完成⼤量数据I/O的进程对应的代码和数据换⼊到了 swap 分区,当I/O设备向内存中指定的进程反馈相关信息(例如存储空间不⾜) 时,由于该进程的相关代码和数据被换⼊到了 swap 分区,也就没有办法接受I/O的 反馈信息,同时I/O设备也收不到后续的操作指令,这种情况下,就会出现因存储空 间不⾜的问题导致数据丢失。

上⾯的过程中,如果数据是⾮常重要的数据,就会导致严重的损失

Linux系统为了防⽌这个问题的出现,提出了Disk Sleeping,该状态可以保证当 内存空间严重不⾜时,该进程不会被操作系统换出

3.4.停止状态(Stopped)

依旧以上⾯C语⾔的代码为例:

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

在kill指令中,存在两个选项:

代号为18的选项代表进程继续,代号为19的选项表示进程停⽌

在终端中输⼊:

// 停止进程
kill -19 进程PID
 
// 继续进程
kill -18 进程PID

需要注意,使⽤ kill -18 进程PID继续指定进程时,对应的进程状态代号后 ⾯不会带+

就可以停⽌进程,即将指定进程的状态更改为Stopped

例如上⾯的程序,运⾏后执⾏ kill -19 20667:

想要程序继续运⾏,可以使⽤ kill -18 20667:

此时想终⽌程序,就必须使⽤ kill -9 21827⽽不能使⽤ ctrl+c ,停⽌进 程后再按下 ctrl+c 即可

3.5.追踪停止状态(tracing stopped)

对于追踪停⽌状态,可以在 gdb 调试指定代码时程序在断点位置暂停看到,例如调试 前⾯的C语⾔代码,查看对应程序进程可以看到:

所以,调试代码之所以可以让程序停⽌运⾏,下⼀次还可以继续运⾏,本质就是通过 追踪停⽌状态(tracing stopped)控制

3.6.僵尸状态(Zombie)和终止状态(Dead)

每⼀个进程需要执⾏都需要管理者的调度,但是进程是否结束管理者也需要知道,这 ⾥管理者有操作系统和其⽗进程,⽽进程告诉操作系统或其⽗进程⾃⼰正常结束的⽅ 式就是通过进程的退出信息,⼀般退出信息存在进程退出码,0表示进程正常退出, ⽽这⼀过程发⽣时刻所处的状态就是僵⼫状态

当操作系统或⽗进程通过某种⽅式获取了对应的进程的退出信息(例如进程退出时的 退出码),进程状态就会变为终⽌状态,但是如果⼀直不查看进程退出信息,进程会 ⼀直处于僵⼫状态

可以使⽤ echo $?显示最近⼀次进程退出的信息,使⽤其查看 ls 命令在⽆法找到⽂ 件时的返回值以及找到⽂件时的返回值:

未找到文件时:

找到文件时:

这⾥使⽤echo $?查看进程退出码本质就是因为bash是ls命令进程的⽗进 程

这也就可以解释为什么之前在写C语⾔程序时,需要在主函数退出前写上return 0,这⾥的0就是告诉操作系统或其⽗进程当前进程正常退出

3.7.进程退出

进程退出:表示当前进程已经进⼊了僵⼫状态,但不⼀定进⼊了终⽌状态

在Linux中,进程退出的特点是:保留对应进程的PCB,但是会销毁对应进程的代码 和数据,⽽之所以要保留PCB就是因为进程的退出信息依旧存在于对应进程的PCB 中,⽽保留的PCB就会被操作系统管理,⽅便未来查看

在Linux最初的源码中,可以看到部分退出信息,例如退出码exit_code:

struct task_struct {
     
    int exit_code;
     
};

4.僵尸进程

僵⼫进程就是处于僵⼫状态的进程,前⾯提到如果操作系统或者⽗进程没有获取对应 (⼦)进程的退出信息,该进程就会⼀直处于僵⼫状态

例如下⾯的代码:

#include <stdio.h>
#include <unistd.h>
 
int main()
{
    printf("I am parent process, mypid: %d, myppid: %d\n", getpid(), getppid());
 
    // 创建子进程
    pid_t id = fork();
 
    if (id == 0) {
        while(1) {
            printf("I am a child process, my pid = %d, my ppid = %d\n", getpid(), getppid());
            sleep(2);
        }
    }
 
    // 父进程不接收子进程的退出信息
    while (1) {
 
    }
 
    return 0;
}

编译运⾏上⾯的代码,再结束掉对应的⼦进程可以看到下⾯的信息:

其中,PID为26544的为⽗进程,PID为26545的为⼦进程,在上⾯的代码中,结束⼦进程后,⽗进程并没有对⼦进程的退出信息进⾏接收,所以此时⼦进程就会持续 保持僵⼫状态,并且对应的进程会被修饰为,表示「失效的」,此时的 task_struct就会被操作系统保存,但是对应进程的代码和数据就会被操作系统移除

因为处于僵⼫状态时,进程已经退出,所以不可以再使⽤kill指令结束该僵⼫进 程:

进⼊僵⼫状态的进程,默认情况下是不会被任何进程托管,所以⼀旦出现了僵⼫进 程,就表示该进程退出信息没有任何进程接收,这种情况下就会出现内存泄漏问题

在前⾯C/C语⾔层⾯提到的内存泄漏表示开辟的空间在没有使⽤的情况下,程序运 ⾏时没有释放导致持续占⽤空间,但是这种内存泄漏最⼤的特点就是程序⼀旦结束, 该空间就会被释放。所以语⾔层⾯的内存泄漏在常驻内存的进程上影响最⼤,但是不 论如何,还是要处理这种内存泄漏问题

此处进程的内存泄漏表示处于僵⼫状态的进程,因为进程退出信息没有被接受,导致 其 task_struct ⼀直存在于内存中,但是这种内存泄漏是⽆法在程序结束后被操作 系统⾃动释放。所以为了避免出现这种内存泄漏问题,需要对每⼀个进程的退出信息 进⾏接收

5.孤儿进程

前⾯提到的是⼦进程先结束,⽗进程没有结束并且不接受⼦进程的退出信息,⼦进程 就处于僵⼫状态,如果反过来先结束⽗进程,再结束⼦进程就会出现⼦进程变为孤⼉ 进程,编译运⾏前⾯的代码,结束对应⽗进程结果如下:

孤⼉进程最⼤的特点就是其PPID 变为1,并且为后台运⾏,所以不可以使⽤Ctrl + c 退出

可以使⽤ top 指令查看 PID 为1对应的进程:

可以看到孤⼉进程会被系统托管

6.进程优先级

进程优先级,表示优先被CPU执⾏的进程的等级,在Linux下,进程优先级等级越 ⼩,优先级越⾼,被优先执⾏的概率越⼤

之所以需要进程优先级,是因为⼤部分的⺠⽤电脑都只有⼀个CPU,但是进程的个数 可以有很多,这种情况下就需要进程对CPU资源的抢夺,为了保证部分进程能以更⼤ 优势抢到CPU资源,就需要进程优先级

在Linux下,可以使⽤下⾯的指令查看到当前⽤户执⾏的进程对应的优先级:

ps -la

在Linux下,进程优先级由两个值进⾏控制,⼀个是 PRI (priority),另⼀个是 NI (nice), PRI 代表进程启动时系统⾃动分配的优先级,⽽NI代表优先级修正 值,这个值的范围是 [-20, 19]

在计算Linux进程的优先级时,使⽤公式:PRI = 初始 PRI + NI值

例如,启动下⾯的C语⾔程序:

#include <stdio.h>
 
int main()
{
    while(1) {
 
    }
    return 0;
}

使⽤p s -la查看效果:

因为初始的PRI为80, NI 为0,所以最终的PRI = 80 + 0 = 80

在Linux下,不可以修改 PRI ,但是可以通过修改NI从⽽改变进程优先级。使⽤ top 指令,输⼊r,再输⼊已启动进程的 PID ,再输⼊对应的NI值即可修改

如果将NI修正为-6,则会出现下⾯的结果:

因为默认的PRI值为80,⽽此时 NI 值为-6,所以最终的PRI = 80 - 6 = 74

如果此时将NI修正为15,则会出现下⾯的结果:

默认的PRI值为80,⽽此时的 NI 值为15,所以最终的PRI = 80 + 15 = 95

可以看到,尽管开始修改了 PRI 为74,下⼀次再更改 NI 值时,计算PRI使⽤的还是 初始的PRI+NI

进程优先级并不⽀持频繁修改,在Linux下,可能修改1次或者2次左右后再修 改NI就需要使⽤ root 权限

在实际中,进程优先级⼀般很少去修改,尽管可以在程序中使⽤函数更改进程优先级 或者使⽤命令修改进程优先级

标签:状态,优先级,操作系统,队列,Linux,进程,CPU
From: https://blog.csdn.net/2301_79582015/article/details/142946328

相关文章

  • PetaLinux工程的常用命令——petalinux-create
    petalinux-create:此命令创建新的PetaLinux项目或组件。注:有些命令我没用过,瞎翻译有可能会翻译错了,像是和fpgamanager相关的部分。用法: petalinux-create[options]<-t|--type<TYPE><-n|--name<COMPONENT_NAME>必须参数: -t,--type<TYPE>      ......
  • 从0开始linux点灯
    从0开始linux点灯......
  • Linux的LVM与磁盘配额
    Linux的LVM与磁盘配额一.LVM1.什么是LVMLogicalVolumeManager逻辑卷管理能够在保持现有数据不变的情况下,动态调整磁盘容量,从而提高磁盘管理的灵活性/boot分区用于存放引导文件,不能基于LVM创建解释:就是将多个不同的物理卷组合在一起形成卷组,再从卷组中划分区域形成......
  • Fileheader 1.13.1 - ColorLinux
    为了在控制台打印彩色内容而设计的头文件早就想封了,今天实现一下普通输出这是第一版写的,因为觉得不好就弃用,但是并没有删,在某些场合可能会用的方便点这一版定义了一个color_print()其定义为#definecolor_print(x)printf("%s",((string)""+(x)+color.NONE).c_str())可以......
  • Linux系统创建新用户后使用新用户登录输入回退键无法回退显示^H怎么解决
    现象使用新建用户远程ssh登录后在页面输入命令后无法是回退键删除,删除会显示^H原因没有对应的bash使用默认的bash为/bin/sh修复方法手动加载bash$bash使用管理员修改文件修改bash#liuym:x:1002:1002::/home/liuym:/bin/bash在创建用户的时候指定bash......
  • linux开启端口监听
    在Linux中,您可以使用socat或nc(netcat)工具来监听特定的端口。以下是使用这两种工具的简单示例:使用socat:安装socat(如果尚未安装):  sudoapt-getinstallsocat开启一个监听在TCP端口8080上的socat实例:  socatTCP-LISTEN:8080,forkEXEC:/bin/bash使......
  • 【Linux】基础IO(文件系统)
     ......
  • Linux各个发行版防火墙处理
    CentOS防火墙处理:1、systemctlstatusfirewalld.service查看防火墙状态如果出现active(running),说明防火墙是开启的。2、systemctlstopfirewalld.service关闭防火墙3、systemctlstatusfirewalld.service再次查看防火墙状态如果出现disavtive(dead),说明防火墙已经关闭......
  • linux分区名字改变导致系统无法正常启动
    linux增加磁盘linux添加磁盘的时候出现如下图所示问题出现这个问题的原因是因为原来的的磁盘被分成了几个分区,分区标识符是sda1、sda2。然后再/etc/fstab文件中配置这两个分区的了开启挂载。但是当插入新磁盘的时候,原来的磁盘分区名称发生了变化,导致开机的时候读取/etc/fstab......
  • linux抓取docker内部服务网络包
    docker内应用抓包分析1、获取docker的网络信息dockernetworkls2、查看docker网络的网段dockernetworkinspectxxx3、找到docker的虚拟网卡ifconfig对网卡的端口进行抓包处理tcpdump-ibr-0c8b954bbb83port9962-w./test1.captcpdump-ieth0port9401-w./......