小张学linux内核:一.中断子系统

2023-10-10 22:10

本文主要是介绍小张学linux内核:一.中断子系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

cpu中断模式

1.arm异常向量表

arm处理器有7种模式:
在这里插入图片描述
当中断发生时,进入irq模式。cpu pc值跳转到异常象量表的irq向量执行。
arm中异常象量表:

异常类型偏移地址(低)偏移地址(高)
复 位0x000000000xffff0000
未定义指令0x000000040xffff0004
软中断0x000000080xffff0008
预取指令终0x0000000c0xffff000c
数据终止0x000000100xffff0010
保留0x000000140xffff0014
中断请求(IRQ)0x000000180xffff0018
快速中断请求(FIQ)0x0000001c0xffff001c

注:异常象量表地址的高低是由协处理器cp15的c1寄存器中V 位(bit[13])控制,v=0:低地址;v=1:高地址。

linux源码中 arm的汇编代码的处理

已sc2440为例,来看看linux4.19中源码的处理:
arch/arm/kernel/arm/entry-armv.S中:
异常向量表:

	.section .vectors, "ax", %progbits
.L__vectors_start:W(b)	vector_rstW(b)	vector_undW(ldr)	pc, .L__vectors_start + 0x1000W(b)	vector_pabtW(b)	vector_dabtW(b)	vector_addrexcptnW(b)	vector_irqW(b)	vector_fiq.data.align	2

.section .vectors, “ax”, %progbits
注:ax表示权限,ax是 allocation execute的缩写,表示该节区可分配并且可执行;progbits是type
向量表放在段vector中。
来看连接脚本arch/arm/kernel/arm/vmlinux.lds.S:

#ifdef CONFIG_STRICT_KERNEL_RWX. = ALIGN(1<<SECTION_SHIFT);
#else. = ALIGN(PAGE_SIZE);
#endif__init_begin = .;ARM_VECTORSINIT_TEXT_SECTION(8).exit.text : {ARM_EXIT_KEEP(EXIT_TEXT)}.init.proc.info : {ARM_CPU_DISCARD(PROC_INFO)}.init.arch.info : {__arch_info_begin = .;*(.arch.info.init)__arch_info_end = .;}

ARM_VECTORS是向量表的段,在vmlinux.lds.h中定义

#define ARM_VECTORS							\__vectors_start = .;						\.vectors 0xffff0000 : AT(__vectors_start) {			\*(.vectors)						\}								\. = __vectors_start + SIZEOF(.vectors);				\__vectors_end = .;						\\__stubs_start = .;						\.stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) {		\*(.stubs)						\}								\. = __stubs_start + SIZEOF(.stubs);				\__stubs_end = .;						\\PROVIDE(vector_fiq_offset = vector_fiq - ADDR(.vectors));

linux中断向量初始化时会用到__vectors_start这些变量,搬移向量。
arch/arm/kernel/traps.c:early_trap_init()函数

void __init early_trap_init(void *vectors_base)
{
#ifndef CONFIG_CPU_V7Munsigned long vectors = (unsigned long)vectors_base;extern char __stubs_start[], __stubs_end[];extern char __vectors_start[], __vectors_end[];unsigned i;vectors_page = vectors_base;/** Poison the vectors page with an undefined instruction.  This* instruction is chosen to be undefined for both ARM and Thumb* ISAs.  The Thumb version is an undefined instruction with a* branch back to the undefined instruction.*/for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)((u32 *)vectors_base)[i] = 0xe7fddef1;/** Copy the vectors, stubs and kuser helpers (in entry-armv.S)* into the vector page, mapped at 0xffff0000, and ensure these* are visible to the instruction stream.*/memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);kuser_init(vectors_base);flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
#else /* ifndef CONFIG_CPU_V7M *//** on V7-M there is no need to copy the vector table to a dedicated* memory area. The address is configurable and so a table in the kernel* image can be used.*/
#endif
}

再回到向量vector_irq,vector_irq是在哪里定义的呢?
看代码,依旧文件arch/arm/kernel/arm/entry-armv.S:
我们在entry-armv.S中搜不到vector_irq。其实它是通过宏vector_stub来定义的:

vector_stub	irq, IRQ_MODE, 4.long	__irq_usr			@  0  (USR_26 / USR_32).long	__irq_invalid			@  1  (FIQ_26 / FIQ_32).long	__irq_invalid			@  2  (IRQ_26 / IRQ_32).long	__irq_svc			@  3  (SVC_26 / SVC_32).long	__irq_invalid			@  4.long	__irq_invalid			@  5.long	__irq_invalid			@  6.long	__irq_invalid			@  7.long	__irq_invalid			@  8.long	__irq_invalid			@  9.long	__irq_invalid			@  a.long	__irq_invalid			@  b.long	__irq_invalid			@  c.long	__irq_invalid			@  d.long	__irq_invalid			@  e.long	__irq_invalid			@  f

