Interrupts and Interrupt Handlers [LKD 07]

Interrupts


中断是hardware device用来通知CPU的一种机制。在系统上,连接着很多的外设,这些外设速度很慢,并且随时会产生数据需要CPU处理,CPU作为高速运行的部件,不能一直等着外设产生数据,为了提高效率,采用了中断这种机制。当外设需要CPU处理数据时,就向CPU发送电信号,CPU收到电信号以后,就知道哪个设备需要处理了,这个电信号就是中断。

中断并不是直接发送给CPU,而是先发送到interrupt controller,也就是中断控制器(一般是8259A),所有的外设都把中断发送到这里,再由中断控制器把中断信号发送给CPU,CPU收到中断信号,就会停下正在执行的任务,开始执行中断处理函数。

CPU收到的中断,都有对应的中断号,每个外设的中断号都是有区别的,这样CPU根据中断号就能直到是哪些设备产生了中断,进而可以调用对应的中断处理程序。这个中断号一般称为IRQ number。

这里提到了异常,异常和中断非常类似,实际上,异常就是软中断,是CPU在执行指令的过程中发生了异常事件,比如除0错误,或者遇到了page fault,此时都需要中断当前程序的执行,并上报错误。异常和硬件中断的一个很大的区别是,异常是同步产生的,因为执行CPU在执行指令的过程中产生异常,因此是和软件指令同步产生,而硬件中断是异步的,CPU此时可能在执行任何代码。

Interrupt Handlers


用来处理IRQ的处理函数通常称为ISR,也就是interrupt service routine,每个会产生中断的hardware device都有对应的ISR,在kernel中,ISR是device driver的一部分。ISR和普通的kernel code有相同之处,也有不同之处,相同之处在于都是C写的function,不同之处在于ISR只有中断产生时才会执行,并且运行在interrupt context,这是特殊的context,执行时不允许block,即不允许被调度。

因为硬件的中断是异步的,当中断产生时,CPU可能在执行任何代码,所以ISR一定是执行的越快越好,否则被抢占的代码会等待很长时间。因此,ISR中就不能处理很多东西,可以把这些费时的操作放到将来的某个时刻再做,ISR尽快返回。

Top Halves Versus Bottom Halves


接上文,ISR中可能要处理很多事情,但是又不能占用太多CPU的时间,所以OS中一般把ISR分为上下两个部分,即top half和bottom half。top half只处理一些紧急的事情,比如从hardware copy数据,然后调度别的task,ISR就可以结束返回;bottom half里就可以处理剩下的比较费时的操作。

Registering an Interrupt Handler


上面提到过,interrupt handler,也即ISR,是device driver的一部分,应当由device driver负责实现。

driver可以注册interrtupt handler,kernel提供了接口来实现:

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);
}

第一个参数,irq,指定要给哪个IRQ line注册handler,对于kernel的timer或者键盘来说,这个irq是固定死的,其他比如PCI设备等,都是动态分配的;第二个参数,handler,是一个函数指针,指向driver自己的interrupt handler函数,当中断发生时,kernel就会调用这个handler;这个handler的类型如下:

typedef irqreturn_t (*irq_handler_t)(int, void *);

第三个参数是flag,要么是0,要么是一些bitmask,这些bitmask定义在。下面介绍一下这些flag,注意,下面对flag的描述只适用于kernel 2.6,在kernel4.15中已经发生了明显的变化。

Interrupt Handler Flags

IRQF_DISABLED

如果设置了这个flag,那么这个handler在执行的时候,所有的中断都被disable。一般情况下,在注册handler时都不会设置这个flag,除非handler对performance特别敏感,必须马上执行。在kernel 4.15中,已经没有这个 flag 了。

IRQF_SAMPLE_RANDOM

如果设置这个flag,说明这个device产生的中断是随机的,那么可以作为随机池的熵参与随机值的生成。在kernel 4.15中,已经没有这个flag了。

IRQF_TIMER

如果设置这个flag,说明这个handler是一个处理timer中断的handler。

IRQF_SHARED

如果设置这个flag,说明这个IRQ line是share的。也就说,要处理的IRQ line是share的,可能有多个device共享了这个IRQ line,因此在kernel中,同一个IRQ line可能存在多个handler。

这本书只列举了上面这几个flag。在kernel 4.15中,还有别的几个flag,这里也列举一下,但是不做深入说明:

