当时间片耗完,不管进程正在执行什么代码,都一定会发生上下文切换。
上下文切换这件事是怎么发生的?
是中断触发的。对于内核来说,会不断地触发时钟中断,通过时钟中断就可以触发上下文的切换。当然,除了时钟中断之外,还有IO中断也可以触发中断。
上下文切换的时候,是运行在内核模式的,可以有权限关闭中断的。
状态符 | 对应的状态 | 描述 |
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 | 进程有多个线程 |
+ | 进程在前台进程组中 |
先看一下对应的
#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;
}
helloworld.out 进程为 S+,代表:可以中断的睡眠状态(等待事件发生),并且进程在前台进程组中。
#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;
}
编译运行,看一下进程状态。
helloworld.out 进程为 R+,代表:可执行状态&执行状态,并且进程在前台进程组中。并且占用CPU 100%,因为存在 while(1) 死循环。
#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;
}
helloworld.out 进程为 Rl+,代表:可执行状态&执行状态,为多线程程序,并且进程在前台进程组中。并且占用CPU 196%,为什么会占用196%呢?超过100%,怎么回事?
lscpu ,可以看到有 8个逻辑CPU,但真的有个8个逻辑 CPU吗?
其实并不是真正有8个逻辑CPU,可以这样理解:一个CPU有8个核,这是一个多核CPU。换句话说,这个CPU能力很强,一个CPU可以运行8个进程,运行的这8个进程,可以同时执行;每一个进程在一个CPU上执行。
taskset -c 0 ./helloworld.out ==>运行这个进程,并且让helloworld.out这个进程上的所有线程都在 0号CPU上运行。所以可以推断0号CPU的负载肯定是100%。
即:Linux系统负载平均值(Linux System Load Averages)
该值表示的是一段时间内任务对系统资源需求的平均值(1、5和15分钟)
如果平均值接近0,意味着系统处于空闲状态
如果平均值大于1,意味着系统繁忙,任务需要等待,无法及时执行
如果1min平均值高于5min或者15min平均值,则负载正在增加
如果1min平均值低于5min或者15min平均值,则负载正在减少
notes:如果系统平均负载值 已经大于处理器的数量,那么系统极有可能遇到了性能问题。
通过Linux性能工具观察进程调度
taskset -c 0,1 ./helloworld.out &
首先在0号处理器和1号处理器上,运行helloworld.out进程。
taskset -c 2,3 ./helloworld.out &
首先在2号处理器和3号处理器上,再运行helloworld.out进程。
可以看到两个进程把CPU占满了 400%以上了。那就意味这CPU被耗完了,因为每个CPU占用100%,那么4个CPU就占用400%。所以此时系统处于繁忙的状态了。
然后用top命令看一下:top d 1 => 打印的结果每秒钟刷新一次。
吞吐量:单位时间内的工作总量(越大越好)
处理器资源消耗越多(空闲状态占比越低),吞吐量越大
对于进程调度而言,吞吐量指单位时间处理的进程数量。
延迟:从开始处理任务到结束处理任务所耗费的时间(越短越好)
对于进程而言,延迟即生命周期,指进程从运行到结束所经历的时间
注意:运行 和 执行 不同,运行时间可能很长,但执行时间可能很短
notes:
运行:将应用程序文件变成进程的过程。
执行:进程拿到了CPU资源,可以执行代码了。
先看系统中只有一个进程的情况,这一个进程独占CPU。这种情况没有发生调度。
当系统中有2个进程的时候,这个时候就肯定会发生调度。这个时候某个CPU肯定不会独占CPU了。
这个时候吞吐量为:16.7进程/秒,比上面系统中只有一个进程的吞吐量 10进程/秒 要大很多。这很明显,因为当系统中只有一个进程的时候,很多时候是进程主动放弃CPU资源,进入睡眠态,这个时候其实是对CPU资源的一种浪费。
当系统中再多一个进程时,有三个进程的时候。
每个进程执行时间的总和是 60ms。调度时间片为20ms,因此这三个进程的执行时间总和为 180ms。
可以发现吞吐量相对于2个进程的时候,并没有发生改变。都是 16.7进程/秒。
发生改变的是延迟,延迟从180ms变为 120ms。
假设:每个进程固定执行60ms
则:进程运行结束时
情况 | 吞吐量 | 延迟 |
1线程 | 10进程/秒 | 100ms |
2线程 | 16.7进程/秒 | 120ms |
3线程 | 16.7进程/秒 | 180ms |
4线程 | 16.7进程/秒 | 240ms |
如何提高系统的吞吐量?
可以发现,当系统从一个CPU增加到2个CPU的时候,吞吐量从 16.7进程/秒 增加到了 33.3进程/秒。
理想状态:进程正在执行,并且没有就绪状态的进程。换句话说:就是所有进程都在执行,进程只有就绪状态,没有其他状态。
空闲状态:处理器占用率低,吞吐量低。
繁忙状态:
多个进程同时运行,但存在多个就绪状态进程。
此时,吞吐量很高(可能达到峰值),但总延迟会变长。
如何验证处理器、进程数量、吞吐量之间的关系?