宏vector_stub

.macro	vector_stub, name, mode, correction=0.align	5vector_\name:.if \correctionsub	lr, lr, #\correction.endif@@ Save r0, lr_<exception> (parent PC) and spsr_<exception>@ (parent CPSR)@stmia	sp, {r0, lr}		@ save r0, lrmrs	lr, spsrstr	lr, [sp, #8]		@ save spsr@@ Prepare for SVC32 mode.  IRQs remain disabled.@mrs	r0, cpsreor	r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)msr	spsr_cxsf, r0@@ the branch table must immediately follow this code@and	lr, lr, #0x0fTHUMB(	adr	r0, 1f			)THUMB(	ldr	lr, [r0, lr, lsl #2]	)mov	r0, spARM(	ldr	lr, [pc, lr, lsl #2]	)movs	pc, lr			@ branch to handler in SVC mode
ENDPROC(vector_\name)

vector_stub宏就是用来定义这些向量的,vector_fiq等都是通过他来定义的。linux中只有svc和user模式,我们看到vector_stub的工作其实是在保存现场并进入svc模式,并根据当前进入异常之前的模式,选择跳转到相应的handle去。就irq模式而言,如果进入中断前是在内核态,svc模式,则执行__irq_svc;用户态则执行__irq_usr。
我们再追溯__irq_svc和__irq_usr在何处定义?

__irq_svc:svc_entryirq_handler#ifdef CONFIG_PREEMPTldr	r8, [tsk, #TI_PREEMPT]		@ get preempt countldr	r0, [tsk, #TI_FLAGS]		@ get flagsteq	r8, #0				@ if preempt count != 0movne	r0, #0				@ force flags to 0tst	r0, #_TIF_NEED_RESCHEDblne	svc_preempt
#endifsvc_exit r5, irq = 1			@ return from exceptionUNWIND(.fnend		)
ENDPROC(__irq_svc).........
__irq_usr:usr_entrykuser_cmpxchg_checkirq_handlerget_thread_info tskmov	why, #0b	ret_to_user_from_irqUNWIND(.fnend		)
ENDPROC(__irq_usr)

svc和usr模式最终都调用irq_handler执行中断处理。
irq_handler宏:

/** Interrupt handling.*/.macro	irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLERldr	r1, =handle_arch_irqmov	r0, spbadr	lr, 9997fldr	pc, [r1]
#elsearch_irq_handler_default
#endif
9997:.endm

此时出现两个分支:handle_arch_irq和默认的处理arch_irq_handler_default。如果定义CONFIG_GENERIC_IRQ_MULTI_HANDLER,则是handle_arch_irq,顾名思义,就是多个handler,该宏在芯片厂商的config文件中定义。使用gic的芯片大多数都走这个分支,有irq_domain的知识,后面在说。
先说默认分支:
arch_irq_handler_default不在arch/arm/kernel中定义,而是在arch/arm/include/entry-macro-multi.S中定义:

	.macro	arch_irq_handler_defaultget_irqnr_preamble r6, lr
1:	get_irqnr_and_base r0, r2, r6, lrmovne	r1, sp@@ routine called with r0 = irq number, r1 = struct pt_regs *@badrne	lr, 1bbne	asm_do_IRQ

get_irqnr_and_base 获取硬件中断号:

在这里插入代码片

最终跳到 asm_do_IRQ,这是c函数的入口。
同样是架构相关的,在arch/arm/kernel/irq.c:

/** asm_do_IRQ is the interface to be used from assembly code.*/
asmlinkage void __exception_irq_entry
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{handle_IRQ(irq, regs);
}

进入c函数后,就进入linux对中断框架了下面再说。
再看mutli handler这个分支。handle_arch_irq这是一个全局变量。
体系无关的文件kernel/irq/handle.c中定义:

#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
void (*handle_arch_irq)(struct pt_regs *) __ro_after_init;
#endif

在何处赋值?
kernel/irq/handle.c中set_handle_irq()函数:

#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{if (handle_arch_irq)return -EBUSY;handle_arch_irq = handle_irq;return 0;
}
#endif

set_handle_irq()在何处调用,这是与中断控制器相关的地方了,以gic为例,在gic的驱动文件drivers/irq_chip.c:

