W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
中斷處理的一個主要問題是如何在處理中進行長時間的任務(wù). 常常大量的工作必須響應(yīng)一個設(shè)備中斷來完成, 但是中斷處理需要很快完成并且不使中斷阻塞太長. 這 2 個需要(工作和速度)彼此沖突, 留給驅(qū)動編寫者一點困擾.
Linux (許多其他系統(tǒng)一起)解決這個問題通過將中斷處理分為 2 半. 所謂的前半部是實際響應(yīng)中斷的函數(shù) -- 你使用 request_irq 注冊的那個. 后半部是由前半部調(diào)度來延后執(zhí)行的函數(shù), 在一個更安全的時間. 最大的不同在前半部處理和后半部之間是所有的中斷在后半部執(zhí)行時都使能 -- 這就是為什么它在一個更安全時間運行. 在典型的場景中, 前半部保存設(shè)備數(shù)據(jù)到一個設(shè)備特定的緩存, 調(diào)度它的后半部, 并且退出: 這個操作非??? 后半部接著進行任何其他需要的工作, 例如喚醒進程, 啟動另一個 I/O 操作, 等等. 這種設(shè)置允許前半部來服務(wù)一個新中斷而同時后半部仍然在工作.
幾乎每個認真的中斷處理都這樣劃分. 例如, 當一個網(wǎng)絡(luò)接口報告有新報文到達, 處理者只是獲取數(shù)據(jù)并且上推給協(xié)議層; 報文的實際處理在后半部進行.
Linux 內(nèi)核有 2 個不同的機制可用來實現(xiàn)后半部處理, 我們都在第 7 章介紹. tasklet 常常是后半部處理的首選機制; 它們非??? 但是所有的 tasklet 代碼必須是原子的. tasklet 的可選項是工作隊列, 它可能有一個更高的運行周期但是允許睡眠.
下面的討論再次使用 short 驅(qū)動. 當使用一個模塊選項加載時, short 能夠被告知在前/后半部模式使用一個 tasklet 或者工作隊列處理者來進行中斷處理. 在這個情況下, 前半部快速地執(zhí)行; 它簡單地記住當前時間并且調(diào)度后半部處理. 后半部接著負責將時間編碼并且喚醒任何可能在等待數(shù)據(jù)的用戶進程.
記住 tasklet 是一個特殊的函數(shù), 可能被調(diào)度來運行, 在軟中斷上下文, 在一個系統(tǒng)決定的安全時間中. 它們可能被調(diào)度運行多次, 但是 tasklet 調(diào)度不累積; ; tasklet 只運行一次, 即便它在被投放前被重復(fù)請求. 沒有 tasklet 會和它自己并行運行, 因為它只運行一次, 但是 tasklet 可以與 SMP 系統(tǒng)上的其他 tasklet 并行運行. 因此, 如果你的驅(qū)動有多個 tasklet, 它們必須采取某類加鎖來避免彼此沖突.
tasklet 也保證作為函數(shù)運行在第一個調(diào)度它們的同一個 CPU 上. 因此, 一個中斷處理可以確保一個 tasklet 在處理者結(jié)束前不會開始執(zhí)行. 但是, 另一個中斷當然可能在 tasklet 在運行時被遞交, 因此, tasklet 和中斷處理之間加鎖可能仍然需要.
tasklet 必須使用 DECLARE_TASKLET 宏來聲明:
DECLARE_TASKLET(name, function, data);
name 是給 tasklet 的名子, function 是調(diào)用來執(zhí)行 tasklet (它帶一個 unsigned long 參數(shù)并且返回 void )的函數(shù), 以及 data 是一個 unsigned long 值來傳遞給 tasklet 函數(shù).
short 驅(qū)動聲明它的 tasklet 如下:
void short_do_tasklet(unsigned long);
DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0);
函數(shù) tasklet_schedule 用來調(diào)度一個 tasklet 運行. 如果 short 使用 tasklet=1 來加載, 它安裝一個不同的中斷處理來保存數(shù)據(jù)并且調(diào)度 tasklet 如下:
irqreturn_t short_tl_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
do_gettimeofday((struct timeval *) tv_head); /* cast to stop 'volatile' warning
*/
short_incr_tv(&tv_head);
tasklet_schedule(&short_tasklet);
short_wq_count++; /* record that an interrupt arrived */
return IRQ_HANDLED;
}
實際的 tasklet 函數(shù), short_do_tasklet, 將在系統(tǒng)方便時很快執(zhí)行. 如同前面提過, 這個函數(shù)進行處理中斷的大量工作; 它看來如此:
void short_do_tasklet (unsigned long unused)
{
int savecount = short_wq_count, written;
short_wq_count = 0; /* we have already been removed from the queue */
/*
* The bottom half reads the tv array, filled by the top half,
* and prints it to the circular text buffer, which is then consumed
* by reading processes */
/* First write the number of interrupts that occurred before this bh */
written = sprintf((char *)short_head,"bh after %6i\n",savecount);
short_incr_bp(&short_head, written);
/*
* Then, write the time values. Write exactly 16 bytes at a time,
* so it aligns with PAGE_SIZE */
do {
written = sprintf((char *)short_head,"%08u.%06u\n",
(int)(tv_tail->tv_sec % 100000000),
(int)(tv_tail->tv_usec));
short_incr_bp(&short_head, written);
short_incr_tv(&tv_tail);
} while (tv_tail != tv_head);
wake_up_interruptible(&short_queue); /* awake any reading process */
}
在別的東西中, 這個 tasklet 記錄了從它上次被調(diào)用以來有多少中斷到達. 一個如 short 一樣的設(shè)備能夠在短時間內(nèi)產(chǎn)生大量中斷, 因此在后半部執(zhí)行前有幾個中斷到達就不是不尋常的. 驅(qū)動必須一直準備這種可能性并且必須能夠從前半部留下的信息中決定有多少工作要做.
回想, 工作隊列在將來某個時候調(diào)用一個函數(shù), 在一個特殊工作者進程的上下文中. 因為這個工作隊列函數(shù)在進程上下文運行, 它在需要時能夠睡眠. 但是, 你不能從一個工作隊列拷貝數(shù)據(jù)到用戶空間, 除非你使用我們在 15 章演示的高級技術(shù); 工作者進程不存取任何其他進程的地址空間.
short 驅(qū)動, 如果設(shè)置 wq 選項為一個非零值來加載, 為它的后半部處理使用一個工作隊列. 它使用系統(tǒng)缺省的工作隊列, 因此不要求特殊的設(shè)置代碼; 如果你的驅(qū)動有特別的運行周期要求(或者可能在工作隊列函數(shù)長時間睡眠), 你可能需要創(chuàng)建你自己的, 專用的工作隊列. 我們確實需要一個 work_struct 結(jié)構(gòu), 它聲明和初始化使用下列:
static struct work_struct short_wq;
/* this line is in short_init() */
INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL);
我們的工作者函數(shù)是 short_do_tasklet, 我們已經(jīng)在前面一節(jié)看到.
當使用一個工作隊列, short 還建立另一個中斷處理, 看來如此:
irqreturn_t short_wq_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
/* Grab the current time information. */
do_gettimeofday((struct timeval *) tv_head);
short_incr_tv(&tv_head);
/* Queue the bh. Don't worry about multiple enqueueing */
schedule_work(&short_wq);
short_wq_count++; /* record that an interrupt arrived */
return IRQ_HANDLED;
}
如你所見, 中斷處理看來非常象這個 tasklet 版本, 除了它調(diào)用 schedule_work 來安排后半部處理.
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: