freezer子系統
freezer子系統用於掛起和恢復cgroup中的進程。freezer有一個控制文件:freezer.state,將FROZEN寫入該文件,可以將cgroup中的進程掛起,將THAWED寫入該文件,可以將已掛起的進程恢復。該文件可能讀出的值有三種,其中兩種就是前面已提到的FROZEN和THAWED,分別代表進程已掛起和已恢復(正常運行),還有一種可能的值為FREEZING,顯示該值表示該cgroup中有些進程現在不能被frozen。當這些不能被frozen的進程從該cgroup中消失的時候,FREEZING會變成FROZEN,或者手動將FROZEN或THAWED寫入一次。
Freezer子系統用來管理cgroup狀態的數據結構:
struct freezer {
struct cgroup_subsys_state css;
enum freezer_state state;
spinlock_t lock; /* protects _writes_ to state */
};
其中內嵌一個cgroup_subsys_state,便於從cgroup或task獲得freezer結構,另一個字段存儲cgroup當前的狀態。
Freezer子系統是通過對freezer.state文件進行寫入來控制進程的,那我們就從這個文件的cftype定義出發。
static struct cftype files[] = {
{
.name = "state",
.read_seq_string = freezer_read,
.write_string = freezer_write,
},
};
從文件讀取是freezer_read實現的,該函數比較簡單,主要就是從freezer結構體從讀出狀態,但是對FREEZING狀態做了特殊處理:
state = freezer->state;
if (state == CGROUP_FREEZING) {
/* We change from FREEZING to FROZEN lazily if the cgroup was
* only partially frozen when we exitted write. */
update_freezer_state(cgroup, freezer);
state = freezer->state;
}
如果是FREEZING狀態,則需要更新狀態(因為之前不能frozen的進程可能已經不在了)。我們來看update_freezer_state:
cgroup_iter_start(cgroup, &it);
while ((task = cgroup_iter_next(cgroup, &it))) {
ntotal++;
if (is_task_frozen_enough(task))
nfrozen++;
}
/*
* Transition to FROZEN when no new tasks can be added ensures
* that we never exist in the FROZEN state while there are unfrozen
* tasks.
*/
if (nfrozen == ntotal)
freezer->state = CGROUP_FROZEN;
else if (nfrozen > 0)
freezer->state = CGROUP_FREEZING;
else
freezer->state = CGROUP_THAWED;
cgroup_iter_end(cgroup, &it);
這里對該cgroup所有的進程迭代了一遍,分別統計進程數和已經frozen的進程數,然后根據統計結果改變狀態。
下面我們來看對freezer.state寫入的情況,該情況由freezer_write來處理,該函數中從寫入值獲取目標狀態,然后調用freezer_change_state(cgroup, goal_state)來完成操作。在freezer_change_state中,根據goal_state分別調用不同的實現函數:
switch (goal_state) {
case CGROUP_THAWED:
unfreeze_cgroup(cgroup, freezer);
break;
case CGROUP_FROZEN:
retval = try_to_freeze_cgroup(cgroup, freezer);
break;
default:
BUG();
}
我們先來看frozen的情況,該情況由try_to_freeze_cgroup來處理,該函數中有:
freezer->state = CGROUP_FREEZING;
cgroup_iter_start(cgroup, &it);
while ((task = cgroup_iter_next(cgroup, &it))) {
if (!freeze_task(task, true))
continue;
if (is_task_frozen_enough(task))
continue;
if (!freezing(task) && !freezer_should_skip(task))
num_cant_freeze_now++;
}
cgroup_iter_end(cgroup, &it);
return num_cant_freeze_now ? -EBUSY : 0;
首先將當前狀態設成CGROUP_FREEZING,然后對cgroup中的進程進行迭代,while循環中對進程進行freeze操作,如果成功直接進行下一次迭代,如果不成功則進行進一步的判斷,如果是進程已經frozen了,那也直接進行下一次迭代,如果不是,則進行計數。最后根據計數結果進行返回,如果所有進程都順利frozen,則返回0,否則返回-EBUSY表示有進程不能被frozen。
下面我們來看free_task這個函數,在這個函數中對task進行freeze操作。
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);
} else if (sig_only) {
return false;
} else {
wake_up_state(p, TASK_INTERRUPTIBLE);
}
return true;
首先檢查進程是不是已經被標記為正在freezing,如果不是再做判斷。如果進程已經被frozen,則返回false。如果進程不是sig_only的或者可以發送信號(即進程無PF_FREEZER_NOSIG 標記),則設置進程的TIF_FREEZE標記。
然后根據進程是否有PF_FREEZER_NOSIG 標記進行進一步處理,如果無這個標記,則給進程發送一個信號,喚醒進程,讓進程處理TIF_FREEZE,即進行freeze操作,如果有這個標記,則如果進程是sig_only的,返回false(即不能完成free操作),否則直接喚醒進程去處理TIF_FREEZE。
總結一下,對於我們這個freezer子系統的調用來說,sig_only=true,那么能成功的執行過程就是set_freeze_flag(p)->fake_signal_wake_up(p)。
下面我們來看thaw 進程的情況,該情況由unfreeze_cgroup處理,在unfreeze_cgroup中有
cgroup_iter_start(cgroup, &it);
while ((task = cgroup_iter_next(cgroup, &it))) {
thaw_process(task);
}
cgroup_iter_end(cgroup, &it);
freezer->state = CGROUP_THAWED;
對該cgroup中所有的進程調用thaw_process,我們來看thaw_process。該函數中有:
if (__thaw_process(p) == 1) {
task_unlock(p);
wake_up_process(p);
return 1;
}
其中__thaw_process中
if (frozen(p)) {
p->flags &= ~PF_FROZEN;
return 1;
}
clear_freeze_flag(p);
如果進程已經frozen,則清掉其frozen標記,如果不是的話,說明進程已經設置了TIF_FREEZE,但還沒有frozen,所以只需要清掉TIF_FREEZE即可。
回到thaw_process中,清掉了相關標記后,只需要喚醒進程,然后內核會自動處理。
最后,我們再來看看freezer子系統結構體的定義:
struct cgroup_subsys freezer_subsys = {
.name = "freezer",
.create = freezer_create,
.destroy = freezer_destroy,
.populate = freezer_populate,
.subsys_id = freezer_subsys_id,
.can_attach = freezer_can_attach,
.attach = NULL,
.fork = freezer_fork,
.exit = NULL,
};
這里說一下can_attach,can_attach是在一個進程加入到一個cgroup之前調用的,檢查是否可以attach,freezer_can_attach中對cgroup當前的狀態做了檢查,如果是frozen就返回錯誤,這說明不能將一個進程加入到一個frozen的cgroup。