static int __init __gic_init_bases(struct gic_chip_data *gic,int irq_start,struct fwnode_handle *handle)
{char *name;int i, ret;if (WARN_ON(!gic || gic->domain))return -EINVAL;if (gic == &gic_data[0]) {/** Initialize the CPU interface map to all CPUs.* It will be refined as each CPU probes its ID.* This is only necessary for the primary GIC.*/for (i = 0; i < NR_GIC_CPU_IF; i++)gic_cpu_map[i] = 0xff;
#ifdef CONFIG_SMPset_smp_cross_call(gic_raise_softirq);
#endifcpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,"irqchip/arm/gic:starting",gic_starting_cpu, NULL);set_handle_irq(gic_handle_irq);     /*此处设置handle_arch_irq*/if (static_branch_likely(&supports_deactivate_key))pr_info("GIC: Using split EOI/Deactivate mode\n");}if (static_branch_likely(&supports_deactivate_key) && gic == &gic_data[0]) {name = kasprintf(GFP_KERNEL, "GICv2");gic_init_chip(gic, NULL, name, true);} else {name = kasprintf(GFP_KERNEL, "GIC-%d", (int)(gic-&gic_data[0]));gic_init_chip(gic, NULL, name, false);}ret = gic_init_bases(gic, irq_start, handle);if (ret)kfree(name);return ret;
}

调用关系:
gic_init
->__gic_init_bases
->-> set_handle_irq
gic_init的调用
最终的处理函数gic_handle_irq()

IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
IRQCHIP_DECLARE(arm11mp_gic, "arm,arm11mp-gic", gic_of_init);
IRQCHIP_DECLARE(arm1176jzf_dc_gic, "arm,arm1176jzf-devchip-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);
IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);
IRQCHIP_DECLARE(pl390, "arm,pl390", gic_of_init);

IRQCHIP_DECLARE 生成init段,或者通过kernel_init里的do_initcall_level函数会调用init段里函数。名称“arm,gic-400”是用来匹配设备树的。

linux中断子系统

linux中断框架
在这里插入图片描述
注:图片摘自嵌入式linux中文站

中断流控层:
由linux内核提供,所谓中断流控是指合理并正确地处理连续发生的中断,比如一个中断在处理中,同一个中断再次到达时如何处理,何时应该屏蔽中断,何时打开中断,何时回应中断控制器等一系列的操作。该层实现了与体系和硬件无关的中断流控处理操作,它针对不同的中断电气类型(level,edge…),实现了对应的标准中断流控处理函数,在这些处理函数中,最终会把中断控制权传递到驱动程序注册中断时传入的处理函数或者是中断线程中。
目前的通用中断子系统实现了以下这些标准流控回调函数,这些函数都定义在:kernel/irq/chip.c中,
handle_simple_irq 用于简易流控处理;
handle_level_irq 用于电平触发中断的流控处理;
handle_edge_irq 用于边沿触发中断的流控处理;
handle_fasteoi_irq 用于需要响应eoi的中断控制器;
handle_percpu_irq 用于只在单一cpu响应的中断;
handle_nested_irq 用于处理使用线程的嵌套中断;
以下这个序列图展示了整个通用中断子系统的中断响应过程,flow_handle一栏就是中断流控层的生命周期
–摘自《嵌入式linux中文站》

1.中断驱动接口层

** request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char devname, void dev_id)

irq是要申请的硬件中断号。
handler是向系统注册的中断处理函数。
irqflags是中断处理的属性,一般用来指定相应的中断流控。
devname设置中断名称,通常是在cat /proc/interrupts中可以看到此名称。
dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。
enable_irq(unsigned int irq)
用来打开中断。
disable_irq(unsigned int irq)
用来关闭中断。
irq_set_chip(irq, *chip)
设置中断控制器
irq_set_handler(irq,handle)
设置中断流控

request_irq()实现在include/linux/interrupt.h

