0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

把进程绑定到某个 CPU 上运行是怎么实现?

Linux爱好者 来源:Linux内核那些事 作者:songsong001 2021-07-02 09:55 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

昨天在群里有朋友问:把进程绑定到某个 CPU 上运行是怎么实现的。

首先,我们先来了解下将进程与 CPU 进行绑定的好处。

进程绑定 CPU 的好处:在多核 CPU 结构中,每个核心有各自的L1、L2缓存,而L3缓存是共用的。如果一个进程在核心间来回切换,各个核心的缓存命中率就会受到影响。相反如果进程不管如何调度,都始终可以在一个核心上执行,那么其数据的L1、L2 缓存的命中率可以显著提高。

所以,将进程与 CPU 进行绑定可以提高 CPU 缓存的命中率,从而提高性能。而进程与 CPU 绑定被称为:CPU 亲和性。

设置进程的 CPU 亲和性前面介绍了进程与 CPU 绑定的好处后,现在来介绍一下在 Linux 系统下怎么将进程与 CPU 进行绑定的(也就是设置进程的 CPU 亲和性)。

Linux 系统提供了一个名为 sched_setaffinity 的系统调用,此系统调用可以设置进程的 CPU 亲和性。我们来看看 sched_setaffinity 系统调用的原型:

int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t *mask);

下面介绍一下 sched_setaffinity 系统调用各个参数的作用:

pid:进程ID,也就是要进行绑定 CPU 的进程ID。

cpusetsize:mask 参数所指向的 CPU 集合的大小。

mask:与进程进行绑定的 CPU 集合(由于一个进程可以绑定到多个 CPU 上运行)。

参数 mask 的类型为 cpu_set_t,而 cpu_set_t 是一个位图,位图的每个位表示一个 CPU。:

例如,将 cpu_set_t 的第0位设置为1,表示将进程绑定到 CPU0 上运行,当然我们可以将进程绑定到多个 CPU 上运行。

我们通过一个例子来介绍怎么通过 sched_setaffinity 系统调用来设置进程的 CPU 亲和性:

#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)

{

cpu_set_t cpuset;

CPU_ZERO(&cpuset); // 初始化CPU集合,将 cpuset 置为空

CPU_SET(2, &cpuset); // 将本进程绑定到 CPU2 上

// 设置进程的 CPU 亲和性

if (sched_setaffinity(0, sizeof(cpuset), &cpuset) == -1) {

printf(“Set CPU affinity failed, error: %s

”, strerror(errno));

return -1;

}

return 0;

}

CPU 亲和性实现知道怎么设置进程的 CPU 亲和性后,现在我们来分析一下 Linux 内核是怎样实现 CPU 亲和性功能的。

本文使用的 Linux 内核版本为 2.6.23

Linux 内核为每个 CPU 定义了一个类型为 struct rq 的 可运行的进程队列,也就是说,每个 CPU 都拥有一个独立的可运行进程队列。

一般来说,CPU 只会从属于自己的可运行进程队列中选择一个进程来运行。也就是说,CPU0 只会从属于 CPU0 的可运行队列中选择一个进程来运行,而绝不会从 CPU1 的可运行队列中获取。

所以,从上面的信息中可以分析出,要将进程绑定到某个 CPU 上运行,只需要将进程放置到其所属的 可运行进程队列 中即可。

下面我们来分析一下 sched_setaffinity 系统调用的实现,sched_setaffinity 系统调用的调用链如下:

sys_sched_setaffinity()

└→ sched_setaffinity()

└→ set_cpus_allowed()

└→ migrate_task()

从上面的调用链可以看出,sched_setaffinity 系统调用最终会调用 migrate_task 函数来完成进程与 CPU 进行绑定的工作,我们来分析一下 migrate_task 函数的实现:

static int

migrate_task(struct task_struct *p, int dest_cpu, struct migration_req *req)

