04 - 中断

---- 整理自 王利涛老师 课程
实验环境:宅学部落 www.zhaixue.cc

文章目录

1. 中断的基本概念

  • 示例:烧水 - 打游戏
    • 阻塞:等水烧好再去打游戏
    • 轮询:每隔一分钟过来看下水烧没烧好
    • 中断:等水烧好发出响声,暂停打游戏去处理热水
  1. 什么是轮询,什么是中断?
  2. 异常、中断
  3. 为什么处理器需要中断?

2. 中断子系统框架

  • CPU
  • 中断控制器
  • 外设
  • 中断向量表
  • 中断号
  • Linux 内核中断子系统
  • 中断编程接口

在这里插入图片描述

  • 中断发生后…
    • 当前正在执行的程序
    • 中断向量表
    • 中断服务程序
    • 回到被打断的程序,继续执行
  • 其实没这么简单…
    • 当前正在执行的程序
    • 保存被打断的上下文
    • 中断向量表
    • 找到发生中断的设备
    • 中断服务程序
    • 退出中断,调度程序运行:
      • 恢复被打断的上下文
      • 回到被打断的程序,继续执行…
      • 或者:
        1. 恢复高优先级的进程的上下文
        2. 切换到高优先级的程序执行…

3. 中断控制器:GIC

3.1 中断控制器概念

  1. 51 单片机的中断
  2. ARM SoC 处理器的中断

在这里插入图片描述

3.2 中断控制器的作用

  1. 负责处理各种中断
  2. 优先级、屏蔽、使能

在这里插入图片描述

3.3 多核下的中断控制器

在这里插入图片描述

3.4 中断分类

  • SGI:16 Software Generated Interrupts
    • 中断号 ID0~ID15,用于多核之间通讯
  • PPI:16 external Private Peripheral Interrupts
    • 每个 CPU core 私有的中断,如本地时钟,ID16~ID31
  • SPI:Shared Peripheral Interrupt
    • 所有 core 共享的中断,可以在多个 core 上运行
      支持范围可配置:32~1019,步进 32,从 ID 32 开始

3.5 中断触发类型

  1. edge-triggered 边沿触发
  2. level-sensitive 电平触发

3.6 中断号

  • HW interrupt ID - 芯片的中断号(硬中断号)
  • IRQ number - 内核中使用的中断号(软中断号)
  • IRQ_domain

在这里插入图片描述

驱动 - 映射 - 硬中断号

4. GIC 控制器中断处理流程

4.1 中断状态

Inactive
Pending
Active
Active and pending

在这里插入图片描述

4.2 GIC处理中断流程

  1. GIC 检测到使能的中断发生,将中断状态设为 pending
  2. GIC 的仲裁器将最高优先级的 pending 中断发送到指定的 CPU interface
  3. CPU interface 根据配置,将中断信号发送到 CPU
  4. CPU 应答该中断,读取寄存器获取 interrupt ID,GIC 更新中断状态为 active
    • Pending ⇒ active
    • Pending ⇒ active and pending 正在处理的中断重新产生
    • Active ⇒ active and pending 若中断状态为 active
  5. CPU 处理完中断后,发送 中断结束信号 EOI 给 GIC

4.3 中断示例

vexpress ARM A9 开发板 + GIC:PL390 + timer:SP804

5. 编写 RTC 裸机中断程序

在这里插入图片描述

6. 中断函数的编写规则

  • 编写中断函数需要注意的几个地方
    • 无传参
    • 无返回值
    • 短小精悍

7. 在 Linux 下编写 RTC 驱动中断程序

7.1 Linux 内核中断接口

// linux-5.10.4\include\linux\interrupt.h
request_irq(unsigned int irq, 
			irq_handler_t handler, 
			unsigned long flags, 
			const char *name, 
			void *dev)
// irq为中断号,handler为中断处理函数,flags为中断标志位,name为中断处理函数的名称,dev为设备指针。
// 中断处理函数就是在中断发生时执行的函数。中断标志位决定了中断的类型,比如是边沿触发还是电平触发。
  • request_irq()
  • free_irq()
  • HW interrupt ID 和中断号
  • 物理地址与虚拟地址

7.2 编程实验

ifneq ($(KERNELRELEASE),)
obj-m := rtc.o
else
EXTRA_CFLAGS += -DDEBUG 
KDIR := /home/code_folder/uboot_linux_rootfs/kernel/linux-5.10.4
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
all:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
	make $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif
// rtc.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>

typedef volatile struct{
        unsigned long  RTCDR;    /* +0x00: data register */
        unsigned long  RTCMR;    /* +0x04: match register */
        unsigned long  RTCLR;    /* +0x08: load register */
        unsigned long  RTCCR;    /* +0x0C: control register */
        unsigned long  RTCIMSC;  /* +0x10: interrupt mask set and clear register*/
        unsigned long  RTCRIS;   /* +0x14: raw interrupt status register*/
        unsigned long  RTCMIS;   /* +0x18: masked interrupt status register */
        unsigned long  RTCICR;   /* +0x1C: interrupt clear register */
}rtc_reg_t;

#define RTC_BASE 0x10017000

volatile rtc_reg_t *regs = NULL;
int counter = 0;

void set_rtc_alarm(rtc_reg_t *regs)
{
    unsigned long tmp = 0;
    tmp = regs->RTCCR;    /* write enable */
    tmp = tmp & 0xFFFFFFFE;
    regs->RTCCR = tmp;

    tmp = regs->RTCDR;    /* get current time */
    regs->RTCMR = tmp + 1;/* set alarm time */

    regs->RTCICR = 1;     /* clear RTCINTR interrupt */ 
    regs->RTCIMSC = 1;    /* set the mask */

    tmp = regs->RTCCR;    /* write disable */
    tmp = tmp | 0x1;
    regs->RTCCR = tmp;
}

static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
    printk("counter: %d, irqnum: %d\n", counter++, irq);
    set_rtc_alarm(regs);     // 下一秒继续发送中断

    return IRQ_HANDLED;
}

static int __init rtc_init(void)
{
    irqreturn_t ret = 0;

    regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t)); 
    printk("rtc_init\n");
        
    // 设置RTC下一秒发送一个中断
    set_rtc_alarm(regs);
    ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
    if (ret == -1){
        printk("request_irq failed!\n");
        return -1;
    }
    
    return 0;
}