static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
{return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

request_threaded_irq() 在kernel/irq/manage.c

int request_threaded_irq(unsigned int irq, irq_handler_t handler,irq_handler_t thread_fn, unsigned long irqflags,const char *devname, void *dev_id)
{struct irqaction *action;struct irq_desc *desc;int retval;if (irq == IRQ_NOTCONNECTED)return -ENOTCONN;/** Sanity-check: shared interrupts must pass in a real dev-ID,* otherwise we'll have trouble later trying to figure out* which interrupt is which (messes up the interrupt freeing* logic etc).** Also IRQF_COND_SUSPEND only makes sense for shared interrupts and* it cannot be set along with IRQF_NO_SUSPEND.*/if (((irqflags & IRQF_SHARED) && !dev_id) ||(!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))return -EINVAL;desc = irq_to_desc(irq);         /*得到irq_desc*/if (!desc)return -EINVAL;if (!irq_settings_can_request(desc) ||WARN_ON(irq_settings_is_per_cpu_devid(desc)))return -EINVAL;if (!handler) {if (!thread_fn)return -EINVAL;handler = irq_default_primary_handler;}action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);if (!action)return -ENOMEM;action->handler = handler;action->thread_fn = thread_fn;action->flags = irqflags;action->name = devname;action->dev_id = dev_id;retval = irq_chip_pm_get(&desc->irq_data);if (retval < 0) {kfree(action);return retval;}retval = __setup_irq(irq, desc, action);if (retval) {irq_chip_pm_put(&desc->irq_data);kfree(action->secondary);kfree(action);}#ifdef CONFIG_DEBUG_SHIRQ_FIXMEif (!retval && (irqflags & IRQF_SHARED)) {/** It's a shared IRQ -- the driver ought to be prepared for it* to happen immediately, so let's make sure....* We disable the irq to make sure that a 'real' IRQ doesn't* run in parallel with our fake.*/unsigned long flags;disable_irq(irq);local_irq_save(flags);handler(irq, dev_id);local_irq_restore(flags);enable_irq(irq);}
#endifreturn retval;
}
EXPORT_SYMBOL(request_threaded_irq);

分配 irqaction 往 irq_desc的action链表中注册。
来看irq_to_desc()函数在文件kernel/irq/irq_desc.c中

#ifdef CONFIG_SPARSE_IRQ
static RADIX_TREE(irq_desc_tree, GFP_KERNEL);  
struct irq_desc *irq_to_desc(unsigned int irq)
{return radix_tree_lookup(&irq_desc_tree, irq);
}
EXPORT_SYMBOL(irq_to_desc);
#else
struct irq_desc *irq_to_desc(unsigned int irq)
{return (irq < NR_IRQS) ? irq_desc + irq : NULL;
}
EXPORT_SYMBOL(irq_to_desc);
#endif
···
配置了 CONFIG_SPARSE_IRQ,是基数树来实现irq_desc_tree的,没有的话是老的实现irq_desc是个全局数组irq_desc;

2. cpu中断处理流程和irq_domain

中断发生后,如何找到irq_desc并执行的呢?
我们接着第一章的思路走下去,C语言中断处理入口:

  1. 默认入口:asm_do_IRQ()
    arch/arm/kernle/irq.c中
void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{__handle_domain_irq(NULL, irq, false, regs);      /*中断域NULL*/
}/** asm_do_IRQ is the interface to be used from assembly code.*/
asmlinkage void __exception_irq_entry
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{handle_IRQ(irq, regs);
}

multi handler 分支

在multi handler分支下中断的handler是gic_handle_irq()
drivers/irqchip/irq-gic.c

static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{u32 irqstat, irqnr;struct gic_chip_data *gic = &gic_data[0];void __iomem *cpu_base = gic_data_cpu_base(gic);do {irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);irqnr = irqstat & GICC_IAR_INT_ID_MASK;if (likely(irqnr > 15 && irqnr < 1020)) {if (static_branch_likely(&supports_deactivate_key))writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);isb();handle_domain_irq(gic->domain, irqnr, regs);       /*调用handle_domain_irq真正处理*/continue;}if (irqnr < 16) {writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);if (static_branch_likely(&supports_deactivate_key))writel_relaxed(irqstat, cpu_base + GIC_CPU_DEACTIVATE);
#ifdef CONFIG_SMP/** Ensure any shared data written by the CPU sending* the IPI is read after we've read the ACK register* on the GIC.** Pairs with the write barrier in gic_raise_softirq*/smp_rmb();handle_IPI(irqnr, regs);
#endifcontinue;}break;} while (1);
}

handle_domain_irq()是__handle_domain_irq()的封装
文件include/linux/irqdesc.h中

static inline int handle_domain_irq(struct irq_domain *domain,unsigned int hwirq, struct pt_regs *regs)
{return __handle_domain_irq(domain, hwirq, true, regs);
}

__handle_domain_irq() 在kernel/irq/irq_desc.c

