軟件中斷(softIRQ)是內核提供的一種延遲執行機制,它完全由軟件觸發,雖然說是延遲機制,實際上,在大多數情況下,它與普通進程相比,能得到更快的響應時間。軟中斷也是其他一些內核機制的基礎,比如tasklet,高分辨率timer等。
/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝!
/*****************************************************************************************************/
點擊(此處)摺疊或打開
-
struct softirq_action
-
{
-
void (*action)(struct
softirq_action *);
- };
非常簡單,只有一個用於回調的函數指針。軟件中斷的資源是有限的,內核目前只實現了10種類型的軟件中斷,它們是:
點擊(此處)摺疊或打開
-
enum
-
{
-
HI_SOFTIRQ=0,
-
TIMER_SOFTIRQ,
-
NET_TX_SOFTIRQ,
-
NET_RX_SOFTIRQ,
-
BLOCK_SOFTIRQ,
-
BLOCK_IOPOLL_SOFTIRQ,
-
TASKLET_SOFTIRQ,
-
SCHED_SOFTIRQ,
-
HRTIMER_SOFTIRQ,
-
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
-
-
NR_SOFTIRQS
- };
內核的開發者們不建議我們擅自增加軟件中斷的數量,如果需要新的軟件中斷,儘可能把它們實現爲基於軟件中斷的tasklet形式。與上面的枚舉值相對應,內核定義了一個softirq_action的結構數組,每種軟中斷對應數組中的一項:
點擊(此處)摺疊或打開
- static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
點擊(此處)摺疊或打開
-
typedef struct {
-
unsigned int __softirq_pending;
- } ____cacheline_aligned irq_cpustat_t;
點擊(此處)摺疊或打開
- irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
1.3 軟中斷的守護進程ksoftirqd
點擊(此處)摺疊或打開
- DEFINE_PER_CPU(struct task_struct *, ksoftirqd);
2. 觸發軟中斷
點擊(此處)摺疊或打開
-
void raise_softirq(unsigned int nr)
-
{
-
unsigned long flags;
-
-
local_irq_save(flags);
-
raise_softirq_irqoff(nr);
-
local_irq_restore(flags);
- }
再看看raise_softirq_irqoff:
點擊(此處)摺疊或打開
-
inline void raise_softirq_irqoff(unsigned int nr)
-
{
-
__raise_softirq_irqoff(nr);
-
-
......
-
if (!in_interrupt())
-
wakeup_softirqd();
- }
3. 軟中斷的執行
3.1 在irq_exit中執行
點擊(此處)摺疊或打開
-
void irq_exit(void)
-
{
-
......
-
sub_preempt_count(IRQ_EXIT_OFFSET);
-
if (!in_interrupt() && local_softirq_pending())
-
invoke_softirq();
-
......
- }
如果中斷髮生嵌套,in_interrupt()保證了只有在最外層的中斷的irq_exit階段,invoke_interrupt纔會被調用,當然,local_softirq_pending也會實現判斷當前cpu有無待決的軟中斷。代碼最終會進入__do_softirq中,內核會保證調用__do_softirq時,本地cpu的中斷處於關閉狀態,進入__do_softirq:
點擊(此處)摺疊或打開
-
asmlinkage void __do_softirq(void)
-
{
-
......
-
pending = local_softirq_pending();
-
-
__local_bh_disable((unsigned long)__builtin_return_address(0),
-
SOFTIRQ_OFFSET);
-
restart:
-
/* Reset the pending bitmask before enabling irqs */
-
set_softirq_pending(0);
-
-
local_irq_enable();
-
-
h = softirq_vec;
-
-
do {
-
if (pending & 1) {
-
......
-
trace_softirq_entry(vec_nr);
-
h->action(h);
-
trace_softirq_exit(vec_nr);
-
......
-
}
-
h++;
-
pending >>= 1;
-
} while (pending);
-
-
local_irq_disable();
-
-
pending = local_softirq_pending();
-
if (pending && --max_restart)
-
goto restart;
-
-
if (pending)
-
wakeup_softirqd();
-
-
lockdep_softirq_exit();
-
-
__local_bh_enable(SOFTIRQ_OFFSET);
- }
- 首先取出pending的狀態;
- 禁止軟中斷,主要是爲了防止和軟中斷守護進程發生競爭;
- 清除所有的軟中斷待決標誌;
- 打開本地cpu中斷;
- 循環執行待決軟中斷的回調函數;
-
如果循環完畢,發現新的軟中斷被觸發,則重新啓動循環,直到以下條件滿足,才退出:
- 沒有新的軟中斷等待執行;
- 循環已經達到最大的循環次數MAX_SOFTIRQ_RESTART,目前的設定值時10次;
- 如果經過MAX_SOFTIRQ_RESTART次循環後還未處理完,則激活守護進程,處理剩下的軟中斷;
- 推出前恢復軟中斷;
3.2 在ksoftirqd進程中執行
- 在irq_exit中執行軟中斷,但是在經過MAX_SOFTIRQ_RESTART次循環後,軟中斷還未處理完,這種情況雖然極少發生,但畢竟有可能;
- 內核的其它代碼主動調用raise_softirq,而這時正好不是在中斷上下文中,守護進程將被喚醒;
4. tasklet
點擊(此處)摺疊或打開
-
void __init softirq_init(void)
-
{
-
......
-
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
-
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
- }
內核用一個tasklet_struct來表示一個tasklet,它的定義如下:
點擊(此處)摺疊或打開
-
struct tasklet_struct
-
{
-
struct tasklet_struct *next;
-
unsigned long state;
-
atomic_t count;
-
void (*func)(unsigned
long);
-
unsigned long data;
- };
next用於把同一個cpu的tasklet鏈接成一個鏈表,state用於表示該tasklet的當前狀態,目前只是用了最低的兩個bit,分別用於表示已經準備被調度執行和已經在另一個cpu上執行:
點擊(此處)摺疊或打開
-
enum
-
{
-
TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */
-
TASKLET_STATE_RUN /* Tasklet is running (SMP
only) */
- };
原子變量count用於tasklet對tasklet_disable和tasklet_enable的計數,count爲0時表示允許tasklet執行,否則不允許執行,每次tasklet_disable時,該值加1,tasklet_enable時該值減1。func是tasklet被執行時的回調函數指針,data則用作回調函數func的參數。 4.2 初始化一個tasklet
-
DECLARE_TASKLET(name, func, data);定義名字爲name的tasklet,默認爲enable狀態,也就是count字段等於0。
-
DECLARE_TASKLET_DISABLED(name, func, data);定義名字爲name的tasklet,默認爲enable狀態,也就是count字段等於1。
點擊(此處)摺疊或打開
-
struct tasklet_struct tasklet_xxx;
-
......
- tasklet_init(&tasklet_xxx, func, data);
-
tasklet_disable() 通過給count字段加1來禁止一個tasklet,如果tasklet正在運行中,則等待運行完畢才返回(通過TASKLET_STATE_RUN標誌)。
-
tasklet_disable_nosync() tasklet_disable的異步版本,它不會等待tasklet運行完畢。
-
tasklet_enable() 使能tasklet,只是簡單地給count字段減1。
-
tasklet_schedule(struct tasklet_struct *t) 如果TASKLET_STATE_SCHED標誌爲0,則置位TASKLET_STATE_SCHED,然後把tasklet掛到該cpu等待執行的tasklet鏈表上,接着發出TASKLET_SOFTIRQ軟件中斷請求。
-
tasklet_hi_schedule(struct tasklet_struct *t) 效果同上,區別是它發出的是HI_SOFTIRQ軟件中斷請求。
-
tasklet_kill(struct tasklet_struct *t) 如果tasklet處於TASKLET_STATE_SCHED狀態,或者tasklet正在執行,則會等待tasklet執行完畢,然後清除TASKLET_STATE_SCHED狀態。
4.4 tasklet的內部執行機制
點擊(此處)摺疊或打開
-
struct tasklet_head
-
{
-
struct tasklet_struct *head;
-
struct tasklet_struct **tail;
-
};
-
-
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
- static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
回到4.1節,我們知道,tasklet是利用TASKLET_SOFTIRQ和HI_SOFTIRQ這兩個軟中斷來實現的,兩個軟中斷只是有優先級的差別,所以我們只討論TASKLET_SOFTIRQ的實現,TASKLET_SOFTIRQ的中斷回調函數是tasklet_action,我們看看它的代碼:
點擊(此處)摺疊或打開
-
static void tasklet_action(struct softirq_action *a)
-
{
-
struct tasklet_struct *list;
-
-
local_irq_disable();
-
list = __this_cpu_read(tasklet_vec.head);
-
__this_cpu_write(tasklet_vec.head, NULL);
-
__this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head);
-
local_irq_enable();
-
-
while (list) {
-
struct tasklet_struct *t = list;
-
-
list = list->next;
-
-
if (tasklet_trylock(t)) {
-
if (!atomic_read(&t->count)) {
-
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
-
BUG();
-
t->func(t->data);
-
tasklet_unlock(t);
-
continue;
-
}
-
tasklet_unlock(t);
-
}
-
-
local_irq_disable();
-
t->next = NULL;
-
*__this_cpu_read(tasklet_vec.tail) = t;
-
__this_cpu_write(tasklet_vec.tail, &(t->next));
-
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
-
local_irq_enable();
-
}
- }
- 關閉本地中斷的前提下,移出當前cpu的待處理tasklet鏈表到一個臨時鏈表後,清除當前cpu的tasklet鏈表,之所以這樣處理,是爲了處理當前tasklet鏈表的時候,允許新的tasklet被調度進待處理鏈表中。
-
遍歷臨時鏈表,用tasklet_trylock判斷當前tasklet是否已經在其他cpu上運行,而且tasklet沒有被禁止:
- 如果沒有運行,也沒有禁止,則清除TASKLET_STATE_SCHED狀態位,執行tasklet的回調函數。
- 如果已經在運行,或者被禁止,則把該tasklet重新添加會當前cpu的待處理tasklet鏈表上,然後觸發TASKLET_SOFTIRQ軟中斷,等待下一次軟中斷時再次執行。
- 同一個tasklet只能同時在一個cpu上執行,但不同的tasklet可以同時在不同的cpu上執行;
- 一旦tasklet_schedule被調用,內核會保證tasklet一定會在某個cpu上執行一次;
- 如果tasklet_schedule被調用時,tasklet不是出於正在執行狀態,則它只會執行一次;
-
如果tasklet_schedule被調用時,tasklet已經正在執行,則它會在稍後被調度再次被執行;
- 兩個tasklet之間如果有資源衝突,應該要用自旋鎖進行同步保護;