1. 程式人生 > >linux新內核的freeze框架以及意義

linux新內核的freeze框架以及意義

過程 狀態 穩定 turn 函數 現在 urn break 相關

linux的電源管理發展非常迅速,比如在掛起到內存的時候,系統會凍結住所有的進程,也就是所有的進程都不再運行,它們被凍結之前,最後的狀態被保存,等到解凍的時候,所有進程恢復運行,linux對此的實現非常巧妙,它沒有用特殊的機制來實現這一點,而是用它的freeze框架加上信號處理來實現的,在freeze所有進程的時候並沒有將之掛起或者說使其不再運行,而是在信號處理的開始掛了一個類似於鉤子的東西,把掛起進程交給信號處理來做,freeze框架要做的就是設置一些標誌位來指示信號處理要凍結它了,然後設置此進程的信號附著位,這樣該進程在返回用戶空間的時候就乖乖進入到凍結狀態了,這一點下面的代碼分析會詳述。這麽做有幾個好處,一來從設計上講這又是一個為了低耦合而將一個框架分離成幾個模塊讓各個模塊分別承擔一部分職責的做法,這樣的話,將來如要升級會非常靈活,這看似復雜,但是一旦你理解了精要,還是覺得蠻好的;二來從效果上說,凍結進程其實是一個很危險的操作,2.6內核有了內核搶占,如果一個任務在內核中正執行或者正在內核中睡眠,那麽它很有可能持有一把鎖或者在等待一把鎖,這個時候如果使其無條件的凍結,那麽等待解凍的時候,解凍的順序就要求很高了,要不然很容易死鎖,因此把凍結操作安排在進程返回用戶空間的時候,這樣可以保證進程將不在內核了,既然已經到了返回用戶空間的前夕,那麽可以保證它肯定不會和內核的其它進程或中斷引起競態,這麽做簡直好的沒法說。
int freeze_processes(void)
{
error = try_to_freeze_tasks(true); //低強度凍結
...//錯誤處理以及信息打印
error = try_to_freeze_tasks(false); //高強度凍結
...//錯誤以及善後處理
}
static int try_to_freeze_tasks(bool sig_only)
{
struct task_struct *g, *p;
unsigned int todo;
...//這些變量和主要問題無關
do {
todo = 0;
read_lock(&tasklist_lock);
do_each_thread(g, p) {
if (frozen(p) || !freezeable(p))
continue;
if (!freeze_task(p, sig_only)) //這個函數本質上造成了進程的凍結,但是在它裏面你卻找不到任何“凍結”的操作。
continue;
if (!task_is_stopped_or_traced(p) && !freezer_should_skip(p))
todo++; //如果有個進程我們目前無法設置它的凍結位,也就是對它無可奈何的時候,我們將todo遞增,以表示要再進行一輪,直到將之凍結或者超過預定期限或者別的什麽更加重要的事情發生。
} while_each_thread(g, p);
read_unlock(&tasklist_lock);
yield(); //剛才在freeze_task中不是可能給要凍結的進程發送信號了嗎(其實就是設置了一個信號位小小欺騙一下,並沒有發送真正的信號)?那麽此時就要給這個進程機會使之運行,然後在信號處理中真正凍結,註意yield並不改變當前操作進程的任何標誌,僅僅讓出cpu而已,理想情況,等到這一輪的進程p被真正凍結以後,這個當前調用try_to_freeze_tasks的進程將繼續運行,以便使下一個進程凍結
if (time_after(jiffies, end_time))
break;
} while (todo);
...//這下面的我們不必關心
}
return todo ? -EBUSY : 0;
}
bool freeze_task(struct task_struct *p, bool sig_only)
{
if (!freezing(p)) {
rmb();
if (frozen(p))
return false;
if (!sig_only || should_send_signal(p))
set_freeze_flag(p);
else
return false;
}
if (should_send_signal(p)) {
if (!signal_pending(p))
fake_signal_wake_up(p); //對進程p設置上_TIF_SIGPENDING標誌,然後喚醒它,這其實是一場欺騙,根本沒有什麽信號被發往p,這麽做是因為在進程被喚醒後,檢查是否有_TIF_SIGPENDING置位,如有的話,在執行get_signal_to_deliver的時候有個死亡之神在等著它,就是get_signal_to_deliver中馬上調用的try_to_freeze,以便進程p上當受騙。
} else if (sig_only) {
return false;
} else {
wake_up_state(p, TASK_INTERRUPTIBLE);
}
return true;
}
在get_signal_to_deliver中會調用try_to_freeze,然後這個函數將進程真正凍結,非常安全,因為信號處理在返回用戶空間時執行,此時該進程已經退出了內核的執行路徑,不會被攪進內核的管理競態中,是的,此時的事情十分安全:
static inline int try_to_freeze(void)
{
if (freezing(current)) {
refrigerator();
...//別的處理
}
以下這個refrigerator函數徹底凍結了一個進程,即當前進程,它其實就是讓當前進程在當前的狀態上定格,等到被喚醒以後馬上恢復到當前情況,這個當前進程其實睡眠在TASK_UNINTERRUPTIBLE的狀態撒謊能夠
void refrigerator(void)
{
long save;
task_lock(current);
if (freezing(current)) {
frozen_process(); //freezing已經完成,將該進程設置為frozen
task_unlock(current);
...
save = current->state;
spin_lock_irq(&current->sighand->siglock);
recalc_sigpending(); //剛才為了欺騙這個進程才使得人家跑到這裏準備被綁,現在綁架任務已經完成,清除掉為了引誘目的而設置的標誌位
spin_unlock_irq(&current->sighand->siglock);
for (;;) {
set_current_state(TASK_UNINTERRUPTIBLE);
if (!frozen(current))
break;
schedule();
}
__set_current_state(save); //被喚醒,繼續執行
}
喚醒的過程比這簡單多了,就是一個一個的調用wake_up_process而已,這實在太簡單了以至於不說了。這裏就有了一個問題,在2.6.25以來新內核中,增加了對cgroup的支持,那麽某種意義上,linux開始支持只有商用unix上才有“容器”的概念,linux內核陸續增加了cgroup對內存,文件等的控制,用戶可以對單個group進行資源限制了,在linux內核上,以資源作為區分相當於運行著好幾個虛擬機,每臺虛擬機都是資源分配的一個單位,如果聯系前面剛說的freeze框架的話就會發現,如果把一個cgroup的進程作為一個組進行凍結的話會很有用,比如這樣的話我們可以將全組的進程“熱遷移”到別的處理器,其實不是真正的熱遷移,畢竟那些進程已經不再運行了,呵呵。其實freeze框架本身就可以對熱插拔cpu進行支持,cgroup的freeze框架僅僅使得凍結的粒度可以切割了,不再是要麽一下子凍結全部然後喚醒全部,要麽就一個也不凍結,這就需要一個內核補丁,在最新的2.6.29內核中已經有了這樣的機制,其實就是在凍結的時候加上了一個croup作為參數,然後把這個cgroup的進程應用上面的機制將之凍結,linux總是這樣,一開始設計時就很靈活,然後後面修改加補丁的時候才不受罪。
另外在2.6.29內核中還有了文件系統的凍結,其實和上述的進程凍結一樣,如果說進程凍結是將進程凍結到內存從而為了cpu而做一點事的話,那麽文件系統就是將文件系統凍結到磁盤,然後讓備份系統做一點事,以使得備份時是一個穩定又一致的文件系統,其實很簡單,所謂凍結就是不讓掛載不讓寫,其實用信號量就可以搞定,凍結一個文件系統的時候要得到其bdev的一個相關的信號量,而且掛載和寫這個文件系統的時候也要得到這個信號量,如此就完結了。

再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智能的隊伍中來!https://blog.csdn.net/jiangjunshow

linux新內核的freeze框架以及意義