Linux内核源代码情景分析-中断半

一、中断初始化

1、中断向量表IDT初始化

void __init init_IRQ(void)
{
	int i;

#ifndef CONFIG_X86_VISWS_APIC
	init_ISA_irqs();
#else
	init_VISWS_APIC_irqs();
#endif
	/*
	 * Cover the whole vector space, no vector can escape
	 * us. (some of these will be overridden and become
	 * ‘special‘ SMP interrupts)
	 */
	for (i = 0; i < NR_IRQS; i++) {//NR_IRQS为224
		int vector = FIRST_EXTERNAL_VECTOR + i;//FIRST_EXTERNAL_VECTOR为0x20
		if (vector != SYSCALL_VECTOR)//SYSCALL_VECTOR为0x80
			set_intr_gate(vector, interrupt[i]);
	}

        ......
}

对从0x20到224的中断向量,设置中断处理程序,set_intr_gate例如以下:

void set_intr_gate(unsigned int n, void *addr)
{
	_set_gate(idt_table+n,14,0,addr);
}

在IDT中设置了门描写叙述符。例如以下图:


    Selector为_KERNEL_CS。P为1;DPL为00。DT为0。TYPE为14,中断门。

Offset就是interrupt[i]的偏移。

那么interrupt[i]是什么函数呢?经过若干宏定义的展开。例如以下:

void (*interrupt[NR_IRQS])(void) = {
	IRQ0x00_interrupt。IRQx01_interrupt,.....IRQx0F_interrupt};

IRQ0x00_interrupt。经过若干宏定义的展开,例如以下:

asmlinkage void IRQ0X00_interrupt();
__asm__( "\n" "IRQ0X00_interrupt: \n\t" "pushl $0x00 - 256 \n\t" "jmp common_interrupt");

2、中断请求队列的初始化

在init_IRQ调用init_ISA_irqs,例如以下:

void __init init_ISA_irqs (void)
{
	int i;

	init_8259A(0);

	for (i = 0; i < NR_IRQS; i++) {//NR_IRQS为224
		irq_desc[i].status = IRQ_DISABLED;
		irq_desc[i].action = 0;
		irq_desc[i].depth = 1;

		if (i < 16) {
			/*
			 * 16 old-style INTA-cycle interrupts:
			 */
			irq_desc[i].handler = &i8259A_irq_type;//将开头16个中断请求队列的handler指针设置成指向数据结构
		} else {
			/*
			 * ‘high‘ PCI IRQs filled in on demand
			 */
			irq_desc[i].handler = &no_irq_type;
		}
	}
}

irq_dec[i]的成员变量action就是这个中断请求队列的头。i从0~15一共16个中断请求队列。其成员变量handler对该共用"中断通道"的控制。enable和disable用来开启和关断其所属通道,ack用于对中断控制器的响应。而end用于每次中断服务返回的前夕。

这些数据结构定义例如以下:

struct hw_interrupt_type {
	const char * typename;
	unsigned int (*startup)(unsigned int irq);
	void (*shutdown)(unsigned int irq);
	void (*enable)(unsigned int irq);
	void (*disable)(unsigned int irq);
	void (*ack)(unsigned int irq);
	void (*end)(unsigned int irq);
	void (*set_affinity)(unsigned int irq, unsigned long mask);
};

typedef struct hw_interrupt_type  hw_irq_controller;

/*
 * This is the "IRQ descriptor", which contains various information
 * about the irq, including what kind of hardware handling it has,
 * whether it is disabled etc etc.
 *
 * Pad this out to 32 bytes for cache and indexing reasons.
 */
typedef struct {
	unsigned int status;		/* IRQ status */
	hw_irq_controller *handler;
	struct irqaction *action;	/* IRQ action list */
	unsigned int depth;		/* nested irq disables */
	spinlock_t lock;
} ____cacheline_aligned irq_desc_t;

extern irq_desc_t irq_desc [NR_IRQS];
static struct hw_interrupt_type i8259A_irq_type = {
	"XT-PIC",
	startup_8259A_irq,
	shutdown_8259A_irq,
	enable_8259A_irq,
	disable_8259A_irq,
	mask_and_ack_8259A,
	end_8259A_irq,
	NULL
};
struct irqaction {
	void (*handler)(int, void *, struct pt_regs *);
	unsigned long flags;
	unsigned long mask;
	const char *name;
	void *dev_id;
	struct irqaction *next;
};

