【linux内核分析与应用-陈莉君】中断程序的动手实践

本文介绍了一个简单的中断编程实验,包括中断的应用场景、中断处理流程及如何编写自己的中断服务程序。通过注册中断线并编写中断服务例程,实现了对外设中断的响应。

目录

 

0.内容参考

1.中断的应用

2.中断的过程分析


0.内容参考

https://www.cnblogs.com/hustcat/archive/2009/08/11/1543889.html----1
https://www.cnblogs.com/hustcat/archive/2009/08/14/1546011.html----2
https://www.cnblogs.com/hustcat/archive/2009/08/15/1546601.html----3


https://www.cnblogs.com/PengfeiSong/p/6431715.html---内核的启动流程

1.中断的应用

中断的常见应用是在设备驱动中,例如我们的鼠标,键盘等,每一次我们的点按都会产生一个中断,
从而让计算机识别,本次实验是我们虚拟出来的设备,将这个设备注册到系统之中,之后我们用自己
编写的中断服务例程来使用中断.

cat /proc/interrupts 系统中的中断查看 

写一个中断程序:
申请一个中断线,一个中断线对应着一个IRQ号,这里我们选择1号对应的中断线i8042;


运行:
insmod interrupt.ko irq=1 devname=myirq
rmmod interrupt   ---为什么我的这里会报错呢?我这里执行这一步会报错,必须强制重启机器才行

问题:
我将【客户机操作系统已禁用CPU.请关闭或重启虚拟机。】这个提醒设置成不再显示此提醒,
如何能将它恢复设置成提醒呢?

 

https://blog.youkuaiyun.com/gatieme/article/details/75108154?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160734636019724838578864%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=160734636019724838578864&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-7-75108154.nonecase&utm_term=rmmod%E5%87%BA%E9%94%99&spm=1018.2118.3001.4449

 

/* 
在模块注册函数中,最重要的就是注册中断线,并将我们自己写的中断服务例程注册进去,这个工作是由request_irq函数完成的
request_irq的函数原型在
D:\005-代码\001-开源项目源码\004-内核源码\linux-4.10.1\linux-4.10.1\include\linux\interrupt.h
中,首先我们介绍一下request_irq的参数,
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的编号,也就是我们一开始在中断中看到的
handler--我们注册的中断服务例程,需要我们自己来实现,它的返回值是一个特定类型irq_handler_t,这个类型可以在我们的
D:\005-代码\001-开源项目源码\004-内核源码\linux-4.10.1\linux-4.10.1\include\linux\irqreturn.h中定义了这个类型的数据数据结构:
enum irqreturn {
	IRQ_NONE		= (0 << 0),
	IRQ_HANDLED		= (1 << 0),
	IRQ_WAKE_THREAD		= (1 << 1),
};
flags--指定了快速中断或者中断共享中中断的处理属性,这里我们给出的是IRQF_SHARED,allow sharing the irq among several devices
name--设备名称,也就是我们刚刚查看的cat /proc/interrupts的最后一列
dev --主要用于共享中断线,注意这个参数的类型是void,也就是说可以通过强制类型转换转成任一类型,可以作为共享中断时,
      中断区别的一个参数

request_threaded_irq在D:\005-代码\001-开源项目源码\004-内核源码\linux-4.10.1\linux-4.10.1\kernel\irq\manage.c中,
这个函数中定义了两个非常重要的结构体:
struct irqaction *action;
struct irq_desc *desc;
接下来进行一系列的判断,
然后进入desc = irq_to_desc(irq);
这一行是根据中断号irq在irq_desc数组中返回一个具体的irqdesc,
实际上我们的注册就是将我们传进来的数据生成一个action,再添加到irq_desc上--通过函数__setup_irq(irq, desc, action)进行;

既然有注册,我们就有对应的注销函数,主要的逻辑就是退出函数中的free_irq
还是在...kernel\irq\manage.c中,原型如下:
--linux-4.10.1
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

	kfree(__free_irq(irq, dev_id));
}

--linux-4.15.1
const void *free_irq(unsigned int irq, void *dev_id)
{
	struct irq_desc *desc = irq_to_desc(irq);
	struct irqaction *action;
	const char *devname;

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

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

	action = __free_irq(irq, dev_id);

	if (!action)
		return NULL;

	devname = action->name;
	kfree(action);
	return devname;
}
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>

static int irq;
static char * devname;//定义设备名称

module_param(irq,int,0644);// 让我们可以在命令行中传入参数
module_param(devname,charp,0644);// 让我们可以在命令行中传入参数,charp相当于char*,是字符指针

// 用在共享irq中
struct myirq
{
    int devid;
};
struct myirq mydev={1119};

/* myirq_handler这个名字要与在request_irq中注册的是一致的
在myirq_handler中实际上也是一个计数
也就是进入中断一次,count+1,
返回的是IRQ_HANDLED,前面说到返回的值代表接收到了准确的中断信号并且做了相应的准确的处理,
到此为止我们的模块就写完了,之后就要插入模块,只要一号中断线上发出了中断,不管是谁发出的中断,
都会执行我们的中断服务例程.
*/
static irqreturn_t myirq_handler(int irq,void* dev)
{
    struct myirq mydev;
    static int count = 1;
    mydev = *(struct myirq*)dev;
    printk("key:%d..\n",count);
    printk("devid:%d ,ISR is working.\n",mydev.devid);
    printk("ISR is leaving.....\n");
    count++;
    return IRQ_HANDLED;
}

