Linux 中断的 CPU 亲和性

embedded/2024/10/22 12:31:21/

文章目录

  • 1. 前言
  • 2. 背景
  • 3. 什么是中断的 CPU 亲和性
  • 3. IRQ 中断 `默认的 CPU 亲和性`
  • 4. 硬件架构 CPU 固有 IRQ 中断亲和性
  • 5. 中断芯片 各中断 CPU 亲和性 `初始化`
    • 5.1 GIC v2 芯片的 SPI 中断 CPU 亲和性 `初始化`
      • 5.1.1 软件层次: 中断 CPU 亲和性 `初始化`
      • 5.1.2 中断芯片层次: GIC v2 中断 CPU 亲和性 `初始化`
  • 6. 中断芯片各中断 CPU 亲和性 `修改`

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 背景

本文以 ARMv7 架构 + GIC v2 中断芯片 + Linux 4.14 为背景,分析 IRQ 中断 的 CPU 亲和性 (Affinity)设置过程。

3. 什么是中断的 CPU 亲和性

什么是 中断的 CPU 亲和性?是指接收处理某个中断信号的 CPU 集合

3. IRQ 中断 默认的 CPU 亲和性

Linux 下的 IRQ 中断,有一个默认的亲和性设置:

start_kernel()early_irq_init()init_irq_default_affinity()static void __init init_irq_default_affinity(void)
{if (!cpumask_available(irq_default_affinity))zalloc_cpumask_var(&irq_default_affinity, GFP_NOWAIT);if (cpumask_empty(irq_default_affinity))cpumask_setall(irq_default_affinity); /* IRQ 中断 的 CPU 亲和性默认值 为 【系统中所有 CPU】 */
}
#ifdef CONFIG_CPUMASK_OFFSTACK
/* Assuming NR_CPUS is huge, a runtime limit is more efficient.  Also,* not all bits may be allocated. */
#define nr_cpumask_bits nr_cpu_ids
#else
#define nr_cpumask_bits ((unsigned int)NR_CPUS)
#endifstatic inline void cpumask_setall(struct cpumask *dstp)
{bitmap_fill(cpumask_bits(dstp), nr_cpumask_bits);
}

4. 硬件架构 CPU 固有 IRQ 中断亲和性

本文以 ARMv7 架构 CPU 为例,简要说明其固有 16 个 IRQ 中断的 CPU 亲和性的配置过程:

start_kernel()early_irq_init()
/* kernel/irq/irqdesc.c */int __init early_irq_init(void)
{int i, initcnt, node = first_online_node;struct irq_desc *desc;init_irq_default_affinity();/* Let arch update nr_irqs and return the nr of preallocated irqs */initcnt = arch_probe_nr_irqs(); /* 获取架构 CPU 支持的 IRQ 中断数目 */printk(KERN_INFO "NR_IRQS: %d, nr_irqs: %d, preallocated irqs: %d\n",NR_IRQS, nr_irqs, initcnt);...for (i = 0; i < initcnt; i++) {desc = alloc_desc(i, node, 0, NULL, NULL); /* 分配 irq_desc 并初始化:包括 CPU 亲和性设置 */set_bit(i, allocated_irqs); /* 标记 IRQ 中断号 @i 已分配 */irq_insert_desc(i, desc); /* 将 IRQ 中断 @i 的描述符插入描述符基树 @irq_desc_tree */}...
}static struct irq_desc *alloc_desc(int irq, int node, unsigned int flags,const struct cpumask *affinity,struct module *owner)
{struct irq_desc *desc;/* 分配一个 IRQ 中断描述符 */desc = kzalloc_node(sizeof(*desc), GFP_KERNEL, node);.../** 分配用来记录 IRQ 中断 @desc 的 CPU 亲和性掩码 的 空间,包括: * desc->irq_common_data.affinity* desc->irq_common_data.effective_affinity (需开启 CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK)*/if (alloc_masks(desc, node))goto err_kstat;...desc_set_defaults(irq, desc, node, affinity, owner);...
}static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node,const struct cpumask *affinity, struct module *owner)
{...desc_smp_init(desc, node, affinity);
}static void desc_smp_init(struct irq_desc *desc, int node,const struct cpumask *affinity)
{if (!affinity)affinity = irq_default_affinity; /* IRQ 使用默认亲和性,即使用 系统中所有 CPU */cpumask_copy(desc->irq_common_data.affinity, affinity);...
}

