多核调度预备知识

进程调度的本质

任务/进程切换

  1. 即:上下文切换,内核对处理器上执行的进程进行切换
  2. “上下文” 指:寄存器的值
  3. “上下文切换”指:
    1. 将寄存器的值保存到内存中(进程被剥夺处理器,停止执行)
    2. 将另一组寄存器的值从内存中加载到寄存器(调度下一个进程执行)

  1. 对于处理器来说,随着时间的流逝,是在不停的执行进程。
  2. 在微观上轮流执行不同的进程,在宏观上,我们的感觉是进程在并行执行。
  3. 在进程切换的时候,会发生上下文改变,上下文改变的是寄存器的值。

当时间片耗完,不管进程正在执行什么代码,都一定会发生上下文切换。

多核调度预备知识_第1张图片

上下文切换怎么发生的?

上下文切换这件事是怎么发生的?

是中断触发的。对于内核来说,会不断地触发时钟中断,通过时钟中断就可以触发上下文的切换。当然,除了时钟中断之外,还有IO中断也可以触发中断。

上下文切换的时候,是运行在内核模式的,可以有权限关闭中断的。

多核调度预备知识_第2张图片

详解Linux进程状态 (ps au)

状态符 对应的状态 描述
R TASK_RUNNING 可执行状态&执行状态
S TASK_INTERRUPTIBLE 可以中断的睡眠状态(等待事件发生)
D TASK_UNINTERRUPTIBLE 不可中断的睡眠状态(IO等待)
T TASK_STOPPED 暂停状态(信号暂停)
t TASK_TRACED 跟踪状态(调试器暂停)
Z TASK_DEAD 退出状态(僵尸进程)
X TASK_DEAD 退出状态

状态符 描述
< 进程优先级比较高
N 进程优先级比较低
L 进程有页面被锁定在内存中
I 内核空闲线程
s 会话首进程
l 进程有多个线程
+ 进程在前台进程组中

先看一下对应的

demo1:

#include 
#include 
#include 
#include 
#include 


static void* thread_entry(void* arg)
{
    while(1);

    return arg;
}

static void make_thread()
{
    pthread_t tid =0;

    pthread_create(&tid,NULL,thread_entry,NULL);
}

int main(void)
{
    printf("pid = %d,ppid =%d,pgid = %d\n",getpid(),getppid(),getpgrp());

    printf("hello world\n");
    

    while(1)
    {
        sleep(1);
    }

    return 0;
}

多核调度预备知识_第3张图片

helloworld.out 进程为 S+,代表:可以中断的睡眠状态(等待事件发生),并且进程在前台进程组中。

demo2:

#include 
#include 
#include 
#include 
#include 


static void* thread_entry(void* arg)
{
    while(1);

    return arg;
}

static void make_thread()
{
    pthread_t tid =0;

    pthread_create(&tid,NULL,thread_entry,NULL);
}

int main(void)
{
    printf("pid = %d,ppid =%d,pgid = %d\n",getpid(),getppid(),getpgrp());

    printf("hello world\n");


    while(1); //demo2 改为不休眠

    return 0;
}

编译运行,看一下进程状态。

多核调度预备知识_第4张图片

多核调度预备知识_第5张图片

helloworld.out 进程为 R+,代表:可执行状态&执行状态,并且进程在前台进程组中。并且占用CPU 100%,因为存在 while(1) 死循环。

demo3:

#include 
#include 
#include 
#include 
#include 


static void* thread_entry(void* arg)
{
    while(1);

    return arg;
}

static void make_thread()
{
    pthread_t tid =0;

    pthread_create(&tid,NULL,thread_entry,NULL);
}

int main(void)
{
    printf("pid = %d,ppid =%d,pgid = %d\n",getpid(),getppid(),getpgrp());

    printf("hello world\n");

    make_thread(); //在这个地方调用make_thread(),此时这个程序就是一个多线程程序了,有2个线程在运行。

    while(1); //

    return 0;
}

多核调度预备知识_第6张图片

helloworld.out 进程为 Rl+,代表:可执行状态&执行状态,为多线程程序,并且进程在前台进程组中。并且占用CPU 196%,为什么会占用196%呢?超过100%,怎么回事?

CPU占有率为什么会超过100%?

lscpu ,可以看到有 8个逻辑CPU,但真的有个8个逻辑 CPU吗?

多核调度预备知识_第7张图片

其实并不是真正有8个逻辑CPU,可以这样理解:一个CPU有8个核,这是一个多核CPU。换句话说,这个CPU能力很强,一个CPU可以运行8个进程,运行的这8个进程,可以同时执行;每一个进程在一个CPU上执行。

继续做实验

taskset -c 0 ./helloworld.out ==>运行这个进程,并且让helloworld.out这个进程上的所有线程都在 0号CPU上运行。所以可以推断0号CPU的负载肯定是100%。

多核调度预备知识_第8张图片

小结

  1. 对于Linux来说,调度的基本单位是线程,而不是进程。
  2. 并且调度不同的线程,在不同的CPU上执行,这里的不同CPU指的是逻辑CPU。
  3. 逻辑CPU可以这样理解:一个物理CPU中的某个核。现在的CPU都是多核的,其中某一个核可以理解为一个逻辑CPU。

怎么样进一步去观察CPU的负载?