{

struct rq *rq = task_rq(p);

// 情况1:

// 如果进程还没有在任何运行队列中

// 那么只需要将进程的 cpu 字段设置为 dest_cpu 即可

if (!p-》se.on_rq && !task_running(rq, p)) {

set_task_cpu(p, dest_cpu);

return 0;

}

// 情况2:

// 如果进程已经在某一个 CPU 的可运行队列中

// 那么需要将进程从之前的 CPU 可运行队列中迁移到新的 CPU 可运行队列中

// 这个迁移过程由 migration_thread 内核线程完成

// 构建进程迁移请求

init_completion(&req-》done);

req-》task = p;

req-》dest_cpu = dest_cpu;

list_add(&req-》list, &rq-》migration_queue);

return 1;

}

我们先来介绍一下 migrate_task 函数各个参数的意义:

p:要设置 CPU 亲和性的进程描述符。

dest_cpu:绑定的 CPU 编号。

req:进程迁移请求对象(下面会介绍)。

所以,migrate_task 函数的作用就是将进程描述符为 p 的进程绑定到编号为 dest_cpu 的目标 CPU 上。

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

情况1:如果进程还没有在任何 CPU 的可运行队列中(不可运行状态),那么只需要将进程描述符的 cpu 字段设置为 dest_cpu 即可。当进程变为可运行时,会根据进程描述符的 cpu 字段来自动放置到对应的 CPU 可运行队列中。

情况2:如果进程已经在某个 CPU 的可运行队列中,那么需要将进程从之前的 CPU 可运行队列中迁移到新的 CPU 可运行队列中。迁移过程由 migration_thread 内核线程完成,migrate_task 函数只是构建一个进程迁移请求,并通知 migration_thread 内核线程有新的迁移请求需要处理。

而进程迁移过程由 __migrate_task 函数完成,我们来看看 __migrate_task 函数的实现:

static int

__migrate_task(struct task_struct *p, int src_cpu, int dest_cpu)

{

struct rq *rq_dest, *rq_src;

int ret = 0, on_rq;

。。。

rq_src = cpu_rq(src_cpu); // 进程所在的原可运行队列

rq_dest = cpu_rq(dest_cpu); // 进程希望放置的目标可运行队列

。。。

on_rq = p-》se.on_rq; // 进程是否在可运行队列中(可运行状态)

if (on_rq)

deactivate_task(rq_src, p, 0); // 把进程从原来的可运行队列中删除

set_task_cpu(p, dest_cpu);

if (on_rq) {

activate_task(rq_dest, p, 0); // 把进程放置到目标可运行队列中

。。。

}

。。。

return ret;

}

__migrate_task 函数主要完成以下两个工作:

把进程从原来的可运行队列中删除。

把进程放置到目标可运行队列中。

其工作过程如下图所示(将进程从 CPU0 的可运行队列迁移到 CPU3 的可运行队列中):

如上图所示,进程原本在 CPU0 的可运行队列中,但由于重新将进程绑定到 CPU3,所以需要将进程从 CPU0 的可运行队列迁移到 CPU3 的可运行中。

迁移过程首先将进程从 CPU0 的可运行队列中删除,然后再将进程插入到 CPU3 的可运行队列中。

当 CPU 要运行进程时,首先从它所属的可运行队列中挑选一个进程,并将此进程调度到 CPU 中运行。

总结从上面的分析可知,其实将进程绑定到某个 CPU 只是将进程放置到 CPU 的可运行队列中。

由于每个 CPU 都有一个可运行队列,所以就有可能会出现 CPU 间可运行队列负载不均衡问题。如 CPU0 可运行队列中的进程比 CPU1 可运行队列多非常多,从而导致 CPU0 的负载非常高,而 CPU1 负载非常低的情况。

当出现上述情况时,就需要对 CPU 间的可运行队列进行重平衡操作,有兴趣的可以自行阅读源码或参考相关资料。

编辑:jq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内核
    +关注

    关注

    4

    文章

    1476

    浏览量

    43098
  • cpu
    cpu
    +关注

    关注

    68

    文章

    11332

    浏览量

    225984
  • Linux
    +关注

    关注

    88

    文章

    11822

    浏览量

    219600

原文标题:图解:进程怎么绑定 CPU