/*
 * These flags used only by the kernel as part of the
 * irq handling routines.
 *
 * IRQF_SHARED - allow sharing the irq among several devices
 * IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
 * IRQF_TIMER - Flag to mark this interrupt as timer interrupt
 * IRQF_PERCPU - Interrupt is per cpu
 * IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
 * IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
 *                registered first in an shared interrupt is considered for
 *                performance reasons)
 * IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
 *                Used by threaded interrupts which need to keep the
 *                irq line disabled until the threaded handler has been run.
 * IRQF_NO_SUSPEND - Do not disable this IRQ during suspend.  Does not guarantee
 *                   that this interrupt will wake the system from a suspended
 *                   state.  See Documentation/power/suspend-and-interrupts.txt
 * IRQF_FORCE_RESUME - Force enable it on resume even if IRQF_NO_SUSPEND is set
 * IRQF_NO_THREAD - Interrupt cannot be threaded
 * IRQF_EARLY_RESUME - Resume IRQ early during syscore instead of at device
 *                resume time.
 * IRQF_COND_SUSPEND - If the IRQ is shared with a NO_SUSPEND user, execute this
 *                interrupt handler after suspending interrupts. For system
 *                wakeup devices users need to implement wakeup detection in
 *                their interrupt handlers.
 */
#define IRQF_SHARED		0x00000080
#define IRQF_PROBE_SHARED	0x00000100
#define __IRQF_TIMER		0x00000200
#define IRQF_PERCPU		0x00000400
#define IRQF_NOBALANCING	0x00000800
#define IRQF_IRQPOLL		0x00001000
#define IRQF_ONESHOT		0x00002000
#define IRQF_NO_SUSPEND		0x00004000
#define IRQF_FORCE_RESUME	0x00008000
#define IRQF_NO_THREAD		0x00010000
#define IRQF_EARLY_RESUME	0x00020000
#define IRQF_COND_SUSPEND	0x00040000

#define IRQF_TIMER		(__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)

说完了request_irq使用的flag以后,再来说一下第四个和第五个参数:name和dev。第四个参数是name,这个就是handler的名字,会在/proc/irq和/proc/interrupts下面显示;dev在share的IRQ line中会用到,因为share的IRQ line中可能存在多个handler,当需要remove handler时,kernel如何知道要remove哪一个?这个时候就需要用到dev,这个dev每个driver都需要传递个kernel,并且保证唯一,这就相当于是handler的ID,通过dev就能准确的知道要操作的是哪个handler。而这个值,在handler被调用时,也会传递给handler。

当rquest_irq调用成功,就会返回0,返回非零值说明发生了错误,driver不能使用这个IRQ line。另外,request_irq可能会sleep(因为调用栈中使用了kmalloc分配内存),所以不能在atomic context中使用,比如interrupt context等。

Freeing an Interrupt Handler

当driver要unload,或者需要需要禁用中断时,可能需要把interrupt handler移除,接口为:

void *free_irq(unsigned int irq, void *dev_id)

可以看到参数只有两个,一个是 IRQ line,一个是dev_id,如果不指定dev_id,kernel不知道driver要remove的是哪个interrupt handler。在free_irq执行时,如果IRQ不是share的,那么handler被移除后就disable IRQ;如果是share的,只有最有一个handler也被remove,IRQ line才会被disable。

注意,free_irq只能在process context中执行。(比如在系统调用,或者kthread中)。

Writing an Interrupt Handler


要实现自己的interrupt handler,第一步得先知道handler的函数原型是啥:

typedef irqreturn_t (*irq_handler_t)(int, void *);

如上所示,kernel中的interrupt handler有两个参数,第一个是IRQ number,也就是中断号,不过driver拿到这个中断号也没啥用处;第二个参数是void *,回忆一下request_irq接口中的dev,这个就是注册handler时,传递给kernel的dev,kernel在调用handler时又回传递进来,一般可以用来存储handler中需要使用的信息。

handler的返回值是一个特殊的类型:irqreturn_t,变量定义在include/linux/irqreturn.h中:

/**
 * enum irqreturn
 * @IRQ_NONE		interrupt was not from this device or was not handled
 * @IRQ_HANDLED		interrupt was handled by this device
 * @IRQ_WAKE_THREAD	handler requests to wake the handler thread
 */