5. 中断芯片 各中断 CPU 亲和性 初始化

本文以 ARM GIC v2 中断芯片为例,分别说明下面 3 类中断 CPU 亲和性的设置过程:

SGI(Software-generated interrupt):用于处理期之间的通信,编号区间为 0~15 ,对于每个处理器,都是相同的编号区间;
PPI(Private peripheral interrupt):处理器私有中断,编号区间为 16~31 ,处理器私有编号,每个处理器都是相同的编号区间;
SPI(Shared peripheral interrupt):全局中断,可发送给任一处理器,编号区间为 32~1019 ,且编号全局唯一。

对于 SGI 中断,在发送指定接收的 CPU,不需要亲和性的配置;对于 PPI 中断,是每个 CPU 私有的,也不需要亲和性配置;只有 SPI 中断,是全局共享的,可以发送给任意 CPU,需要进行亲和性配置。

5.1 GIC v2 芯片的 SPI 中断 CPU 亲和性 初始化

对于中断的 CPU 亲和性配置,也有两个层次:软件层次硬件中断芯片层次(本文指 GIC v2)。

5.1.1 软件层次: 中断 CPU 亲和性 初始化

通用层次,是和具体中断芯片无关的、Linux 内核通用层次的配置。来看一个 I2C 总线中断 CPU 亲和性配置过程的例子。I2C 的中断相关 DTS 配置如下:

i2c0: i2c@ff040000 {...interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>; // I2C 中断...
};

在 I2C 总线驱动加载过程中,解析并初始化中断配置(只重点关注中断 CPU 亲和性的配置过程):

irq = platform_get_irq(pdev, 0);of_irq_get(dev->dev.of_node, num);...rc = of_irq_parse_one(dev, index, &oirq); /* 解析 DTS 中断配置 */...return irq_create_of_mapping(&oirq);return irq_create_fwspec_mapping(&fwspec);virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);__irq_domain_alloc_irqs(domain, -1, nr_irqs, node, arg, false, NULL)virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node, affinity);virq = __irq_alloc_descs(-1, hint, cnt, node, THIS_MODULE, affinity);ret = alloc_descs(start, cnt, node, affinity, owner);desc = alloc_desc(start + i, node, flags, mask, owner);...

可以看到,解析 DTS 中断配置的过程,最后调用了 alloc_desc(),这里 alloc_desc() 的流程,同前面 4. 硬件架构 CPU 固有 IRQ 中断亲和性 的分析,所以,这里设置的中断亲和性也是系统中所有 CPU 集合

5.1.2 中断芯片层次: GIC v2 中断 CPU 亲和性 初始化

所有的中断 CPU 亲和性的设置,最终都要落实到硬件层次的具体中断芯片才有意义,毕竟中断转发给哪些 CPU 处理,是中断芯片控制的。本文以 GIC v2 中断芯片为例,简要说明中断 CPU 亲和性硬件层次的设置过程。GIC 包含两大部分:DistributorCPU Interface 两大部分,所以中断 CPU 亲和性的设置,也是对它们进行配置。这部分的细节不在本文展开,感兴趣的读者可参考博文 Linux: 中断实现简析 章节 3.2.2 GIC芯片初始化 对函数 gic_dist_init()gic_cpu_init() 的分析:gic_dist_init() 初始化了 Distributorgic_cpu_init() 初始化了 CPU Interface

6. 中断芯片各中断 CPU 亲和性 修改

