在多CPU系统中,通过sched_setaffinity()可以设置进程的CPU亲和力,使进程绑定在某一个或几个CPU上运行,避免在CPU之间来回切换,从而提高该进程的实时性能。
一、CPU MASK
cpumask.h中定义了4个全局变量来表示CPU的个数:
- cpu_possible_mask:系统中总的CPU核数,初始化时设置
- cpu_present_mask: 系统中实际的CPU核数。
- cpu_online_mask :能被scheduler使用的CPU核数。
- cpu_active_mask :能被migration的CPU核数。
如果CONFIG_HOTPLUG_CPU没有被使能,则,
cpu_present_mask=cpu_possible_mask。
cpu_active_mask =cpu_online_mask 。
同时,进程的task_struct中有一个成员cpus_allowed,表示本进程可以在哪些CPU上运行:
//linux4.2.1\include\linux\sched.h
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
...
cpumask_t cpus_allowed;
...
}
二、简单实验
在ubuntu14.04上,测试的机器有4个CPU,如下测试代码将进程绑定到CPU2上运行:
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char **argv)
{
int cpus = 0;
int i = 0;
cpu_set_t mask;
cpu_set_t get;
cpus = sysconf(_SC_NPROCESSORS_ONLN);
printf("cpus: %d\n", cpus);
CPU_ZERO(&mask); /* 初始化set集,将set置为空*/
/*将本进程绑定到CPU2上*/
CPU_SET(2, &mask);
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
printf("Set CPU affinity failue, ERROR:%s\n", strerror(errno));
return -1;
}
while(1)
{
sleep(1);
}
return 0;
}
编译并执行:
使用ps -eo pid,args,psr命令查看main进程确实在CPU2上运行:
三、sched_setaffinity内核源码
linux4.2.1中,sched_setaffinity()的实现在/kernel/sched/Core.c中:
long sched_setaffinity(pid_t pid, const struct cpumask *in_mask)
{
cpumask_var_t cpus_allowed, new_mask;
struct task_struct *p;
int retval;
rcu_read_lock();
p = find_process_by_pid(pid);
if (!p) {
rcu_read_unlock();
return -ESRCH;
}
/* Prevent p going away */
get_task_struct(p);
rcu_read_unlock();
if (p->flags & PF_NO_SETAFFINITY) {
retval = -EINVAL;
goto out_put_task;
}
if (!alloc_cpumask_var(&cpus_allowed, GFP_KERNEL)) {
retval = -ENOMEM;
goto out_put_task;
}
if (!alloc_cpumask_var(&new_mask, GFP_KERNEL)) {
retval = -ENOMEM;
goto out_free_cpus_allowed;
}
retval = -EPERM;
if (!check_same_owner(p)) {
rcu_read_lock();
if (!ns_capable(__task_cred(p)->user_ns, CAP_SYS_NICE)) {
rcu_read_unlock();
goto out_free_new_mask;
}
rcu_read_unlock();
}
retval = security_task_setscheduler(p);
if (retval)
goto out_free_new_mask;
cpuset_cpus_allowed(p, cpus_allowed);
cpumask_and(new_mask, in_mask, cpus_allowed);
/*
* Since bandwidth control happens on root_domain basis,
* if admission test is enabled, we only admit -deadline
* tasks allowed to run on all the CPUs in the task's
* root_domain.
*/
#ifdef CONFIG_SMP
if (task_has_dl_policy(p) && dl_bandwidth_enabled()) {
rcu_read_lock();
if (!cpumask_subset(task_rq(p)->rd->span, new_mask)) {
retval = -EBUSY;
rcu_read_unlock();
goto out_free_new_mask;
}
rcu_read_unlock();
}
#endif
again:
retval = set_cpus_allowed_ptr(p, new_mask);
if (!retval) {
cpuset_cpus_allowed(p, cpus_allowed);
if (!cpumask_subset(new_mask, cpus_allowed)) {
/*
* We must have raced with a concurrent cpuset
* update. Just reset the cpus_allowed to the
* cpuset's cpus_allowed
*/
cpumask_copy(new_mask, cpus_allowed);
goto again;
}
}
out_free_new_mask:
free_cpumask_var(new_mask);
out_free_cpus_allowed:
free_cpumask_var(cpus_allowed);
out_put_task:
put_task_struct(p);
return retval;
}