一:前言
在前一個專題里曾分析過所有IRQ中斷處理流程,經過SAVE_ALL保存硬件環境后,都會進入do_IRQ()進行處理,今天接著分析do_IRQ()處理的相關東西.分為兩部中斷處理程序與軟中斷兩個大的部份進行介紹.
二:中斷處理程序
在驅動程序中,通常使用request_irq()來注冊中斷處理程序.我們先從注冊中斷處理程序的實現說起.
/*
irq:可斷號
handler:中斷處理程序
irqflags:中斷處理標志.SA_SHIRQ:共享中斷線 SA_INTERRUPT:快速處理中斷
必須在關中斷的情況下運行.SA_SAMPLE_RANDOM:該中斷可能用于產生一個隨機數
devname dev_id:設備名稱與ID?????
*/
int request_irq(unsigned int irq,
irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long irqflags,
const char * devname,
void *dev_id)
{
int retval;
#if 1
if (irqflags & SA_SHIRQ) {
if (!dev_id)
printk("Bad boy: %s (at 0x%x) called us without a dev_id!\n", devname, (&irq)[-1]);
}
#endif
//參數有效性判斷
if (irq >= NR_IRQS)
return -EINVAL;
if (!handler)
return -EINVAL;
// 分配一個irqaction
action = (struct irqaction *)
kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
if (!action)
return -ENOMEM;
action->handler = handler;
action->flags = irqflags;
cpus_clear(action->mask);
action->name = devname;
action->next = NULL;
action->dev_id = dev_id;
//將創建并初始化完在的action加入irq_desc[NR_IRQS]
retval = setup_irq(irq, action);
if (retval)
kfree(action);
return retval;
}
上面涉及到的irqaction結構與irq_desc[]的關系我們在上一節我們已經詳細分析過了,這里不再贅述.
轉進setup_irq():
int setup_irq(unsigned int irq, struct irqaction * new)
{
int shared = 0;
unsigned long flags;
struct irqaction *old, **p;
irq_desc_t *desc = irq_desc + irq;
//如果hander == no_irq_type:說明中斷控制器不支持該IRQ線
if (desc->handler == &no_irq_type)
return -ENOSYS;
sif (new->flags & SA_SAMPLE_RANDOM) {
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) {
//判斷這條中斷線上的中斷處理程序是否允許SHARE
/* Can't share interrupts unless both agree to */
if (!(old->flags & new->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;
//如果這一條線還沒有被占用,初始化這條中斷線
//包含清標志,在8259A上啟用這條中斷線
if (!shared) {
desc->depth = 0;
desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT | IRQ_WAITING | IRQ_INPROGRESS);
desc->handler->startup(irq);
}
spin_unlock_irqrestore(&desc->lock,flags);
//在proc下建立相關的文件
register_irq_proc(irq);
return 0;
}
現在知道怎么打一個中斷處理程序掛到irq_desc[NR_IRQS]數組上了,繼續分析中斷處理中如何調用中斷處理函數.從我們開篇時說到的do_IRQ()說起.
asmlinkage unsigned int do_IRQ(struct pt_regs regs)
{????
//屏蔽高位,取得中斷號
int irq = regs.orig_eax & 0xff; /* high bits used in ret_from_ code? */
//取得中斷號對應的desc結構
irq_desc_t *desc = irq_desc + irq;
struct irqaction * action;
unsigned int status;
irq_enter();
// 調試用,忽略
#ifdef CONFIG_DEBUG_STACKOVERFLOW
/* Debugging check for stack overflow: is there less than 1KB free? */
{
long esp;
__asm__ __volatile__("andl %%esp,%0" :
"=r" (esp) : "0" (THREAD_SIZE - 1));
if (unlikely(esp < (sizeof(struct thread_info) + STACK_WARN))) {
printk("do_IRQ: stack overflow: %ld\n",
esp - sizeof(struct thread_info));
dump_stack();
}
}
#endif
//更新統計計數
kstat_this_cpu.irqs[irq]++;
spin_lock(&desc->lock);
//給8259 回一個ack.回ack之后,通常中斷控制會屏蔽掉此條IRQ線
desc->handler->ack(irq);
//清除IRQ_REPLAY IRQ_WAITING標志
status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);
//設置IRQ_PENDING:表示中斷被應答,但沒有真正被處理
status |= IRQ_PENDING; /* we _want_ to handle it */
/*
* If the IRQ is disabled for whatever reason, we cannot
* use the action we have.
*/
action = NULL;
//中斷被屏蔽或者正在處理
//IRQ_DIASBLED:中斷被禁用
//IRQ_INPROGRESS:這個類型的中斷已經在被另一個CPU處理了
if (likely(!(status & (IRQ_DISABLED | IRQ_INPROGRESS)))) {
action = desc->action;
status &= ~IRQ_PENDING; /* we commit to handling */
//置位,表示正在處理中...
status |= c; /* we are handling it */
}
desc->status = status;
//沒有掛上相應的中斷處理例程或者不滿足條件
if (unlikely(!action))
goto out;
for (;;) {
irqreturn_t action_ret;
u32 *isp;
union irq_ctx * curctx;
union irq_ctx * irqctx;
curctx = (union irq_ctx *) current_thread_info();
irqctx = hardirq_ctx[smp_processor_id()];
spin_unlock(&desc->lock);
//通常curctx == irqctx.除非中斷程序使用獨立的4K堆棧.
if (curctx == irqctx)
action_ret = handle_IRQ_event(irq, ?s, action);
else {
/* build the stack frame on the IRQ stack */
isp = (u32*) ((char*)irqctx + sizeof(*irqctx));
irqctx->tinfo.task = curctx->tinfo.task;
irqctx->tinfo.real_stack = curctx->tinfo.real_stack;
irqctx->tinfo.virtual_stack = curctx->tinfo.virtual_stack;
irqctx->tinfo.previous_esp = current_stack_pointer();
*--isp = (u32) action;
*--isp = (u32) ?s;
*--isp = (u32) irq;
asm volatile(
"?????? xchgl?? %%ebx,%%esp???? \n"
"?????? call??? handle_IRQ_event \n"
"?????? xchgl?? %%ebx,%%esp???? \n"
: "=a"(action_ret)
: "b"(isp)
: "memory", "cc", "edx", "ecx"
);
}
spin_lock(&desc->lock);
//調試用,忽略
if (!noirqdebug)
note_interrupt(irq, desc, action_ret, ?s);
if (curctx != irqctx)
irqctx->tinfo.task = NULL;
//如果沒有要處理的中斷了,退出
if (likely(!(desc->status & IRQ_c)))
break;
//又有中斷到來了,繼續處理
desc->status &= ~c;
}
//處理完了,清除IRQ_INPROGRESS標志
desc->status &= ~IRQ_INPROGRESS;
out:
/*
* The ->end() handler has to deal with interrupts which got
* disabled while the handler was running.
*/
//處理完了,調用中斷控制器的end.通常此函數會使中斷控制器恢復IRQ線中斷
desc->handler->end(irq);
spin_unlock(&desc->lock);
//irq_exit():理論上中斷處理完了,可以處理它的下半部了
irq_exit();
return 1;
}
這段代碼比較簡單,但里面幾個標志讓人覺的很迷糊,列舉如下:
IRQ_DISABLED:相應的IRQ被禁用.既然中斷線被禁用了,也就不會產生中斷,進入do_IRQ()了?因為電子器件的各種原因可能會產生 “偽中斷”上報給CPU.
IRQ_PENDING:CPU收到這個中斷信號了,已經給出了應答,但并末對其進行處理.回顧上面的代碼,進入do_IRQ后,發送ack,再設置此標志.
IRQ_ INPROGRESS:表示這條IRQ線的中斷正在被處理.為了不弄臟CPU的高速緩存.把相同IRQ線的中斷放在一起處理可以提高效率,且使中斷處理程序不必重入
舉例說明:如果CPU A接收到一個中斷信號.回一個ACK,設置c,假設此時末有這個中斷線的中斷處理程序在處理,繼而會將標志位設為IRQ_ INPROGRESS.轉去中斷處理函數執行.如果此時,CPU B檢測到了這條IRQ線的中斷信號.它會回一個ACK.設置
IRQ_PENDING.但時此時這條IRQ線的標志為IRQ_ INPROGRESS.所以,它會進經過goto out退出.如果cpu A執行完了中斷處理程序,判斷它的標志線是否為IRQ_PENDING.因為CPU B已將其設為了IRQ_PENDING.所以繼續循環一次.直到循環完后,清除IRQ_INPROGRESS標志
注意上述讀寫標志都是加鎖的.linux采用的這個方法,不能不贊一個 *^_^*
繼續看代碼:
asmlinkage int handle_IRQ_event(unsigned int irq,
struct pt_regs *regs, struct irqaction *action)
{
int status = 1;? /* Force the "do bottom halves" bit */
int ret, retval = 0;
//如果沒有設置SA_INTERRUPT.將CPU 中斷打開
//應該盡量的避免CPU關中斷的情況,因為CPU屏弊本地中斷,會使
//中斷丟失
if (!(action->flags & SA_INTERRUPT))
local_irq_enable();
//遍歷運行中斷處理程序
do {
ret = action->handler(irq, action->dev_id, regs);
if (ret == IRQ_HANDLED)
status |= action->flags;
retval |= ret;
action = action->next;
} while (action);
if (status & SA_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
//關中斷
local_irq_disable();
return retval;
}
可能會有這樣的疑問.如果在一根中斷線上掛上了很多個中斷處理程序,會不會使這一段程序的效率變得很低下呢?事實上,我們在寫驅動程序的過程中,都會首先在中斷處理程序里判斷設備名字與設備ID,只有條件符合的設備中斷才會變處理.
三:軟中斷
為了提高中斷的響應速度,很多操作系統都把中斷分成了兩個部份,上半部份與下半部份.上半部份通常是響應中斷,并把中斷所得到的數據保存進下半部.耗時的操作一般都會留到下半部去處理.
接下來,我們看一下軟中斷的處理模型:
Start_kernel() à softirq_init();
在softirq_init()中會注冊兩個常用類型的軟中斷,看具體代碼:
void __init softirq_init(void)
{
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
//參數含義:nr:軟中斷類型 action:軟中斷處理函數??? data:軟中斷處理函數參數
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
}
static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;
struct softirq_action
{
void? (*action)(struct softirq_action *);
void? *data;
};
在上面的代碼中,我們可以看到:open_softirq()中.其實就是對softirq_vec數組的nr項賦值.softirq_vec是一個32元素的數組,實際上linux內核只使用了六項. 如下示:
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
SCSI_SOFTIRQ,
TASKLET_SOFTIRQ
}
另外.如果使軟中斷能被CPU調度,還得要讓它激活才可以.激活所使用的函數為__raise_softirq_irqoff()
代碼如下:
#define __raise_softirq_irqoff(nr) do { local_softirq_pending() |= 1UL << (nr); } while (0)
這個宏使local_softirq_pending的nr位置1
好了,經過open_softirq()à local_softirq_pending()后,我們來看下軟中斷怎么被CPU調度.
繼續上面中斷處理的代碼.在處理完硬件中斷后,會調用irq_exit().這就是軟中斷的入口點了,我們來看下
#define irq_exit()????????????????????????????????????? \
do {????????????????????????????????????????????? \
preempt_count() -= IRQ_EXIT_OFFSET;?????????????? \
//注意了,軟中斷不可以在硬件中斷上下文或者是在軟中斷環境中使用哦 ^_^
//softirq_pending()的判斷,注意我們上面分析過的_raise_softirqoff().它判斷當前cpu有沒有激活軟中斷
if (!in_interrupt() && softirq_pending(smp_processor_id())) \
do_softirq();????????????????????????? \
preempt_enable_no_resched();????????????????????? \
} while (0)
跟蹤進do_softirq()
asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
//在硬件中斷環境中,退出
if (in_interrupt())
return;
//禁止本地中斷,不要讓其受中斷的影響
local_irq_save(flags);
pending = local_softirq_pending();
//是否有軟中斷要處理?
if (pending)
__do_softirq();
//恢復CPU中斷
local_irq_restore(flags);
}
轉入__do_softirq()
asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
pending = local_softirq_pending();
//禁止軟中斷,不允許軟中斷嵌套
local_bh_disable();
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
//把掛上去的軟中斷清除掉,因為我們在這里會全部處理完
local_softirq_pending() = 0;
//開CPU中斷
local_irq_enable();
//softirq_vec:32元素數組
h = softirq_vec;
//依次處理掛上去的軟中斷
do {
if (pending & 1) {
//調用軟中斷函數
h->action(h);
rcu_bh_qsctr_inc(cpu);
}
h++;
pending >>= 1;
} while (pending);
//關CPU 中斷
local_irq_disable();
pending = local_softirq_pending();
//在規定次數內,如果有新的軟中斷了,可以繼續在這里處理完
if (pending && --max_restart)
goto restart;
//依然有沒有處理完的軟中斷,為了提高系統響應效率,喚醒softirqd進行處理
if (pending)
wakeup_softirqd();
//恢復軟中斷
__local_bh_enable();
}
從上面的處理流程可以看到,軟中斷處理就是調用open_ softirq()的action參數.這個函數對應的參數是軟中斷本身(h->action(h)),采用這樣的形式,可以在改變softirq_action結構的時候,不會重寫軟中斷處理函數
在進入了軟中斷的時候,使用了in_interrupt()來防止軟中斷嵌套,和搶占硬中斷環境。然后軟中斷以開中斷的形式運行,軟中斷的處理隨時都會被硬件中斷搶占,由于在軟中斷運行之前調用了local_bh_disable(),所以in_interrupt()為真,不會執行軟中斷.
來看下in_interrupt() local_bh_disable() __local_bh_enable()的具體代碼:
#define in_interrupt()????? (irq_count())
#define irq_count()?? (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK))
#define local_bh_disable() \
do { preempt_count() += SOFTIRQ_OFFSET; barrier(); } while (0)
#define __local_bh_enable() \
do { barrier(); preempt_count() -= SOFTIRQ_OFFSET; } while (0)
相當于local_bh_disable設置了preempt_count的SOFTIRQ_OFFSET。In_interrupt判斷就會返回一個真值
相應的__local_bh_enable()清除了SOFTIRQ_OFFSET標志
還有幾個常用的判斷,列舉如下:
in_softirq():判斷是否在一個軟中斷環境
hardirq_count():判斷是否在一個硬中斷環境
local_bh_enable()與__local_bh_enable()作用是不相同的:前者不僅會清除SOFTIRQ_OFFSET,還會調用do_softirq(),進行軟中斷的處理
上述幾個判斷的代碼都很簡單,可自行對照分析
四:幾種常用的軟中斷分析
經過上面的分析,看到了linux的軟中斷處理模式,我們具體分析一下2.6kernel中常用的幾種軟中斷
1:tasklet分析
Tasklet也是俗稱的小任務機制,它使用比較方法,另外,還分為了高優先級tasklet與一般tasklet。還記得我們剛開始分析過的softirq_init()這個函數嗎
void __init softirq_init(void)
{
//普通優先級
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
//高優先級
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
它們的軟中斷處理函數其實是tasklet_action與tasklet_hi_action.
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
//禁止本地中斷
local_irq_disable();
//per_cpu變量
list = __get_cpu_var(tasklet_vec).list;
//鏈表置空
__get_cpu_var(tasklet_vec).list = NULL;
//恢復本地中斷
local_irq_enable();
//接下來要遍歷鏈表了
while (list) {
struct tasklet_struct *t = list;
list = list->next;
//為了避免競爭,下列操作都是在加鎖情況下進行的
if (tasklet_trylock(t)) {
//t->count為零才會調用task_struct里的函數
if (!atomic_read(&t->count)) {
//t->count 為1。但又沒有置調度標志。系統BUG
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
BUG();
//調用tasklet函數
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
//注意?:所有運行過的tasklet全被continue過去了,只有沒有運行的tasklet才會重新加入到鏈表里面
//禁本地中斷
local_irq_disable();
//把t放入隊列頭,準備下一次接收調度
t->next = __get_cpu_var(tasklet_vec).list;
__get_cpu_var(tasklet_vec).list = t;
//置軟中斷調用標志。下次運行到do_softirq的時候,可以繼續被調用
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
//啟用本地中斷
local_irq_enable();
}
}
高優先級tasklet的處理其實與上面分析的函數是一樣的,只是per_cpu變量不同而已。
另外,有幾個問題值得考慮:
1)?????? cpu怎么計算軟中斷優先級的
在do_softirq()à__do_softirq()有:
{
pending = local_softirq_pending();
......
do {
if (pending & 1) {
h->action(h);
rcu_bh_qsctr_inc(cpu);
}
h++;
pending >>= 1;
} while (pending);
......
}
從上面看到,從softirq_vec[]中取項是由pending右移位計算的。
另外,在激活軟中斷的操作中:
#define __raise_softirq_irqoff(nr) do { local_softirq_pending() |= 1UL << (nr); } while (0)
可以看到 nr越小的就會越早被do_softirq遍歷到
2)?????? 在什么條件下才會運行tasklet 鏈表上的任務
我們在上面的代碼里看到只有在t->count為零,且設置了TASKLET_STATE_SCHED標志才會被遍歷到鏈表上對應的函數
那在我們自己的代碼里該如何使用tasklet呢?舉個例子:
#include
#include
#include
#include
static void tasklet_test_handle(unsigned long arg)
{
printk("in tasklet test\n");
}
//聲明一個tasklet
DECLARE_TASKLET(tasklet_test,tasklet_test_handle,0);
MODULE_LICENSE("GPL xgr178@163.com");
int kernel_test_init()
{
printk("test_init\n");
//調度這個tasklet
tasklet_schedule(&tasklet_test);
}
int kernel_test_exit()
{
printk("test_exit\n");
//禁用這個tasklet
tasklet_kill(&tasklet_test);
return 0;
}
module_init(kernel_test_init);
module_exit(kernel_test_exit);
示例模塊里涉及到tasklet通用的三個API.分別是DECLARE_TASKLET(), tasklet_schedule(),tasklet_kill()
跟蹤一下內核代碼:
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
實際上,DECLARE_TASKLET就是定義了一個tasklet_struct的變量.相應的tasklet調用函數為func().函數參數為data
static inline void tasklet_schedule(struct tasklet_struct *t)
{
//如果tasklet沒有置調度標置,也就是說該tasklet沒有被調度
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
void fastcall __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
//把tasklet加到__get_cpu_var(tasklet_vec).list鏈表頭
local_irq_save(flags);
t->next = __get_cpu_var(tasklet_vec).list;
__get_cpu_var(tasklet_vec).list = t;
//激活相應的軟中斷
raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_restore(flags);
}
這個函數比較簡單,不詳細分析了
void tasklet_kill(struct tasklet_struct *t)
{
//不允許在中斷環境中進行此操作
if (in_interrupt())
printk("Attempt to kill tasklet from interrupt\n");
//一直等待tasklet被調度完
while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
do
yield();
while (test_bit(TASKLET_STATE_SCHED, &t->state));
}
//一直等待tasklet被運行完
tasklet_unlock_wait(t);
//清除調度標志
clear_bit(TASKLET_STATE_SCHED, &t->state);
}
該函數會一直等待該tasklet調度并運行完,可能會睡眠,所以不能在中斷環境中使用它
2:網絡協議棧里專用軟中斷
在前面分析網絡協議協的時候分析過,網卡有兩種模式,一種是中斷,即數據到來時給CPU上傳中斷,等到CPU處理中斷.第二種是輪詢,即在接收到第一個數據包之后,關閉中斷,CPU每隔一定時間就去網卡DMA緩沖區取數據.其實,所謂的輪詢就是軟中斷.接下來就來研究一下網絡協議棧的軟中斷
static int __init net_dev_init(void)
{
……
open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
……
}
在這里注冊了兩個軟中斷,一個用于接收一個用于發送,函數大體差不多,我們以接收為例.從前面的分析可以知道,軟中斷的處理函數時就是它調用open_softirq的action參數.在這里即是net_rx_action.代碼如下:
static void net_rx_action(struct softirq_action *h)
{
//per_cpu鏈表.所有網卡的輪詢處理函數都通過napi_struct結構存放在這鏈表里面
struct list_head *list = &__get_cpu_var(softnet_data).poll_list;
unsigned long start_time = jiffies;
int budget = netdev_budget;
void *have;
//關中斷
local_irq_disable();
//遍歷鏈表
while (!list_empty(list)) {
struct napi_struct *n;
int work, weight;
if (unlikely(budget <= 0 || jiffies != start_time))
goto softnet_break;
local_irq_enable();
// 取鏈表里的相應數據
n = list_entry(list->next, struct napi_struct, poll_list);
have = netpoll_poll_lock(n);
weight = n->weight;
work = 0;
//如果允許調度,則運行接口的poll函數
if (test_bit(NAPI_STATE_SCHED, &n->state))
work = n->poll(n, weight);
WARN_ON_ONCE(work > weight);
budget -= work;
//關中斷
local_irq_disable();
if (unlikely(work == weight)) {
//如果被禁用了,就從鏈表中刪除
if (unlikely(napi_disable_pending(n)))
__napi_complete(n);
Else
//否則加入鏈表尾,等待下一次調度
list_move_tail(&n->poll_list, list);
}
netpoll_poll_unlock(have);
}
out:
//啟用中斷
local_irq_enable();
//選擇編譯部份,忽略
#ifdef CONFIG_NET_DMA
/*
* There may not be any more sk_buffs coming right now, so push
* any pending DMA copies to hardware
*/
if (!cpus_empty(net_dma.channel_mask)) {
int chan_idx;
for_each_cpu_mask(chan_idx, net_dma.channel_mask) {
struct dma_chan *chan = net_dma.channels[chan_idx];
if (chan)
dma_async_memcpy_issue_pending(chan);
}
}
#endif
return;
softnet_break:
__get_cpu_var(netdev_rx_stat).time_squeeze++;
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
goto out;
}
一般在接口驅動中,會調用__napi_schedule.將其添加進遍歷鏈表.代碼如下示:
void fastcall __napi_schedule(struct napi_struct *n)
{
unsigned long flags;
local_irq_save(flags);
//加至鏈表末尾
list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);
//激活軟中斷
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
local_irq_restore(flags);
}
關于網卡選擇哪一種模式最為合適,我們在前面已經講述過,這里不再贅述.
五:小結
本節主要分析了中斷程序的處理過程與軟中斷的實現.雖然軟中斷實現有很多種類,究其模型都是一樣的,就是把中斷的一些費時操作在響應完中斷之后再進行.另外,中斷與軟中斷處理中有很多臨界區,需要關閉CPU中斷和打開CPU中斷.其中的奧妙還需要慢慢的體會
?
評論
查看更多