本文转载自微信公众号《人人都是极客》,作者布道者Peter。转载本文请联系大家是极客公众号。进程的CPU亲和力是多少?在多核结构中,每个核心都有自己的一级缓存,同类型的核心被划分到同一个集群中,不同集群之间共享二级缓存。在讲负载均衡的时候,我们说过当一个进程在内核之间来回切换时,各个内核之间的缓存命中率会降低。因此,将进程绑定到CPU可以提高CPU缓存的命中率,从而提高性能。.这种绑定关系称为:进程的CPU亲和性。如何设置进程的CPU亲和力?Linux系统提供了一个名为sched_setaffinity的系统调用,可以设置进程的CPU亲和性。sched_setaffinity(pid_tpid,size_tcpusetsize,constcpu_set_t*mask)pid:绑定CPU的进程ID号,通过宏清除、设置和判断://初始化,置空voidCPU_ZERO(cpu_set_t*set);//添加一个cpu到cpusetvoidCPU_SET(intcpu,cpu_set_t*set);//setacpufromRemovevoidCPU_CLR(intcpu,cpu_set_t*set)fromthecpuset;//判断某个cpu是否设置了cpuset中的intCPU_ISSET(intcpu,constcpu_set_t*set);CPUset可以看作是一个掩码,每个set位对应一个合法可调度的CPU,而一个unset位对应一个不可调度的CPU。换句话说,线程只能在相应位已设置的处理器上运行。通常,掩码中的所有位都已设置,即可以在所有CPU上进行调度。让我们看一个sched_setaffinity系统调用的例子,将进程绑定到CPU2上运行:#define_GNU_SOURCE#include#include#include#include#include#includeintmain(intargc,char**argv){intcpus=0;inti=0;cpu_set_tmask;cpu_set_tget;cpus=sysconf(_SC_NPROCESSORS_ONLN);printf("cpus:%d\n",cpus);CPU_ZERO(&mask);/*初始化set集合,set集合为空*/CPU_SET(2,&mask);/*将本进程绑定到CPU2*/if(sched_setaffinity(0,sizeof(mask),&mask)==-1){printf("SetCPUaffinityfailue,ERROR:%s\n",strerror(errno));return-1;}return0;}CPUaffinity的实现我们知道每个CPU每个有一个独立的可运行进程队列。系统运行时,CPU只会根据CFS策略从自己的可运行进程队列中选择一个进程运行。因此,通过将进程放到CPU对应的runnable进程队列中,也可以将进程绑定到指定的CPU上。接下来我们跟踪sched_setaffinity函数的调用顺序,分析进程是如何绑定CPU的。SYSCALL_DEFINE3(sched_setaffinity,pid_t,pid,unsignedint,len,unsignedlong__user*,user_mask_ptr)--sched_setaffinity(pid_tpid,conststructcpumask*in_mask)---__set_cpus_allowed_ptr(structtask_struct*p,conststructonechsign_ctopmask*new_mask),_fn_argstop(cpu_argstop),_fn_argstop(cpu_argstop),_fn_argstop----migration_cpu_stop(void*data)------__migrate_task(structrq*rq,structtask_struct*p,intdest_cpu)------move_queued_task(structrq*rq,structtask_struct*p,intnew_cpu)--------enqueue_task(structrq*rq,structtask_struct*p,intflags)--------returnsthenewrunqueueofdestinationCPU__set_cpus_allowed_ptr函数主要分为两种情况将进程绑定到一个CPU上:stop_one_cpu(cpu_of(rq),migration_cpu_stop,&arg):将源运行队列中还没有运行的进程放入指定CPU可运行队列move_queued_task(rq,&rf,p,dest_cpu):将已经运行的进程迁移到指定CPU可运行队列。这两种情况最终都会调用move_queued_task:_MIGRATING;dequeue_task(rq,p,DEQUEUE_NOCLOCK);set_task_cpu(p,new_cpu);rq_unlock(rq,rf);rq=cpu_rq(new_cpu);rq_lock(rq,rf);BUG_ON(task_cpu(p)!=new_cpu);enqueue_task(rq,p,0);p->on_rq=TASK_ON_RQ_QUEUED;check_preempt_curr(rq,p,0);returnrq;}这里先根据目标CPU找到对应的工作队列rq,然后将任务迁移到targetCPU通过enqueue_task进入工作队列,CFS调度器会调用函数enqueue_task_fairenqueue_task_fair,执行过程如下:如果structsched_entity的on_rq成员判断进程已经在就绪队列,则无事可做。否则,将具体工作委托给enqueue_entity,将任务插入到CFS红黑树中合适的节点中。