在讲解具体实现之前,我们先来看一个场景:
启动一个进程,并且在启动的时候就绑定该进程运行的CPU,如下图(我们是在KVM中启动一个虚拟机,该虚拟机启动时就进行了CPU绑定):
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TP3NmeWdkYpxmMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLzgzN2UjMyUTMwEjMxgTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
可以看到虚拟机进程vCPU确实被绑定在了0-11,24-35号CPU上,下面我们使用taskset修改该进程绑定的CPU上,比如修改为12-23,结果如下:
可以看到上面设置失败,于是我们编程再次尝试一下,程序如下:
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/sysinfo.h>
#include<unistd.h>
#define __USE_GNU
#include<sched.h>
#include<ctype.h>
#include<string.h>
int main(int argc, char const *argv[]){
if(argc<2){
printf("参数错误\n");
exit(-1);
}
char str[128]="error";
cpu_set_t mask; //CPU核的集合
pid_t pid=atoi(argv[1]);
int i,res=0;
int cpu_nums=sysconf(_SC_NPROCESSORS_CONF);//获取系统CPU总数
printf("pid=%d\tcpu_nums=%d\n",pid,cpu_nums);
CPU_ZERO(&mask); //置空
sched_getaffinity(pid, sizeof(mask),&mask);//获取进程当前的CPU亲和性
//打印进程CPU亲和性
printf("进程 %d 使用了下面的CPU:\n",pid);
for(i=0;i<cpu_nums;i++){
if(CPU_ISSET(i,&mask)){
printf("%d ",i);
}
}
printf("\n");
//接下来,尝试设置进程CPU亲和性为12-23,36-47
CPU_ZERO(&mask); //置空
for(i=12;i<=23;i++){
CPU_SET(i,&mask); //设置亲和力值
}
for(i=36;i<=47;i++){
CPU_SET(i,&mask); //设置亲和力值
}
res=sched_setaffinity(pid, sizeof(mask), &mask);//执行设置
if(res!=0){
printf("设置进程亲和性失败\n");
perror(str);//打印错误原因
}else{
printf("设置进程亲和性成功!\n");
}
CPU_ZERO(&mask); //置空
sched_getaffinity(pid, sizeof(mask), &mask);
printf("进程 %d 使用了下面的CPU:\n",pid);
for(i=0;i<cpu_nums;i++){
if(CPU_ISSET(i,&mask)){
printf("%d ",i);
}
}
printf("\n");
return 0;
}
程序运行的结果如下:
其实和taskset的结果一样。然后我们尝试将进程的CPU绑定为0-11,运行结果如下:
结果也是不允许。那为什么会出现这样的结果呢?下面我们来看一下sched_setaffinity()函数在linux内核中的实现。
注:内核代码版本:3.14.69
内核层sched_setaffinity实现如下:
/**
* sys_sched_setaffinity - set the cpu affinity of a process 设置一个进程的CPU亲和性
* @pid: pid of the process 进程编号PID
* @len: length in bytes of the bitmask pointed to by user_mask_ptr 用户层掩码所占的字节长度
* @user_mask_ptr: user-space pointer to the new cpu get_user_cpu_mask 指向新的要修改的掩码的用户层指针
*
* Return: 0 on success. An error code otherwise.
*/
SYSCALL_DEFINE3(sched_setaffinity, pid_t, pid, unsigned int, len,
unsigned long __user *, user_mask_ptr)
{
cpumask_var_t new_mask;
int retval;
if (!alloc_cpumask_var(&new_mask, GFP_KERNEL))//在内核中分配一个cpumask_var_t的结构体
return -ENOMEM;
//将用户层的user_mask_ptr参数拷贝到内核中,也就是将用户设置的亲和性参数拷贝到内核中
retval = get_user_cpu_mask(user_mask_ptr, len, new_mask);//将用户空间的user_mask_ptr的值拷贝到内核层的new_mask中
if (retval == 0){//如果用户参数设置是合法的
retval = sched_setaffinity(pid, new_mask);
}
free_cpumask_var(new_mask);//释放内核中申请的cpumask_var_t结构体
return retval;
}
long sched_setaffinity(pid_t pid, const struct cpumask *in_mask)
{
cpumask_var_t cpus_allowed, new_mask;
struct task_struct *p;
int retval, i;
rcu_read_lock();
p = find_process_by_pid(pid);//根据进程PID获取进程结构体task_struct
if (!p)
{
rcu_read_unlock();
return -ESRCH;
}
/* Prevent p going away */
get_task_struct(p);//使进程结构体task_struct的使用计数加1,放置在修改CPU亲和性期间task_struct被撤销
rcu_read_unlock();
//根据PF_NO_SETAFFINITY判断用户空间是否可以修改cpus_allowed属性
if (p->flags & PF_NO_SETAFFINITY)
{
retval = -EINVAL;
goto out_put_task;
}
//在内核空间中分配一个cpumask_var_t结构体
if (!alloc_cpumask_var(&cpus_allowed, GFP_KERNEL))
{
retval = -ENOMEM;//not enough space错误
goto out_put_task;
}
if (!alloc_cpumask_var(&new_mask, GFP_KERNEL))
{
retval = -ENOMEM; //not enough space错误
goto out_free_cpus_allowed;
}
retval = -EPERM;//operation not permitted错误
if (!check_same_owner(p))//判断当前进程的uid和要修改亲和性的目标进程的uid是否相等,相等返回1,否则返回0
{
rcu_read_lock();
if (!ns_capable(__task_cred(p)->user_ns, CAP_SYS_NICE))//权限检查
{
rcu_read_unlock();
goto out_unlock;
}
rcu_read_unlock();
}
//判断是否允许修改调度策略
retval = security_task_setscheduler(p);
if (retval)
{
goto out_unlock;
}
//获取进程cpuset的cpus_allowed,并将结果存于cpus_allowed变量中
cpuset_cpus_allowed(p, cpus_allowed);
//将进程原来的cpus_allowed与用户设置的cpus_allowed相与,并将结果存于new_mask变量中
cpumask_and(new_mask, in_mask, cpus_allowed); //*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_unlock;
}
rcu_read_unlock();
}
#endif
again:
retval = set_cpus_allowed_ptr(p, new_mask);
if (!retval)//如果前面的操作成功
{
cpuset_cpus_allowed(p, cpus_allowed);//获取进程当前cpuset的cpus_allowed
if (!cpumask_subset(new_mask, cpus_allowed))//如果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); //*new_mask = *cpus_allowed,这里其实就是保证你设置的cpus_allowed需要是该进程cpuset中的cpus_allowed的子集
goto again;
}
}
out_unlock:
free_cpumask_var(new_mask);//释放内存
out_free_cpus_allowed:
free_cpumask_var(cpus_allowed);//释放内存
out_put_task:
put_task_struct(p);//减少使用计数
return retval;
}
/*
* Change a given task's CPU affinity. Migrate the thread to a
* proper CPU and schedule it away if the CPU it's executing on
* is removed from the allowed bitmask.
*
* NOTE: the caller must have a valid reference to the task, the
* task must not exit() & deallocate itself prematurely. The
* call is not atomic; no spinlocks may be held.
*/
int set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask)
{
unsigned long flags;
struct rq *rq;
unsigned int dest_cpu;
int ret = 0, i;
rq = task_rq_lock(p, &flags);
//判断new_mask与进程的cpus_allowed是否相等,相等的话就没有必要重新设置,就可以退出
if (cpumask_equal(&p->cpus_allowed, new_mask))
{
goto out;
}
//判断设置的CPU亲和性与活跃的CPU是否有交集,有交集则cpumask_intersects()返回1,否则返回0
if (!cpumask_intersects(new_mask, cpu_active_mask)) // !((*new_mask & *cpu_active_mask) != 0)
{
//如果设置的new_mask和cpu_active_mask没有交集,退出,并返回错误值
ret = -EINVAL;//invalid argument错误
goto out;
}
//这一步在有宏定义CONFIG_SMP时才有操作,否则是一个空函数。
do_set_cpus_allowed(p, new_mask); // 该函数设置进程结构体的cpus_allowed以及修改进程结构体中的nr_cpus_allowed的值
/* Can the task run on the task's current CPU? If so, we're done */
if (cpumask_test_cpu(task_cpu(p), new_mask))
{
goto out;
}
//从 *cpu_active_mask & *new_mask 结果中随机挑选一个CPU
dest_cpu = cpumask_any_and(cpu_active_mask, new_mask);
if (p->on_rq)
{
struct migration_arg arg = {p, dest_cpu};
/* Need help from migration thread: drop lock and wait. */
task_rq_unlock(rq, p, &flags);
stop_one_cpu(cpu_of(rq), migration_cpu_stop, &arg);
tlb_migrate_finish(p->mm);
return 0;
}
out:
task_rq_unlock(rq, p, &flags);
return ret;
}
在sched_setaffinity()调用中,返回的错误是:operation not permitted,错误应该是出现在security_task_setscheduler(p);函数的调用中。
另外,从上面函数的分析过程中,我们可以看到,用户设置的要亲和的CPU需要和系统中活跃的CPU有交集,而且最终设置的是和cpuset中的cpus_allowed相与的结果。设置成功时,最终设置的结果也应该是cpuset中cpus_allowed的子集。
下一步就是要分析security_task_setscheduler(p)函数在有CPU绑定和没有CPU绑定时的一个区别。
未完待续。