enum irqreturn {
	IRQ_NONE		= (0 << 0),
	IRQ_HANDLED		= (1 << 0),
	IRQ_WAKE_THREAD		= (1 << 1),
};

typedef enum irqreturn irqreturn_t;
#define IRQ_RETVAL(x)	((x) ? IRQ_HANDLED : IRQ_NONE)

可以看到,这是一个枚举类型,有三个值,每个值代表不同的含义:IRQ_NONE表明这个中断不是这个driver对应的device产生的,不予处理;IRQ_HANDLED表明这个中断已经被device driver处理;IRQ_WAKE_THREAD还不知道是什么用。

Linux kernel中interrupt handler是不会重入的,当某个IRQ line的interrupt handler正在执行时,所有CPU都会屏蔽这个IRQ line,不再对它产生的中断做出相应,此外,当interrupt handler正在被执行时,它不会再次被执行(考虑一下同一个handler支持多个IRQ line的情况),大大的简化了interrupt handler的实现。要注意的是,CPU只是屏蔽了这一个IRQ line,别的IRQ仍然有效。

Shared Handlers

share handler,就是IRQ line是share的,这些handler注册了同样的IRQ line。share handler和非share的handler类似,也有不同点,不同点主要有三个:

1. share的handler在注册时,要指定IRQ line是share的,也就是要设置flag:IRQF_SHARED。

2. share了IRQ line的handler,在注册时必须指定非空的dev参数。

3. interrupt handler要能够区分是否真的是自己的device产生的中断,这就要求hardware要有这样的机制来让driver检查。如果不能区分是不是自己的device产生的中断,那driver是没法正常工作的。

如果注册handler时,指定了IRQF_SHARED,那么只有在这个IRQ line没有被人注册,或者所有注册这个IRQ line的handler都设置IRQF_SHARED才行,否则就会失败。

在中断发生时,kernel无法直到具体是哪个device产生的中断,所以会把这个IRQ line上的handler全部调用一遍,因此如果不是自己的device产生的中断,要尽快返回。

A Real-Life Interrupt Handler

下面是一个完整的interrupt handler的例子——RTC driver的interrupt handler。所谓的RTC,就是real-time clock,它是一个hardware devcie,和system timer是相互独立的。system timer用来设置system clock,提供alarm,或者提供某个时间间隔的计时服务:在大多数机器上,system clock都是通过寄存器或者I/O range,设置system clock就是设置这个register或者I/O range,而alarm或者计时服务,都是通过interrupt来实现的。我们看一下RTC driver是如何实现interrupt hander的,在rtc_init函数中,通过request_irq注册了handler:

	if (is_hpet_enabled()) {
		int err;

		rtc_int_handler_ptr = hpet_rtc_interrupt;
		err = hpet_register_irq_handler(rtc_interrupt);
		if (err != 0) {
			printk(KERN_WARNING "hpet_register_irq_handler failed "
					"in rtc_init().");
			return err;
		}
	} else {
		rtc_int_handler_ptr = rtc_interrupt;
	}

	if (request_irq(RTC_IRQ, rtc_int_handler_ptr, 0, "rtc", NULL)) {
		/* Yeah right, seeing as irq 8 doesn't even hit the bus. */
		rtc_has_irq = 0;
		printk(KERN_ERR "rtc: IRQ %d is not free.\n", RTC_IRQ);
		rtc_release_region();

		return -EIO;
	}

在x86_64上,rtc_int_handler_ptr应该是rtc_interrupt,RTC_IRQ是8(#define RTC_IRQ 8),这里的code是基于kernel 4.15,可以看到RTC注册handler这个逻辑和书中讲的不一样,因为4.15没有设置IRQF_SHARED,也就意味着其他人都无法注册timer中断的hanlder了。因为不是share的IRQ line,所以第五个参数设置为了NULL。我们看一下rtc_interrupt的实现:

static irqreturn_t rtc_interrupt(int irq, void *dev_id)
{
	/*
	 *	Can be an alarm interrupt, update complete interrupt,
	 *	or a periodic interrupt. We store the status in the
	 *	low byte and the number of interrupts received since
	 *	the last read in the remainder of rtc_irq_data.
	 */

	spin_lock(&rtc_lock);
	rtc_irq_data += 0x100;
	rtc_irq_data &= ~0xff;
	if (is_hpet_enabled()) {
		/*
		 * In this case it is HPET RTC interrupt handler
		 * calling us, with the interrupt information
		 * passed as arg1, instead of irq.
		 */
		rtc_irq_data |= (unsigned long)irq & 0xF0;
	} else {
		rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
	}

	if (rtc_status & RTC_TIMER_ON)
		mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);

	spin_unlock(&rtc_lock);

	/* Now do the rest of the actions */
	spin_lock(&rtc_task_lock);
	if (rtc_callback)
		rtc_callback->func(rtc_callback->private_data);
	spin_unlock(&rtc_task_lock);
	wake_up_interruptible(&rtc_wait);

	kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);

	return IRQ_HANDLED;
}