static void __exit rtc_exit(void)
{
    free_irq(39, NULL);
    printk("Goodbye rtc module!\n");
}

module_init(rtc_init);
module_exit(rtc_exit);

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8. Linux中断处理流程

  • 中断处理,CPU 硬件自动完成的部分
    保存 CPSR 到 SPSR_irq 寄存器
    设置 CPSR 控制位,让 CPU 进入 ARM 状态、IRQ 模式
    CPSR 中的 IRQ 位置一,硬件自动关闭 IRQ
    将当前中断地址(返回地址)保存到 LR_irq 寄存器
    设置 PC 指针 PC=0x00000018,跳转到中断向量表执行
  • ARM 中断向量表
    内核定义了中断向量表,当中断发生时,可以跳到 L_vectors_start + 偏移地址
// kernel\linux-5.10.4\arch\arm\kernel\entry-armv.S
.L__vectors_start:
	W(b)	vector_rst
	W(b)	vector_und
	W(ldr)	pc, .L__vectors_start + 0x1000
	W(b)	vector_pabt
	W(b)	vector_dabt
	W(b)	vector_addrexcptn
	W(b)	vector_irq  // <==
	W(b)	vector_fiq
  • IRQ 中断向量表
// kernel\linux-5.10.4\arch\arm\kernel\entry-armv.S

/*
 * Interrupt dispatcher
 */
	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
// kernel\linux-5.10.4\arch\arm\kernel\entry-armv.S
	.align	5
__irq_usr:
	usr_entry  //保存现场
	kuser_cmpxchg_check
	irq_handler  // <== 处理中断
	get_thread_info tsk
	mov	why, #0
	b	ret_to_user_from_irq  // 恢复
 UNWIND(.fnend		)
ENDPROC(__irq_usr)
// kernel\linux-5.10.4\arch\arm\kernel\entry-armv.S

/*
 * Interrupt handling.
 */
	.macro	irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
	ldr	r1, =handle_arch_irq
	mov	r0, sp
	badr	lr, 9997f
	ldr	pc, [r1]
#else
	arch_irq_handler_default  // <==
#endif
9997:
	.endm
  • 根据 HW interrupt ID 找到 IRQ number,调用 asm_do_IRQ
// kernel\linux-5.10.4\arch\arm\include\asm\entry-macro-multi.S

/*
 * Interrupt handling.  Preserves r7, r8, r9
 */
	.macro	arch_irq_handler_default
	get_irqnr_preamble r6, lr
1:	get_irqnr_and_base r0, r2, r6, lr
	movne	r1, sp
	@
	@ routine called with r0 = irq number, r1 = struct pt_regs *
	@
	badrne	lr, 1b
	bne	asm_do_IRQ  // <==
// kernel\linux-5.10.4\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); //  <==
}

...

/*
 * handle_IRQ handles all hardware IRQ's.  Decoded IRQs should
 * not come via this function.  Instead, they should provide their
 * own 'handler'.  Used by platform code implementing C-based 1st
 * level decoding.
 */
void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
	__handle_domain_irq(NULL, irq, false, regs); //  <==
}
// kernel\linux-5.10.4\kernel\irq\irqdesc.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_DOMAIN
	if (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;
}

...

/**
 * generic_handle_irq - Invoke the handler for a particular irq
 * @irq:	The irq number to handle
 *
 */
int generic_handle_irq(unsigned int irq)
{
	struct irq_desc *desc = irq_to_desc(irq);
	struct irq_data *data;

	if (!desc)
		return -EINVAL;

	data = irq_desc_get_irq_data(desc);
	if (WARN_ON_ONCE(!in_irq() && handle_enforce_irqctx(data)))
		return -EPERM;

	generic_handle_irq_desc(desc); // <==
	return 0;
}
// kernel\linux-5.10.4\include\linux\irqdesc.h
/*
 * Architectures call this to let the generic IRQ layer
 * handle an interrupt.
 */
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
	desc->handle_irq(desc);
}
// kernel\linux-5.10.4\kernel\irq\chip.c
void handle_fasteoi_irq(struct irq_desc *desc)
{
	struct irq_chip *chip = desc->irq_data.chip;
	...

	handle_irq_event(desc); // <==
	...
}
// kernel\linux-5.10.4\kernel\irq\handle.c
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
	irqreturn_t ret;

	desc->istate &= ~IRQS_PENDING;
	irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
	raw_spin_unlock(&desc->lock);

	ret = handle_irq_event_percpu(desc);  // <==

	raw_spin_lock(&desc->lock);
	irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
	return ret;
}
// kernel\linux-5.10.4\kernel\irq\handle.c
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
	irqreturn_t retval = IRQ_NONE;
	unsigned int irq = desc->irq_data.irq;
	struct irqaction *action;
	
	...

	for_each_action_of_desc(desc, action) {
		irqreturn_t res;

		...

		trace_irq_handler_entry(irq, action);
		res = action->handler(irq, action->dev_id);
		trace_irq_handler_exit(irq, action, res);
		
		...

		retval |= res;
	}

	return retval;
}
  • 系统启动过程中,每个控制器(irq_domain)初始化,会建立硬件中断号(HW interrupt ID)和软中断号(IRQ number)之间的映射
// kernel\linux-5.10.4\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;
	struct irq_data *irqd = irq_desc_get_irq_data(irq_to_desc(irq));

	switch (hw) {
	case 0 ... 15: // <==
		irq_set_percpu_devid(irq);
		irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
				    handle_percpu_devid_fasteoi_ipi,
				    NULL, NULL);
		break;
	case 16 ... 31: // <==
		irq_set_percpu_devid(irq);
		irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
				    handle_percpu_devid_irq, NULL, NULL);
		break;
	default: // <==
		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(irqd);
		break;
	}

	/* Prevents SW retriggers which mess up the ACK/EOI ordering */
	irqd_set_handle_enforce_irqctx(irqd);
	return 0;
}
// kernel\linux-5.10.4\kernel\irq\irqdomain.c
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);
}
// kernel\linux-5.10.4\kernel\irq\chip.c
void
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
		  const char *name)
{
	unsigned long flags;
	struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);

	if (!desc)
		return;

	__irq_do_set_handler(desc, handle, is_chained, name); // <==
	irq_put_desc_busunlock(desc, flags);
}

