[patch V4 15/15] genirq/proc: Speed up /proc/interrupts iteration

Thomas Gleixner
2026-03-31T07:26:43Z

Reading /proc/interrupts iterates over the interrupt number space one by
one and looks up the descriptors one by one. That's just a waste of time.

When CONFIG_GENERIC_IRQ_SHOW is enabled this can utilize the maple tree and
cache the descriptor pointer efficiently for the sequence file operations.

Implement a CONFIG_GENERIC_IRQ_SHOW specific version in the core code and
leave the fs/proc/ variant for the legacy architectures which ignore generic
code.

This reduces the time wasted for looking up the next record significantly.

Signed-off-by: Thomas Gleixner <tglx@kernel.org>
---
fs/proc/Makefile | 4 +
kernel/irq/proc.c | 118 ++++++++++++++++++++++++++++++++++++++++++------------
2 files changed, 95 insertions(+), 27 deletions(-)
--- a/fs/proc/Makefile
+++ b/fs/proc/Makefile
@@ -16,7 +16,9 @@ proc-y += cmdline.o
proc-y += consoles.o
proc-y += cpuinfo.o
proc-y += devices.o
-proc-y += interrupts.o
+ifneq ($(CONFIG_GENERIC_IRQ_SHOW),y)
+proc-y += interrupts.o
+endif
proc-y += loadavg.o
proc-y += meminfo.o
proc-y += stat.o
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -451,6 +451,8 @@ void irq_proc_update_valid(struct irq_de

#ifdef CONFIG_GENERIC_IRQ_SHOW

+#define ARCH_PROC_IRQDESC ((void *)0x00001111)
+
int __weak arch_show_interrupts(struct seq_file *p, int prec)
{
return 0;
@@ -459,6 +461,7 @@ int __weak arch_show_interrupts(struct s
static DEFINE_RAW_SPINLOCK(irq_proc_constraints_lock);

static struct irq_proc_constraints {
+ bool print_header;
unsigned int num_prec;
unsigned int chip_width;
} irq_proc_constraints __read_mostly = {
@@ -531,34 +534,28 @@ void irq_proc_emit_counts(struct seq_fil
irq_proc_emit_zero_counts(p, zeros);
}

-int show_interrupts(struct seq_file *p, void *v)
+static int irq_seq_show(struct seq_file *p, void *v)
{
- unsigned int chip_width = READ_ONCE(irq_proc_constraints.chip_width);
- unsigned int prec = READ_ONCE(irq_proc_constraints.num_prec);
- int i = *(loff_t *) v, j;
+ struct irq_proc_constraints *constr = p->private;
+ struct irq_desc *desc = v;
struct irqaction *action;
- struct irq_desc *desc;
-
- if (i > ACTUAL_NR_IRQS)
- return 0;

- if (i == ACTUAL_NR_IRQS)
- return arch_show_interrupts(p, prec);
+ if (desc == ARCH_PROC_IRQDESC)
+ return arch_show_interrupts(p, constr->num_prec);

- /* print header and calculate the width of the first column */
- if (i == 0) {
- seq_printf(p, "%*s", prec + 8, "");
- for_each_online_cpu(j)
- seq_printf(p, "CPU%-8d", j);
+ /* print header for the first interrupt indicated by !p>private */
+ if (constr->print_header) {
+ unsigned int cpu;
+
+ seq_printf(p, "%*s", constr->num_prec + 8, "");
+ for_each_online_cpu(cpu)
+ seq_printf(p, "CPU%-8d", cpu);
seq_putc(p, '\n');
+ constr->print_header = false;
}

- guard(rcu)();
- desc = irq_to_desc(i);
- if (!desc || !irq_settings_proc_valid(desc))
- return 0;
-
- seq_printf(p, "%*d:", prec, i);
+ seq_put_decimal_ull_width(p, "", irq_desc_get_irq(desc), constr->num_prec);
+ seq_putc(p, ':');

/*
* Always output per CPU interrupts. Output device interrupts only when
@@ -578,18 +575,18 @@ int show_interrupts(struct seq_file *p,
if (desc->irq_data.chip->irq_print_chip)
desc->irq_data.chip->irq_print_chip(&desc->irq_data, p);
else if (desc->irq_data.chip->name)
- seq_printf(p, "%-*s", chip_width, desc->irq_data.chip->name);
+ seq_printf(p, "%-*s", constr->chip_width, desc->irq_data.chip->name);
else
- seq_printf(p, "%-*s", chip_width, "-");
+ seq_printf(p, "%-*s", constr->chip_width, "-");
} else {
- seq_printf(p, "%-*s", chip_width, "None");
+ seq_printf(p, "%-*s", constr->chip_width, "None");
}

seq_putc(p, ' ');
if (desc->irq_data.domain)
- seq_put_decimal_ull_width(p, "", desc->irq_data.hwirq, prec);
+ seq_put_decimal_ull_width(p, "", desc->irq_data.hwirq, constr->num_prec);
else
- seq_printf(p, " %*s", prec, "");
+ seq_printf(p, " %*s", constr->num_prec, "");

if (IS_ENABLED(CONFIG_GENERIC_IRQ_SHOW_LEVEL))
seq_printf(p, " %-8s", irqd_is_level_type(&desc->irq_data) ? "Level" : "Edge");
@@ -607,4 +604,73 @@ int show_interrupts(struct seq_file *p,
seq_putc(p, '\n');
return 0;
}
+
+static void *irq_seq_next_desc(loff_t *pos)
+{
+ if (*pos > total_nr_irqs)
+ return NULL;
+
+ guard(rcu)();
+ for (;;) {
+ struct irq_desc *desc = irq_find_desc_at_or_after((unsigned int) *pos);
+
+ if (desc) {
+ *pos = irq_desc_get_irq(desc);
+ /*
+ * If valid for output try to acquire a reference count
+ * on the descriptor so that it can't be freed after
+ * dropping RCU read lock on return.
+ */
+ if (irq_settings_proc_valid(desc) && irq_desc_get_ref(desc))
+ return desc;
+ (*pos)++;
+ } else {
+ *pos = total_nr_irqs;
+ return ARCH_PROC_IRQDESC;
+ }
+ }
+}
+
+static void *irq_seq_start(struct seq_file *f, loff_t *pos)
+{
+ if (!*pos) {
+ struct irq_proc_constraints *constr = f->private;
+
+ constr->num_prec = READ_ONCE(irq_proc_constraints.num_prec);
+ constr->chip_width = READ_ONCE(irq_proc_constraints.chip_width);
+ constr->print_header = true;
+ }
+ return irq_seq_next_desc(pos);
+}
+
+static void *irq_seq_next(struct seq_file *f, void *v, loff_t *pos)
+{
+ if (v && v != ARCH_PROC_IRQDESC)
+ irq_desc_put_ref(v);
+
+ (*pos)++;
+ return irq_seq_next_desc(pos);
+}
+
+static void irq_seq_stop(struct seq_file *f, void *v)
+{
+ if (v && v != ARCH_PROC_IRQDESC)
+ irq_desc_put_ref(v);
+}
+
+static const struct seq_operations irq_seq_ops = {
+ .start = irq_seq_start,
+ .next = irq_seq_next,
+ .stop = irq_seq_stop,
+ .show = irq_seq_show,
+};
+
+static int __init irq_proc_init(void)
+{
+ proc_create_seq_private("interrupts", 0, NULL, &irq_seq_ops,
+ sizeof(irq_proc_constraints), NULL);
+ return 0;
+}
+fs_initcall(irq_proc_init);
+
#endif