一,线程迁移和负载均衡
Linux系统在多核CPU和SMP系统上有完善的负载均衡支持。在SMP系统中,每个CPU的核都有一个迁移线程守护程序migration(一般是系统最高优先级139,实时99),以实现执行资源平衡作业。当我们调用sched_setaffinity系统调用将一个线程从源CPU核迁移到目标CPU核,并且该线程正在运行或处于TASK_WAKING状态,则迁移作业将打包到源CPU的迁移线程的工作队列中,然后,迁移线程将被唤醒以完成迁移工作。
在对多核系统的进程或者线程进行性能分析时,有一些需要分析和注意的点,如:线程的优先级,线程对CPU核的占用情况,线程调度等等。
二,通过ps命令查看线程的调度和运行核
在linux系统中,执行线程任务的核用P或者PSR表示,即Last Used CPU(SMP),表示最近一次执行线程的CPU core核。
1,ps -eLF
下面的命令可以持续监测某一个核调度运行的所有线程,本实例为排序显示核5调度的所有线程。
while true; do ps -eLF | awk '{print $2,$3,$4,$9}' | sort -n -k 4 | grep " 5$"; done
2,ps命令指定查看某些特定的项
ps H -eotid,pid,ppid,pri,rtprio,psr,state,%cpu,%mem,cmd
- 备注,STATE(S):代表该进程目前的状态,主要的状态有:
- R:该进程正在运行
- S:该进程正在休眠,但可被某些信号(signal)唤醒
- D:无法中断的休眠状态(通常为IO进程)
- T:该进程已经停止
- Z:僵死状态,该进程应该已经终止,但是其父进程却无法正常的终止它,造成zombie(疆尸)程序的状态
- W:等待状态,等待内存的分配
- <:高优先级的进程
- N:低优先级的进程
三,通过top命令查看线程的调度和运行核
top H -d 1 [-p pid]
通过top命令查看线程的(调度)信息,也可以通过-p指定进程或者线程号查看某一个进程的所有线程,或者某个线程的信息。
下图是在查看KVM虚拟机性能分析时的一个实例:top H -d 1 -p 10166
在top命令里面,有几个小的命令说明一下以备忘
1)查看所有核的CPU使用情况:在top界面上按数字“1”;再次按“1”可以收起
2)高亮运行的线程:在top界面上按字母“b”会高亮R状态的线程。
3)top字段管理。在top界面上按字母“f”,会调出字段管理界面。上下箭头可以移动,空格键可以用于选择或者取消字段,注意一定是空格键而不是回车键进行选择和取消,已经选中显示的列会变为黑体。
对调度和性能分析需要关注的几个主要指标:
P :查看线程被调用的CPU核
PR:线程优先级
%CPU:线程占用CPU的实时情况
%MEM:内存占用情况,同一个进程内的线程一样
WCHAN:线程睡眠所在的函数,一般都是内核的函数
4)按某一字段排序:按“f”进入字段管理界面后,先用上下键移到要排序的字段位置,然后按字母“s”,然后“q”退回到top的主界面,此时看到的就是排序后的显示。
在top命令行也可以通过-o选定排序的字段。
如 top H -d 1 -o COMMAND,需要注意大小写。
排序有一些快捷键,如“N”对线程排序,“M”对内存排序,“P”对CPU排序。“R”反序。
排序对于分析一些性能问题其实非常重要,可以聚焦在某些线程上。
通用排序方法:可以在top主界面下按字母"x",会高亮显示当前排序的列。x和b可以联合使用。
四、pidstat周期查看进程/线程使用cpu情况
pidstat命令可以周期查看(最小周期1秒)一个或者多个线程(或者进程)的CPU使用情况。如果线程在多个核上调度(或者绑定在多个CPU核上),多个cpu核都会都显示出来,可以显示每个线程在用户空间,系统空间分别占用的CPU百分比,以及在哪个核上运行。
显示多个线程用“,”隔开,命令:pidstat -p 15380,13000 -t 1
五、CPU亲和性(亲和力 CPU Affinity)
有了前面的感性认识,现在终于来到重点,在SMP多处理系统中CPU亲和力。
CPU Affinity 是一种调度属性(scheduler property),是指进程(或线程)在某个给定CPU核上尽量长时间运行而不被迁移到其他核的倾向性。它可以将一个线程(进程)"绑定" 到一个或一组CPU核上。在SMP(Symmetric Multi-Processing对称多处理)架构下,Linux调度器(scheduler)会根据CPU affinity的设置让指定的进程运行在"绑定"的CPU上,而不会在别的CPU上运行。
在多核计算机中,每个CPU核都有自己的缓存,一旦线程(或进程)被操作系统调度到其他核上,整个缓存都需要重建,缓存的命中率降低,系统性能下降。在对延迟敏感的系统中,合理地分配线程(或进程)与CPU的亲和性,能够有效提升系统性能,降低延迟。
CPU亲和性可以分为软亲和性和硬亲和性。
软亲和性
线程(或进程)保持在一个CPU核上持续运行而尽量不被迁移到其他核。但是在如负载过重等不得已的情况下,依然会执行迁移操作。
Linux内核的进程调度器天然就支持软亲和性。所以无需做额外配置。
硬亲和性
将线程(或进程)强行绑定在某个核上运行,不允许被调度到其他核上。
如果想让特定进程或线程独占某一或某些CPU,需要做三件事:
- 隔离CPU核
硬亲和性支持首先需要将核隔离出来,被隔离出来的核不会再被操作系统的调度器使用,也就是说其他的CPU就算再忙,隔离出来的CPU也是空闲的。这样可以避免其它线程运行在被隔离的CPU上,只有你的程序会跑在这个(些)指定的CPU核上,并且没有其他任务会来抢占你的CPU。,这些被移除的CPU称为"isolated" CPU。
被隔离的CPU虽然Linux调度器不会让线程run在上面,但是仍会收到interrupt!对于性能和延迟要求高的线程,还需要做中断隔离。
隔离CPU核需要在Linux系统启动前指定,打开/etc/default/grub,修改参数GRUB_CMDLINE_LINUX,增加isolcpus=0(假设我们仅仅隔离CPU的第0核),如果要隔离多个核,只需要在核间用逗号","隔开即可。如我们隔离20,29-31四个核,那么增加isolcpus=20,29,30,31或者isolcpus=20,29-31。执行update-grub让配置生效,重启系统后这些核就不会被Linux系统的调度器调度了。
- 绑定CPU核
硬亲和性支持还需要将用户的线程(进程)绑定在隔离的核上,使其不会被Linux系统调度到其他空闲的核上而造成CPU上下文切换,导致程序性能下降。
绑定CPU核有两种方式,一种是用CPU命令taskset,另外一种就是变成程序时通过API sched_setaffinity(sched_getaffinity)在写程序时指定。
taskset
# 命令行形式
taskset [options] mask command [arg]...
taskset [options] -p [mask] pidPARAMETER
mask : cpu亲和性,当没有-c选项时, 其值前无论有没有0x标记都是16进制的,当有-c选项时,其值是十进制的。
command : 命令或者可执行程序
arg : command的参数
pid : 进程ID,可以通过ps/top/pidof等命令获取OPTIONS
-a, --all-tasks (旧版本中没有这个选项)
这个选项涉及到了linux中TID的概念,他会将一个进程中所有的TID都执行一次CPU亲和性设置,TID就是Thread ID,他和POSIX中pthread_t表示的线程ID完全不是同一个东西。Linux中的POSIX线程库实现的线程其实也是一个进程(LWP),这个TID就是这个线程的真实PID。
-p, --pid
操作已存在的PID,而不是加载一个新的程序
-c, --cpu-list
声明CPU的亲和力使用数字表示而不是用位掩码表示. 例如 0,5,7,9-11.
-h, --help
display usage information and exit
-V, --version
output version information and exit
USAGE1) 使用指定的CPU亲和性运行一个新程序
taskset [-c] mask command [arg]...
使用CPU0运行ls命令显示/etc/init.d下的所有内容
taskset -c 0 ls -al /etc/init.d/2) 显示已经运行的进程的CPU亲和性 taskset -p pid [root@localhost ~]# taskset -cp 1393 pid 1393's current affinity list: 0-7 3) 改变已经运行进程的CPU亲和力 taskset -p[c] mask pid 更改具体某一进程(或 线程)CPU亲和性 taskset -p hexadecimal mask PID/LWP 上面1393号线程可以在0~7号CPU之间允许,现在设置掩码0x11(二进制0001 0001),表示可以在0~4号CPU上允许。 [root@localhost ~]# taskset -p 0x11 1393 pid 1393's current affinity mask: ff pid 1393's new affinity mask: 11 [root@localhost ~]# taskset -p 1393 pid 1393's current affinity mask: 11 [root@localhost ~]# taskset -cp 1393 pid 1393's current affinity list: 0,4为具体某一进程(或 线程)CPU亲和性指定一组范围
使用-c参数
[root@localhost ~]# taskset -cp 20,29-31 1393
pid 1393's current affinity list: 0,4
pid 1393's new affinity list: 20,29-31
[root@localhost ~]# taskset -cp 1393
pid 1393's current affinity list: 20,29-31PERMISSIONS
一个用户要设定一个进程的CPU亲和性,如果目标进程是该用户的,则可以设置,如果是其他用户的,则会设置失败,提示 Operation not permitted.当然root用户没有任何限制.
任何用户都可以获取任意一个进程的CPU亲和性。
sched_setaffinity(sched_getaffinity)
常用的C的代码段如下:
#include <sched.h>
cpu_set_t mask; // cpu核的位掩码
CPU_ZERO(&mask); // 将CPU核列表置空
CPU_SET(20, &mask); // 将需要绑定的cpu号设置在mask中
CPU_SET(29, &mask); // 将需要绑定的cpu号设置在mask中
CPU_SET(30, &mask); // 将需要绑定的cpu号设置在mask中
CPU_SET(31, &mask); // 将需要绑定的cpu号设置在mask中
if (sched_setaffinity(pthread_self(), sizeof(mask), &mask) == -1) // CPU绑定
{
printf("failed to set affinity.\n");
}
else
{
printf("succeeded to set affinity.\n");
}
- 中断隔离
绑定所有的interrupts到非隔离的CPU上,避免被隔离的CPU收到interrupt而被消耗,造成绑定在其上的线程的调度。
IRQ(Interrupt request)是硬件级别的服务请求,IRQ都有一个亲和度属性smp_affinity,smp_affinity决定允许哪些CPU核心处理该IRQ。当前Linux的某一特定IRQ的亲和度值储存在/proc/irq/IRP_NUMBER/smp_affinity文件中,只有ROOT权限用户可见和可操作。该值是一个十六进制位掩码(hexadecimal bit-mask),代表着系统的所有CPU核,跟taskset和CPU_SET一样,每一个bit对应一个CPU核。
命令cat /proc/interrupts可以看到所有设备的interrupts信息,第一列即为IRP_NUMBER。
命令cat /proc/irq/32/smp_affinity可以看到IRQ号为32的亲和度,默认值为f(跟CPU的核数紧密相关,此处f表示4核CPU),代表这个IRP能被所有CPU接受处理。
命令echo 1 >/proc/irq/32/smp_affinity把IRQ号为32的亲和度值设为1,代表这个IRP仅能被CPU0接受处理。echo 1fefffff > /proc/irq/32/smp_affinity,表示32号中断不送到CPU的20,29-31核。
其他类推,我们可以据要求任意绑定IRQ到CPU。不过,系统中仍有一部分中断没有被绑定,例如:Single function call interrupts, Local timer interrupts等等。
六、结语
对于性能和延迟要求高的线程,以及如何提升CPU资源紧张的系统,本文仅从Linux系统的调度方面进行了简单的描述,通过上述的方法和一些原则进行调整和优化,也许可以逐步使系统和线程达到最佳的平衡。当然线程优先级的合理分配,系统资源的平衡调度,对于性能要求高的系统的整体性能,也有很大的影响!