细说CPU空闲状态

  1. 处理器上电后,开始一直不停的向下执行指令。
  2. 当系统中没有进程时候,会执行一个 "不执行任何操作"的空闲进程(IDLE进程)。
  3. 空闲进程的职责:执行特殊指令使处理器进入休眠状态(低功耗状态)
  4. 空闲状态是一种暂态,但凡出现就绪进程,空闲状态立即结束。

Linux 性能工具简介

  1. PS -- 查看进程运行时数据(ps au)
  2. top -- Linux整体性能监测工具(类似任务管理器)
  3. sar -- Linux活动情况报告(系统性能分析工具)

Linux系统平均负载

即:Linux系统负载平均值(Linux System Load Averages)

该值表示的是一段时间内任务对系统资源需求的平均值(1、5和15分钟)

        如果平均值接近0,意味着系统处于空闲状态

        如果平均值大于1,意味着系统繁忙,任务需要等待,无法及时执行

                如果1min平均值高于5min或者15min平均值,则负载正在增加

                如果1min平均值低于5min或者15min平均值,则负载正在减少

详解   sar -q

  1. runq-sz :执行队列的长度
  2. plist -sz : 运行中的任务(进程&线程)总数
  3. ldavg-1 :最近1min系统平均负载
  4. ldavg-5 :最近5min系统平均负载
  5. ldavg-15 :最近15min系统平均负载

notes:如果系统平均负载值 已经大于处理器的数量,那么系统极有可能遇到了性能问题。

系统调度观察实验

通过Linux性能工具观察进程调度

  1. 单处理器运行过程
  2. 多处理器运行过程

taskset -c 0,1 ./helloworld.out &

首先在0号处理器和1号处理器上,运行helloworld.out进程。

taskset -c 2,3 ./helloworld.out &

首先在2号处理器和3号处理器上,再运行helloworld.out进程。

多核调度预备知识_第9张图片

可以看到两个进程把CPU占满了 400%以上了。那就意味这CPU被耗完了,因为每个CPU占用100%,那么4个CPU就占用400%。所以此时系统处于繁忙的状态了。

然后用top命令看一下:top d 1 => 打印的结果每秒钟刷新一次。

多核调度预备知识_第10张图片

系统调度核心性能指标

吞吐量:单位时间内的工作总量(越大越好)

        处理器资源消耗越多(空闲状态占比越低),吞吐量越大

        对于进程调度而言,吞吐量指单位时间处理的进程数量。

延迟:从开始处理任务到结束处理任务所耗费的时间(越短越好)

        对于进程而言,延迟即生命周期,指进程从运行到结束所经历的时间

        注意:运行 和 执行 不同,运行时间可能很长,但执行时间可能很短

notes:

运行:将应用程序文件变成进程的过程。

执行:进程拿到了CPU资源,可以执行代码了。

吞吐量计算一

先看系统中只有一个进程的情况,这一个进程独占CPU。这种情况没有发生调度。

多核调度预备知识_第11张图片

吞吐量计算二

当系统中有2个进程的时候,这个时候就肯定会发生调度。这个时候某个CPU肯定不会独占CPU了。

多核调度预备知识_第12张图片

这个时候吞吐量为:16.7进程/秒,比上面系统中只有一个进程的吞吐量 10进程/秒 要大很多。这很明显,因为当系统中只有一个进程的时候,很多时候是进程主动放弃CPU资源,进入睡眠态,这个时候其实是对CPU资源的一种浪费。

吞吐量计算三

当系统中再多一个进程时,有三个进程的时候。

每个进程执行时间的总和是 60ms。调度时间片为20ms,因此这三个进程的执行时间总和为 180ms。

多核调度预备知识_第13张图片

可以发现吞吐量相对于2个进程的时候,并没有发生改变。都是 16.7进程/秒。

发生改变的是延迟,延迟从180ms变为 120ms。

小结

假设:每个进程固定执行60ms

则:进程运行结束时

情况 吞吐量 延迟
1线程 10进程/秒 100ms
2线程 16.7进程/秒 120ms
3线程 16.7进程/秒 180ms
4线程 16.7进程/秒 240ms
  1. 处理器的能力是由硬件设计决定,吞吐量存在一个上限。
  2. 当吞吐量未达到上限,进程的延迟取决于进程自身。
  3. 当吞吐量到达上限,随着进程数量增加,总延迟增加,但平均延迟不变。

思考

如何提高系统的吞吐量?

  1. 提高处理器的能力
  2. 提高处理器的数量

增加到2个CPU,吞吐量该如何变化?

多核调度预备知识_第14张图片

可以发现,当系统从一个CPU增加到2个CPU的时候,吞吐量从 16.7进程/秒 增加到了 33.3进程/秒。

现实中的系统

理想状态:进程正在执行,并且没有就绪状态的进程。换句话说:就是所有进程都在执行,进程只有就绪状态,没有其他状态。

空闲状态:处理器占用率低,吞吐量低。

繁忙状态:

        多个进程同时运行,但存在多个就绪状态进程。

        此时,吞吐量很高(可能达到峰值),但总延迟会变长。

思考?

如何验证处理器、进程数量、吞吐量之间的关系?

你可能感兴趣的:(#,Linux,系统/文件编程,linux,多核调度)