上面的code中,第一个spinlock——rtc_lock,用来防止SMP上多个CPU同时访问RTC的data;第二个spinlock——rtc_task_lock,用来防止SMP上多个CPU同时调用rtc_callback。rtc_irq_data是一个unsigned long类型的数据,用来存放RTC的信息。如果设置了计时器,就会调用mod_timer,如果存在rtc的callback,就会在此时调用。

最后,在rtc_interrupt执行完以后返回IRQ_HANDLED,表明中断已被处理。

Interrupt Context


kernel在执行interrupt handler时,是运行在interrupt context里。回忆一下之前讲的,当用户态进程通过系统调用或者异常等陷入内核时,kernel都是运行在process context中,kernel thread也是运行在process context中。

interrupt context没有和任何的process关联,因此当kernel在interrupt context中执行时,是不能使用current指针的(尽管current指针存在,但是它指向被这个interrupt打断的process contex的task结构体)。因为没有task结构体,自然也不能被调度,这就决定了interrupt context在执行时不能睡眠,不能被block,不能放弃CPU,因此interrupt context中调用的函数也是受到限制的。

interrupt context对时间比较敏感,因为interrupt handler在执行时,是中断了别的code,你不知道被中断的code是做什么的,也许是process,也许是另一个IRQ line的interrupt handler(别的IRQ line没有被CPU屏蔽,并且它的handler没有关闭抢占),无论哪一种,interrupt handler都应该尽快的执行完。

interrupt handler的stack是可以配置的,但是因为历史原因,一般没有给interrupt handler单独分配stack,而是和被它中断的process共享kernel stack,不过每个process的kernel stack也是有限的,32位机器上是8KB,64位机器上是16KB,因此要省着用。

不过在新的kernel中,interrupt已经有自己的stack了,不需要再和别的process共享stack。

Implementing Interrupt Handlers


这里是说的linux kernel中interrupt handler的实现,这些实现是架构相关的。

Interrupts and Interrupt Handlers [LKD 07]_第1张图片

上图是键盘鼠标中断的处理过程。

当硬件设备产生中断时,电信号就会通过连接的bus传递给interrupt controller,如果interrupt line是enable的状态(有些interrupt line可能是被屏蔽的),这个电信号就会传递给CPU。如果CPU没有disable这个interrupt line,CPU就会停止执行当前的task,然后关闭interrupt,然后跳转到内存中预定的位置,开始执行里面的code。这个预定的位置是kernel自己配置好的,也是所有interrupt handler的入口位置。

对于每一个interrupt line,kernel都会跳转到不同的位置,kernel也知道产生中断的IRQ number,当kernel准备处理中断时,首先保存IRQ number?,然后保存被它中断的task的寄存器到stack中,然后调用do_IRQ函数。这里列一下kernel 4.15中irq处理相关的汇编code:

首先是irq_entries_start,这是irq开始处理的入口:

ENTRY(irq_entries_start)
    vector=FIRST_EXTERNAL_VECTOR
    .rept (FIRST_SYSTEM_VECTOR - FIRST_EXTERNAL_VECTOR)
	UNWIND_HINT_IRET_REGS
	pushq	$(~vector+0x80)			/* Note: always in signed byte range */
	jmp	common_interrupt
	.align	8
	vector=vector+1
    .endr
END(irq_entries_start)

里面核心的code是common_interrupt:


/* Interrupt entry/exit. */

	/*
	 * The interrupt stubs push (~vector+0x80) onto the stack and
	 * then jump to common_interrupt.
	 */
	.p2align CONFIG_X86_L1_CACHE_SHIFT