...

static void
__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,
		     int is_chained, const char *name)
{
	...
	desc->handle_irq = handle;
	desc->name = name;
	...
}
  • 用户注册部分
// kernel\drivers\11rtc\rtc.c
...
ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
...
// kernel\linux-5.10.4\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(39, rtc_alarm_handler, NULL, 0, "rtc0-test", NULL);
}
// kernel\linux-5.10.4\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;
	
	...

	desc = irq_to_desc(irq); // 申请中断描述结构体
	
	...
	
	// 分配action
	action->handler = handler; // rtc_alarm_handler
	action->thread_fn = thread_fn; // NULL
	action->flags = irqflags; // 0
	action->name = devname; // "rtc0-test"
	action->dev_id = dev_id; // NULL
	...
retval = __setup_irq(irq, desc, action); // 开启中断
	...
  • 中断触发
    根据中断号找到 struct irq_desc,遍历 desc->action 链表,执行 action->handler

9. 中断的上半部和下半部

9.1 为什么要分上半部和下半部?

  • 上半部:响应中断,硬件配置,发送 EOI 给 GIC
  • 下半部:数据复制、数据包封装转发、编解码…
    • 为了在 中断执行时间尽可能短和中断处理需完成大量工作 之间找到一个平衡点,Linux 将中断处理程序分解为两个半部:上半部(top half)和下半部(bottom half)。
    • 上半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将下半部处理程序挂到该设备的下半部执行队列中去。这样,上半部执行的速度就会很快,可以服务更多的中断请求。
    • 现在,中断处理工作的重心就落在了下半部的头上,它来完成中断事件的绝大多数任务。下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是上半部和下半部的最大不同,因为上半部往往被设计成不可中断。下半部则相对来说并不是非常紧急的,而且相对比较耗时,不在硬件中断服务程序中执行。

9.2 中断下半部

  • 软中断
  • tasklet
  • 工作队列:workqueue
  • 中断线程化

9.3 编程实验

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>

typedef volatile struct{
        unsigned long  RTCDR;    /* +0x00: data register */
        unsigned long  RTCMR;    /* +0x04: match register */
        unsigned long  RTCLR;    /* +0x08: load register */
        unsigned long  RTCCR;    /* +0x0C: control register */
        unsigned long  RTCIMSC;  /* +0x10: interrupt mask set and clear register*/
        unsigned long  RTCRIS;   /* +0x14: raw interrupt status register*/
        unsigned long  RTCMIS;   /* +0x18: masked interrupt status register */
        unsigned long  RTCICR;   /* +0x1C: interrupt clear register */
}rtc_reg_t;

struct rtc_time{
    unsigned int year;
    unsigned int mon;
    unsigned int day;
    unsigned int hour;
    unsigned int min;
    unsigned int sec;
};

#define RTC_BASE 0x10017000

volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;

void set_rtc_alarm(rtc_reg_t *regs)
{
    unsigned long tmp = 0;
    tmp = regs->RTCCR;    /* write enable */
    tmp = tmp & 0xFFFFFFFE;
    regs->RTCCR = tmp;

    tmp = regs->RTCDR;    /* get current time */
    current_time = tmp;
    regs->RTCMR = tmp + 1;/* set alarm time */

    regs->RTCICR = 1;     /* clear RTCINTR interrupt */ 
    regs->RTCIMSC = 1;    /* set the mask */

    tmp = regs->RTCCR;    /* write disable */
    tmp = tmp | 0x1;
    regs->RTCCR = tmp;
}

static void time_translate(void) 
{
    struct rtc_time tm;
    tm.hour = (current_time % 86400) / 3600;
    tm.min  = (current_time % 3600) / 60;
    tm.sec  = current_time % 60;
    printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
}

static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
    set_rtc_alarm(regs);
    time_translate();
    return IRQ_HANDLED;
}

static int __init rtc_init(void)
{
    irqreturn_t ret = 0;

    regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t)); 
    printk("rtc_init\n");

    set_rtc_alarm(regs);
    ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
    if (ret == -1){
        printk("request_irq failed!\n");
        return -1;
    }
    
    return 0;
}

static void __exit rtc_exit(void)
{
    free_irq(39, NULL);
    printk("Goodbye rtc module!\n");
}

module_init(rtc_init);
module_exit(rtc_exit);

在这里插入图片描述

10. 中断下半部 - SoftIRQ:软中断

10.1 编程接口

void open_softirq(int nr, void (*action)(struct softirq_action *)); //注册
void raise_softirq(unsigned int nr); // 触发软中断
void raise_softirq_irqoff(unsigned int nr); // 中断下半部中关掉此中断

enum {
    HI_SOFTIRQ=0,
    TIMER_SOFTIRQ,
    NET_TX_SOFTIRQ,
    NET_RX_SOFTIRQ,
    BLOCK_SOFTIRQ,
    IRQ_POLL_SOFTIRQ,
    TASKLET_SOFTIRQ,
    SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ,
    RCU_SOFTIRQ,
    NR_SOFTIRQS
};
  • 数字越小,优先级越高。

10.2 编程实验:给内核添加一个软中断

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>

static void hello_softirq_handler(struct softirq_action *sa)
{
    printk("soft irq handler run!\n");
}

static int __init hello_init(void)
{
    printk("hello module!\n");

    open_softirq(HELLO_SOFTIRQ, hello_softirq_handler);
    raise_softirq(HELLO_SOFTIRQ);
    
    return 0;
}

static void __exit hello_exit(void)
{
    printk("Goodbye hello module!\n");
}

module_init(hello_init);
module_exit(hello_exit);

内核中修改如下位置:

在这里插入图片描述

在这里插入图片描述

接着修改我们之前的 rtc 驱动:(编译时需要关闭 rtc pl031 )

在这里插入图片描述

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>

typedef volatile struct{
    unsigned long  RTCDR;    /* +0x00: data register */
    unsigned long  RTCMR;    /* +0x04: match register */
    unsigned long  RTCLR;    /* +0x08: load register */
    unsigned long  RTCCR;    /* +0x0C: control register */
    unsigned long  RTCIMSC;  /* +0x10: interrupt mask set and clear register*/
    unsigned long  RTCRIS;   /* +0x14: raw interrupt status register*/
    unsigned long  RTCMIS;   /* +0x18: masked interrupt status register */
    unsigned long  RTCICR;   /* +0x1C: interrupt clear register */
}rtc_reg_t;

