观察发现4核CPU,只有第1个核心(CPU#0)非常忙,其他都处于idle状态。
不了解Linux是如何调度的,但目前显然有优化的余地。除了处理正常任务,CPU#0还需要处理每秒网卡中断。因此,若能将CPU#0分担的任务摊派到其他CPU核心上,可以预见,系统的处理能力将有更大的提升。
两个名词
SMP (Symmetrical Multi-Processing):指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构。SMP意为对称多处理系统,内有许多紧耦合多处理器,这种系统的最大特点就是共享所有资源。另外与之相对立的标准是MPP (Massively Parallel Processing),意为大规模并行处理系统,这样的系统是由许多松耦合处理单元组成的,要注意的是这里指的是处理单元而不是处理器。每个单元内的CPU都有自己私有的资源,如总线、内存、硬盘等。在每个单元内都有操作系统和管理数据库的实例复本。这种结构最大的特点在于不共享资源 [更多...]
CPU affinity:中文唤作“CPU亲和力”,是指在CMP架构下,能够将一个或多个进程绑定到一个或多个处理器上运行。[更多...]
一、在Linux上修改进程的“CPU亲和力”
在Linux上,可以通过 taskset 命令进行修改,安装taskset工具。
# yum install util-linux-ng
对运行中的进程,文档上说可以用下面的命令,把CPU#1 #2 #3分配给PID为2345的进程:
# taskset -cp 1,2,3 2345
但我尝试没奏效,于是我关掉了MySQL,并用taskset将它启动:
# taskset -c 1,2,3 /etc/init.d/mysql start
对于其他进程,也可如此处理(nginx除外,详见下文)。之后用top查看CPU的使用情况,原来空闲的#1 #2 #3,已经在辛勤工作了。
二、配置nginx绑定CPU
刚才说nginx除外,是因为nginx提供了更精确的控制。
在conf/nginx.conf中,有如下一行:
worker_processes 1;
这是用来配置nginx启动几个工作进程的,默认为1。而nginx还支持一个名为worker_cpu_affinity的配置项,也就是说,nginx可以为每个工作进程绑定CPU。我做了如下配置:
worker_processes 3;
worker_cpu_affinity 0010 0100 1000;
这里0001、0010、0100、1000是掩码,分别代表第1、2、3、4颗cpu核心。
重启nginx后,3个工作进程就可以各自用各自的CPU了。
三、刨根问底
- 如果自己写代码,要把进程绑定到CPU,该怎么做?可以用sched_setaffinity函数。在Linux上,这会触发一次系统调用。
- 如果父进程设置了affinity,之后其创建的子进程是否会有同样的属性?我发现子进程确实继承了父进程的affinity属性。
四、Windows?
在Windows上修改“CPU亲和力”,可以通过任务管理器搞定。
* 个人感觉,Windows系统中翻译的“处理器关系”比“CPU亲和力”容易理解点儿
—————–
进行了这样的修改后,即使系统负载达到3以上,不带缓存打开blogkid.net首页(有40多次查询)依然顺畅;以前一旦负载超过了1.5,响应就很慢了。效果很明显。
linux taskset命令详解
SYNOPSIS
taskset [options] [mask | list ] [pid | command [arg]...]
OPTIONS
-p, --pid
operate on an existing PID and not launch a new task
-c, --cpu-list
specifiy a numerical list of processors instead of a bitmask.
The list may contain multiple items, separated by comma, and
ranges. For example, 0,5,7,9-11.
-h, --help
display usage information and exit
-V, --version
output version information and exit
# taskset -p -c 0 16380 或者 taskset -cp 0 16380
# ps -e -o psr,pid,cmd |grep 29165 查验
psr : 进程当前被分配的处理器
---------------------------------------------------------------
使CPU隔离,专门用于某个服务或进程:
为客户提供一个有2个逻辑CPU计算能力的一个客户机。要求CPU资源独立被占用,不受宿主机中其他客户机的负载水平的影响。为了满足这个需求,可以分为如下两个步骤来实现。
第一步,启动宿主机时隔离出两个逻辑CPU专门供一个客户机使用。在Linux内核启动的命令行加上“isolcpus=”参数,可以实现CPU的隔离,让系统启动后普通进程默认都不会调度到被隔离的CPU上执行。例如,隔离了cpu2和cpu3的grub的配置文件如下:
title Red Hat Enterprise Linux Server (3.5.0)
root (hd0,0)
kernel /boot/vmlinuz-3.5.0 ro root=UUID=1a65b4bb-cd9b-4bbf-97ff-7e1f7698d3db isolcpus=2,3
initrd /boot/initramfs-3.5.0.img
系统启动后,在宿主机中检查是否隔离成功,命令行如下:
[root@jay-linux ~]# ps -eLo psr | grep 0 | wc -l
106
[root@jay-linux ~]# ps -eLo psr | grep 1 | wc -l
107
[root@jay-linux ~]# ps -eLo psr | grep 2 | wc -l
4
[root@jay-linux ~]# ps -eLo psr | grep 3 | wc -l
4
[root@jay-linux ~]# ps -eLo ruser,pid,ppid,lwp,psr,args | awk ‘{if($5==2) print $0}’
root 10 2 10 2 [migration/2]
root 11 2 11 2 [kworker/2:0]
root 12 2 12 2 [ksoftirqd/2]
root 245 2 245 2 [kworker/2:1]
[root@jay-linux ~]# ps –eLo ruser,pid,ppid,lwp,psr,args | awk ‘{if($5==3) print $0}’
root 13 2 13 3 [migration/3]
root 14 2 14 3 [kworker/3:0]
root 15 2 15 3 [ksoftirqd/3]
root 246 2 246 3 [kworker/3:1]
从上面的命令行输出信息可知,cpu0和cpu1上分别有106和107个线程在运行,而cpu2和cpu3上都分别只有4个线程在运行。而且,根据输出信息中cpu2和cpu3上运行的线程信息(也包括进程在内),分别有migration进程(用于进程在不同CPU间迁移)、两个kworker进程(用于处理workqueues)、ksoftirqd进程(用于调度CPU软中断的进程),这些进程都是内核对各个CPU的一些守护进程,而没有其他的普通进程在cup2和cpu3上运行,说明对其的隔离是生效的。
另外,简单解释一下上面的一些命令行工具及其参数的意义。ps命令显示当前系统的进程信息的状态,它的“-e”参数用于显示所有的进程,“-L”参数用于将线程(LWP,light-weight process)也显示出来,“-o”参数表示以用户自定义的格式输出(其中“psr”这列表示当前分配给进程运行的处理器编号,“lwp”列表示线程的ID,“ruser”表示运行进程的用户,“pid”表示进程的ID,“ppid”表示父进程的ID,“args”表示运行的命令及其参数)。结合ps和awk工具的使用,是为了分别将在处理器cpu2和cpu3上运行的进程打印出来。
第二步,启动一个拥有2个vCPU的客户机并将其vCPU绑定到宿主机中两个CPU上。此操作过程的命令行如下:
#(启动一个客户机)
[root@jay-linux kvm_demo]# qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
VNC server running on ‘::1:5900’
#(查看代表vCPU的QEMU线程)
[root@jay-linux ~]# ps -eLo ruser,pid,ppid,lwp,psr,args | grep qemu | grep -v grep
root 3963 1 3963 0 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
root 3963 1 3967 0 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
root 3963 1 3968 1 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 –daemonize
#(绑定代表整个客户机的QEMU进程,使其运行在cpu2上)
[root@jay-linux ~]# taskset -p 0×4 3963
pid 3963′s current affinity mask: 3
pid 3963′s new affinity mask: 4
#(绑定第一个vCPU的线程,使其运行在cpu2上)
[root@jay-linux ~]# taskset -p 0×4 3967
pid 3967′s current affinity mask: 3
pid 3967′s new affinity mask: 4
#(绑定第二个vCPU的线程,使其运行在cpu3上)
[root@jay-linux ~]# taskset -p 0×8 3968
pid 3968′s current affinity mask: 4
pid 3968′s new affinity mask: 8
#(查看QEMU线程的绑定是否生效,如下的第5列为处理器亲和性)
[root@jay-linux ~]# ps -eLo ruser,pid,ppid,lwp,psr,args | grep qemu | grep -v grep
root 3963 1 3963 2 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
root 3963 1 3967 2 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
root 3963 1 3968 3 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 –daemonize
#(执行vCPU的绑定后,查看在cpu2上运行的线程)
[root@jay-linux ~]# ps -eLo ruser,pid,ppid,lwp,psr,args | awk ‘{if($5==2) print $0}’
root 10 2 10 2 [migration/2]
root 11 2 11 2 [kworker/2:0]
root 12 2 12 2 [ksoftirqd/2]
root 245 2 245 2 [kworker/2:1]
root 3963 1 3963 2 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
root 3963 1 3967 2 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
#(执行vCPU的绑定后,查看在cpu3上运行的线程)
[root@jay-linux ~]# ps –eLo ruser,pid,ppid,lwp,psr,args | awk ‘{if($5==3) print $0}’
root 13 2 13 3 [migration/3]
root 14 2 14 3 [kworker/3:0]
root 15 2 15 3 [ksoftirqd/3]
root 246 2 246 3 [kworker/3:1]
root 3963 1 3968 3 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
由上面的命令行及其输出信息可知,CPU绑定之前,代表这个客户机的QEMU进程和代表各个vCPU的QEMU线程分别被调度到cpu0和cpu1上。使用taskset命令将QEMU进程和第一个vCPU的线程绑定到cpu2,将第二个vCPU线程绑定到cpu3上。绑定之后,即可查看到绑定的结果是生效的,代表两个vCPU的QEMU线程分别运行在cpu2和cpu3上(即使再过一段时间后,它们也不会被调度到其他CPU上去)。
对taskset命令解释一下,此处使用的语法是:taskset -p [mask] pid 。其中,mask是一个代表了处理器亲和性的掩码数字,转化为二进制表示后,它的值从最低位到最高位分别代表了第一个逻辑CPU到最后一个逻辑CPU,进程调度器可能将该进程调度到所有标为“1”的位代表的CPU上去运行。根据上面的输出,taskset运行之前,QEMU线程的处理器亲和性mask值是0×3(其二进制值为:0011),可知其可能会被调度到cpu0和cpu1上运行;而运行“taskset -p 0×4 3967”命令后,提示新的mask值被设为0×4(其二进制值为:0100),所以该进程就只能被调度到cpu2上去运行,即通过taskset工具实现了vCPU进程绑定到特定的CPU上。
上面命令行中,根据ps命令可以看到QEMU的线程和进程的关系,但如何查看vCPU与QEMU线程之间的关系呢?可以切换(“Ctrl+Alt+2”快捷键)到QEMU monitor中进行查看,运行“info cpus”命令即可(还记得3.6节中运行过的“info kvm”命令吧),其输出结果如下:
(qemu) info cpus
* CPU #0: pc=0xffffffff810375ab thread_id=3967
CPU #1: pc=0xffffffff812b2594 thread_id=3968
从上面的输出信息可知,客户机中的cpu0对应的线程ID为3967,cpu1对应的线程ID为3968。另外,“CPU #0”前面有一个星号(*),是标识cpu0是BSP(Boot Strap Processor,系统最初启动时在SMP生效前使用的CPU)。
总的来说,在KVM环境中,一般并不推荐手动地人为设置QEMU进程的处理器亲和性来绑定vCPU,但是,在非常了解系统硬件架构的基础上,根据实际应用的需求,是可以将其绑定到特定的CPU上去从而提高客户机中的CPU执行效率或者实现CPU资源独享的隔离性。
CPU中断请求隔离方法:
应该将中断绑定至那些非隔离的CPU上,从而避免那些隔离的CPU处理中断程序:
echo CPU_MASK > /proc/irq/ <irq number>/smp_affinity
[root@lopdb1 ~]# cat /proc/irq/2/smp_affinity
7fffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff
1. 不同的设备一般都有自己的IRQ号码(当然一个设备还有可能有多个IRQ号码)
通过命令:cat /proc/interrupts查看
如:cat /proc/interrupts | grep -e “CPU|eth4″
2. 中断的smp affinity在cat /proc/irq/$Num/smp_affinity
可以echo “$bitmask” > /proc/irq/$num/smp_affinity来改变它的值。
注意smp_affinity这个值是一个十六进制的bitmask,它和cpu No.序列的“与”运算结果就是将affinity设置在那个(那些)CPU了。(也即smp_affinity中被设置为1的位为CPU No.)
比如:我有8个逻辑core,那么CPU#的序列为11111111 (从右到左依次为#0~#7的CPU)
如果cat /proc/irq/84/smp_affinity的值为:20(二进制为:00100000),则84这个IRQ的亲和性为#5号CPU。
每个IRQ的默认的smp affinity在这里:cat /proc/irq/default_smp_affinity
另外,cat /proc/irq/$Num/smp_affinity_list 得到的即是CPU的一个List。
3. 默认情况下,有一个irqbalance在对IRQ进行负载均衡,它是/etc/init.d/irqbalance
在某些特殊场景下,可以根据需要停止这个daemon进程。
4. 如果要想提高性能,将IRQ绑定到某个CPU,那么最好在系统启动时,将那个CPU隔离起来,不被scheduler通常的调度。
可以通过在Linux kernel中加入启动参数:isolcpus=cpu-list来将一些CPU隔离起来。
参考文档:
https://www.kernel.org/doc/Documentation/IRQ-affinity.txt