Linux CGroup之freezer分析與應用


Linux Kernel:4.4.17

CGroup的freezer子系統對於成批作業管理系統很有用,可以成批啟動/停止任務,以達到及其資源的調度。

freezer子系統也有助於針對運行一組任務設置檢查點。通過強制一組任務進入靜默狀態(quiescent state),freezer子系統可以獲得任務的鏡像。如果任務處於靜默狀態,其他任務就可以查看其proc或者讀取內核接口來獲取信息。通過收集必要信息到另一個node,然后在新node重啟任務,被檢查的任務可以在cluster中不同node之間遷移。

freezer是按等級划分的,凍結一個CGroup會凍結旗下的所有任務,並且包括他的所有子CGroup。每個freezer都有自己的狀態和從父集成的狀態。只有父子狀態都為THAWED的時候,當前的CGroup才是THAWED。

代碼解析

freezer的代碼位於kernel/cgroup_freezer.c中,執行freeze的具體函數位於kernel/freezer.c中。

freezer_cgrp_subsys結構體如下:

struct cgroup_subsys freezer_cgrp_subsys = {
    .css_alloc    = freezer_css_alloc,
    .css_online    = freezer_css_online,
    .css_offline    = freezer_css_offline,
    .css_free    = freezer_css_free,
    .attach        = freezer_attach,
    .fork        = freezer_fork,
    .legacy_cftypes    = files,
};

freezer子系統用來管理CGroup的結構體如下,只有一個參數state:

struct freezer {
    struct cgroup_subsys_state    css;
    unsigned int            state;
};

freezer的sysfs文件節點:

static struct cftype files[] = {
    {
        .name = "state",  子系統當前的狀態
        .flags = CFTYPE_NOT_ON_ROOT,
        .seq_show = freezer_read,
        .write = freezer_write,
    },
    {
        .name = "self_freezing",  自身當前是否處於freezing狀態
        .flags = CFTYPE_NOT_ON_ROOT,
        .read_u64 = freezer_self_freezing_read,
    },
    {
        .name = "parent_freezing",  父子系統是否處於freezing狀態
        .flags = CFTYPE_NOT_ON_ROOT,
        .read_u64 = freezer_parent_freezing_read,
    },
    { }    /* terminate */
};

繼續引申出freezer的狀態:

enum freezer_state_flags {
    CGROUP_FREEZER_ONLINE    = (1 << 0), /* freezer is fully online */  freezer沒有被凍結
    CGROUP_FREEZING_SELF    = (1 << 1), /* this freezer is freezing */  freezer自身正在凍結中
    CGROUP_FREEZING_PARENT    = (1 << 2), /* the parent freezer is freezing */  父freezer正在凍結中
    CGROUP_FROZEN        = (1 << 3), /* this and its descendants frozen */  自身和者子freezer已經被凍結

    /* mask for all FREEZING flags */
    CGROUP_FREEZING        = CGROUP_FREEZING_SELF | CGROUP_FREEZING_PARENT,  自身或者父freezer處於凍結過程中
};

freezer.state

那么這些狀態和freezer.state的對應關系如何呢?

CGROUP_FREEZING       FREEZING (凍結中)

CGROUP_FROZEN        FROZEN(已凍結)

CGROUP_FREEZER_ONLINE    THAWED(解凍狀態)

FREEZING不是一個常態,他是當前CGroup(或其子CGroup)一組任務將要轉換到FROZEN狀態的一種中間狀態。同時,如果當前或子CGroup有新任務加入,狀態會從FROZEN返回到FRZEEING,直到任務被凍結。

只有FROZEN和THAWED兩個狀態是寫有效的。如果寫入FROZEN,當CGroup沒有完全進入凍結狀態,包括其所有子CGroup都會進入FREEZING狀態。

如果寫入THAWED,當前的CGroup狀態就會變成THAWED。有一種例外是如果父CGroup還是被凍結,則不會變成THAWED。如果一個CGroup的有效狀態變成THAWED,因當前CGroup造成的凍結都會停止,並離開凍結狀態。

freezer.self_freezing

只讀。0表示狀態是THAWED,其他為1。

freezer.parent_freezing

只讀。0表示父CGroup沒有一個進入凍結狀態,其他為1。

freezer_read

此函數會從子CGroup向上遍歷所有CGroup,直到最后一個遍歷當前CGroup。

static int freezer_read(struct seq_file *m, void *v)
{
    struct cgroup_subsys_state *css = seq_css(m), *pos;

    mutex_lock(&freezer_mutex);
    rcu_read_lock();

    /* update states bottom-up */
    css_for_each_descendant_post(pos, css) { 倒序遍歷當前css的所有子css,最后一個遍歷根css。
        if (!css_tryget_online(pos))
            continue;
        rcu_read_unlock();

        update_if_frozen(pos);  更新當前css的state,這樣確保當前css狀態是最新的。然后根css的狀態也是最新的。

        rcu_read_lock();
        css_put(pos);
    }

    rcu_read_unlock();
    mutex_unlock(&freezer_mutex);

    seq_puts(m, freezer_state_strs(css_freezer(css)->state));
    seq_putc(m, '\n');
    return 0;
}

freezer_write