struct rtc_time{
    unsigned int year;
    unsigned int mon;
    unsigned int day;
    unsigned int hour;
    unsigned int min;
    unsigned int sec;
};

#define RTC_BASE 0x10017000

volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;

void set_rtc_alarm(rtc_reg_t *regs)
{
    unsigned long tmp = 0;
    tmp = regs->RTCCR;    /* write enable */
    tmp = tmp & 0xFFFFFFFE;
    regs->RTCCR = tmp;

    tmp = regs->RTCDR;    /* get current time */
    current_time = tmp;
    regs->RTCMR = tmp + 1;/* set alarm time */

    regs->RTCICR = 1;     /* clear RTCINTR interrupt */ 
    regs->RTCIMSC = 1;    /* set the mask */

    tmp = regs->RTCCR;    /* write disable */
    tmp = tmp | 0x1;
    regs->RTCCR = tmp;
}

// 中断下半部
static void rtc_softirq_handler(struct softirq_action *sa) 
{
    printk("enter rtc_softirq_handler\n");

    struct rtc_time tm;
    tm.hour = (current_time % 86400) / 3600;
    tm.min  = (current_time % 3600) / 60;
    tm.sec  = current_time % 60;
    printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
}

static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
    set_rtc_alarm(regs);
    raise_softirq(HELLO_SOFTIRQ); // 触发软中断
    return IRQ_HANDLED;
}

static int __init rtc_init(void)
{
    irqreturn_t ret = 0;

    regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t)); 
    printk("rtc_init\n");

    set_rtc_alarm(regs);
    ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
    if (ret == -1){
        printk("request_irq failed!\n");
        return -1;
    }

    // 可以使用我们在内核中添加的自己的软中断号,也可以使用本身就存在的软中断号
    // 如 TASKLET_SOFTIRQ
    open_softirq(HELLO_SOFTIRQ, rtc_softirq_handler);
    
    return 0;
}

static void __exit rtc_exit(void)
{
    free_irq(39, NULL);
    printk("Goodbye rtc module!\n");
}

module_init(rtc_init);
module_exit(rtc_exit);

在这里插入图片描述

10.3 软中断的运行

10.3.1 软中断的实现

softirq 的数据结构:struct softirq_action
软中断描述符数组:struct softirq_action softirq_vec[]

10.3.2 软中断的运行过程

  • 运行时机:中断退出后的某个时机
    (软中断一般会在硬件中断处理程序(上半部)退出时开始执行, 一个软中断不会抢占另外一个软中断,唯一可以抢占软中断的是硬件中断处理程序。)
  • 开中断,可以被打断,不允许嵌套
  • 中断延后到线程执行:ksoftirqd
  • 过程如下:
    • 普通进程 1 正在运行,一个 IRQ 中断产生
      • CPU 自动执行部分:关闭 IRQ 中断、寄存器备份
      • 跳转到中断向量表执行流程:vector_irq
      • irq_handler
        asm_do_IRQ ⇒ handle_IRQ ⇒ __handle_domain_irq
        irq_enter
        generic_handle_irq:具体的外设 IRQ 中断处理
        irq_exit
      • 检测是否有pending 的软中断,有则执行
        • 软中断是否频繁执行,有则放到 softirqd 执行
      • 退出中断,检测是否有更高优先级的进程:
        • 无,恢复被打断进程 1 的上下文,继续执行进程 1 …
        • 有,调度更高优先级的进程 2 执行 …

11. 中断下半部 - tasklet

因为内核已经定义好了 10 种软中断类型,并且不建议我们自行添加额外的软中断,并且由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,作为设备驱动程序的开发者来说,增加了负担。而如果某种应用并不需要在多个 CPU 上并行执行,那么软中断其实是没有必要的。但是,对于某些情况下,我们不希望一些操作直接在中断的 handler 中执行,但是又希望在稍后的时间里得到快速地处理,这就需要使用 tasklet 机制。 tasklet 是建立在软中断上的一种延迟执行机制,它的实现基于 TASKLET_SOFTIRQ 和 HI_SOFTIRQ 这两个软中断类型。

  • 它具有以下特性:
    a)一种特定类型的 tasklet 只能运行在一个 CPU 上,不能并行,只能串行执行。
    b)多个不同类型的 tasklet 可以并行在多个 CPU 上。
    c)软中断是静态分配的,在内核编译好之后,就不能改变。但 tasklet 就灵活许多,可以在运行时改变(比如添加模块时)。

11.1 编程接口

DECLARE_TASKLET(name, _callback);
void tasklet_init(struct tasklet_struct *t, 
                  void (*func)(unsigned long), 
 unsigned long data
);
void tasklet_schedule(struct tasklet_struct *t);
void tasklet_hi_schedule(struct tasklet_struct *t);
void tasklet_kill(struct tasklet_struct *t);

11.2 编程实验:将 RTC 驱动的下半部改为 tasklet 来实现

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>

typedef volatile struct{
    unsigned long  RTCDR;    /* +0x00: data register */
    unsigned long  RTCMR;    /* +0x04: match register */
    unsigned long  RTCLR;    /* +0x08: load register */
    unsigned long  RTCCR;    /* +0x0C: control register */
    unsigned long  RTCIMSC;  /* +0x10: interrupt mask set and clear register*/
    unsigned long  RTCRIS;   /* +0x14: raw interrupt status register*/
    unsigned long  RTCMIS;   /* +0x18: masked interrupt status register */
    unsigned long  RTCICR;   /* +0x1C: interrupt clear register */
}rtc_reg_t;

struct rtc_time{
    unsigned int year;
    unsigned int mon;
    unsigned int day;
    unsigned int hour;
    unsigned int min;
    unsigned int sec;
};

#define RTC_BASE 0x10017000

volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
static struct tasklet_struct rtc_tasklet;

void set_rtc_alarm(rtc_reg_t *regs)
{
    unsigned long tmp = 0;
    tmp = regs->RTCCR;    /* write enable */
    tmp = tmp & 0xFFFFFFFE;
    regs->RTCCR = tmp;

    tmp = regs->RTCDR;    /* get current time */
    current_time = tmp;
    regs->RTCMR = tmp + 1;/* set alarm time */

    regs->RTCICR = 1;     /* clear RTCINTR interrupt */ 
    regs->RTCIMSC = 1;    /* set the mask */

    tmp = regs->RTCCR;    /* write disable */
    tmp = tmp | 0x1;
    regs->RTCCR = tmp;
}

