天天看点

linux系统调用sched_setaffinity内核实现分析

在讲解具体实现之前,我们先来看一个场景:

启动一个进程,并且在启动的时候就绑定该进程运行的CPU,如下图(我们是在KVM中启动一个虚拟机,该虚拟机启动时就进行了CPU绑定):

linux系统调用sched_setaffinity内核实现分析

可以看到虚拟机进程vCPU确实被绑定在了0-11,24-35号CPU上,下面我们使用taskset修改该进程绑定的CPU上,比如修改为12-23,结果如下:

linux系统调用sched_setaffinity内核实现分析

可以看到上面设置失败,于是我们编程再次尝试一下,程序如下:

#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;
}
           

程序运行的结果如下:

linux系统调用sched_setaffinity内核实现分析

其实和taskset的结果一样。然后我们尝试将进程的CPU绑定为0-11,运行结果如下:

linux系统调用sched_setaffinity内核实现分析

结果也是不允许。那为什么会出现这样的结果呢?下面我们来看一下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绑定时的一个区别。

未完待续。

继续阅读