int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,bool lookup, struct pt_regs *regs)
{struct pt_regs *old_regs = set_irq_regs(regs);unsigned int irq = hwirq;int ret = 0;irq_enter();#ifdef CONFIG_IRQ_DOMAINif (lookup)irq = irq_find_mapping(domain, hwirq);    /*中断域中硬件中断号和软件中断号的映射*/
#endif/** Some hardware gives randomly wrong interrupts.  Rather* than crashing, do something sensible.*/if (unlikely(!irq || irq >= nr_irqs)) {ack_bad_irq(irq);ret = -EINVAL;} else {generic_handle_irq(irq);}irq_exit();set_irq_regs(old_regs);return ret;
}
#endif

set_irq_regs保护中断发生时的寄存器现场,是一个per_cpu变量__irq_regs
include/asm-generic/irq_regs.h中

DECLARE_PER_CPU(struct pt_regs *, __irq_regs);static inline struct pt_regs *get_irq_regs(void)
{return __this_cpu_read(__irq_regs);
}static inline struct pt_regs *set_irq_regs(struct pt_regs *new_regs)
{struct pt_regs *old_regs;old_regs = __this_cpu_read(__irq_regs);__this_cpu_write(__irq_regs, new_regs);return old_regs;
}

若配置了CONFIG_IRQ_DOMAIN则irq_find_mapping()根据中断域irq_domain完成硬件中断号和软件中断号的映射。中断域irq_domain的概念后面再将。
然后调用generic_handle_irq(irq),此时的irq已是软件irq号。
kernel/irq/irqdesc.c中

int generic_handle_irq(unsigned int irq)
{struct irq_desc *desc = irq_to_desc(irq);if (!desc)return -EINVAL;generic_handle_irq_desc(desc);return 0;
}

generic_handle_irq_desc()在include/linux/irqdesc.h中,是一内联函数:

static inline void generic_handle_irq_desc(struct irq_desc *desc)
{desc->handle_irq(desc);
}

generic_handle_irq_desc()调用desc的handle_irq()函数,该函数是上面所说的流控函数:

struct irq_desc {struct irq_common_data	irq_common_data;struct irq_data		irq_data;unsigned int __percpu	*kstat_irqs;irq_flow_handler_t	handle_irq;      /*流控函数*/
#ifdef CONFIG_IRQ_PREFLOW_FASTEOIirq_preflow_handler_t	preflow_handler;
#endifstruct irqaction	*action;	/* IRQ action list */...
}

那该handle_irq是在何处设置的呢?又是怎样调到我们设置的irq_action函数的呢?
1.handle_irq在何处设置?
这个是中断控制器驱动初始化时设置,如gic驱动:
设置handle_irq的函数:kernel/irq/chip.c:

static void
__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,int is_chained, const char *name)
{if (!handle) {handle = handle_bad_irq;} else {struct irq_data *irq_data = &desc->irq_data;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY/** With hierarchical domains we might run into a* situation where the outermost chip is not yet set* up, but the inner chips are there.  Instead of* bailing we install the handler, but obviously we* cannot enable/startup the interrupt at this point.*/while (irq_data) {if (irq_data->chip != &no_irq_chip)break;/** Bail out if the outer chip is not set up* and the interrrupt supposed to be started* right away.*/if (WARN_ON(is_chained))return;/* Try the parent */irq_data = irq_data->parent_data;}
#endifif (WARN_ON(!irq_data || irq_data->chip == &no_irq_chip))return;}/* Uninstall? */if (handle == handle_bad_irq) {if (desc->irq_data.chip != &no_irq_chip)mask_ack_irq(desc);irq_state_set_disabled(desc);if (is_chained)desc->action = NULL;desc->depth = 1;}desc->handle_irq = handle;desc->name = name;if (handle != handle_bad_irq && is_chained) {unsigned int type = irqd_get_trigger_type(&desc->irq_data);/** We're about to start this interrupt immediately,* hence the need to set the trigger configuration.* But the .set_type callback may have overridden the* flow handler, ignoring that we're dealing with a* chained interrupt. Reset it immediately because we* do know better.*/if (type != IRQ_TYPE_NONE) {__irq_set_trigger(desc, type);desc->handle_irq = handle;      /*设置handle_irq */}irq_settings_set_noprobe(desc);irq_settings_set_norequest(desc);irq_settings_set_nothread(desc);desc->action = &chained_action;irq_activate_and_startup(desc, IRQ_RESEND);}
}

irq_set_handler
__irq_set_handler
__irq_do_set_handler
irq_set_handler设置流控函数在include/llinux/irq.h中,内联函数:

static inline void
irq_set_handler(unsigned int irq, irq_flow_handler_t handle)
{__irq_set_handler(irq, handle, 0, NULL);
}

同时kernel/irq/irqdomain.c中

#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY   /*域层级*/
void irq_domain_set_info(struct irq_domain *domain, unsigned int virq,irq_hw_number_t hwirq, struct irq_chip *chip,void *chip_data, irq_flow_handler_t handler,void *handler_data, const char *handler_name)
{irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data);__irq_set_handler(virq, handler, 0, handler_name);irq_set_handler_data(virq, handler_data);
}