// 中断下半部
static void rtc_tasklet_handler(unsigned long a) 
{
    printk("enter rtc_softirq_handler\n");

    struct rtc_time tm;
    tm.hour = (current_time % 86400) / 3600;
    tm.min  = (current_time % 3600) / 60;
    tm.sec  = current_time % 60;
    printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
    printk("args = %ld\n", a);
}

static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
    set_rtc_alarm(regs);
    // raise_softirq(HELLO_SOFTIRQ); // 触发软中断
    tasklet_schedule(&rtc_tasklet);
    return IRQ_HANDLED;
}

static int __init rtc_init(void)
{
    irqreturn_t ret = 0;

    regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t)); 
    printk("rtc_init\n");

    set_rtc_alarm(regs);
    ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
    if (ret == -1){
        printk("request_irq failed!\n");
        return -1;
    }

    // open_softirq(HELLO_SOFTIRQ, rtc_softirq_handler);
    tasklet_init(&rtc_tasklet, rtc_tasklet_handler, 10);

    return 0;
}

static void __exit rtc_exit(void)
{
    tasklet_kill(&rtc_tasklet);
    free_irq(39, NULL);
    printk("Goodbye rtc module!\n");
}

module_init(rtc_init);
module_exit(rtc_exit);

在这里插入图片描述

也可以在 tasklet_schedule(&rtc_tasklet); 之前通过 rtc_tasklet.data = 100; 重新赋值:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>

typedef volatile struct{
    unsigned long  RTCDR;    /* +0x00: data register */
    unsigned long  RTCMR;    /* +0x04: match register */
    unsigned long  RTCLR;    /* +0x08: load register */
    unsigned long  RTCCR;    /* +0x0C: control register */
    unsigned long  RTCIMSC;  /* +0x10: interrupt mask set and clear register*/
    unsigned long  RTCRIS;   /* +0x14: raw interrupt status register*/
    unsigned long  RTCMIS;   /* +0x18: masked interrupt status register */
    unsigned long  RTCICR;   /* +0x1C: interrupt clear register */
}rtc_reg_t;

struct rtc_time{
    unsigned int year;
    unsigned int mon;
    unsigned int day;
    unsigned int hour;
    unsigned int min;
    unsigned int sec;
};

#define RTC_BASE 0x10017000

volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
static struct tasklet_struct rtc_tasklet;

void set_rtc_alarm(rtc_reg_t *regs)
{
    unsigned long tmp = 0;
    tmp = regs->RTCCR;    /* write enable */
    tmp = tmp & 0xFFFFFFFE;
    regs->RTCCR = tmp;

    tmp = regs->RTCDR;    /* get current time */
    current_time = tmp;
    regs->RTCMR = tmp + 1;/* set alarm time */

    regs->RTCICR = 1;     /* clear RTCINTR interrupt */ 
    regs->RTCIMSC = 1;    /* set the mask */

    tmp = regs->RTCCR;    /* write disable */
    tmp = tmp | 0x1;
    regs->RTCCR = tmp;
}

// 中断下半部
static void rtc_tasklet_handler(unsigned long a) 
{
    printk("enter rtc_softirq_handler\n");

    struct rtc_time tm;
    tm.hour = (current_time % 86400) / 3600;
    tm.min  = (current_time % 3600) / 60;
    tm.sec  = current_time % 60;
    printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
    printk("args = %ld\n", a);
}

static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
    set_rtc_alarm(regs);
    // raise_softirq(HELLO_SOFTIRQ); // 触发软中断
    rtc_tasklet.data = 100;
    tasklet_schedule(&rtc_tasklet);
    return IRQ_HANDLED;
}

static int __init rtc_init(void)
{
    irqreturn_t ret = 0;

    regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t)); 
    printk("rtc_init\n");

    set_rtc_alarm(regs);
    ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
    if (ret == -1){
        printk("request_irq failed!\n");
        return -1;
    }

    // open_softirq(HELLO_SOFTIRQ, rtc_softirq_handler);
    tasklet_init(&rtc_tasklet, rtc_tasklet_handler, 10);

    return 0;
}

static void __exit rtc_exit(void)
{
    tasklet_kill(&rtc_tasklet);
    free_irq(39, NULL);
    printk("Goodbye rtc module!\n");
}

module_init(rtc_init);
module_exit(rtc_exit);

在这里插入图片描述

注:上述的代码,在 rtc_exit 退出的里面,需要加上 tasklet_kill。

  • tasklet_schedule() 触发的软中断,优先级是 TASKLET_SOFTIRQ,tasklet_hi_schedule() 触发的软中断,优先级是更高的 HI_SOFTIRQ。每个 CPU 对应维护了两个链表:tasklet_vec 和 tasklet_hi_vec。

在这里插入图片描述

11.3 tasklet 的运行

11.3.1 tasklet 的实现:基于软中断

tasklet_vec 链表:软中断优先级 6,TASKLET_SOFTIRQ
tasklet_hi_vec 链表:软中断优先级 0,HI_SOFTIRQ

在这里插入图片描述

11.3.2 tasklet 的执行过程

  • 普通进程 1 正在运行,一个 IRQ 中断产生
    • CPU 自动执行部分:关闭 IRQ 中断、寄存器备份
    • 跳转到中断向量表执行流程:vector_irq
    • irq_handler
      asm_do_IRQ ⇒ handle_IRQ ⇒ __handle_domain_irq
      irq_enter
      generic_handle_irq:具体的外设 IRQ 中断处理
      irq_exit
    • 检测是否有 pending 的软中断,有则执行
      执行 tasklet
      tasklet 是否频繁执行,有则放到 softirqd 执行
    • 退出中断,检测是否有更高优先级的进程:
      • 无,恢复被打断进程 1 的上下文,继续执行进程 1 …
      • 有,调度更高优先级的进程 2 执行 …

12. 中断下半部 - 工作队列 workqueue

12.1 编程接口

DECLARE_WORK(n, f)
INIT_WORK(_work, _func)
schedule_work (struct work_struct *work);
cancel_work_sync (struct work_struct *work);
struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;
	#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
	#endif
};
typedef void (*work_func_t)(struct work_struct *work);