二、将irqaction数据结构的irq0,链入相应的中断请求队列

void __init time_init(void)
{
	......
	setup_irq(0, &irq0);
        ......
}

当中irq0,例如以下:

static struct irqaction irq0  = { timer_interrupt, SA_INTERRUPT, 0, "timer", NULL, NULL};

结合上面的struct irqaction去理解。

setup_irq。例如以下:

int setup_irq(unsigned int irq, struct irqaction * new)//irq为中断请求号
{
	int shared = 0;
	unsigned long flags;
	struct irqaction *old, **p;
	irq_desc_t *desc = irq_desc + irq;//找到相应的通道

	/*
	 * Some drivers like serial.c use request_irq() heavily,
	 * so we have to be careful not to interfere with a
	 * running system.
	 */
	if (new->flags & SA_SAMPLE_RANDOM) {
		/*
		 * This function might sleep, we want to call it first,
		 * outside of the atomic block.
		 * Yes, this might clear the entropy pool if the wrong
		 * driver is attempted to be loaded, without actually
		 * installing a new handler, but is this really a problem,
		 * only the sysadmin is able to do this.
		 */
		rand_initialize_irq(irq);
	}

	/*
	 * The following block of code has to be executed atomically
	 */
	spin_lock_irqsave(&desc->lock,flags);
	p = &desc->action;//找到通道相应的中断处理队列
	if ((old = *p) != NULL) {//假设中断请求队列中已经有元素了
		/* Can‘t share interrupts unless both agree to */
		if (!(old->flags & new->flags & SA_SHIRQ)) {//那么须要原元素和新元素的flags都为SA_SHIRQ,表示与其它中断源公用该中断请求通道
			spin_unlock_irqrestore(&desc->lock,flags);
			return -EBUSY;
		}

		/* add new interrupt at end of irq queue */
		do {
			p = &old->next;
			old = *p;
		} while (old);//链入相应的位置
		shared = 1;
	}

	*p = new;//假设中断请求队列没有元素,则直接把irq0链入中断请求队列

	if (!shared) {//第一个元素链入后
		desc->depth = 0;
		desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT | IRQ_WAITING);//status为0
		desc->handler->startup(irq);
	}
	spin_unlock_irqrestore(&desc->lock,flags);

	register_irq_proc(irq);
	return 0;
}

三、中断响应

我们拿时钟中断,举例说明。

假设已经发生了时钟中断。

1、运行中断处理函数之前

假设中断发生在用户态,则会形成例如以下图:

(1)、CPU从中断控制器取得中断向量,然后依据详细的中断向量(本例中为0x20)。从中断向量表IDT中找到相应的表项,而该表项应该是一个中断门。

首先把用户态堆栈的SS,用户堆栈的ESP。EFLAGS。用户空间的CS,EIP存入到系统堆栈中(从TSS中获取)。

(2)、CPU依据中断门的设置到达了该通道的总服务程序的入口。

    asmlinkage void IRQ0X00_interrupt();
__asm__( "\n" "IRQ0X00_interrupt: \n\t" "pushl $0x00 - 256 \n\t" "jmp common_interrupt");

把中断号-256压入堆栈。

#define BUILD_COMMON_IRQ() asmlinkage void call_do_IRQ(void); __asm__( 	"\n" __ALIGN_STR"\n" 	"common_interrupt:\n\t" 	SAVE_ALL 	"pushl $ret_from_intr\n\t" 	SYMBOL_NAME_STR(call_do_IRQ)":\n\t" 	"jmp "SYMBOL_NAME_STR(do_IRQ));
#define SAVE_ALL 	"cld\n\t" 	"pushl %es\n\t" 	"pushl %ds\n\t" 	"pushl %eax\n\t" 	"pushl %ebp\n\t" 	"pushl %edi\n\t" 	"pushl %esi\n\t" 	"pushl %edx\n\t" 	"pushl %ecx\n\t" 	"pushl %ebx\n\t" 	"movl $" STR(__KERNEL_DS) ",%edx\n\t" 	"movl %edx,%ds\n\t" 	"movl %edx,%es\n\t"

运行完毕SAVE_ALL后,就形成了如上图一样的堆栈。此时cs已经是_KERNEL_CS了,ds和es为_KERNEL_DS。