gic驱动drivers/irqchip/irq-gic.c中:

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,irq_hw_number_t hw)
{struct gic_chip_data *gic = d->host_data;if (hw < 32) {irq_set_percpu_devid(irq);irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,handle_percpu_devid_irq, NULL, NULL);irq_set_status_flags(irq, IRQ_NOAUTOEN);} else {irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,handle_fasteoi_irq, NULL, NULL);irq_set_probe(irq);irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));}return 0;
}

可以看到,最终设置的流控函数:
handle_percpu_devid_irq()或
handle_fasteoi_irq()不同的中断流控函数:
通用函数,在文件kernel/irq/chip.c中:

void handle_percpu_devid_irq(struct irq_desc *desc)
{struct irq_chip *chip = irq_desc_get_chip(desc);struct irqaction *action = desc->action;unsigned int irq = irq_desc_get_irq(desc);irqreturn_t res;/** PER CPU interrupts are not serialized. Do not touch* desc->tot_count.*/__kstat_incr_irqs_this_cpu(desc);if (chip->irq_ack)chip->irq_ack(&desc->irq_data);if (likely(action)) {trace_irq_handler_entry(irq, action);/*执行irq_action的handler*/res = action->handler(irq, raw_cpu_ptr(action->percpu_dev_id));trace_irq_handler_exit(irq, action, res);} else {unsigned int cpu = smp_processor_id();bool enabled = cpumask_test_cpu(cpu, desc->percpu_enabled);if (enabled)irq_percpu_disable(desc, cpu);pr_err_once("Spurious%s percpu IRQ%u on CPU%u\n",enabled ? " and unmasked" : "", irq, cpu);}if (chip->irq_eoi)chip->irq_eoi(&desc->irq_data);
}

可知,irq_action->handle是通过irq_desc的流控函数调用的。

irq_domain的概念

从上面可以看出,无论是default handler还是multi handler,都存在一个irq domain的概念,即中断域。
__handle_domain_irq() <kernel/irq/irqdesc.c>
irq = irq_find_mapping(domain, hwirq);
调用irq_find_mapping将硬件中断号转换成软件中断号。
硬件中可以有多个gic级联,那么每一个gic就是一个irq_domain;与cpu直连的gic,下级的gic的中断全通过cpu直连的gic的某一个通道通知cpu,当该中断发生时我们需要再次去查该gic内的硬件中断号,在映射成软件中断号,而软件中断号是同一管理的。
irq_find_mapping <kernel/irq/irqdomain.c>

unsigned int irq_find_mapping(struct irq_domain *domain,irq_hw_number_t hwirq)
{struct irq_data *data;/* Look for default domain if nececssary */if (domain == NULL)domain = irq_default_domain;if (domain == NULL)return 0;/*直连的irq,不用映射*/if (hwirq < domain->revmap_direct_max_irq) {data = irq_domain_get_irq_data(domain, hwirq);if (data && data->hwirq == hwirq)return hwirq;}/*线性映射*//* Check if the hwirq is in the linear revmap. */if (hwirq < domain->revmap_size)return domain->linear_revmap[hwirq];/*基数树*/rcu_read_lock();data = radix_tree_lookup(&domain->revmap_tree, hwirq);rcu_read_unlock();return data ? data->irq : 0;
}

那么irq_domain是在何处初始化的呢?
这个是跟中断控制器芯片相关的,是在中断控制器初始化时,设置irq_domain, 简单理解irq_domain就是提供一个硬件中断到软件irq的映射

void __init gic_init(unsigned int gic_nr, int irq_start,
void __iomem *dist_base, void __iomem *cpu_base)
{
struct gic_chip_data *gic;

if (WARN_ON(gic_nr >= CONFIG_ARM_GIC_MAX_NR))return;/** Non-DT/ACPI systems won't run a hypervisor, so let's not* bother with these...*/
static_branch_disable(&supports_deactivate_key);gic = &gic_data[gic_nr];
gic->raw_dist_base = dist_base;
gic->raw_cpu_base = cpu_base;__gic_init_bases(gic, irq_start, NULL);
}

irq_domain的添加
gic_init
__gic_init_bases
gic_init_bases