12.2 实验:将 RTC 驱动的中断下半部改用工作队列实现

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>

typedef volatile struct{
        unsigned long  RTCDR;    /* +0x00: data register */
        unsigned long  RTCMR;    /* +0x04: match register */
        unsigned long  RTCLR;    /* +0x08: load register */
        unsigned long  RTCCR;    /* +0x0C: control register */
        unsigned long  RTCIMSC;  /* +0x10: interrupt mask set and clear register*/
        unsigned long  RTCRIS;   /* +0x14: raw interrupt status register*/
        unsigned long  RTCMIS;   /* +0x18: masked interrupt status register */
        unsigned long  RTCICR;   /* +0x1C: interrupt clear register */
}rtc_reg_t;

struct rtc_time{
    unsigned int year;
    unsigned int mon;
    unsigned int day;
    unsigned int hour;
    unsigned int min;
    unsigned int sec;
};

#define RTC_BASE 0x10017000

volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
static struct work_struct rtc_workstruct;

void set_rtc_alarm(rtc_reg_t *regs)
{
    unsigned long tmp = 0;
    tmp = regs->RTCCR;    /* write enable */
    tmp = tmp & 0xFFFFFFFE;
    regs->RTCCR = tmp;

    tmp = regs->RTCDR;    /* get current time */
    current_time = tmp;
    regs->RTCMR = tmp + 1;/* set alarm time */

    regs->RTCICR = 1;     /* clear RTCINTR interrupt */ 
    regs->RTCIMSC = 1;    /* set the mask */

    tmp = regs->RTCCR;    /* write disable */
    tmp = tmp | 0x1;
    regs->RTCCR = tmp;
}

// 中断下半部
static void rtc_work_handler(struct work_struct *work) 
{
    struct rtc_time tm;
    tm.hour = (current_time % 86400) / 3600;
    tm.min  = (current_time % 3600) / 60;
    tm.sec  = current_time % 60;
    printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
}

static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
    set_rtc_alarm(regs);

    schedule_work(&rtc_workstruct);

    return IRQ_HANDLED;
}

static int __init rtc_init(void)
{
    irqreturn_t ret = 0;

    regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t)); 
    printk("rtc_init\n");

    set_rtc_alarm(regs);
    ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
    if (ret == -1){
        printk("request_irq failed!\n");
        return -1;
    }
    
    INIT_WORK(&rtc_workstruct, rtc_work_handler);

    return 0;
}

static void __exit rtc_exit(void)
{
    cancel_work_sync(&rtc_workstruct);
    free_irq(39, NULL);
    printk("Goodbye rtc module!\n");
}

module_init(rtc_init);
module_exit(rtc_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");

在这里插入图片描述

12.3 延迟工作队列

12.3.1 编程接口

DECLARE_DELAYED_WORK(n, f)
INIT_DELAYED_WORK(_work, _func)
schedule_delayed_work(delayed_work, jiffies);
bool flush_delayed_work (struct delayed_work *dwork);
cancel_delayed_work_sync (struct work_struct *work);
struct delayed_work {
	struct work_struct work;
	struct timer_list timer;
	/* target workqueue and CPU ->timer uses to queue ->work */
	struct workqueue_struct *wq;
	int cpu;
};
typedef void (*work_func_t)(struct work_struct *work);

12.3.2 实验:将 RTC 驱动的下半部改用延迟工作队列实现

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>

typedef volatile struct{
    unsigned long  RTCDR;    /* +0x00: data register */
    unsigned long  RTCMR;    /* +0x04: match register */
    unsigned long  RTCLR;    /* +0x08: load register */
    unsigned long  RTCCR;    /* +0x0C: control register */
    unsigned long  RTCIMSC;  /* +0x10: interrupt mask set and clear register*/
    unsigned long  RTCRIS;   /* +0x14: raw interrupt status register*/
    unsigned long  RTCMIS;   /* +0x18: masked interrupt status register */
    unsigned long  RTCICR;   /* +0x1C: interrupt clear register */
}rtc_reg_t;

struct rtc_time{
    unsigned int year;
    unsigned int mon;
    unsigned int day;
    unsigned int hour;
    unsigned int min;
    unsigned int sec;
};

#define RTC_BASE 0x10017000

volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;
static struct delayed_work rtc_delayed_work;

void set_rtc_alarm(rtc_reg_t *regs)
{
    unsigned long tmp = 0;
    tmp = regs->RTCCR;    /* write enable */
    tmp = tmp & 0xFFFFFFFE;
    regs->RTCCR = tmp;

    tmp = regs->RTCDR;    /* get current time */
    current_time = tmp;
    regs->RTCMR = tmp + 1;/* set alarm time */

    regs->RTCICR = 1;     /* clear RTCINTR interrupt */ 
    regs->RTCIMSC = 1;    /* set the mask */

    tmp = regs->RTCCR;    /* write disable */
    tmp = tmp | 0x1;
    regs->RTCCR = tmp;
}

// 中断下半部
static void rtc_delayed_work_handler(struct work_struct *work) 
{
    struct rtc_time tm;
    tm.hour = (current_time % 86400) / 3600;
    tm.min  = (current_time % 3600) / 60;
    tm.sec  = current_time % 60;
    printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
}

static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
    set_rtc_alarm(regs);

    schedule_delayed_work(&rtc_delayed_work, 3*HZ); // 注意这里

    return IRQ_HANDLED;
}

static int __init rtc_init(void)
{
    irqreturn_t ret = 0;

    regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t)); 
    printk("rtc_init\n");

    set_rtc_alarm(regs);
    ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
    if (ret == -1){
        printk("request_irq failed!\n");
        return -1;
    }
    
    INIT_DELAYED_WORK(&rtc_delayed_work, rtc_delayed_work_handler);

    return 0;
}

static void __exit rtc_exit(void)
{
    cancel_delayed_work_sync(&rtc_delayed_work);
    free_irq(39, NULL);
    printk("Goodbye rtc module!\n");
}