static ssize_t freezer_write(struct kernfs_open_file *of,
                 char *buf, size_t nbytes, loff_t off)
{
    bool freeze;

    buf = strstrip(buf);

    if (strcmp(buf, freezer_state_strs(0)) == 0)  對應THAWED狀態
        freeze = false;
    else if (strcmp(buf, freezer_state_strs(CGROUP_FROZEN)) == 0)  對應FROZEN狀態
        freeze = true;
    else
        return -EINVAL;

    freezer_change_state(css_freezer(of_css(of)), freeze); 切換freezer狀態
    return nbytes;
}

freezer_change_state

static void freezer_change_state(struct freezer *freezer, bool freeze)
{
    struct cgroup_subsys_state *pos;

    /*
     * Update all its descendants in pre-order traversal.  Each
     * descendant will try to inherit its parent's FREEZING state as
     * CGROUP_FREEZING_PARENT.
     */
    mutex_lock(&freezer_mutex);
    rcu_read_lock();
    css_for_each_descendant_pre(pos, &freezer->css) {  這里和freezer_read是一個相反的過程,這是從跟css開始,逐級遍歷所有css。
        struct freezer *pos_f = css_freezer(pos);
        struct freezer *parent = parent_freezer(pos_f);

        if (!css_tryget_online(pos))
            continue;
        rcu_read_unlock();

        if (pos_f == freezer)  如果是根css則進入CGROUP_FREEZING_SELF
            freezer_apply_state(pos_f, freeze,
                        CGROUP_FREEZING_SELF);
        else
            freezer_apply_state(pos_f,  其他css,表示是繼承CGROUP_FREEZING_PARENT
                        parent->state & CGROUP_FREEZING,
                        CGROUP_FREEZING_PARENT);

        rcu_read_lock();
        css_put(pos);
    }
    rcu_read_unlock();
    mutex_unlock(&freezer_mutex);
}

freezer_apply_state

static void freezer_apply_state(struct freezer *freezer, bool freeze,
                unsigned int state)
{
    /* also synchronizes against task migration, see freezer_attach() */
    lockdep_assert_held(&freezer_mutex);

    if (!(freezer->state & CGROUP_FREEZER_ONLINE))
        return;

    if (freeze) {  需要freeze,調用freeze_cgroup。凍結當前Cgroup下面所有task
        if (!(freezer->state & CGROUP_FREEZING))
            atomic_inc(&system_freezing_cnt);
        freezer->state |= state;
        freeze_cgroup(freezer);
    } else {  不需要freeze
        bool was_freezing = freezer->state & CGROUP_FREEZING;

        freezer->state &= ~state;

        if (!(freezer->state & CGROUP_FREEZING)) {  並且不是CGROUP_FREEZING狀態
            if (was_freezing)
                atomic_dec(&system_freezing_cnt);
            freezer->state &= ~CGROUP_FROZEN;
            unfreeze_cgroup(freezer);  此CGroup下的所有tasks解凍
        }
    }
}

freeze_task和__thaw_task

在kernel/freezer.c中定義了凍結和解凍task的執行函數freeze_task和__thaw_task。

在freezer的tasks中存放了所有的進程,遍歷所有進程執行freeze_task或者__thaw_task,即可凍結或解凍此freezer CGroup。

 

bool freeze_task(struct task_struct *p)
{
    unsigned long flags;

    /*
     * This check can race with freezer_do_not_count, but worst case that
     * will result in an extra wakeup being sent to the task.  It does not
     * race with freezer_count(), the barriers in freezer_count() and
     * freezer_should_skip() ensure that either freezer_count() sees
     * freezing == true in try_to_freeze() and freezes, or
     * freezer_should_skip() sees !PF_FREEZE_SKIP and freezes the task
     * normally.
     */
    if (freezer_should_skip(p))  在需要凍結的時候,是否跳過此進程
        return false;

    spin_lock_irqsave(&freezer_lock, flags);
    if (!freezing(p) || frozen(p)) {  如果進程不是freezing,或已經被FROZEN,返回false
        spin_unlock_irqrestore(&freezer_lock, flags);
        return false;
    }

    if (!(p->flags & PF_KTHREAD))
        fake_signal_wake_up(p);  不是內核線程,發送偽喚醒信號
    else
        wake_up_state(p, TASK_INTERRUPTIBLE);  設置進程喚醒條件為TASK_INTERRUPTIBLE

    spin_unlock_irqrestore(&freezer_lock, flags);
    return true;
}

 

void __thaw_task(struct task_struct *p)
{
    unsigned long flags;

    spin_lock_irqsave(&freezer_lock, flags);
    if (frozen(p))  如果已經被FROZEN,則簡單的去喚醒
        wake_up_process(p);
    spin_unlock_irqrestore(&freezer_lock, flags);
}

 

應用?

一個小實驗

為了直觀的理解,做一個小實驗。

源碼在:https://github.com/arnoldlu/common-use/blob/master/tools/loop.py

1.啟動一個占用率100%的進程:

image

2.top查看進程情況,CPU占用率100%:

image

3.新建一個freezer CGroup,並將7234進程寫入tasks:

image

4.將FROZEN寫入freezer.state:

image

5.top –p 7234查看進程情況,:

image

6.將THAWED寫入freezer.state之后:

image

7.可以看到7234的CPU占用率立馬又變成100%:

image

其他應用

比如LISA工具在測試的時候,為了排除干擾,只保留必須的進程,凍結其余進程:

image

 

參考資料

Freezer Subsystem:http://lxr.free-electrons.com/source/Documentation/cgroups/freezer-subsystem.txt?v=4.4

freezer子系統:http://www.cnblogs.com/lisperl/archive/2012/04/25/2469587.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM