Linux 驱动开发 三十九:Linux 中断(一)

一、Linux 中断子系统概述

参考博客:Linux中断(interrupt)子系统之一:中断系统基本原理_DroidPhone的专栏-CSDN博客_linux 中断

1、设备、中断控制器和CPU

一个完整的设备中,与中断相关的硬件可以划分为3类,它们分别是:设备、中断控制器和CPU本身,下图展示了一个smp系统中的中断硬件的组成结构:
在这里插入图片描述

  • 设备:发起中断的源,可以发起一个硬件中断信号,该信号发送到中断控制器,由中断处理器进行进一步处理。中断发起设备可能是 SOC 外(外部器件),也可能是 SOC 内(例如SOC集成的 uart 等)。
  • 中断控制器:管理设备发出的中断信号。
  • CPU:响应中断信号,进行相关处理。

2、IRQ编号

特别说明:IRQ 编号和中断 ID 不是同一个东西。IRQ 编号通过中断 ID 经过映射得到。

系统中每一个注册的中断源,都会分配一个唯一的编号用于识别该中断,我们称之为IRQ编号。IRQ编号贯穿在整个Linux的通用中断子系统中。

3、irq_desc 结构描述

整个通用中断子系统几乎都是围绕着irq_desc结构进行,系统中每一个irq都对应着一个irq_desc结构。

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
	[0 ... NR_IRQS-1] = {
		.handle_irq	= handle_bad_irq,
		.depth		= 1,
		.lock		= __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
	}
};

NR_IRQS 针对不同平台有所不同。

1、struct irq_desc 类型包含关系

在这里插入图片描述
上图为 struct irq_desc 数据类型不完全整理。

2、struct irq_desc 数据类型

/**
 * struct irq_desc - interrupt descriptor
 * @irq_data:		per irq and chip data passed down to chip functions
 * @kstat_irqs:		irq stats per cpu
 * @handle_irq:		highlevel irq-events handler
 * @preflow_handler:	handler called before the flow handler (currently used by sparc)
 * @action:		the irq action chain
 * @status:		status information
 * @core_internal_state__do_not_mess_with_it: core internal status information
 * @depth:		disable-depth, for nested irq_disable() calls
 * @wake_depth:		enable depth, for multiple irq_set_irq_wake() callers
 * @irq_count:		stats field to detect stalled irqs
 * @last_unhandled:	aging timer for unhandled count
 * @irqs_unhandled:	stats field for spurious unhandled interrupts
 * @threads_handled:	stats field for deferred spurious detection of threaded handlers
 * @threads_handled_last: comparator field for deferred spurious detection of theraded handlers
 * @lock:		locking for SMP
 * @affinity_hint:	hint to user space for preferred irq affinity
 * @affinity_notify:	context for notification of affinity changes
 * @pending_mask:	pending rebalanced interrupts
 * @threads_oneshot:	bitfield to handle shared oneshot threads
 * @threads_active:	number of irqaction threads currently running
 * @wait_for_threads:	wait queue for sync_irq to wait for threaded handlers
 * @nr_actions:		number of installed actions on this descriptor
 * @no_suspend_depth:	number of irqactions on a irq descriptor with
 *			IRQF_NO_SUSPEND set
 * @force_resume_depth:	number of irqactions on a irq descriptor with
 *			IRQF_FORCE_RESUME set
 * @dir:		/proc/irq/ procfs entry
 * @name:		flow handler name for /proc/interrupts output
 */
