Linux 进程管理之任务绑定

系统 Linux
将进程与 CPU 进行绑定可以提高 CPU 缓存的命中率,从而提高性能。这种绑定关系就叫做:进程的 CPU 亲和性。

[[405508]]

本文转载自微信公众号「人人都是极客」,作者布道师Peter 。转载本文请联系人人都是极客公众号。

什么是进程的 CPU 亲和性?

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

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

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

  1. 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 集,通过宏来进行清除、设置以及判断:

  1. //初始化,设为空 
  2. void CPU_ZERO (cpu_set_t *set);  
  3. //将某个cpu加入cpu集中  
  4. void CPU_SET (int cpu, cpu_set_t *set);  
  5. //将某个cpu从cpu集中移出  
  6. void CPU_CLR (int cpu, cpu_set_t *set);  
  7. //判断某个cpu是否已在cpu集中设置了  
  8. int CPU_ISSET (int cpu, const cpu_set_t *set); 

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

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

  1. #define _GNU_SOURCE 
  2. #include <sched.h> 
  3. #include <stdio.h> 
  4. #include <string.h> 
  5. #include <stdlib.h> 
  6. #include <unistd.h> 
  7. #include <errno.h> 
  8.  
  9. int main(int argc, char **argv) 
  10.     int cpus = 0; 
  11.     int  i = 0; 
  12.     cpu_set_t mask; 
  13.     cpu_set_t get; 
  14.  
  15.     cpus = sysconf(_SC_NPROCESSORS_ONLN); 
  16.     printf("cpus: %d\n", cpus); 
  17.  
  18.     CPU_ZERO(&mask);    /* 初始化set集,将set置为空*/ 
  19.     CPU_SET(2, &mask);  /*将本进程绑定到CPU2上*/ 
  20.     if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { 
  21.         printf("Set CPU affinity failue, ERROR:%s\n", strerror(errno)); 
  22.         return -1;  
  23.     }    
  24.         
  25.     return 0; 

CPU 亲和性的实现

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

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

  1. SYSCALL_DEFINE3(sched_setaffinity, pid_t, pid, unsigned int, len, unsigned long __user *, user_mask_ptr) 
  2. -- sched_setaffinity(pid_t pid, const struct cpumask *in_mask) 
  3. --- __set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask, bool check) 
  4. ---- stop_one_cpu(unsigned int cpu, cpu_stop_fn_t fn, void *arg) 
  5. ----- migration_cpu_stop(void *data) 
  6. ------ __migrate_task(struct rq *rq, struct task_struct *p, int dest_cpu) 
  7. ------- move_queued_task(struct rq *rq, struct task_struct *p, int new_cpu) 
  8. -------- enqueue_task(struct rq *rq, struct task_struct *p, int flags) 
  9. --------- 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:

  1. static struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf, 
  2.        struct task_struct *p, int new_cpu) 
  3.  lockdep_assert_held(&rq->lock); 
  4.  
  5.  p->on_rq = TASK_ON_RQ_MIGRATING; 
  6.  dequeue_task(rq, p, DEQUEUE_NOCLOCK); 
  7.  set_task_cpu(p, new_cpu); 
  8.  rq_unlock(rq, rf); 
  9.  
  10.  rq = cpu_rq(new_cpu); 
  11.  
  12.  rq_lock(rq, rf); 
  13.  BUG_ON(task_cpu(p) != new_cpu); 
  14.  enqueue_task(rq, p, 0); 
  15.  p->on_rq = TASK_ON_RQ_QUEUED; 
  16.  check_preempt_curr(rq, p, 0); 
  17.  
  18.  return rq; 

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

enqueue_task_fair 的执行流程如下:

  1. 如果通过struct sched_entity 的 on_rq 成员判断进程已经在就绪队列上, 则无事可。

 

否则, 具体的工作委托给 enqueue_entity,将任务插入到 CFS 红黑树中合适的结点。

 

责任编辑:武晓燕 来源: 人人都是极客
相关推荐

2011-01-11 13:47:27

Linux管理进程

2023-03-05 16:12:41

Linux进程线程

2023-03-02 23:50:36

Linux进程管理

2023-03-03 00:03:07

Linux进程管理

2021-04-22 07:47:46

Linux进程管理

2021-04-15 05:51:25

Linux

2021-05-17 18:28:36

Linux CFS负载均衡

2021-05-12 07:50:02

CFS调度器Linux

2023-03-05 15:28:39

CFSLinux进程

2010-02-25 10:28:43

Linux进程管理

2011-01-14 14:49:05

2014-08-01 15:38:37

Linux进程管理

2009-10-23 17:35:16

linux进程管理

2017-03-03 10:10:44

Linux进程管理基础知识

2010-07-21 09:32:03

Linux多核

2011-01-11 13:53:33

Linux管理磁盘

2021-07-06 21:30:06

Linux进程通信

2009-10-27 16:34:02

linux top命令

2022-11-09 08:12:07

2021-07-07 10:05:38

进程CPULinux
点赞
收藏

51CTO技术栈公众号