module_init(rtc_init);
module_exit(rtc_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");

在这里插入图片描述

12.4 工作队列的运行

12.4.1 工作队列的实现

  1. 先 struct 一个 work (内含中断下半部的处理函数)
  2. schedule_work:将 work 添加 workqueue
  3. 执行 workqueue 的线程:worker thread

12.4.2 运行模式

  • 单线程式

在这里插入图片描述

  • 多线程式

在这里插入图片描述

12.4.3 workqueue 队列的弊端

  • 假设现在 work item 工作项 work0、work1 和 work2 被绑定到同一 CPU 的工作队列 worqueue0上。work0 执行 5 毫秒后休眠 10 毫秒,接着再次运行 5 毫秒,然后结束。work1 和 work2 都是运行 5 毫秒后休眠 10 毫秒。
  • 弊端:因为只有 1 个 worker 线程,所以即便在执行某个 work item 的时候休眠,其他的 work item 也得不到执行,因此将这 3 个 work item 执行完毕将总共需要 50ms 的时间。

在这里插入图片描述

13. 并发管理工作队列 CMWQ

13.1 CMWQ:Concurrency Managed Workqueue

一个 CPU 上是不可能“同时”运行多个线程的,所以这里的名称是 concurrency(并发),而不是 parallelism(并行)

  • 在一个 CPU 上运行多个 worker 线程
  • 工作项:work item,添加到工作队列(workqueue)中
  • 提供两种 worker 线程池:bound 和 unbound
  • workqueue 可通过 flag 参数绑定到指定类型的线程池执行
  • max_active:工作队列在每个 CPU 上并发处理的 work 个数

在这里插入图片描述

13.2 编程接口

struct workqueue_struct * alloc_workqueue(const char *fmt, unsigned int flags, int max_active, ...);
/*
flags:
	WQ_UNBOUND
	WQ_FREEZABLE
	WQ_MEM_RECLAIM
	WQ_HIGHPRI
	WQ_CPU_INTENSIVE
*/
INIT_WORK (_work, _func)
bool queue_work (struct workqueue_struct *wq,struct work_struct *work);
flush_workqueue();

13.3 CMWQ 工作队列编程实战

  • 实验:将 RTC 驱动的中断下半部改用 CMWQ 实现
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>

typedef volatile struct{
    unsigned long  RTCDR;    /* +0x00: data register */
    unsigned long  RTCMR;    /* +0x04: match register */
    unsigned long  RTCLR;    /* +0x08: load register */
    unsigned long  RTCCR;    /* +0x0C: control register */
    unsigned long  RTCIMSC;  /* +0x10: interrupt mask set and clear register*/
    unsigned long  RTCRIS;   /* +0x14: raw interrupt status register*/
    unsigned long  RTCMIS;   /* +0x18: masked interrupt status register */
    unsigned long  RTCICR;   /* +0x1C: interrupt clear register */
}rtc_reg_t;

struct rtc_time{
    unsigned int year;
    unsigned int mon;
    unsigned int day;
    unsigned int hour;
    unsigned int min;
    unsigned int sec;
};

#define RTC_BASE 0x10017000

volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;

static struct work_struct rtc_work;
struct workqueue_struct *rtc_cmwq_workqueue;

void set_rtc_alarm(rtc_reg_t *regs)
{
    unsigned long tmp = 0;
    tmp = regs->RTCCR;    /* write enable */
    tmp = tmp & 0xFFFFFFFE;
    regs->RTCCR = tmp;

    tmp = regs->RTCDR;    /* get current time */
    current_time = tmp;
    regs->RTCMR = tmp + 1;/* set alarm time */

    regs->RTCICR = 1;     /* clear RTCINTR interrupt */ 
    regs->RTCIMSC = 1;    /* set the mask */

    tmp = regs->RTCCR;    /* write disable */
    tmp = tmp | 0x1;
    regs->RTCCR = tmp;
}

// 中断下半部
static void rtc_work_handler(struct work_struct *work) 
{
    struct rtc_time tm;
    tm.hour = (current_time % 86400) / 3600;
    tm.min  = (current_time % 3600) / 60;
    tm.sec  = current_time % 60;
    printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);
}

static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
    set_rtc_alarm(regs);

    queue_work(rtc_cmwq_workqueue, &rtc_work);

    return IRQ_HANDLED;
}

static int __init rtc_init(void)
{
    irqreturn_t ret = 0;

    regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t)); 
    printk("rtc_init\n");

    set_rtc_alarm(regs);
    ret = request_irq(39, rtc_alarm_handler, 0, "rtc0-test", NULL);
    if (ret == -1){
        printk("request_irq failed!\n");
        return -1;
    }

    rtc_cmwq_workqueue = alloc_workqueue("rtc", WQ_MEM_RECLAIM, 3);
    INIT_WORK(&rtc_work, rtc_work_handler);

    return 0;
}

static void __exit rtc_exit(void)
{
    flush_workqueue(rtc_cmwq_workqueue);
    cancel_work_sync(&rtc_work);
    free_irq(39, NULL);
    printk("Goodbye rtc module!\n");
}

module_init(rtc_init);
module_exit(rtc_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");

在这里插入图片描述

13.4 CMWQ 的运行

假设现在 work item 工作项 work0、work1 和 work2 被绑定到同一 CPU 的工作队列 worqueue0 上。work0 执行 5 毫秒后休眠 10 毫秒,接着再次运行 5 毫秒,然后结束。work1 和 work2 都是运行 5 毫秒后休眠 10 毫秒。

13.4.1 simple FIFO scheduling

在这里插入图片描述

13.4.2 max_active >= 3

在这里插入图片描述

13.4.3 max_active = 2

在这里插入图片描述

13.4.4 w1 and w2 are queued to wq q1

w0 在 wq q0 上,w1 和 w2 在 wq q1 上
在这里插入图片描述

14. 中断线程化:request_threaded_irq

14.1 编程接口

int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long flags,
const char *name, 
void *dev);

14.2 实验:将 RTC 驱动的中断下半部改为线程化执行

  • 将 RTC 驱动的中断下半部改为线程化执行
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>

typedef volatile struct{
    unsigned long  RTCDR;    /* +0x00: data register */
    unsigned long  RTCMR;    /* +0x04: match register */
    unsigned long  RTCLR;    /* +0x08: load register */
    unsigned long  RTCCR;    /* +0x0C: control register */
    unsigned long  RTCIMSC;  /* +0x10: interrupt mask set and clear register*/
    unsigned long  RTCRIS;   /* +0x14: raw interrupt status register*/
    unsigned long  RTCMIS;   /* +0x18: masked interrupt status register */
    unsigned long  RTCICR;   /* +0x1C: interrupt clear register */
}rtc_reg_t;