static int gic_init_bases(struct gic_chip_data *gic, int irq_start,struct fwnode_handle *handle)
{irq_hw_number_t hwirq_base;int gic_irqs, irq_base, ret;if (IS_ENABLED(CONFIG_GIC_NON_BANKED) && gic->percpu_offset) {/* Frankein-GIC without banked registers... */unsigned int cpu;gic->dist_base.percpu_base = alloc_percpu(void __iomem *);gic->cpu_base.percpu_base = alloc_percpu(void __iomem *);if (WARN_ON(!gic->dist_base.percpu_base ||!gic->cpu_base.percpu_base)) {ret = -ENOMEM;goto error;}for_each_possible_cpu(cpu) {u32 mpidr = cpu_logical_map(cpu);u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);unsigned long offset = gic->percpu_offset * core_id;*per_cpu_ptr(gic->dist_base.percpu_base, cpu) =gic->raw_dist_base + offset;*per_cpu_ptr(gic->cpu_base.percpu_base, cpu) =gic->raw_cpu_base + offset;}gic_set_base_accessor(gic, gic_get_percpu_base);} else {/* Normal, sane GIC... */WARN(gic->percpu_offset,"GIC_NON_BANKED not enabled, ignoring %08x offset!",gic->percpu_offset);gic->dist_base.common_base = gic->raw_dist_base;gic->cpu_base.common_base = gic->raw_cpu_base;gic_set_base_accessor(gic, gic_get_common_base);}/** Find out how many interrupts are supported.* The GIC only supports up to 1020 interrupt sources.*/gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;gic_irqs = (gic_irqs + 1) * 32;if (gic_irqs > 1020)gic_irqs = 1020;gic->gic_irqs = gic_irqs;if (handle) {		/* DT/ACPI */gic->domain = irq_domain_create_linear(handle, gic_irqs,&gic_irq_domain_hierarchy_ops,gic);        /*gic_init传入的handle为空未走该分支*/} else {		/* Legacy support *//** For primary GICs, skip over SGIs.* For secondary GICs, skip over PPIs, too.*/if (gic == &gic_data[0] && (irq_start & 31) > 0) {hwirq_base = 16;if (irq_start != -1)irq_start = (irq_start & ~31) + 16;} else {hwirq_base = 32;}gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,numa_node_id());if (irq_base < 0) {WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",irq_start);irq_base = irq_start;}/*老的方式*/gic->domain = irq_domain_add_legacy(NULL, gic_irqs, irq_base,hwirq_base, &gic_irq_domain_ops, gic);}if (WARN_ON(!gic->domain)) {ret = -ENODEV;goto error;}gic_dist_init(gic);ret = gic_cpu_init(gic);if (ret)goto error;ret = gic_pm_init(gic);if (ret)goto error;return 0;error:if (IS_ENABLED(CONFIG_GIC_NON_BANKED) && gic->percpu_offset) {free_percpu(gic->dist_base.percpu_base);free_percpu(gic->cpu_base.percpu_base);}return ret;
}
struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,unsigned int size,unsigned int first_irq,irq_hw_number_t first_hwirq,const struct irq_domain_ops *ops,void *host_data)
{struct irq_domain *domain;domain = __irq_domain_add(of_node_to_fwnode(of_node), first_hwirq + size,first_hwirq + size, 0, ops, host_data);if (domain)irq_domain_associate_many(domain, first_irq, first_hwirq, size);return domain;
}
struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,irq_hw_number_t hwirq_max, int direct_max,const struct irq_domain_ops *ops,void *host_data)
{struct device_node *of_node = to_of_node(fwnode);struct irqchip_fwid *fwid;struct irq_domain *domain;static atomic_t unknown_domains;domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),GFP_KERNEL, of_node_to_nid(of_node));if (WARN_ON(!domain))return NULL;if (fwnode && is_fwnode_irqchip(fwnode)) {fwid = container_of(fwnode, struct irqchip_fwid, fwnode);switch (fwid->type) {case IRQCHIP_FWNODE_NAMED:case IRQCHIP_FWNODE_NAMED_ID:domain->fwnode = fwnode;domain->name = kstrdup(fwid->name, GFP_KERNEL);if (!domain->name) {kfree(domain);return NULL;}domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;break;default:domain->fwnode = fwnode;domain->name = fwid->name;break;}
#ifdef CONFIG_ACPI} else if (is_acpi_device_node(fwnode)) {struct acpi_buffer buf = {.length = ACPI_ALLOCATE_BUFFER,};acpi_handle handle;handle = acpi_device_handle(to_acpi_device_node(fwnode));if (acpi_get_name(handle, ACPI_FULL_PATHNAME, &buf) == AE_OK) {domain->name = buf.pointer;domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;}domain->fwnode = fwnode;
#endif} else if (of_node) {char *name;/** DT paths contain '/', which debugfs is legitimately* unhappy about. Replace them with ':', which does* the trick and is not as offensive as '\'...*/name = kasprintf(GFP_KERNEL, "%pOF", of_node);if (!name) {kfree(domain);return NULL;}strreplace(name, '/', ':');domain->name = name;domain->fwnode = fwnode;domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;}if (!domain->name) {if (fwnode)pr_err("Invalid fwnode type for irqdomain\n");domain->name = kasprintf(GFP_KERNEL, "unknown-%d",atomic_inc_return(&unknown_domains));if (!domain->name) {kfree(domain);return NULL;}domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;}of_node_get(of_node);/* Fill structure */INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);mutex_init(&domain->revmap_tree_mutex);domain->ops = ops;domain->host_data = host_data;domain->hwirq_max = hwirq_max;domain->revmap_size = size;domain->revmap_direct_max_irq = direct_max;irq_domain_check_hierarchy(domain);mutex_lock(&irq_domain_mutex);debugfs_add_domain_dir(domain);list_add(&domain->link, &irq_domain_list);mutex_unlock(&irq_domain_mutex);pr_debug("Added domain %s\n", domain->name);return domain;
}
EXPORT_SYMBOL_GPL(__irq_domain_add);