struct irq_desc {
	struct irq_data		irq_data;
	unsigned int __percpu	*kstat_irqs;
	irq_flow_handler_t	handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
	irq_preflow_handler_t	preflow_handler;
#endif
	struct irqaction	*action;	/* IRQ action list */
	unsigned int		status_use_accessors;
	unsigned int		core_internal_state__do_not_mess_with_it;
	unsigned int		depth;		/* nested irq disables */
	unsigned int		wake_depth;	/* nested wake enables */
	unsigned int		irq_count;	/* For detecting broken IRQs */
	unsigned long		last_unhandled;	/* Aging timer for unhandled count */
	unsigned int		irqs_unhandled;
	atomic_t		threads_handled;
	int			threads_handled_last;
	raw_spinlock_t		lock;
	struct cpumask		*percpu_enabled;
#ifdef CONFIG_SMP
	const struct cpumask	*affinity_hint;
	struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
	cpumask_var_t		pending_mask;
#endif
#endif
	unsigned long		threads_oneshot;
	atomic_t		threads_active;
	wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PM_SLEEP
	unsigned int		nr_actions;
	unsigned int		no_suspend_depth;
	unsigned int		cond_suspend_depth;
	unsigned int		force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry	*dir;
#endif
	int			parent_irq;
	struct module		*owner;
	const char		*name;
} ____cacheline_internodealigned_in_smp;
数据成员说明
irq_data封装硬件
kstat_irqs用于irq的一些统计信息,这些统计信息可以从proc文件系统中查询
action中断响应链表,主要目的是为了实现中断的共享,多个设备共享同一个irq
status_use_accessors记录该irq的状态信息
depth用于管理enable_irq()/disable_irq()这两个API的嵌套深度管理
lock用于保护irq_desc结构本身的自旋锁
affinity_hint用于提示用户空间,作为优化irq和cpu之间的亲缘关系的依据
pending_mask用于调整irq在各个cpu之间的平衡
wait_for_threads用于synchronize_irq(),等待该irq所有线程完成

3、irq_data 数据类型

/**
 * struct irq_data - per irq and irq chip data passed down to chip functions
 * @mask:		precomputed bitmask for accessing the chip registers
 * @irq:		interrupt number
 * @hwirq:		hardware interrupt number, local to the interrupt domain
 * @node:		node index useful for balancing
 * @state_use_accessors: status information for irq chip functions.
 *			Use accessor functions to deal with it
 * @chip:		low level interrupt hardware access
 * @domain:		Interrupt translation domain; responsible for mapping
 *			between hwirq number and linux irq number.
 * @parent_data:	pointer to parent struct irq_data to support hierarchy
 *			irq_domain
 * @handler_data:	per-IRQ data for the irq_chip methods
 * @chip_data:		platform-specific per-chip private data for the chip
 *			methods, to allow shared chip implementations
 * @msi_desc:		MSI descriptor
 * @affinity:		IRQ affinity on SMP
 *
 * The fields here need to overlay the ones in irq_desc until we
 * cleaned up the direct references and switched everything over to
 * irq_data.
 */
struct irq_data {
	u32			mask;
	unsigned int		irq;
	unsigned long		hwirq;
	unsigned int		node;
	unsigned int		state_use_accessors;
	struct irq_chip		*chip;
	struct irq_domain	*domain;
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	struct irq_data		*parent_data;
#endif
	void			*handler_data;
	void			*chip_data;
	struct msi_desc		*msi_desc;
	cpumask_var_t		affinity;
};
数据成员说明
irq该结构所对应的IRQ编号
hwirq硬件irq编号,它不同于上面的irq
node通常用于hwirq和irq之间的映射操作
state_use_accessors硬件封装层需要使用的状态信息
chip指向该irq所属的中断控制器的irq_chip结构指针
handler_data每个irq的私有数据指针
chip_data中断控制器的私有数据
msi_desc用于PCIe总线的MSI或MSI-X中断机制
affinity记录该irq与cpu之间的亲缘关系

二、设备树对中断描述

1、设备树中中断属性描述

设备树语法中针对中断定义以下属性(部分内容):

  • interrupt-controller:标志中断控制器

  • interrupt-parent:为当前节点指定中断父节点

  • interrupts:定义相关信息

  • interrupt-cells:定义 interrupts 赋值内容所需字段数

  • interrupt-map:中断映射

关于更多信息见:https://blog.csdn.net/OnlyLove_/article/details/121963868?spm=1001.2014.3001.5501

2、iMX6ULL 官方Linux源码中断树分析

涉及文件说明:

  • skeleton.dtsi:定义系统启动基本节点。

  • imx6ull.dtsi:描述 SOC 级设备信息(对 skeleton.dtsi 内容追加和覆盖)。

  • imx6ull-14x14-evk.dts:描述板级信息(对 skeleton.dtsi 和 imx6ull.dtsi 追加和覆盖)。

根据设备树相关信息,大体结构如下:
在这里插入图片描述
intc:中断控制器节点

gpc:通用功耗控制器节点

imx6ull 芯片中断控制器设备树描述文档 linux\Documentation\devicetree\bindings\arm\gic.txt。在设备树中对应节点为 imx6ull.dtsi 文件中 intc 节点。关于 gic.txt 详细内容见

imx6ull 芯片 GPIO 控制器设备树描述文档 linux\Documentation\devicetree\bindings\gpio\fsl-imx-gpio.txt。在设备树中对应 imx6ull.dtsi 文件中 gpio1 节点。关于 fsl-imx-gpio.txt 详细内容见 Linux 驱动开发 三十八:《fsl-imx-gpio.txt》翻译_OnlyLove_的博客-CSDN博客

三、设备树中断属性处理

1、获取中断号

编写驱动的时候需要用到中断号,我们用到中断号,中断信息已经写到了设备树里面,因此可以通过 irq_of_parse_and_map函数从 interupts属性中提取到对应的设备号,函数原型如下:

/**
 * irq_of_parse_and_map - Parse and map an interrupt into linux virq space
 * @dev: Device node of the device whose interrupt is to be mapped
 * @index: Index of the interrupt to map
 *
 * This function is a wrapper that chains of_irq_parse_one() and
 * irq_create_of_mapping() to make things easier to callers
 */
// 返回值:中断号
unsigned int irq_of_parse_and_map(struct device_node *dev, 	// 设备节点
                                  int index)				// 索引号(interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息)
{
	struct of_phandle_args oirq;

	if (of_irq_parse_one(dev, index, &oirq))
		return 0;

	return irq_create_of_mapping(&oirq);
}
EXPORT_SYMBOL_GPL(irq_of_parse_and_map);

如果使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号,函数原型如下:

// 返回值:GPIO对应的中断号
int gpio_to_irq(unsigned int gpio)	// 要获取的 GPIO 编号

四、Linux 中断API

参考博客:Linux kernel中断子系统之(五):驱动申请中断API (wowotech.net)

1、中断申请

/*
 * 返回值:
 * 0:申请成功
 * 负值:申请失败
 * EBUSY:中断已被申请
 */

static inline int __must_check
request_irq(unsigned int irq, 		// 中断号
            irq_handler_t handler, 	// 中断处理函数
            unsigned long flags,	// 中断标志
	    	const char *name, 		// 中断名字(设置以后可以在/proc/interrupts 文件中看到对应的中断名字)
            void *dev)				// 如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断
{
	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
/*
 * 特变说明:
 *    flags = IRQF_TRIGGER_NONE 时,需要调用 enable_irq 触发中断
 *
 */

中断标志整理:

标志说明
IRQF_SHARED表明该中断是共享的,也就是一个中断线对应多个外设中断源
IRQF_PROBE_SHARED
__IRQF_TIMER标记该中断属于 timer 类型的中断,方便内核进行特殊处理
IRQF_PERCPUpercpu 类型的中断
IRQF_NOBALANCING表示当前中断不受 irqbalance 的影响
IRQF_IRQPOLL中断用于轮询
IRQF_ONESHOT单次中断,中断执行一次就结束。
IRQF_NO_SUSPEND
IRQF_FORCE_RESUME
IRQF_NO_THREAD中断不能被线程化
IRQF_EARLY_RESUME
IRQF_COND_SUSPEND
IRQF_TRIGGER_NONE无触发
IRQF_TRIGGER_RISING上升沿触发中断
IRQF_TRIGGER_FALLING下降沿触发中断
IRQF_TRIGGER_HIGH高电平触发中断
IRQF_TRIGGER_LOW低电平触发中断
IRQF_TRIGGER_MASK
IRQF_TRIGGER_PROBE

2、释放中断

/**
 *	free_irq - free an interrupt allocated with request_irq
 *	@irq: Interrupt line to free
 *	@dev_id: Device identity to free
 *
 *	Remove an interrupt handler. The handler is removed and if the
 *	interrupt line is no longer in use by any driver it is disabled.
 *	On a shared IRQ the caller must ensure the interrupt is disabled
 *	on the card it drives before calling this function. The function
 *	does not return until any executing interrupts for this IRQ
 *	have completed.
 *
 *	This function must not be called from interrupt context.
 */
/*
 * irq:中断号
 * dev_id:如果设置为共享中断,用于区分具体中断
 */
void free_irq(unsigned int irq, void *dev_id)
{
	struct irq_desc *desc = irq_to_desc(irq);

	if (!desc || WARN_ON(irq_settings_is_per_cpu_devid(desc)))
		return;

#ifdef CONFIG_SMP
	if (WARN_ON(desc->affinity_notify))
		desc->affinity_notify = NULL;
#endif

	chip_bus_lock(desc);
	kfree(__free_irq(irq, dev_id));
	chip_bus_sync_unlock(desc);
}
EXPORT_SYMBOL(free_irq);

3、中断处理函数

使用 request_irq 函数申请中断的时候需要设置中断处理函数,中断处理函数格式如下所示:

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

第一个参数是要中断处理函数要相应的中断号。第二个参数是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备, dev 也可以指向设备数据结构。

中断处理函数的返回值为 irqreturn_t 类型,irqreturn_t 类型定义 如下所示:

/**
 * enum irqreturn
 * @IRQ_NONE		interrupt was not from this device
 * @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)

可以看出 irqreturn_t 是个枚举类型,一共有三种返回值。一般中断服务函数返回值使用如下形式:

return IRQ_RETVAL(IRQ_HANDLED)

4、单个中断使能

/**
 *	enable_irq - enable handling of an irq
 *	@irq: Interrupt to enable
 *
 *	Undoes the effect of one call to disable_irq().  If this
 *	matches the last disable, processing of interrupts on this
 *	IRQ line is re-enabled.
 *
 *	This function may be called from IRQ context only when
 *	desc->irq_data.chip->bus_lock and desc->chip->bus_sync_unlock are NULL !
 */
void enable_irq(unsigned int irq)
{
	unsigned long flags;
	struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);

	if (!desc)
		return;
	if (WARN(!desc->irq_data.chip,
		 KERN_ERR "enable_irq before setup/request_irq: irq %u\n", irq))
		goto out;

	__enable_irq(desc, irq);
out:
	irq_put_desc_busunlock(desc, flags);
}
EXPORT_SYMBOL(enable_irq);

enable_irq 用于使能指定中断。

5、单个中断禁止

/**
 *	disable_irq - disable an irq and wait for completion
 *	@irq: Interrupt to disable
 *
 *	Disable the selected interrupt line.  Enables and Disables are
 *	nested.
 *	This function waits for any pending IRQ handlers for this interrupt
 *	to complete before returning. If you use this function while
 *	holding a resource the IRQ handler may need you will deadlock.
 *
 *	This function may be called - with care - from IRQ context.
 */
void disable_irq(unsigned int irq)
{
	if (!__disable_irq_nosync(irq))
		synchronize_irq(irq);
}
EXPORT_SYMBOL(disable_irq);

disable_irq 用于关闭指定中断。disable_irq 函数要到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。

/**
 *	disable_irq_nosync - disable an irq without waiting
 *	@irq: Interrupt to disable
 *
 *	Disable the selected interrupt line.  Disables and Enables are
 *	nested.
 *	Unlike disable_irq(), this function does not ensure existing
 *	instances of the IRQ handler have completed before returning.
 *
 *	This function may be called from IRQ context.
 */
void disable_irq_nosync(unsigned int irq)
{
	__disable_irq_nosync(irq);
}
EXPORT_SYMBOL(disable_irq_nosync);

disable_irq_nosync 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。

6、全局中断使能

#define local_irq_enable()	do { raw_local_irq_enable(); } while (0)
#define raw_local_irq_enable()		arch_local_irq_enable()
static inline void arch_local_irq_enable(void)
{
	barrier();
	setipl(IPL_MIN);
}

local_irq_enable 函数直接开启全局中断,这种方式会影响到其他进程。因此不应该直接粗暴开启全局中断,而是应该恢复以前状态。使用 local_irq_restore 函数完成中断恢复。

#define local_irq_restore(flags) do { raw_local_irq_restore(flags); } while (0)
#define raw_local_irq_restore(flags)			\
	do {						\
		typecheck(unsigned long, flags);	\
		arch_local_irq_restore(flags);		\
	} while (0)

7、全局中断关闭