static int __init myirq_init(void)
{
     printk("Module is working..\n");
     if(request_irq(irq,myirq_handler,IRQF_SHARED,devname,&mydev)!=0)
     {
        printk("%s request IRQ: %d failed ..\n",devname,irq);
        return -1; 
     }
     printk("%s request IRQ: %d succeed ..\n",devname,irq);
     return 0; 
}

static void __init myirq_exit(void)
{
     printk("Module is leaving...\n");
     free_irq(irq,&mydev);
     printk("Free the irq:%d..\n",irq);
}

MODULE_LICENSE("GLP");
module_init(myirq_init);
module_exit(myirq_exit);

2.中断的过程分析

D:\005-代码\001-开源项目源码\004-内核源码\linux-4.15.1\linux-4.15.1\arch\x86\entry\entry_32.S
common_interrupt:
	ASM_CLAC
	addl	$-0x80, (%esp)			/* Adjust vector into the [-256, -1] range */
	SAVE_ALL
	ENCODE_FRAME_POINTER
	TRACE_IRQS_OFF
	movl	%esp, %eax
	call	do_IRQ
	jmp	ret_from_intr
ENDPROC(common_interrupt)



D:\005-代码\001-开源项目源码\004-内核源码\linux-4.15.1\linux-4.15.1\arch\x86\entry\entry_64.S
common_interrupt:
	ASM_CLAC
	addq	$-0x80, (%rsp)			/* Adjust vector to [-256, -1] range */
	interrupt do_IRQ
	/* 0(%rsp): old RSP */



上面的实验只是让我们简单地体验了一下中断的程序,实际上
我们可以从源代码中来验证从理论课中学到的知识.
从中断的发生到真正地执行我们的中断服务例程,是一个非常复杂的过程,
如果需要追根溯源的话,我们可以从内核的初始化开始,其实这个过程也是
非常有意思的,我们可以找到我们内核中的main函数,从初始化我们的
中断到真正执行我们的中断服务例程.

我们这里就从do_irq函数看起,我们每次进入do_irq函数都是从汇编代码中
跳入的,可以说发生中断之后,do_irq是我们执行的第一个C语言函数,我们就
从这里开始分析.这里的代码和体系结构息息相关.
我们的do_irq代码位于:
D:\005-代码\001-开源项目源码\004-内核源码\linux-4.15.1\linux-4.15.1\arch\x86\kernel\irq.c
这个函数最核心的部分就是执行handle_irq函数,
/*
 * do_IRQ handles all normal device IRQ's (the special
 * SMP cross-CPU interrupts have their own specific
 * handlers).
 */
__visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);
	struct irq_desc * desc;
	/* high bit used in ret_from_ code  */
	unsigned vector = ~regs->orig_ax;

	entering_irq();

	/* entering_irq() tells RCU that we're not quiescent.  Check it. */
	RCU_LOCKDEP_WARN(!rcu_is_watching(), "IRQ failed to wake up RCU");

	desc = __this_cpu_read(vector_irq[vector]);

	if (!handle_irq(desc, regs)) {
		ack_APIC_irq();

		if (desc != VECTOR_RETRIGGERED) {
			pr_emerg_ratelimited("%s: %d.%d No irq handler for vector\n",
					     __func__, smp_processor_id(),
					     vector);
		} else {
			__this_cpu_write(vector_irq[vector], VECTOR_UNUSED);
		}
	}

	exiting_irq();

	set_irq_regs(old_regs);
	return 1;
}


handle_irq这个函数位于:
D:\005-代码\001-开源项目源码\004-内核源码\linux-4.15.1\linux-4.15.1\arch\x86\kernel\irq_64.c中
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;
}

实际上最终执行的是generic_handle_irq_desc函数:
在D:\005-代码\001-开源项目源码\004-内核源码\linux-4.15.1\linux-4.15.1\include\linux\irqdesc.h中,
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
	desc->handle_irq(desc);
}
最后是执行desc->handle_irq(desc);




D:\005-代码\001-开源项目源码\004-内核源码\linux-4.15.1\linux-4.15.1\arch\x86\kernel\apic\io_apic.c
中可以找到handle_irq的挂载过程:
{
	irq_flow_handler_t hdl;
	bool fasteoi;

	if (trigger) {
		irq_set_status_flags(irq, IRQ_LEVEL);
		fasteoi = true;
	} else {
		irq_clear_status_flags(irq, IRQ_LEVEL);
		fasteoi = false;
	}

	hdl = fasteoi ? handle_fasteoi_irq : handle_edge_irq;
	__irq_set_handler(irq, hdl, 0, fasteoi ? "fasteoi" : "edge");
}
这个函数的最后两行可以看到其实在这里进行了一个选择是哪种方式,之后我们会以handle_edge_irq
方式来进行分析.执行是__irq_set_handler这个函数