然后把ret_from_intr也压入堆栈,并运行do_IRQ。

2、运行中断处理函数

asmlinkage unsigned int do_IRQ(struct pt_regs regs)//就是上面堆栈中的内容
{
	/*
	 * We ack quickly, we don‘t want the irq controller
	 * thinking we‘re snobs just because some other CPU has
	 * disabled global interrupts (we have already done the
	 * INT_ACK cycles, it‘s too late to try to pretend to the
	 * controller that we aren‘t taking the interrupt).
	 *
	 * 0 return value means that this irq is already being
	 * handled by some other CPU. (or is disabled)
	 */
	int irq = regs.orig_eax & 0xff; //取得了中断号,为0
	int cpu = smp_processor_id();
	irq_desc_t *desc = irq_desc + irq;//找到相应的通道
	struct irqaction * action;
	unsigned int status;

	kstat.irqs[cpu][irq]++;
	spin_lock(&desc->lock);
	desc->handler->ack(irq);//我已经处理了
	/*
	   REPLAY is when Linux resends an IRQ that was dropped earlier
	   WAITING is used by probe to mark irqs that are being tested
	   */
	status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);
	status |= IRQ_PENDING;//status为IRQ_PENDING

	/*
	 * If the IRQ is disabled for whatever reason, we cannot
	 * use the action we have.
	 */
	action = NULL;
	if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS))) {//status为IRQ_PENDING。运行以下代码
		action = desc->action;//找到通道相应的中断处理队列
		status &= ~IRQ_PENDING; //status为0,把IRQ_PENDING位清零了
		status |= IRQ_INPROGRESS; // status为IRQ_INPROCESS
	}
	desc->status = status;//desc->status为IRQ_INPROCESS

	/*
	 * If there is no IRQ handler or it was disabled, exit early.
	   Since we set PENDING, if another processor is handling
	   a different instance of this same irq, the other processor
	   will take care of it.
	 */
	if (!action)//假设action为NULL。直接退出
		goto out;

	/*
	 * Edge triggered interrupts need to remember
	 * pending events.
	 * This applies to any hw interrupts that allow a second
	 * instance of the same irq to arrive while we are in do_IRQ
	 * or in the handler. But the code here only handles the _second_
	 * instance of the irq, not the third or fourth. So it is mostly
	 * useful for irq hardware that does not mask cleanly in an
	 * SMP environment.
	 */
	for (;;) {
		spin_unlock(&desc->lock);
		handle_IRQ_event(irq, &regs, action);//action是中断请求队列的头指针,irq为0,
		spin_lock(&desc->lock);

		if (!(desc->status & IRQ_PENDING))
			break;
		desc->status &= ~IRQ_PENDING;
	}
	desc->status &= ~IRQ_INPROGRESS;//处理完,把IRQ_INPROCESS位置0
out:
	/*
	 * The ->end() handler has to deal with interrupts which got
	 * disabled while the handler was running.
	 */
	desc->handler->end(irq);//开中断
	spin_unlock(&desc->lock);

	if (softirq_active(cpu) & softirq_mask(cpu))//处理中断下半部
		do_softirq();
	return 1;
}

struct pt_regs {
	long ebx;
	long ecx;
	long edx;
	long esi;
	long edi;
	long ebp;
	long eax;
	int  xds;
	int  xes;
	long orig_eax;
	long eip;
	int  xcs;
	long eflags;
	long esp;
	int  xss;
};

handle_IRQ_event。代码例如以下:

int handle_IRQ_event(unsigned int irq, struct pt_regs * regs, struct irqaction * action)
{
	int status;
	int cpu = smp_processor_id();

	irq_enter(cpu, irq);

	status = 1;	/* Force the "do bottom halves" bit */

	if (!(action->flags & SA_INTERRUPT))//假设这个标志位置0,那么要在开中断的情况下运行
		__sti();//开中断

	do {
		status |= action->flags;
		action->handler(irq, action->dev_id, regs);//依次运行中断请求队列上的中断处理函数
		action = action->next;
	} while (action);
	if (status & SA_SAMPLE_RANDOM)
		add_interrupt_randomness(irq);
	__cli();//关中断	

	irq_exit(cpu, irq);

	return status;
}

在本例中,中断处理函数为timer_interrupt(action->handler)。