#define local_irq_disable()	do { raw_local_irq_disable(); } while (0)
#define raw_local_irq_disable()		arch_local_irq_disable()
static inline void arch_local_irq_disable(void)
{
	setipl(IPL_MAX);
	barrier();
}

local_irq_disable 函数直接关闭全局中断,这种方式会影响到其他进程。因此应该避免使用 local_irq_disable 函数,而是使用 local_irq_save 函数保存当前的中断状态,然后禁用当前处理器上的中断。

#define local_irq_save(flags)					\
	do {							\
		raw_local_irq_save(flags);			\
	} while (0)

五、Linux 中断设计思路一

1、在驱动入口函数调用 request_irq 函数申请中断。

2、 调用 request_irq 函数时会注册中断处理函数。

3、当中断发生后。中断处理函数自动执行。

4、在驱动退出函数中调用 free_irq 函数释放中断。

/* @description		: 中断服务函数
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t xxx_handler(int irq, void *dev_id)
{
	// 中断触发后,执行此函数
	……

	return IRQ_RETVAL(IRQ_HANDLED);
}

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init xxx_init(void)
{
	……

	// 申请中断,注册中断服务函数
	ret = request_irq(中断号, xxx_handler, 中断标志,中断名子,dev);

	return 0;
}


/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit xxx_exit(void)
{
	……
		
	// 释放中断
	free_irq(中断号, dev_id);

	……
}

六、中断上下部

中断最重要属性就是响应必须及时,而实际实现中中断处理过程往往比较费事,这将会影响后续中断响应。为了解决这个问题,因此提出中断上下部的概念,将中断处理过程分为两部分:

  • 上部分:处理过程比较快。不会占用很长时间的放在上半部。
  • 下半部:处理过程比较耗时操作放在下半部。

1、中断上下部设计思路

1、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。

2、如果要处理的任务对时间敏感,可以放到上半部。

3、如果要处理的任务与硬件有关,可以放到上半部。

4、除了上述三点以外的其他任务,优先考虑放到下半部。

2、中断上下部设计

中断处理函数可以作为中断上半部,而下半部 Liunx 内核提供多种机制:

  • 软中断

  • tasklet

  • 工作队列

3、软中断

Linux 内核使用结构体 softirq_action 表示软中断, softirq_action 结构体定义在文件 include/linux/interrupt.h 中,内容如下:

/* softirq mask and active fields moved to irq_cpustat_t in
 * asm/hardirq.h to get better cache usage.  KAO
 */

struct softirq_action
{
	void	(*action)(struct softirq_action *);
};

在 kernel/softirq.c 文件中一共定义了 10 个软中断,如下所示:

/* map softirq index to softirq name. update 'softirq_to_name' in
 * kernel/softirq.c when adding a new softirq.
 */
extern const char * const softirq_to_name[NR_SOFTIRQS];

NR_SOFTIRQS 是枚举类型,定义在文件 include/linux/interrupt.h 中,定义如下:

/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
   frequency threaded job scheduling. For almost all the purposes
   tasklets are more than enough. F.e. all serial device BHs et
   al. should be converted to tasklets, not to softirqs.
 */

enum
{
	HI_SOFTIRQ=0, 			/* 高优先级软中断 */
	TIMER_SOFTIRQ,			/* 定时器软中断 */
	NET_TX_SOFTIRQ,			/* 网络数据发送软中断 */
	NET_RX_SOFTIRQ,			/* 网络数据接收软中断 */
	BLOCK_SOFTIRQ,			/* 块设备的软中断 */
	BLOCK_IOPOLL_SOFTIRQ,	/* 支持IO轮询的块设备软中断 */
	TASKLET_SOFTIRQ,		/* tasklet 软中断 */
	SCHED_SOFTIRQ,			/* 调度软中断 */
	HRTIMER_SOFTIRQ,		/* 高精度定时器软中断 */
	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */ /* RCU软中断 */

	NR_SOFTIRQS
};

可以看出,一共有 10 个软中断,因此NR_SOFTIRQS 为 10,因此数组 softirq_vec 有 10 个元素。softirq_action 结构体中的 action 成员变量就是软中断的服务函数,数组 softirq_vec 是个全局数组,因此所有的 CPU(对于 SMP 系统而言)都可以访问到,每个 CPU 都有自己的触发和控制机制,并且只执行自己所触发的软中断。