系统运行过程中,在中断 CPU 亲和性初始化后,也可以通过系统提供的接口对中断 CPU 亲和性进行修改。

Linux 内核提供下面两个文件节点,允许用户空间修改 IRQ 中断的 CPU 亲和性:

/proc/irq/<N>/smp_affinity # IRQ N 的 CPU 亲和性,以十六进制掩码形式配置接口,如 f
/proc/irq/<N>/smp_affinity_list # smp_affinity 列表形式配置接口,如 0-3

其中 <N>Linux 中断号(非硬件中断号),通过对 smp_affinity 的写入来修改中断的 CPU 亲和性。如:

# echo 3 > /proc/irq/<N>/smp_affinity ## 将中断 N 的亲和性设置为 CPU 0,1

其设置流程为:

/* kernel/irq/proc.c */static ssize_t write_irq_affinity(int type, struct file *file,const char __user *buffer, size_t count, loff_t *pos)
{unsigned int irq = (int)(long)PDE_DATA(file_inode(file));cpumask_var_t new_value;int err;/* 新的 CPU 亲和性掩码值 */if (!alloc_cpumask_var(&new_value, GFP_KERNEL))return -ENOMEM;/** @type == 0: /proc/irq/<N>/smp_affinity_list* @type == 1: /proc/irq/<N>/smp_affinity*/if (type) /* @buffer 参数列表形式: 如 0-3 */err = cpumask_parselist_user(buffer, count, new_value);else /* @buffer 参数掩码形式: 如 f */err = cpumask_parse_user(buffer, count, new_value);if (!cpumask_intersects(new_value, cpu_online_mask)) {/** Special case for empty set - allow the architecture code* to set default SMP affinity.*//* 非正常设置,最终会走到 irq_do_set_affinity() */err = irq_select_affinity_usr(irq) ? -EINVAL : count;} else { /* 按用户指定亲和掩码值进行配置 */irq_set_affinity(irq, new_value);err = count;}...
}
/* include/linux/interrupt.h */static inline int
irq_set_affinity(unsigned int irq, const struct cpumask *cpumask)
{return __irq_set_affinity(irq, cpumask, false);
}
/* kernel/irq/manage.c */int __irq_set_affinity(unsigned int irq, const struct cpumask *mask, bool force)
{struct irq_desc *desc = irq_to_desc(irq);...raw_spin_lock_irqsave(&desc->lock, flags);ret = irq_set_affinity_locked(irq_desc_get_irq_data(desc), mask, force);raw_spin_unlock_irqrestore(&desc->lock, flags);...
}int irq_set_affinity_locked(struct irq_data *data, const struct cpumask *mask,bool force)
{struct irq_chip *chip = irq_data_get_irq_chip(data);struct irq_desc *desc = irq_data_to_desc(data);...if (irq_can_move_pcntxt(data)) {ret = irq_do_set_affinity(data, mask, force);} else {...}...
}int irq_do_set_affinity(struct irq_data *data, const struct cpumask *mask,bool force)
{struct irq_desc *desc = irq_data_to_desc(data);struct irq_chip *chip = irq_data_get_irq_chip(data);.../* 在中断芯片 (GIC v2) 设置 中断 的 CPU 亲和性 */ret = chip->irq_set_affinity(data, mask, force); /* gic_set_affinity(), ... */switch (ret) {case IRQ_SET_MASK_OK:case IRQ_SET_MASK_OK_DONE:cpumask_copy(desc->irq_common_data.affinity, mask); /* 更新 中断 的 CPU 亲和性掩码 affinity */case IRQ_SET_MASK_OK_NOCOPY:irq_validate_effective_affinity(data);irq_set_thread_affinity(desc); /* 保存 中断线程的 CPU 亲和性设置 */ret = 0;}...
}
/* drivers/irqchip/irq-gic.c */#ifdef CONFIG_SMP
static int gic_set_affinity(struct irq_data *d, const struct cpumask *mask_val,bool force)
{...if (!force)cpu = cpumask_any_and(mask_val, cpu_online_mask);elsecpu = cpumask_first(mask_val);...gic_lock_irqsave(flags);mask = 0xff << shift;/** 用户可能以为是根据 @mask_val 来设置中断派发配置值,但是代码* 是从 gic_cpu_map[@cpu] 来取派发配置值!!!* gic_cpu_map[] 是每个 CPU 在启动期间初始化好的值,之后* 就不会再改变,它的初始值是只会将中断派发给 BOOT CPU (通常是 CPU 0)。*/bit = gic_cpu_map[cpu] << shift;val = readl_relaxed(reg) & ~mask;writel_relaxed(val | bit, reg);gic_unlock_irqrestore(flags);/* 更新中断亲和性的 effecive_affinity 掩码 */irq_data_update_effective_affinity(d, cpumask_of(cpu));return IRQ_SET_MASK_OK_DONE;
}
#endif
/* include/linux/irq.h */static inline void irq_data_update_effective_affinity(struct irq_data *d,const struct cpumask *m)
{cpumask_copy(d->common->effective_affinity, m);
}
/* kernel/irq/manage.c */void irq_set_thread_affinity(struct irq_desc *desc)
{struct irqaction *action;for_each_action_of_desc(desc, action)if (action->thread)set_bit(IRQTF_AFFINITY, &action->thread_flags);
}

