前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux 进程管理之任务绑定

Linux 进程管理之任务绑定

作者头像
刘盼
发布2021-07-05 21:08:54
1.4K0
发布2021-07-05 21:08:54
举报
文章被收录于专栏:人人都是极客人人都是极客

什么是进程的 CPU 亲和性?

在多核结构中,每个核有各自的L1缓存,相同类型的核被划分在同一个cluster中,而不同cluster之间又有共用的L2缓存。讲负载均衡的时候我们讲过一个进程在核之间来回切换的时候,各个核之间的缓存命中率会降低,所以,将进程与 CPU 进行绑定可以提高 CPU 缓存的命中率,从而提高性能。这种绑定关系就叫做:进程的 CPU 亲和性。

如何设置进程的 CPU 亲和性?

Linux 系统提供了一个名为 sched_setaffinity 的系统调用,此系统调用可以设置进程的 CPU 亲和性。

代码语言:javascript
复制
sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t *mask)
  • pid:进行绑定 CPU 的进程ID号
  • cpusetsize:参数 mask 指向的 CPU 集合的大小
  • mask:与进程绑定的 CPU 集合

cpu_set_t 类型是个位图,可以理解为 CPU 集,通过宏来进行清除、设置以及判断:

代码语言:javascript
复制
//初始化,设为空
void CPU_ZERO (cpu_set_t *set); 
//将某个cpu加入cpu集中 
void CPU_SET (int cpu, cpu_set_t *set); 
//将某个cpu从cpu集中移出 
void CPU_CLR (int cpu, cpu_set_t *set); 
//判断某个cpu是否已在cpu集中设置了 
int CPU_ISSET (int cpu, const cpu_set_t *set);

CPU 集可以认为是一个掩码,每个设置的位都对应一个可以合法调度的 CPU,而未设置的位则对应一个不可调度的 CPU。换言之,线程都被绑定了,只能在那些对应位被设置了的处理器上运行。通常,掩码中的所有位都被置位了,也就是可以在所有的 CPU 中调度。

我们来看看 sched_setaffinity 系统调用的例子,将进程绑定到 CPU2 上运行:

代码语言:javascript
复制
#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置为空*/
    CPU_SET(2, &mask);  /*将本进程绑定到CPU2上*/
    if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
        printf("Set CPU affinity failue, ERROR:%s\n", strerror(errno));
        return -1; 
    }   
       
    return 0;
}

CPU 亲和性的实现

我们知道每个 CPU 都拥有一个独立的可运行进程队列,系统运行的时候 CPU 只会从属于自己的可运行进程队列中按照 CFS 策略,选择一个进程来运行。所以,把进程放置在 CPU 对应的可运行进程队列上,也就可将进程绑定到指定的 CPU 上。

下面我们追踪函数 sched_setaffinity 的调用顺序,分析一下进程如何与 CPU 进行绑定的。

代码语言:javascript
复制
SYSCALL_DEFINE3(sched_setaffinity, pid_t, pid, unsigned int, len, unsigned long __user *, user_mask_ptr)
-- sched_setaffinity(pid_t pid, const struct cpumask *in_mask)
--- __set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask, bool check)
---- stop_one_cpu(unsigned int cpu, cpu_stop_fn_t fn, void *arg)
----- migration_cpu_stop(void *data)
------ __migrate_task(struct rq *rq, struct task_struct *p, int dest_cpu)
------- move_queued_task(struct rq *rq, struct task_struct *p, int new_cpu)
-------- enqueue_task(struct rq *rq, struct task_struct *p, int flags)
--------- returns the new run queue of destination CPU

__set_cpus_allowed_ptr 函数主要分两种情况来将进程绑定到某个 CPU 上:

  1. stop_one_cpu(cpu_of(rq), migration_cpu_stop, &arg):把还没运行且在源运行队列中进程,放到指定的 CPU 可运行队列中
  2. move_queued_task(rq, &rf, p, dest_cpu):把已经运行的进程迁移到指定的 CPU 可运行队列中

这两种情况最终都会调用 move_queued_task:

代码语言:javascript
复制
static struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf,
       struct task_struct *p, int new_cpu)
{
 lockdep_assert_held(&rq->lock);

 p->on_rq = TASK_ON_RQ_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);

 return rq;
}

这里首先根据目标 CPU 找到对应的工作队列 rq,然后通过 enqueue_task 把任务迁移到目标 CPU 对应的工作队列中,CFS 调度器的话会调用到函数 enqueue_task_fair。

enqueue_task_fair 的执行流程如下:

  1. 如果通过struct sched_entity 的 on_rq 成员判断进程已经在就绪队列上, 则无事可。
  2. 否则, 具体的工作委托给 enqueue_entity,将任务插入到 CFS 红黑树中合适的结点。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-06-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 人人都是极客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是进程的 CPU 亲和性?
  • 如何设置进程的 CPU 亲和性?
  • CPU 亲和性的实现
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档