__irq_domain_add 分配一个irq_domain结构,然后加入irq_domain_list列表中。

驱动程序中的上下半部
这个很多书上都有,中断紧急的放入上班部,非紧急的放入下半部,进行调度执行:
下半部的实现方式有:
1.软中断;
2.tasklet
3.workqueue

4.tick中断处理函数,及调度器的中断驱动和处理。

内核是由中断驱动,调度器也是tick中断驱动的。那么我们来看看linu中tick中断处理函数是如何运行到调度器的。
在这里插入图片描述
图片摘自https://blog.csdn.net/force_eagle/article/details/8624084

tick中断处理函数是tick_handle_periodic()看名称周期性,还有一种oneshot模式。

1.该函数在何处设置

tick_handle_periodic()函数在文件kernel/time/tick-common.c中

void tick_handle_periodic(struct clock_event_device *dev)
{int cpu = smp_processor_id();ktime_t next = dev->next_event;tick_periodic(cpu);#if defined(CONFIG_HIGH_RES_TIMERS) || defined(CONFIG_NO_HZ_COMMON)/** The cpu might have transitioned to HIGHRES or NOHZ mode via* update_process_times() -> run_local_timers() ->* hrtimer_run_queues().*/if (dev->event_handler != tick_handle_periodic)return;
#endifif (!clockevent_state_oneshot(dev))return;for (;;) {/** Setup the next period for devices, which do not have* periodic mode:*/next = ktime_add(next, tick_period);if (!clockevents_program_event(dev, next, false))return;/** Have to be careful here. If we're in oneshot mode,* before we call tick_periodic() in a loop, we need* to be sure we're using a real hardware clocksource.* Otherwise we could get trapped in an infinite* loop, as the tick_periodic() increments jiffies,* which then will increment time, possibly causing* the loop to trigger again and again.*/if (timekeeping_valid_for_hres())tick_periodic(cpu);}
}

2.是怎么运行到线程调度函数,即schedler()的?

看了下,linux时间子系统

这篇关于小张学linux内核:一.中断子系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/183430

相关文章

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决

新特性抢先看! Ubuntu 25.04 Beta 发布:Linux 6.14 内核

《新特性抢先看!Ubuntu25.04Beta发布:Linux6.14内核》Canonical公司近日发布了Ubuntu25.04Beta版,这一版本被赋予了一个活泼的代号——“Plu... Canonical 昨日(3 月 27 日)放出了 Beta 版 Ubuntu 25.04 系统镜像,代号“Pluc

Linux安装MySQL的教程

《Linux安装MySQL的教程》:本文主要介绍Linux安装MySQL的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux安装mysql1.Mysql官网2.我的存放路径3.解压mysql文件到当前目录4.重命名一下5.创建mysql用户组和用户并修

Linux上设置Ollama服务配置(常用环境变量)

《Linux上设置Ollama服务配置(常用环境变量)》本文主要介绍了Linux上设置Ollama服务配置(常用环境变量),Ollama提供了多种环境变量供配置,如调试模式、模型目录等,下面就来介绍一... 目录在 linux 上设置环境变量配置 OllamPOgxSRJfa手动安装安装特定版本查看日志在

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用