我们看到大部分中断处理函数都是在关中断下运行的。

可是action->flags的SA_INTERRUPT置0,是在开中断的情况下运行的。

假设运行中断处理函数时。处于开中断的情况。并且此时恰好是同一通道的中断,也就是irq中断号(假设都为0)一样。因为上一次中断还没有退出。此时desc->status为IRQ_INPROGRESS。我们看这段代码:

    	status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);
	status |= IRQ_PENDING;//此时status为IRQ_PENDING| IRQ_INPROGRESS

	/*
	 * If the IRQ is disabled for whatever reason, we cannot
	 * use the action we have.
	 */
	action = NULL;
	if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS))) {//不运行以下程序
		action = desc->action;
		status &= ~IRQ_PENDING;
		status |= IRQ_INPROGRESS;
	}
	desc->status = status;//desc->status为IRQ_PENDING| IRQ_INPROGRESS

	if (!action)//action为NULL,退出
		goto out;

假设新中断退出后,原中断继续运行:

        for (;;) {
		spin_unlock(&desc->lock);
		handle_IRQ_event(irq, &regs, action);
		spin_lock(&desc->lock);

		if (!(desc->status & IRQ_PENDING))//因为新的中断运行时desc->status为IRQ_PENDING| IRQ_INPROGRESS。所以继续运行for循环
			break;
		desc->status &= ~IRQ_PENDING;//desc->status为IRQ_INPROGRESS
	}

这样就把发生在同一通道上的中断嵌套化解成为一个循环了。

我们继续分析中断处理函数。timer_interrupt,代码例如以下:

static void timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	int count;

	......

	do_timer_interrupt(irq, NULL, regs);

	write_unlock(&xtime_lock);

}

static inline void do_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
        ......
	do_timer(regs);
        ......
}
void do_timer(struct pt_regs *regs)
{
	(*(unsigned long *)&jiffies)++;
#ifndef CONFIG_SMP
	/* SMP process accounting uses the local APIC timer */

	update_process_times(user_mode(regs));//与进程调度有关
#endif
	mark_bh(TIMER_BH);//中断下半部相关
	if (TQ_ACTIVE(tq_timer))
		mark_bh(TQUEUE_BH);
}

运行完中断处理函数之后。返回do_IRQ,会检查是否有中断下半部须要运行。假设须要运行,则调用do_softirq,下半部是在开中断的情况下開始运行的。

3、运行中断处理函数之后

do_IRQ运行完毕后,会调用ret。返回到ret_from_intr运行。代码例如以下:

ENTRY(ret_from_intr)
	GET_CURRENT(%ebx)  //将指向当前进程的task_struct结构的指针置入寄存器EBX
	movl EFLAGS(%esp),%eax		# mix EFLAGS and CS
	movb CS(%esp),%al
	testl $(VM_MASK | 3),%eax	//看发生中断时是否处于用户态
	jne ret_with_reschedule   //假设处于用户态,那么运行ret_with_reschedule
	jmp restore_all
ret_with_reschedule:
	cmpl $0,need_resched(%ebx)//查看该task_struct结构中位移为need_resched处的内容
	jne reschedule
	cmpl $0,sigpending(%ebx)//查看该task_struct结构中位移为sigpending处的内容
	jne signal_return
restore_all:
	RESTORE_ALL
signal_return:
	sti				# we can get here from an interrupt handler
	testl $(VM_MASK),EFLAGS(%esp)
	movl %esp,%eax
	jne v86_signal_return
	xorl %edx,%edx
	call SYMBOL_NAME(do_signal)//处理信号
	jmp restore_all
restore_all:
	RESTORE_ALL
#define RESTORE_ALL	\ //返回中断前
	popl %ebx;		popl %ecx;		popl %edx;		popl %esi;		popl %edi;		popl %ebp;		popl %eax;	1:	popl %ds;	2:	popl %es;		addl $4,%esp;	\ //跳过orig_eax
3:	iret;	
state		=  0
flags		=  4
sigpending	=  8
addr_limit	= 12
exec_domain	= 16
need_resched	= 20
tsk_ptrace	= 24
processor	= 52

版权声明:本文博主原创文章。博客,未经同意不得转载。

时间: 2024-10-25 04:40:33

Linux内核源代码情景分析-中断半的相关文章

