`
simohayha
  • 浏览: 1407393 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

内核中的中断处理

阅读更多


首先来看当网络接收帧到达时,设备如何唤醒驱动。


1 轮询
也就是内核不断的监测相应的寄存器,从而得到是否有网络帧到来。

2中断
当有数据时,设备执行一个硬件中断,然后内核调用相应的处理函数。这种处理当网络在高负载的情况时,效率会很低(中断太频繁)。并且会引起receive-livelock.这是因为内核处理输入帧分为了两部分,一部分是驱动复制帧到输入队列,一部分是内核执行相关代码。第一部分的优先级比第二部分高。这时在高负载的情况下会出现输入队列由于队列已满而阻塞,而已复制的帧由于中断太频繁而无法占用cpu。。

3在一个中断执行多个帧
老的处理方法,也就是上面的处理方法,就是每个帧都会产生中断,而且每次进入中断都要关闭中断。现在内核新的NAPI接口所做的是,在第一次硬件中断之后,关闭中断,然后进入轮询处理,这样就大大的降低了高负载下中断太频繁的缺点.

3定时器驱动中断

这种方法是上一种方法的增强,不过需要硬件的支持。这种方法是驱动驱使设备在规定间隔内产生中断。然后handler监测是否有帧已经抵达,从而在一次处理多个帧(硬件的存储器内)。而这个定时器必须是硬件的。所以说必须硬件支持定时器。


相关的中断注册函数请看我前面的blog:

http://simohayha.iteye.com/blogs/361971

接下来看3c59x.c的中断处理函数  vortex_interrupt.这个函数在probe函数里面通过request_irq注册为中断handler。

这里要注意,网络设备有可能一个中断会包含多个原因(也就是下面的status变量)


static irqreturn_t
vortex_interrupt(int irq, void *dev_id)
{
.....................................
///这里也就是轮询的最大次数,默认是32.
	int work_done = max_interrupt_work;
	int handled = 0;

	ioaddr = vp->ioaddr;
	spin_lock(&vp->lock);
///读取寄存器从而得到当前的中断状态。
	status = ioread16(ioaddr + EL3_STATUS);

	if (vortex_debug > 6)
		printk("vortex_interrupt(). status=0x%4x\n", status);
..................................................

	do {
		if (vortex_debug > 5)
				printk(KERN_DEBUG "%s: In interrupt loop, status %4.4x.\n",
					   dev->name, status);
///RxComplete代表着新的帧已经接收,驱动可以去取了。因此调用vortex_rx得到数据,在这个函数里面会屏蔽掉中断,虽然屏蔽掉中断,我们的函数依然可以轮询硬件的中断寄存器,从而继续得到相应的中断状态。
		if (status & RxComplete)
			vortex_rx(dev);

		if (status & TxAvailable) {
			if (vortex_debug > 5)
				printk(KERN_DEBUG "	TX room bit was handled.\n");
			/* There's room in the FIFO for a full-sized packet. */
			iowrite16(AckIntr | TxAvailable, ioaddr + EL3_CMD);
			netif_wake_queue (dev);
		}

......................................................................

///进入最后一次轮询
		if (--work_done < 0) {
			printk(KERN_WARNING "%s: Too much work in interrupt, status "
				   "%4.4x.\n", dev->name, status);
			/* Disable all pending interrupts. */
			do {
				vp->deferred |= status;
				iowrite16(SetStatusEnb | (~vp->deferred & vp->status_enable),
					 ioaddr + EL3_CMD);
				iowrite16(AckIntr | (vp->deferred & 0x7ff), ioaddr + EL3_CMD);
			} while ((status = ioread16(ioaddr + EL3_CMD)) & IntLatch);
			/* The timer will reenable interrupts. */
///打开中断然后跳出循环
			mod_timer(&vp->timer, jiffies + 1*HZ);
			break;
		}
		/* Acknowledge the IRQ. */
		iowrite16(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD);
///这里是判断条件,当有未决中断,并且新的网络帧已经可以接受,我们就会一直循环.
	} while ((status = ioread16(ioaddr + EL3_STATUS)) & (IntLatch | RxComplete));

	if (vortex_debug > 4)
		printk(KERN_DEBUG "%s: exiting interrupt, status %4.4x.\n",
			   dev->name, status);
handler_exit:
	spin_unlock(&vp->lock);
	return IRQ_RETVAL(handled);
}


接下来来看上下半部

我们要知道,内核处理中断的机制,在linux内核中,将中断的处理分为上半部和下半部,这里上半部的处理是在中断上下文中,而下半部的处理则不是。也就是说当上半部处理完后就会直接打开中断,下半部可以在开中断下执行。之所以要分为上下半部,是由于下面几个原因(摘抄自linux内核的设计与实现:

引用

中断处理程序以异步方式执行并且它有可能会打断其他重要代码的执行。因此,它们应该执行得越快越好。
如果当前有一个中断处理程序正在执行,在最好的情况下,与该中断同级的其他中断会被屏蔽,在最坏的情况下,所有其他中断都会被屏蔽。因此,仍应该让它们执行得越快越好。
由于中断处理程序往往需要对硬件进行操作,所以它们通常有很高的时限要求。
中断处理程序不在进程上下文中运行,所以它们不能阻塞。


我的理解就是上半部用来得到数据,而下半部用来处理数据。一切都为了使中断更早结束。

在内核中实现下半部有三种机制,softirq,tasklet和work queue.由于网络设备主要使用前两种(最主要还是软中断),因此work queue就不做介绍了。

tasklet和softirq的主要区别就是tasklet在任何时候相同类型的都只有一个实例,就算在smp上。而softirq则是同时在一个cpu上才只有一个实例。因此使用softirq就要注意锁的实现。

tasklet可以动态的创建,而软中断则是静态创建的。

有时我们需要关闭掉软件中断或者硬件中断。下面就是一些中断(包括软件和硬件的)相关的函数或宏:





由于内核现在是可抢占的,因此开发者必须显示的在很多地方关闭抢占(比如硬件软件中断中等等)。


网络部分代码不直接调用抢占提供的相关api。它是通过一些其他的函数,比如rcu_read_lock,spin_lock等等这些函数间接的调用。

对于每个进程都有一个preempt_count位图变量,他表示了当前进程是否允许被抢占。这个变量能通过preempt_count()来读取,能通过inc_preempt_count和dec_preempt_count来增加和减少引用计数。他被分为三部分.硬件中断部分,软件中断部分和非抢占部分:




接下来我们来看下半部的处理。首先来看软中断。

软终端模式有下面几种类型,其中的优先级是从大到小,也就是HI_SOFTIRQ的优先级最高:
enum
{
	HI_SOFTIRQ=0,
	TIMER_SOFTIRQ,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	BLOCK_SOFTIRQ,
	TASKLET_SOFTIRQ,
	SCHED_SOFTIRQ,
#ifdef CONFIG_HIGH_RES_TIMERS
	HRTIMER_SOFTIRQ,
#endif
	RCU_SOFTIRQ, 	/* Preferable RCU should always be the last softirq */
};


网络部分使用的类型主要是NET_TX_SOFTIRQ和NET_RX_SOFTIRQ.软中断主要运行在开中断(硬件中断)的情况下,并且内核不允许在一个cpu上已经挂起的软中断,然后再次请求此软中断(可以同时在多个cpu上,可以运行相同的软中断)。每个软中断都包含一个softnet_data数据结构,它存储了当前软中断的状态。

软中断通过open_irq来注册:

static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;


void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}


这里softirq_vec是一个全局的向量,存储软中断的信息。软中断能通过__raise_softirq_irqoff,raise_softirq_irqoff和raise_softirq来对软中断进行排队的。

为了防止软中断独占cpu资源,内核有一个每个cpu都独有的一个软中断线程,ksoftirqd_cpu0,等等。。


在下列的时刻,软中断会被执行和检测:

1 从一个硬件中断代码返回
2 在ksoftirqd(后面会介绍)中执行
3 显式检测和执行待处理的软中断代码(网络部分)

在网络设备的代码中,我们一般通过raise_softirq(上面的那张图中),来将一个软中断挂起,从而在下次处理时执行此软中断。

而不管怎么样,软中断都要通过do_softirq来处理。下面我们来看它的代码:

asmlinkage void do_softirq(void)
{
	__u32 pending;
	unsigned long flags;
///如果当前cpu正在处理一个软中断或者硬件中断,直接返回
	if (in_interrupt())
		return;
	local_irq_save(flags);

///得到当前的cpu是否有未决软中断(也就是需要执行的0.
	pending = local_softirq_pending();
///然后调用处理函数
	if (pending)
		__do_softirq();

	local_irq_restore(flags);
}



asmlinkage void __do_softirq(void)
{
	struct softirq_action *h;
	__u32 pending;
///由于在这个函数执行过程中,可能会出现新的挂起软中断,为了防止软中断独占cpu,因此这里只执行固定的次数的轮询。
	int max_restart = MAX_SOFTIRQ_RESTART;
	int cpu;

	pending = local_softirq_pending();
	account_system_vtime(current);

	__local_bh_disable((unsigned long)__builtin_return_address(0));
	trace_softirq_enter();

	cpu = smp_processor_id();
restart:
	/* Reset the pending bitmask before enabling irqs */
	set_softirq_pending(0);

///打开软中断
	local_irq_enable();
///当前的全局软中断变量
	h = softirq_vec;

	do {
		if (pending & 1) {
///调用相应处理函数
			h->action(h);

			rcu_bh_qsctr_inc(cpu);
		}
		h++;
		pending >>= 1;
	} while (pending);

	local_irq_disable();

///得到当前的未决中断的个数
	pending = local_softirq_pending();
///如果轮询次数已到最大值,则跳出循环
	if (pending && --max_restart)
		goto restart;

///如果还有未处理的未决软中断则调用wakeup_softirq唤醒内核软中断处理线程。
	if (pending)
		wakeup_softirqd();

	trace_softirq_exit();

	account_system_vtime(current);
	_local_bh_enable();
}



来看ksoftirqd的源码;

static int ksoftirqd(void * __bind_cpu)
{
///设置内核线程的运行状态
	set_current_state(TASK_INTERRUPTIBLE);

///开始进入循环,执行软中断处理
	while (!kthread_should_stop()) {
		preempt_disable();
///没有未决软中断,此时让出cpu。然后关闭抢占。
		if (!local_softirq_pending()) {
			preempt_enable_no_resched();
			schedule();
			preempt_disable();
		}

///设置线程状态。
		__set_current_state(TASK_RUNNING);
///进入处理软中断
		while (local_softirq_pending()) {
			/* Preempt disable stops cpu going offline.
			   If already offline, we'll be on wrong CPU:
			   don't process */
///线程执行太长时间,并且被请求释放cpu。比如定时器中断等等。此时跳转到wait__to_die,下面我会介绍.
			if (cpu_is_offline((long)__bind_cpu))
				goto wait_to_die;
///调用do_softirq处理软中断
			do_softirq();
			preempt_enable_no_resched();
			cond_resched();
			preempt_disable();
		}
		preempt_enable();
		set_current_state(TASK_INTERRUPTIBLE);
	}
	__set_current_state(TASK_RUNNING);
	return 0;

wait_to_die:
///打开抢占
	preempt_enable();
	/* Wait for kthread_stop */
	set_current_state(TASK_INTERRUPTIBLE);
///如果当前的线程是否应该被停止,如果不是,则释放cpu。设置相关状态。
	while (!kthread_should_stop()) {
		schedule();
		set_current_state(TASK_INTERRUPTIBLE);
	}
///设置状态为running,等待稍后resume.
	__set_current_state(TASK_RUNNING);
	return 0;
}



一个tasklet也就是一个中断或者其他任务将要稍后执行的函数。它是基于软中断来实现的。而tasklet的软中断类型是HI_SOFTIRQ或者TASKLET_SOFTIRQ.

来看tasklet的结构:
struct tasklet_struct
{
///相同的cpu上的挂起的中断的链表
	struct tasklet_struct *next;
///当前tasklet的状态(位图表示)
	unsigned long state;
///引用计数
	atomic_t count;
///所需执行的函数
	void (*func)(unsigned long);
///可能传递给fucn的数据
	unsigned long data;
};



每个cpu都会有两个tasklet链表,一个是HI_SOFTIRQ一个是TASKLET_SOFTIRQ类型的:

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);



接下来我们来看tasklet的这两种类型的软中断的初始化:

void __init softirq_init(void)
{
	int cpu;
///每个cpu初始化两个tasklet链表
	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);
}


这里要提到的是,网络设备的软中断的注册是在net_dev_init中注册的。

HI_SOFTIRQ这个最高的优先级只在声卡设备驱动中使用。网卡中更多使用TASKLET_SOFTIRQ(这个优先级比网络的哪两个低).

最后我们来介绍一下cpu_chain,他也就是把cpu的一些信息通知给这条链上的子系统。。比如当cpu初始化完成后,我们才能启动软中断线程。这里对应的事件就是CPU_ONLINE,更多的事件需要去看notifier.h

  • 大小: 201.4 KB
  • 大小: 55.2 KB
  • 大小: 32.4 KB
分享到:
评论

相关推荐

    内核中断处理-内核中断处理

    在内核中断处理中,中断配置至关重要。不同的硬件设备可能有不同的中断请求线,需要在系统初始化时进行正确配置,以确保中断能够被正确地识别和处理。配置过程通常包括设置中断向量、分配中断处理程序和启用中断线。...

    内核ARM中断

    在Linux内核中,中断处理分为两个主要阶段:中断处理入口和中断服务例程。当发生中断时,CPU会停止当前正在执行的代码,保存上下文(包括寄存器状态),然后跳转到中断向量表中的相应地址,开始执行中断处理程序。 ...

    Linux内核的中断机制分析

    在Linux内核中,中断处理分为两个级别:软中断(Software Interrupts,也称为Doorbell Interrupts)和底半部(Bottom Halves)。软中断通常用于处理那些可以延迟的任务,比如网络协议栈的数据包处理。底半部则是...

    linux内核中断实例

    在Linux操作系统中,内核中断是系统处理外部事件和硬件信号的核心机制。中断允许硬件设备在需要时立即通知CPU,从而高效地响应各种输入、输出请求和其他系统级...这个实例提供了一个学习内核中断处理机制的实用起点。

    linux内核中断处理流程

    把内核中断的流程说的很清楚,学习Linux的必备资料

    Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈

    Linux操作系统中的栈是一种重要的数据结构,尤其在内存管理、函数调用以及多任务处理中扮演着至关重要的角色。在Linux系统中,栈可以分为进程栈、线程栈、内核栈以及中断栈。 首先,我们需要了解栈(Stack)的基本...

    linux内核中断处理.doc

    linux内核中断处理.doc

    Linux 2.6内核的中断机制.pdf

    在Linux 2.6内核中,中断处理分为两个阶段:中断处理子程序(Interrupt Service Routine, ISR)和中断底半部(Bottom Half)。ISR是中断发生时立即执行的部分,通常用于快速保存现场信息,执行紧急处理,并安排底半...

    Linux内核中断分析

    ### Linux内核中断分析 #### 重要性及作用 中断系统是计算机系统中不可或缺的关键组件,在整个系统的运行中扮演着至关重要的角色。特别是在Linux操作系统中,中断机制的高效性和灵活性对于提升系统的整体性能至关...

    Linux 内核-中断和系统调用.pdf

    Linux内核中断是操作系统中极其重要的概念。中断是计算机系统处理异步事件的一种机制,它允许CPU响应外部和内部的事件请求。在Linux内核中,中断处理通常分为两个部分:顶半部(Top Half)和底半部(Bottom Half)。...

    最完整的LINUX的中断代码分析

    这些结构体是Linux内核中断处理机制的核心组成部分,通过它们实现了对中断的有效管理和调度。 #### 六、总结 通过对给定文件内容的深入分析,我们了解到Linux内核中中断处理的相关代码和机制。中断处理机制在操作...

    中断 下半部 锁 进程调度

    内核通过嵌套执行机制来管理中断处理过程中的上下文切换,确保高优先级的中断能够打断低优先级的中断处理过程,从而提高系统的整体响应速度。 ##### 3. IRQ 数据结构 IRQ 数据结构用于描述一个特定的中断线路,它...

    uclinux内核中断处理_v0.2

    ### uclinux内核中断处理_v0.2 #### 数据结构 在uclinux内核中,为了管理和处理中断事件,定义了几种重要的数据结构,包括`irq_desc`、`irq_chip`以及`ivg_table`等。 ##### irq_desc `irq_desc`是用于描述每个...

    uclinux内核中断处理_v0.1

    ### uclinux内核中断处理_v0.1 #### 1.1 中断配置 ##### 1.1.1 中断入口设置 在uclinux内核中,针对ADSP-BF561 CPU的中断处理是通过特定的配置来实现的。这部分配置主要发生在内核初始化阶段,具体来说是在`setup...

    linux内核软中断_linux内核源码详解.docx

    如果当前CPU不在中断上下文中,唤醒内核线程ksoftirqd来检查被挂起的软中断,然后执行相应软中断处理函数。 内核在几个点上检查被挂起的软中断:当调用local_bh_enable函数激活本地CPU的软中断时,条件满足就调用do...

    使用内核软中断的zigbee转串口驱动设计(和kill_fasync没反应的解决)

    软中断处理函数通常需要在原子上下文中执行,不能睡眠,因为它们可能在中断服务例程(ISR)或底半部(DMA完成、定时器等)中被调度。 描述中提到的“kill_fasync没反应”,可能是指在驱动程序中使用fasync_helper函数...

    深入理解LINUX内核(中文第三版)第四章 中断和异常

    中断处理程序是非进程的内核控制路径,它们相对轻量,可以快速建立和终止。内核必须尽可能快地处理中断,将大部分处理工作推迟到以后执行,以保持系统的响应性。 此外,内核在处理中断时,也需要考虑中断嵌套执行的...

Global site tag (gtag.js) - Google Analytics