从上面的代码流程分析可以看到,中断 CPU 亲和性的设置,其实也分软件硬件两个层次。软件上,更新了中断的两个掩码 desc->irq_common_data.affinitydesc->irq_common_data.effective_affinity,同时如果中断线程化的话,还会设置相关中断线程的 CPU 亲和性(注意,这里仅仅是记录);而硬件层次是设置中断在中断芯片(GIC v2)上的 CPU 亲和性。

对于中断的 CPU 亲和性设置,还剩下最后一个片段,那就是在中断线程化处理场景,将中断线程绑定到 irq_set_thread_affinity() 记录的 CPU 集合上:

/* kernel/irq/manage.c */request_threaded_irq()retval = __setup_irq(irq, desc, action);if (new->thread_fn && !nested) {ret = setup_irq_thread(new, irq, false);}static int
setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{struct task_struct *t;struct sched_param param = {.sched_priority = MAX_USER_RT_PRIO/2, /* 中断线程使用实时优先级 */};if (!secondary) {t = kthread_create(irq_thread, new, "irq/%d-%s", irq,new->name);} else {t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,new->name);param.sched_priority -= 1;}...sched_setscheduler_nocheck(t, SCHED_FIFO, &param);/** We keep the reference to the task struct even if* the thread dies to avoid that the interrupt code* references an already freed task_struct.*/get_task_struct(t);new->thread = t;/** Tell the thread to set its affinity. This is* important for shared interrupt handlers as we do* not invoke setup_affinity() for the secondary* handlers as everything is already set up. Even for* interrupts marked with IRQF_NO_BALANCE this is* correct as we want the thread to move to the cpu(s)* on which the requesting code placed the interrupt.*/set_bit(IRQTF_AFFINITY, &new->thread_flags);return 0;
}
/* kernel/irq/manage.c *//** Interrupt handler thread*/
static int irq_thread(void *data)
{...irq_thread_check_affinity(desc, action);while (!irq_wait_for_interrupt(action)) {...irq_thread_check_affinity(desc, action);action_ret = handler_fn(desc, action);...}...
}#ifdef CONFIG_SMP
/** Check whether we need to change the affinity of the interrupt thread.*/
static void
irq_thread_check_affinity(struct irq_desc *desc, struct irqaction *action)
{cpumask_var_t mask;bool valid = true;if (!test_and_clear_bit(IRQTF_AFFINITY, &action->thread_flags))return;/** In case we are out of memory we set IRQTF_AFFINITY again and* try again next time*/if (!alloc_cpumask_var(&mask, GFP_KERNEL)) {set_bit(IRQTF_AFFINITY, &action->thread_flags);return;}raw_spin_lock_irq(&desc->lock);/** This code is triggered unconditionally. Check the affinity* mask pointer. For CPU_MASK_OFFSTACK=n this is optimized out.*/if (cpumask_available(desc->irq_common_data.affinity))cpumask_copy(mask, desc->irq_common_data.affinity);elsevalid = false;raw_spin_unlock_irq(&desc->lock);if (valid)set_cpus_allowed_ptr(current, mask); /* 设置 中断线程的 CPU 亲和性 */free_cpumask_var(mask);
}
#else
static inline void
irq_thread_check_affinity(struct irq_desc *desc, struct irqaction *action) { }
#endif

http://www.ppmy.cn/embedded/87428.html

相关文章

scrapy出现OSError: could not get source code错误解决

出现问题如下&#xff1a; Traceback (most recent call last):File "C:\Users\admin\Desktop\crawler_scrapy_us\venv\lib\site-packages\scrapy\utils\defer.py", line 73, in mustbe_deferredresult f(*args, **kw)File "C:\Users\admin\Desktop\crawler_sc…

架构师的36项修炼 学习笔记

架构师的36项修炼 学习笔记 分布式缓存 缓存特点 1.技术简单 2.性能提升明显 3.应用场景多 缓存数据存储 hash表 缓存的关键指标 命中率 缓存失效方式 超时失效 LLT 实时清除 代理缓存 反向代理缓存 多层反向代理缓存 内容分发网络CDN 通读缓存 包括代理缓存…

昇思25天学习打卡营第1天|快速入门实操教程

昇思25天学习打卡营第1天|快速入门实操教程 目录 昇思25天学习打卡营第1天|快速入门实操教程 一、MindSpore内容简介 主要特点&#xff1a; MindSpore的组成部分&#xff1a; 二、入门实操步骤 1. 安装必要的依赖包 2. 下载并处理数据集 3. 构建网络模型 4. 训练模型…

PyTorch和TensorFlow概念及对比

PyTorch和TensorFlow是两个流行的深度学习框架&#xff0c;用于构建和训练机器学习和深度学习模型。它们各自有一些独特的特点和优点&#xff1a; 一 、PyTorch 动态计算图&#xff1a; PyTorch使用动态计算图&#xff08;Dynamic Computation Graph&#xff09;&#xff0c;…

Aider + Llama 3.1:无需编码开发全栈APP

Llama 3.1在代码生成方面的卓越表现 在代码生成领域&#xff0c;Llama 3.1的表现尤为出色&#xff0c;几乎成为了开源模型中的佼佼者。它不仅在代码自动化和生成方面表现突出&#xff0c;还可以作为AI编程助手&#xff0c;帮助调试代码和开发完整的应用程序。在多个基准测试中…

手机空号过滤批量查询的意义及方法

手机空号过滤批量查询是现代营销和通信管理中常用的技术手段&#xff0c;旨在通过批量处理手机号码&#xff0c;筛选出活跃号码和空号等无效号码&#xff0c;以提高营销效率和减少不必要的通信成本。以下是关于手机空号过滤批量查询的详细解答&#xff1a; 一、手机空号过滤批…

CentOS 7.x 的 YUM 仓库问题

背景 CentOS Linux 7 的生命周期&#xff08;EOL&#xff09;已经于 2024 年 6 月 30 日终止这意味着 CentOS 7.x 的官方镜像站点将不再提供服务&#xff0c;导致在使用 yum 安装或更新程序时可能会遇到 错误。本文将介绍如何解决这一问题&#xff0c;使得你可以继续在 CentOS…

网络安全Sirius

关于Sirius Sirius是一款功能强大的通用漏洞扫描工具&#xff0c;该工具可以帮助广大研究人员在大多数场景下识别和验证应用程序中存在的安全漏洞。 现如今&#xff0c;信息安全社区仍然是收集网络安全情报数据最佳且最有利的来源&#xff0c;而且社区本身的表现经常会优于商…