文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    飞凌嵌入式ElfBoard-进程进程状态

    可能在 CPU 运行。而同一时刻可能有多个进程处于可执行状态;根据是否在CPU
    发表于 03-27 09:12

    飞凌嵌入式ElfBoard-进程的相关信息之父进程和子进程

    的PID标准输出。调用getppid()函数,获取当前进程的父进程ID,并将其存储在变量ppid中,随后打印当前进程的父进程的PID
    发表于 03-12 17:12

    飞凌嵌入式ElfBoard-进程之什么是进程

    ID,简称 PID),用来标识当前的进程,区分不同的进程。这使得系统能够有效地管理多个并发运行进程,避免混淆。在Linux命令行中直接使用ps命令即可查看到
    发表于 03-02 08:49

    操作系统运行机制

    不同的层次。一些与硬件关联较紧密的模块,诸如时钟管理、中断处理、设备驱动等处于最底层。其次是运行频率较髙的程序,诸如进程管理、存储器管理和设备管理等。这两部分内容构成了操作系统的内核。这部分内容的指令
    发表于 01-15 07:12

    进程概念和特征

    进程的基本情况和运行状态,进而控制和管理进程。相应地,由程序段、相关数据段和PCB三部分构成了进程映像(进程实体)。所谓创建
    发表于 01-15 06:39

    进程通信

    直接消息发送给接收进程,并将它挂在接收进程的消息缓冲队列上,接收进程从消息缓冲队列中取得消息。   2) 间接通信方式:发送进程
    发表于 01-15 06:16

    进程的控制

    是:   1. 找到将要被阻塞进程的标识号对应的PCB。   2. 若该进程运行状态,则保护其现场,将其状态转为阻塞状态,停止运行。   3.
    发表于 01-15 06:05

    深入Linux内核:进程调度的核心逻辑与实现细节

    在Linux系统中,进程调度就像一位精明的“CPU管理员”——它决定着哪个进程能优先使用CPU,多久切换一次进程,如何平衡系统响应速度与资源
    的头像 发表于 12-24 07:05 4614次阅读
    深入Linux内核:<b class='flag-5'>进程</b>调度的核心逻辑与<b class='flag-5'>实现</b>细节

    解析Linux的进程、线程和协程

    一、基础概念 进程(Process) 进程是计算机中运行的程序的实例,它是操作系统中最基本的执行单元之一。每个进程都有自己的独立内存空间、系统资源和代码执行流。这意味着一个
    发表于 12-22 11:00

    嵌入式基础知识-系统调度

    被强制剥夺CPU使用权 2、进程切换 进程切换是指,操作系统为了控制进程的执行,必须有能力挂起正在CPU
    发表于 12-16 08:15

    Arm Neoverse CPU大代码量Java应用的性能测试

    Java 是互联网领域广泛使用的编程语言。Java 应用的一些特性使其性能表现与提前编译的原生应用(例如 C 程序)大相径庭。由于 Java 字节码无法直接在 CPU 执行,因此通常运行时在
    的头像 发表于 11-05 11:25 965次阅读
    Arm Neoverse <b class='flag-5'>CPU</b><b class='flag-5'>上</b>大代码量Java应用的性能测试

    在极海APM32系列MCU中如何代码重定位SDRAM运行

    在有些情况下,我们想要把代码放到SDRAM运行。下面介绍在APM32的MCU中,如何代码重定位SDRAM运行。对于不同APM32系列的MCU,方法都是一样的。
    的头像 发表于 11-04 09:14 5431次阅读
    在极海APM32系列MCU中如何<b class='flag-5'>把</b>代码重定位<b class='flag-5'>到</b>SDRAM<b class='flag-5'>运行</b>

    时序约束管脚绑定不成功问题

    {[新手提问]: 关于引脚绑定问题}在绑定vivado引脚中时,选项中没有原理图可绑定的引脚,需要绑定的引脚在l/O Bank88,但是我需要绑定
    发表于 08-21 17:18

    如何将 UID 代码绑定 M031 LDROM 和 APROM 固件?

    将 UID 代码绑定 M031 LDROM 和 APROM 固件
    发表于 08-20 06:38

    STM32IDE如何设定代码ITCM中运行

    摸索到了如何将变量定义某个地址,但是不清楚如何让代码在指定RAM中运行。按照设定变量的方式设定代码,程序直接进入了异常中断{:16:}。
    发表于 06-24 06:45