进入__irq_set_handler函数中:
代码在D:\005-代码\001-开源项目源码\004-内核源码\linux-4.15.1\linux-4.15.1\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);
}
EXPORT_SYMBOL_GPL(__irq_set_handler);

__irq_do_set_handler函数原型:
代码在D:\005-代码\001-开源项目源码\004-内核源码\linux-4.15.1\linux-4.15.1\kernel\irq\chip.c中,原型如下:
最重要的一句就是desc->handle_irq = handle;
这一步就是将我们的handle挂载到handle_irq上.
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;
		}
#endif
		if (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;
		}

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


下面以handle_eage_irq为例来分析,搜索handle_edge_irq,
代码在D:\005-代码\001-开源项目源码\004-内核源码\linux-4.15.1\linux-4.15.1\kernel\irq\chip.c中,原型如下,
次函数中有一个非常重要的函数handle_irq_event(desc)
void handle_edge_irq(struct irq_desc *desc)
{
	raw_spin_lock(&desc->lock);

	desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

	if (!irq_may_run(desc)) {
		desc->istate |= IRQS_PENDING;
		mask_ack_irq(desc);
		goto out_unlock;
	}

	/*
	 * If its disabled or no action available then mask it and get
	 * out of here.
	 */
	if (irqd_irq_disabled(&desc->irq_data) || !desc->action) {
		desc->istate |= IRQS_PENDING;
		mask_ack_irq(desc);
		goto out_unlock;
	}

	kstat_incr_irqs_this_cpu(desc);

	/* Start handling the irq */
	desc->irq_data.chip->irq_ack(&desc->irq_data);

	do {
		if (unlikely(!desc->action)) {
			mask_irq(desc);
			goto out_unlock;
		}

		/*
		 * When another irq arrived while we were handling
		 * one, we could have masked the irq.
		 * Renable it, if it was not disabled in meantime.
		 */
		if (unlikely(desc->istate & IRQS_PENDING)) {
			if (!irqd_irq_disabled(&desc->irq_data) &&
			    irqd_irq_masked(&desc->irq_data))
				unmask_irq(desc);
		}

		handle_irq_event(desc);

	} while ((desc->istate & IRQS_PENDING) &&
		 !irqd_irq_disabled(&desc->irq_data));

out_unlock:
	raw_spin_unlock(&desc->lock);
}
EXPORT_SYMBOL(handle_edge_irq);


handle_irq_event这个函数在D:\005-代码\001-开源项目源码\004-内核源码\linux-4.15.1\linux-4.15.1\kernel\irq\handle.c中,原型如下,实际上这个函数执行的最主要的函数是:
handle_irq_event_percpu(定义也在本文件中)以及_handle_irq_event_percpu(定义也在本文件中).

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

irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
	irqreturn_t retval;
	unsigned int flags = 0;

	retval = __handle_irq_event_percpu(desc, &flags);

	add_interrupt_randomness(desc->irq_data.irq, flags);

	if (!noirqdebug)
		note_interrupt(desc, retval);
	return retval;
}

/*
 
这个函数中就实现了依次执行这条中断线上所有的中断服务例程,通过for_each_action_of_desc(desc, action)实现,
for_each_action_of_desc的原型在
D:\005-代码\001-开源项目源码\004-内核源码\linux-4.15.1\linux-4.15.1\kernel\irq\internals.h中
#define for_each_action_of_desc(desc, act)			\
	for (act = desc->action; act; act = act->next);// 就是一个循环遍历
res = action->handler(irq, action->dev_id);--执行中断服务例程,将返回值赋值给res;
res可能的值是IRQ_WAKE_THREAD或IRQ_HANDLED
*/
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;

	record_irq_time(desc);

	for_each_action_of_desc(desc, action) {
		irqreturn_t res;

		trace_irq_handler_entry(irq, action);
		res = action->handler(irq, action->dev_id);// 执行中断服务例程,将返回值赋值给res
		trace_irq_handler_exit(irq, action, res);

		if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
			      irq, action->handler))
			local_irq_disable();

		switch (res) {
		case IRQ_WAKE_THREAD:
			/*
			 * Catch drivers which return WAKE_THREAD but
			 * did not set up a thread function
			 */
			if (unlikely(!action->thread_fn)) {
				warn_no_thread(irq, action);
				break;
			}

			__irq_wake_thread(desc, action);

			/* Fall through to add to randomness */
		case IRQ_HANDLED:
			*flags |= action->flags;
			break;

		default:
			break;
		}

		retval |= res;
	}

	return retval;
}

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值