common_interrupt:
	addq	$-0x80, (%rsp)			/* Adjust vector to [-256, -1] range */
	call	interrupt_entry
	UNWIND_HINT_REGS indirect=1
	call	do_IRQ	/* rdi points to pt_regs */
	/* 0(%rsp): old RSP */
    ........
END(common_interrupt)

common_interrupt非常长,这里只列举了一部分,可以看到最主要的处理函数是do_IRQ函数。

__visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)

这个是do_IRQ函数的原型。看上去interrupt处理所有需要依赖的参数都在regs里存着,我们看一下pt_regs的layout:

struct pt_regs {
/*
 * C ABI says these regs are callee-preserved. They aren't saved on kernel entry
 * unless syscall needs a complete, fully filled "struct pt_regs".
 */
	unsigned long r15;
	unsigned long r14;
	unsigned long r13;
	unsigned long r12;
	unsigned long bp;
	unsigned long bx;
/* These regs are callee-clobbered. Always saved on kernel entry. */
	unsigned long r11;
	unsigned long r10;
	unsigned long r9;
	unsigned long r8;
	unsigned long ax;
	unsigned long cx;
	unsigned long dx;
	unsigned long si;
	unsigned long di;
/*
 * On syscall entry, this is syscall#. On CPU exception, this is error code.
 * On hw interrupt, it's IRQ number:
 */
	unsigned long orig_ax;
/* Return frame for iretq */
	unsigned long ip;
	unsigned long cs;
	unsigned long flags;
	unsigned long sp;
	unsigned long ss;
/* top of stack page */
};

可以看到里面记录很多register相关的信息,以及IRQ number。

在do_IRQ开始执行时,先对interrupt的信息做了检查,如果没有问题,就会调用handle_irq函数:

bool handle_irq(struct irq_desc *desc, struct pt_regs *regs)
{
	stack_overflow_check(regs);

	if (IS_ERR_OR_NULL(desc))
		return false;

	generic_handle_irq_desc(desc);
	return true;
}

handle_irq里最终会调用generic_handle_irq_desc来处理所有的interrupt handler。其中的实现细节和书中不同,这里先放着,后面再补。我们接着看handle_irq返回之后的code。handle_irq返回到do_IRQ之后,do_IRQ做了一些cleanup的工作,然后返回到汇编代码,也就是ret_from_intr这里,ret_from_intr会根据当前是要返回到user mode(user mode process被中断打断),还是kernel mode(kernel自己的code被中断打断),会有不同的处理。如果是返回到user mode,就检查是否需要reschedule,如果是,就会调用schedule,等schedule返回之后再返回到user mode process执行;如果是返回到kernel mode,就检查当前CPU的preempt counter是否为0,如果不是,说明不能抢占,直接返回到原来被打断的地方继续执行;如果premmpt counter 0,说明没有spinlock,此时就会发生reschedule。

Interrupt Control


Linux kernel提供了一组接口,用来控制系统中的interrupt,通过这些接口可以关闭当前CPU上的中断,或者屏蔽整个系统的某个中断。因为中断本身和架构相关的,所以这些接口也是架构相关的。代码在 and

之所以需要关闭中断,是因为很多时候需要通过中断来实现同步机制。如果关闭了中断,interrupt handler就不会被调用,也就不会抢占当前的code(通常情况下,关闭中断的同时也会关闭抢占)。然后要注意,关闭中断或者关闭抢占,并不能防止别的CPU访问在critical section的code,它要和锁机制一起使用才可以达到这个效果。关闭中断或者抢占只对单个CPU有效,也就说,当前CPU不会调用interrupt handler,也不会抢占你的code,但是别的CPU不受影响,他们仍然有可能调用interrupt handler或者执行别的code来访问同样的critical section,所以只关闭当前CPU的中断或者抢占是起不到保护作用的,还得依赖lock才可以。

Disabling and Enabling Interrupts

在kernel中,关闭和打开中断都只针对当前的CPU有效,不能全局关闭中断。code一般长这样:

local_irq_disable();
/* interrupts are disabled .. */
local_irq_enable();

在x86上,打开和关闭中断通常通过cli和sti两个汇编指令来实现:

static inline void native_irq_disable(void)
{
	asm volatile("cli": : :"memory");
}

static inline void native_irq_enable(void)
{
	asm volatile("sti": : :"memory");
}

