為什麼在中斷上下文中不能休眠?
1.中斷處理的時候,不應該發生進程切換,因為在中斷context中,唯一能打斷當前中斷handler的只有更高優先順序的中斷,它不會被進程打斷(這點對於softirq,tasklet也一樣,因此這些bottom half也不能休眠),如果在中斷context中休眠,則沒有辦法喚醒它,因為所有的wake_up_xxx都是針對某個進程而言的,而在中斷context中,沒有進程的概念,沒有一個task_struct(這點對於softirq和tasklet一樣),因此真的休眠了,比如調用了會導致block的常式,核心幾乎肯定會死.
2.schedule()在切換進程時,儲存當前的進程上下文(CPU寄存器的值、進程的狀態以及堆棧中的內容),以便以後恢複此進程運行。中斷髮生後,核心會先儲存當前被中斷的進程上下文(在調用中斷處理常式後恢複);
但在中斷處理常式裡,CPU寄存器的值肯定已經變化了吧(最重要的程式計數器PC、堆棧SP等),如果此時因為睡眠或阻塞操作調用了schedule(),則儲存的進程上下文就不是當前的進程context了.所以不可以在中斷處理常式中調用schedule()。
3.2.4核心中schedule()函數本身在進來的時候判斷是否處於中斷上下文:
if(unlikely(in_interrupt()))
BUG();
因此,強行調用schedule()的結果就是核心BUG,但我看2.6.18的核心schedule()的實現卻沒有這句,改掉了.
4.中斷handler會使用被中斷的進程核心堆棧,但不會對它有任何影響,因為handler使用完後會完全清除它使用的那部分堆棧,恢複被中斷前的原貌.
module_init和module_exit
void init(void)
{
init_a();
init_b();
}
如果再加入一個初始化函數呢,那麼再init_b()後面再加一行:
init_c();
這樣確實能完成我們的功能,但這樣有一定的問題,就是不能獨立的添加初始化函數,每次添加一個新的函數都要修改init函數,blob中的初始化函數就是完全獨立的,只要用一個宏來修飾一下:
void init_a(void)
{
}
__initlist(init_a, 1);
它是通過這個宏來實現初始化函數列表的呢?
先來看__initlist的定義:
#define __init __attribute__((unused, __section__(".initlist")))
#define __initlist(fn, lvl) \
static initlist_t __init_##fn __init = { \
magic: INIT_MAGIC, \
callback: fn, \
level: lvl }
看來就是定義了一個結構體,存了初始化函數的指標,沒什麼特別的。請注意:__section__(".initlist")
這個屬性起什麼作用呢?它告訴連接器這個變數存放在.initlist區段,如果所有的初始化函數都是用這個宏,那麼每個函數會有對應的一個initlist_t結構體變數存放在.initlist區段,也就是說我們可以在.initlist區段找到所有初始化函數的指標。怎麼找到.initlist區段的地址呢?
extern u32 __initlist_start;
extern u32 __initlist_end;
這兩個變數起作用了,__initlist_start是.initlist區段的開始,__initlist_end是結束,通過這兩個變數我們就可以訪問到所有的初始化函數了。
這兩個變數在那定義的呢?
在一個連接器指令檔裡
. = ALIGN(4);
.initlist : {
__initlist_start = .;
*(.initlist)
__initlist_end = .;
}
這兩個變數的值正好定義在.initlist區段的開始和結束位址,所以我們能通過這兩個變數訪問到所有的初始化函數。
與此類似,核心中也是用到這種方法,所以我們寫驅動的時候比較獨立,不用我們自己添加代碼在一個固定的地方來調用我們自己的初始化函數和退出函數,連接器已經為我們做好了。當然module_init還有其他的特性,比如:我們的初始化函數在完成初始化後,代碼佔用的空間會被釋放
5.處於中斷context時候,核心是不可搶佔的,因此,如果休眠,則核心一定掛起