LINUX的中断处理

  一、概念 
首先我们要知道为什么中断需要下半部 。我们可以想象一下,如果没有下半部的概念,一个网卡中断过来了以后会是什么样的情况。首先,我们会从网卡硬件buffer中把网卡收到的packet拷贝到系统内存中,然后对这个packet进行TCP/IP协议栈的处理。我们知道TCP/IP协议栈是一个比较复杂的软件模块,里面对packet的处理会经过非常多的步骤,首先是链路层,然后是IP层(这里又包括分片,奇偶校验之类的),然后是TCP层(TCP层的实现相当复杂,会花费比较长的时间对packet进行一些状态或者内容的分析处理),最后通过socket把packet传入用户空间。在传入用户空间之间的这些动作,都必须在中断处理中完成,因为这些操作都是在kernel中的,并且这些操作会花费比较长的时间。在这段时间里,cpu由于进入了中断门,会自动关中断,也就是说cpu不会去响应在这段时间里网卡另外发过来的中断,这样的话很有可能网卡硬件buffer会由于网卡自身的缓存不足而导致丢包 。所以linux为了解决这样的问题,把copy packet这样比较紧急的动作放在了上半部去处理(上半部默认情况下是在关中断中完成的),把协议栈这些不是特别紧急的任务放到了下半部去处理(下半部是在开中断中进行的,有就是说,处理下半部的过程中,允许cpu被其他中断打断)。 

二、软件构架和实现
1. 一些基础数据结构
文件softirq.c 
/*PER-CPU变量,每个cpu对应一个,描述当前cpu中关于softirq的一些状态,比如是否有softirq挂起需要执行等等*/
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned; 

typedef struct {
unsigned int __softirq_pending; /*32位,对应Linux中32种softirq是否被上半部触发了(为1表示被触发,为0表示未被触发)*/
unsigned long idle_timestamp;
unsigned int __nmi_count; /* arch dependent */
unsigned int apic_timer_irqs; /* arch dependent */
} ____cacheline_aligned irq_cpustat_t;

/*表示softirq最多有32种类型,实际上Linux只用了6种,见文件interrupt.h*/
static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp; 

/* 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, /*用于高优先级的tasklet*/
TIMER_SOFTIRQ, /*用于定时器的下半部*/
NET_TX_SOFTIRQ,/*用于网络层发包*/
NET_RX_SOFTIRQ, /*用于网络层收包*/
SCSI_SOFTIRQ, /*用于SCSI设备*/
TASKLET_SOFTIRQ /*用于低优先级的tasklet*/
};

struct softirq_action
{
void (*action)(struct softirq_action *); /*softirq的回调函数*/
void *data; /*传入action的参数*/
};

Struct softirq_action是每个softirq的配置结构,一般在系统启动的时候,6个不同的softirq,会通过函数open_softirq()来注册自己的softirq_action,实现很简单:
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
}
关键是传入的函数指针,具体指明了该softirq要实现的功能或要做的动作。
这里分开看下这六个注册点:
Net/core/dev.c中的net_dev_init()里面注册了网络层需要用到的收包和发包的两个softirq:
open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
对应的函数指针为net_tx_action和net_rx_action,具体功能就不在本文的范围之内的。

Driver/scsi/scsi.c中init_scsi()注册了SCSI_SOFTIRQ
open_softirq(SCSI_SOFTIRQ, scsi_softirq, NULL);

start_kernel() àsiftirq_init() 中注册了两种tasklet,一种是高优先级的tasklet,一直是低优先级的tasklet:
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);

start_kernel() àinit_timers() 中注册了TIMER_SOFTIRQ
open_softirq(TIMER_SOFTIRQ, run_timer_softirq, NULL);

2. softirq运行时机:
系统在运行过程中,会在合适的地方使用函数local_softirq_pending()检查系统是否有softirq需要处理;需要时会调用函数do_softirq()进行处理。这些检查点主要包括以下几个地方:
(1) 中断过程退出函数irq_exit();
(2) 内核线程ksoftirqd;
(3) 内核网络子系统中显示调用;
(4) 函数local_bh_enable().

先前我们分析irq_cpustat_t结构的时候,看到__softirq_pending字段。这是一个32位无符号的变量,对应Linux中32种softirq是否被上半部触发了(为1表示被触发,为0表示未被触发)。那么在softirq运行之前肯定就有地方设置了这个变量的各个位,才会触发到softirq运行。这个触发动作一般是在上半部中进行的,即上半部通过系统,还有下半部需要运行。触发函数如下:
#define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL 
#define or_softirq_pending(x) (local_softirq_pending() |= (x))
#define local_softirq_pending() \
__IRQ_STAT(smp_processor_id(), __softirq_pending)
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
由此可以看出, __raise_softirq_irqoff(nr)实际上是把当前cpu的per-cpu变量irq_stat的__softirq_pending 的从右往左数的第nr位置1,这样系统就知道某个softirq需要在某个时刻运行了。

当然,__raise_sofritq_irqoff()被很多地方封装过,不同子系统用自己封装的函数,比如网络子系统就用netif_rx_reschedule和net_rx_action来激活softirq。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值