使用这个两个接口存在一个问题,local_irq_enable会无条件打开中断,设想一个,如果我们的函数进来之前,别的函数已经关闭了中断,我们的函数也关闭了中断,在code执行完以后我们打开了中断,此时别的函数期望的行为是中断仍然是关闭的状态,因为它还没调用local_irq_enable,这就会出现问题。为了解决这个问题,引入了另外两个接口,在关闭中断时,先保存中断的状态,在打开时,恢复之前保存的状态,这样被人对中断状态的设定不会被我们破坏。code长这样:

unsigned long flags;
local_irq_save(flags); /* interrupts are now disabled */
/* ... */
local_irq_restore(flags); /* interrupts are restored to their previous state */

flags就是用来保存当前CPU的中断信息的,local_irq_save会关闭中断,并把中断信息保存在flag中,local_irq_restore会打开中断,并把中断状态恢复成flags保存的状态。这两个函数都是宏,flags是按值传递的,不是地址。另外,要注意的是,flags里面记录的是架构相关的中断系统的信息,但是某些架构上,也会记录当前stack的一些信息,所以中断信息的保存和恢复都必须发生在同一个函数里,也就说flags的保存和恢复都在同一个函数栈帧中完成。

以上这些中断相关的接口可以在interrupt context中调用,也可以在process context中调用。

Disabling a Specific Interrupt Line

上面的接口可以把当前CPU上的所有中断全部关闭,但是有时候我们只是想关闭某一个中断,就需要使用另外一套接口:

extern void disable_irq_nosync(unsigned int irq);
extern void disable_irq(unsigned int irq);
extern void disable_percpu_irq(unsigned int irq);
extern void enable_irq(unsigned int irq);
extern void enable_percpu_irq(unsigned int irq, unsigned int type);

disable_irq_nosync和disable_irq会把所有CPU上的irq参数指定的IRQ line关闭,区别在于disable_irq在关闭interrupt controller的irq时,会等待当前正在执行的interrupt handler,当他们都执行结束以后,disable_irq才会返回;而disable_irq_nosync则不会等待,而是直接返回。

中断的关闭和打开是配对的,也就说调用了一次disable,就必须调用一次enable,比如调用了两次disable,那么必须调用两次enable之后中断才能被打开。

以上的这些函数可以在interrupt context中调用,也可以在process context中调用,但是如果是在interrupt context中要注意,interrupt handler在执行时,kernel已经把它对应的IRQ line关闭了,因此切记不要在handler中把自己的IRQ line打开。

如果这个IRQ line是share的,那么device driver尽量不要关闭中断,否则别的device也都无法收到中断。比如PCI设备,标准就规定了通过share的方式使用IRQ line,如果PCI device driver关闭了中断,那么别的PCI设备都无法收到中断了。

Status of the Interrupt System

在kernel中,有时候需要知道在什么样的context中执行,或者当前中断系统的状态,kernel也提供了这样的接口:

//当前是否关闭了中断,0表示没有关闭中断,1表示关闭了中断。
irqs_disabled()
//下面两个函数用来检查当前的context
in_interrupt()
in_irq()

in_interrupt比较常用,如果kernel正在执行interrupt hanling(包括interrupt handler和bottom half),那么就会返回true;in_irq只有在执行interrupt handler时会返回true。不过在kernel 4.15中,in_interrupt已经不推荐使用了,4.15中相关的函数列举如下:

/*
 * Are we doing bottom half or hardware interrupt processing?
 *
 * in_irq()       - We're in (hard) IRQ context
 * in_softirq()   - We have BH disabled, or are processing softirqs
 * in_interrupt() - We're in NMI,IRQ,SoftIRQ context or have BH disabled
 * in_serving_softirq() - We're in softirq context
 * in_nmi()       - We're in NMI context
 * in_task()	  - We're in task context
 *
 * Note: due to the BH disabled confusion: in_softirq(),in_interrupt() really
 *       should not be used in new code.
 */
#define in_irq()		(hardirq_count())
#define in_softirq()		(softirq_count())
#define in_interrupt()		(irq_count())
#define in_serving_softirq()	(softirq_count() & SOFTIRQ_OFFSET)
#define in_nmi()		(preempt_count() & NMI_MASK)
#define in_task()		(!(preempt_count() & \
				   (NMI_MASK | HARDIRQ_MASK | SOFTIRQ_OFFSET)))

 

 

 

 

你可能感兴趣的:(LKD3,Linux,LKD)