1、注册软中断处理函数

/*
 * nr:要开启的软件中断
 * action:软中断对应处理函数
 * 返回值:无
 */
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}

2、触发软中断

/*
 * nr:要触发的软中断
 * 返回值:无
 */
void raise_softirq(unsigned int nr)
{
	unsigned long flags;

	local_irq_save(flags);
	raise_softirq_irqoff(nr);
	local_irq_restore(flags);
}

3、特别说明

软中断必须在编译的时候静态注册!

Linux 内核使用 softirq_init 函数初始化软中断,softirq_init 函数定义在 kernel/softirq.c 文件里面,函数内容如下:

void __init softirq_init(void)
{
	int cpu;

	for_each_possible_cpu(cpu) {
		per_cpu(tasklet_vec, cpu).tail =
			&per_cpu(tasklet_vec, cpu).head;
		per_cpu(tasklet_hi_vec, cpu).tail =
			&per_cpu(tasklet_hi_vec, cpu).head;
	}

	open_softirq(TASKLET_SOFTIRQ, tasklet_action);
	open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

softirq_init 函数默认会打开 TASKLET_SOFTIRQ 和 HI_SOFTIRQ。

4、tasklet

tasklet 是利用软中断来实现的另外一种下半部机制,在软中断和 tasklet 之间,建议大家使用 tasklet。Linux 内核使用结构体:

/* Tasklets --- multithreaded analogue of BHs.

   Main feature differing them of generic softirqs: tasklet
   is running only on one CPU simultaneously.

   Main feature differing them of BHs: different tasklets
   may be run simultaneously on different CPUs.

   Properties:
   * If tasklet_schedule() is called, then tasklet is guaranteed
     to be executed on some cpu at least once after this.
   * If the tasklet is already scheduled, but its execution is still not
     started, it will be executed only once.
   * If this tasklet is already running on another CPU (or schedule is called
     from tasklet itself), it is rescheduled for later.
   * Tasklet is strictly serialized wrt itself, but not
     wrt another tasklets. If client needs some intertask synchronization,
     he makes it with spinlocks.
 */

struct tasklet_struct
{
	struct tasklet_struct *next;	/* 下一个 tasklet */
	unsigned long state;			/* tasklet 状态 */
	atomic_t count;					/* 计数器,记录对 tasklet 的引用数 */
	void (*func)(unsigned long);	/* tasklet 执行的函数 */
	unsigned long data;				/* 函数 func 的参数 */
};

func 就是 tasklet 要执行的处理函数,用户定义函数内容,相当于中断处理函数。

如果要使用 tasklet,必须先定义一个 tasklet 变量,然后使用 tasklet_init 函数初始化 tasklet。

1、taskled 初始化

void tasklet_init(struct tasklet_struct *t,	// 要初始化的tasklet变量
		  void (*func)(unsigned long), 		// tasklet 的处理函数
                  unsigned long data)		// 要传递给 func 函数的参数
{
	t->next = NULL;
	t->state = 0;
	atomic_set(&t->count, 0);
	t->func = func;
	t->data = data;
}
EXPORT_SYMBOL(tasklet_init);

也可以使用宏 DECLARE_TASKLET 来一次性完成 tasklet 的定义和初始化,DECLARE_TASKLET 定义在 include/linux/interrupt.h 文件中,定义如下:

/*
 * name:要定义的tasklet
 * func:tasklet 的处理函数
 * data:传递给 func 函数的参数
 */
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

2、触发 tasklet

/*
 * t:要调度的 tasklet
 * 返回值:无
 */
static inline void tasklet_schedule(struct tasklet_struct *t)
{
	if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
		__tasklet_schedule(t);
}

5、工作队列

工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度

Linux 内核使用work_struct 结构体表示一个工作,内容如下:

struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

这些工作组织成工作队列,工作队列使用workqueue_struct 结构体表示,内容如下:

/*
 * The externally visible workqueue.  It relays the issued work items to
 * the appropriate worker_pool through its pool_workqueues.
 */
struct workqueue_struct {
	struct list_head	pwqs;		/* WR: all pwqs of this wq */
	struct list_head	list;		/* PR: list of all workqueues */

	struct mutex		mutex;		/* protects this wq */
	int			work_color;	/* WQ: current work color */
	int			flush_color;	/* WQ: current flush color */
	atomic_t		nr_pwqs_to_flush; /* flush in progress */
	struct wq_flusher	*first_flusher;	/* WQ: first flusher */
	struct list_head	flusher_queue;	/* WQ: flush waiters */
	struct list_head	flusher_overflow; /* WQ: flush overflow list */

	struct list_head	maydays;	/* MD: pwqs requesting rescue */
	struct worker		*rescuer;	/* I: rescue worker */

	int			nr_drainers;	/* WQ: drain in progress */
	int			saved_max_active; /* WQ: saved pwq max_active */

	struct workqueue_attrs	*unbound_attrs;	/* WQ: only for unbound wqs */
	struct pool_workqueue	*dfl_pwq;	/* WQ: only for unbound wqs */

#ifdef CONFIG_SYSFS
	struct wq_device	*wq_dev;	/* I: for sysfs interface */
#endif
#ifdef CONFIG_LOCKDEP
	struct lockdep_map	lockdep_map;
#endif
	char			name[WQ_NAME_LEN]; /* I: workqueue name */

	/*
	 * Destruction of workqueue_struct is sched-RCU protected to allow
	 * walking the workqueues list without grabbing wq_pool_mutex.
	 * This is used to dump all workqueues from sysrq.
	 */
	struct rcu_head		rcu;

	/* hot fields used during command issue, aligned to cacheline */
	unsigned int		flags ____cacheline_aligned; /* WQ: WQ_* flags */
	struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */
	struct pool_workqueue __rcu *numa_pwq_tbl[]; /* FR: unbound pwqs indexed by node */
};

每个 worker 都有一个工作队列,工作者线程处理自己工作队列中的所有工作。

在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作 队列和工作者线程我们基本不用去管。

1、初始化工作

简单创建工作很简单,直接定义一个work_struct 结构体变量即可,然后使用 INIT_WORK宏来初始化工作,INIT_WORK 宏定义如下:

/*
 * _work:要初始化的工作
 * _func:工作对应的处理函数
 *
 */
#define INIT_WORK(_work, _func)						\
	__INIT_WORK((_work), (_func), 0)

2、触发处理函数

/**
 * schedule_work - put work task in global workqueue
 * @work: job to be done
 *
 * Returns %false if @work was already on the kernel-global workqueue and
 * %true otherwise.
 *
 * This puts a job in the kernel-global workqueue if it was not already
 * queued and leaves it in the same position on the kernel-global
 * workqueue otherwise.
 * 返回值:0 - 成功
 */
static inline bool schedule_work(struct work_struct *work) // 要调度软件
{
	return queue_work(system_wq, work);
}

七、Linux 中断设计思路二

/* @description		: 中断服务函数
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t xxx_handler(int irq, void *dev_id)
{
	// 中断触发后,执行此函数
	……

	// 触发中断下半部(软中断、tasklet、工作队列其中之一)

	return IRQ_RETVAL(IRQ_HANDLED);
}

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init xxx_init(void)
{
	……

	// 申请中断,注册中断服务函数
	ret = request_irq(中断号, xxx_handler, 中断标志,中断名子,dev);

	// 注册中断下半部(软中断、tasklet、工作队列其中之一)

	return 0;
}


/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit xxx_exit(void)
{
	……
		
	// 释放中断
	free_irq(中断号, dev_id);

	……
}
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux驱动开发中,中断是一种重要的机制。在Linux中,中断有两种类型:可抢占的内核代码运行在进程上下文中,而中断上下文则是不可被抢占的,会一直运行直到结束。对于硬件产生的中断,比如按键中断或网卡中断,被称为硬件中断,每个硬件中断都有对应的处理函数。在中断处理函数中,由于中断必须短暂且不能休眠,因此不能使用信号量或互斥锁,而是使用自旋锁来实现对共享资源的保护。此外,在Linux驱动开发中,可以使用计时函数来获取当前的系统时间。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [linux驱动开发-中断](https://blog.csdn.net/weixin_29898767/article/details/124320089)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* [关于Linux驱动开发中断处理](https://blog.csdn.net/weixin_44468026/article/details/119763134)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值