Linux内核源代码情景分析-中断上半部

一.中断初始化 1.中断向量表IDT的初始化 void __init init_IRQ(void) { int i; #ifndef CONFIG_X86_VISWS_APIC init_ISA_irqs(); #else init_VISWS_APIC_irqs(); #endif /* * Cover the whole vector space, no vector can escape * us. (some of these will be overridden and become

Linux内核源代码情景分析-中断下半部(软中断)

Tasklet机制是一种较为特殊的软中断.Tasklet一词的原意是"小片任务"的意思,这里是指一小段可执行的代码,且通常以函数的形式出现.软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet机制来实现的.      从某种程度上讲,tasklet机制是Linux内核对BH机制的一种扩展.在2.4内核引入了softirq机制后,原有的BH机制正是通过tasklet机制这个桥梁来纳入softirq机制的整体框架中的.正是由于这种历史的延伸关系,使得taskl

Linux内核源代码情景分析-强制性调度

Linux内核中进程的强制性调度,也就是非自愿的.被动的.剥夺式的调度,主要是由时间引起的.前面讲过这种调度发生在中断,异常,系统调用从系统空间返回用户空间的前夕,也就是在ret_with_reschedule可以看出,此时是否真的调用schedule(),最终还要取决于当前进程task_struct结构中的need_resched是否为1(非0),因此,问题就结为当前进程的need_resched是在什么情况下才置成1的.主要有如下几种情况: 1.在时钟中断的服务程序中,发现当前进程(连续)运

Linux内核源代码情景分析-fork()

父进程fork出子进程: fork经过系统调用,来到了sys_fork,详细过程请参考Linux内核源代码情景分析-系统调用. asmlinkage int sys_fork(struct pt_regs regs) { return do_fork(SIGCHLD, regs.esp, &regs, 0); } int do_fork(unsigned long clone_flags, unsigned long stack_start, //stack_start为用户空间堆栈指针 str

Linux内核源代码情景分析-系统初始化

我们跳过boot,setup,直接来到head代码,内核映像的起点是stext,也是_stext,引导和解压缩以后的整个映像放在内存从0x100000即1MB开始的区间.CPU执行内核映像的入口startup_32就在内核映像开头的地方,因此其物理地址也是0x100000. 然而,在正常运行时整个内核映像都应该在系统空间中,系统空间的虚拟地址与物理地址间有个固定的位移,这就是0xC0000000,即3GB.所以,在连接内核映像时已经在所有的符号地址加了一个偏移量0xC0000000,这样star

Linux内核源代码情景分析-内存管理之slab-回收

在上一篇文章Linux内核源代码情景分析-内存管理之slab-分配与释放,最后形成了如下图的结构: 图 1 我们看到空闲slab块占用的若干页面,不会自己释放:我们是通过kmem_cache_reap和kmem_cache_shrink来回收的.他们的区别是: 1.我们先看kmem_cache_shrink,代码如下: int kmem_cache_shrink(kmem_cache_t *cachep) { if (!cachep || in_interrupt() || !is_chaine

Linux内核源代码情景分析-系统调用mknod

普通文件可以用open或者create创建,FIFO文件可以用pipe创建,mknod主要用于设备文件的创建. 在内核中,mknod是由sys_mknod实现的,代码如下: asmlinkage long sys_mknod(const char * filename, int mode, dev_t dev) //比如filename为/tmp/server_socket,dev是设备号 { int error = 0; char * tmp; struct dentry * dentry;

Linux内核源代码情景分析-文件系统的安装

执行sudo mount -t ext2 /dev/sdb1 /mnt/sdb,将文件系统挂在到/mnt/sdb上.系统调用mount,映射到内核层执行的是sys_mount.假设/dev/sdb1和/mnt/sdb都位于ext2文件系统中. asmlinkage long sys_mount(char * dev_name, char * dir_name, char * type, unsigned long flags, void * data)//dev_name指向了"/dev/sdb

Linux内核源代码情景分析-访问权限与文件安全性

在Linux内核源代码情景分析-从路径名到目标节点,一文中path_walk代码中,err = permission(inode, MAY_EXEC)当前进程是否可以访问这个节点,代码如下: int permission(struct inode * inode,int mask) { if (inode->i_op && inode->i_op->permission) { int retval; lock_kernel(); retval = inode->i_