struct rtc_time{
    unsigned int year;
    unsigned int mon;
    unsigned int day;
    unsigned int hour;
    unsigned int min;
    unsigned int sec;
};

#define RTC_BASE 0x10017000

volatile rtc_reg_t *regs = NULL;
static unsigned long current_time = 0;

void set_rtc_alarm(rtc_reg_t *regs)
{
    unsigned long tmp = 0;
    tmp = regs->RTCCR;    /* write enable */
    tmp = tmp & 0xFFFFFFFE;
    regs->RTCCR = tmp;

    tmp = regs->RTCDR;    /* get current time */
    current_time = tmp;
    regs->RTCMR = tmp + 1;/* set alarm time */

    regs->RTCICR = 1;     /* clear RTCINTR interrupt */ 
    regs->RTCIMSC = 1;    /* set the mask */

    tmp = regs->RTCCR;    /* write disable */
    tmp = tmp | 0x1;
    regs->RTCCR = tmp;
}

static irqreturn_t rtc_irq_thread(int irq, void *p) 
{
    struct rtc_time tm;
    tm.hour = (current_time % 86400) / 3600;
    tm.min  = (current_time % 3600) / 60;
    tm.sec  = current_time % 60;
    printk("%d:%d:%d\n", tm.hour, tm.min, tm.sec);

    return IRQ_HANDLED;
}

static irqreturn_t rtc_alarm_handler(int irq, void *dev_id)
{
    set_rtc_alarm(regs);

    return IRQ_WAKE_THREAD;
}

static int __init rtc_init(void)
{
    irqreturn_t ret = 0;

    regs = (rtc_reg_t *)ioremap(RTC_BASE, sizeof(rtc_reg_t)); 
    printk("rtc_init\n");

    set_rtc_alarm(regs);
    ret = request_threaded_irq(39, rtc_alarm_handler, rtc_irq_thread, \
                               0, "rtc0-test", NULL);
    if (ret == -1){
        printk("request_irq failed!\n");
        return -1;
    }

    return 0;
}

static void __exit rtc_exit(void)
{
    free_irq(39, NULL);
    printk("Goodbye rtc module!\n");
}

module_init(rtc_init);
module_exit(rtc_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("uuxiang");

在这里插入图片描述

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
8259是一种可编程中断控制器,它可以处理多个设备的中断请求。在计算机系统中,中断是一种非常重要的机制,可以使CPU不必等待一些外设的操作完成,从而提高了系统的效率。 8259最常见的应用是在IBM PC和兼容机中,它可以管理PC中的各种设备,如键盘、鼠标、硬盘、打印机等等。在8259中,有8个中断请求线(IRQ0-IRQ7),可以连接到不同的设备上。当设备需要CPU的处理时,会向8259发送一个中断请求信号,8259会将这个请求转发给CPU,CPU会暂停当前正在执行的程序,转而去处理中断程序。当中断程序执行完毕后,CPU会回到原来的程序继续执行。 在本实验中,我们将学习如何使用汇编语言编写8259的初始化程序,使其可以正确地处理中断请求。 首先,我们需要了解8259的一些基本概念和寄存器。 8259有两个级联的芯片,一个是主芯片(master),另一个是从芯片(slave)。主芯片的IRQ0-IRQ7可以连接到不同的设备上,而从芯片只能连接到主芯片的某一个IRQ上。当从芯片需要处理中断请求时,它会向主芯片发送一个中断请求信号,由主芯片将这个请求转发给CPU。 8259有两个重要的寄存器,一个是控制寄存器(ICW),另一个是中断屏蔽寄存器(IMR)。 控制寄存器(ICW)分为4个字节,分别是ICW1、ICW2、ICW3、ICW4。ICW1用于设置8259的工作方式,ICW2用于设置中断向量号,ICW3用于设置级联方式,ICW4用于设置一些特殊功能。在初始化8259时,需要写入这些寄存器的值,以完成8259的设置。 中断屏蔽寄存器(IMR)用于控制8259对中断请求的响应。当某个设备需要中断处理时,它会向8259发送一个中断请求信号,如果该设备所在的IRQ对应的IMR位被设置为1,则8259会忽略该请求。只有当该位被清零后,8259才会将请求转发给CPU。 下面是一个简单的8259初始化程序,可以将主芯片的IRQ0-IRQ7全部开启,并设置中断向量号为0x20-0x27。该程序的实现过程如下: 1. 关闭中断,以免在初始化过程中被其他中断打断。 2. 向8259的控制寄存器写入ICW1,设置8259的工作方式为初始化。 3. 向8259的控制寄存器写入ICW2,设置中断向量号为0x20-0x27。 4. 向8259的控制寄存器写入ICW3,设置主芯片的从芯片连接方式。 5. 向8259的控制寄存器写入ICW4,设置一些特殊功能。 6. 向8259的中断屏蔽寄存器写入初始值,使得所有中断请求都能被响应。 7. 打开中断,使得CPU能够响应中断请求。 下面是该程序的具体实现过程: ``` ; 8259初始化程序 ; 将主芯片的IRQ0-IRQ7全部打开 ; 中断向量号为0x20-0x27 section .text global init_8259 init_8259: cli ; 关闭中断 ; 初始化8259 mov al, 0x11 ; ICW1:初始化,边沿触发,级联8259 out 0x20, al mov al, 0x20 ; ICW2:中断向量号从0x20开始 out 0x21, al mov al, 0x04 ; ICW3:连接从芯片到IRQ2 out 0x21, al mov al, 0x01 ; ICW4:8086模式,正常EOI out 0x21, al ; 设置IMR,打开所有中断 mov al, 0x00 ; IMR初始化为0,使得所有中断请求都能被响应 out 0x21, al sti ; 打开中断 ret ``` 在上述程序中,我们使用了汇编语言的in/out指令向8259的寄存器中写入数据,从而完成了8259的初始化设置。 该程序的主要作用是将8259初始化为可用状态,以便处理各种设备的中断请求。在实际应用中,我们可以将该程序作为系统启动时的一部分,以保证系统能够正确地响应各种中断请求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

uuxiang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值