1. 程式人生 > >Suspend to RAM和Suspend to Idle分析,以及在HiKey上效能對比

Suspend to RAM和Suspend to Idle分析,以及在HiKey上效能對比

測試環境:AOSP 7.1.1+Kernel 4.4.17 HW:HiKey

             Ubuntu 14.04+Kernel 4.4.0-31

1. Linux核心suspend狀態

Linux核心支援多種型別的睡眠狀態,通過設定不同的模組進入低功耗模式來達到省電功能。

目前存在四種模式:suspend to idle、power-on standby(Standby)、suspend to ram(STR)和sudpend to disk(Hibernate),分別對應ACPI狀態的S0、S1、S3和S4。

State in Linux Label state ACPI state 註釋
#define PM_SUSPEND_ON        ((__force suspend_state_t) 0)  一切正常
#define PM_SUSPEND_FREEZE    ((__force suspend_state_t) 1) freeze Suspend-to-Idle S0 凍結程序+掛起裝置+CPU空閒 
#define PM_SUSPEND_STANDBY    ((__force suspend_state_t) 2) standby Standby/Power-on Suspend S1 凍結程序+掛起裝置+關閉nonbootCPU 
#define PM_SUSPEND_MEM        ((__force suspend_state_t) 3) mem Suspend-to-RAM S3 僅保留RAM自重新整理 
#define PM_SUSPEND_MAX        ((__force suspend_state_t) 4) disk Suspend-to-disk S4

關閉所有裝置包括RAM也被稱為Hibernate

從freeze-->standby-->mem睡眠程度越來越深,喚醒花費的時間也越來越多。

Suspend-To-Idle

此狀態包括frozen processes+suspended devices+idle processors,具有輕量化的特點;

並且相對於相對於Idle狀態能節省更多的功耗,因為此時的使用者空間被凍結且I/O裝置進入了低功耗狀態。

相對於Suspend-To-RAM它具有低延時的優勢。

Standby/Power-On Suspend

此狀態包括frozen processes+suspended devices+offline nonboot CPUs+suspend low-level system,對CPU的處理更近一步。

所以相對於Suspend-To-Idle節省了更多的功耗,但是由於需要恢復CPU和一些底層功能也花費了更多的時間。

Suspend-to-RAM

此狀態使所有的裝置進入低功耗狀態,僅保留RAM自重新整理。

所有的裝置和系統狀態都儲存在RAM中,所有外設被掛起。

在HiKey的實際測試中,boot CPU是沒有關閉的!實際上這裡也沒有standby,mem和standby基本上沒有區別。

Suspend-to-disk

此狀態是最省功耗的模式。

相對Suspend-to-RAMRAM能節省更多功耗的原因是資料會被寫入磁碟中,RAM也可以被關閉。

但是這也導致了,更多的恢復延時,在resume的時候讀回到RAM,然後在進行系統和裝置狀態恢復工作。

但是在一般的嵌入式裝置上,此種狀態不支援。

下面用STR表示Suspend to RAM,STI表示Suspend to Idle。

2. Suspend狀態,以及STR 和STI區別

寫入/sys/power/state不同字串,可以讓系統進入不同睡眠狀態。

針對state sysfs節點的寫入,最終會進入到state_store這個函式,將字串轉換成上表中不同狀態。

state_store(kernel/power/main.c)
    -->pm_suspend (kernel/power/suspend.c)-------------處理除freeze、standby、mem三種類型suspend
        -->enter_state---------------------------------在進入睡眠之前,做一些準備工作
            -->suspend_devices_and_enter
                -->suspend_enter-----------------------這裡才是freeze與standby/mem區別所在。
    -->hibernate---------------------------------------進入suspend to disk流程

STR和STI的最主要區別就是下面一段程式碼:

static int suspend_enter(suspend_state_t state, bool *wakeup)
{

…

    /*
     * PM_SUSPEND_FREEZE equals
     * frozen processes + suspended devices + idle processors.
     * Thus we should invoke freeze_enter() soon after
     * all the devices are suspended.
     */
//====================================FREEZE===============================================================
if (state == PM_SUSPEND_FREEZE) {------------------------------------如果要進入freeze狀態,就會執行此段程式碼。 trace_suspend_resume(TPS("machine_suspend"), state, true); freeze_enter(); trace_suspend_resume(TPS("machine_suspend"), state, false); goto Platform_wake;----------------------------------------------在執行結束跳轉到Platform_wake,中間一段綠色程式碼將會被跳過。所以說freeze和standby、mem相比,多了freeze_enter,少了對non-boot CPUs、arch、syscore的操作。 } //=====================================MEM=============================================================== error = disable_nonboot_cpus(); if (error || suspend_test(TEST_CPUS)) { log_suspend_abort_reason("Disabling non-boot cpus failed"); goto Enable_cpus; } arch_suspend_disable_irqs(); BUG_ON(!irqs_disabled()); error = syscore_suspend(); if (!error) { *wakeup = pm_wakeup_pending(); if (!(suspend_test(TEST_CORE) || *wakeup)) { trace_suspend_resume(TPS("machine_suspend"), state, true); error = suspend_ops->enter(state); trace_suspend_resume(TPS("machine_suspend"), state, false); events_check_enabled = false; } else if (*wakeup) { pm_get_active_wakeup_sources(suspend_abort, MAX_SUSPEND_ABORT_LEN); log_suspend_abort_reason(suspend_abort); error = -EBUSY; } syscore_resume(); } arch_suspend_enable_irqs(); BUG_ON(irqs_disabled()); Enable_cpus: enable_nonboot_cpus(); Platform_wake: platform_resume_noirq(state); dpm_resume_noirq(PMSG_RESUME); … }

3 suspend/resume流程梳理

下面分析一下suspend/resume每個細分階段。

整個suspend可以分為若干階段,每個階段函式—>關鍵節點Trace—>analyze_suspend.py解析Trace—>根據Trace時間畫出Timeline圖表

這樣就可以分析出總的時間差異,每個階段差異,甚至一個裝置suspend/resume、一個子系統suspend/resume的時間差異。

analyze_suspend.py 基於預設基於ftrace進行分析(在指定dmesg的時候,會發現缺失了很多log資訊,無法生成timeline型別的html檔案),將suspend/resume分為若干階段。

下面簡要介紹一下各個階段,然後基於此進行程式碼分析。

在kernel版本大於等與3.15之後,解析需要的所有log資訊都可以從ftrace中獲取。之前的核心版本還需要藉助於dmesg。

由於使用的kernel版本是4.4.17,sysvals.usetraceeventsonly被置位,所以只會parseTraceLog()。

下表中的各個階段通過解析suspend_resume: XXXXXXX型別的ftrace來獲取。

各子模組、子系統的解析通過device_pm_callback_start和device_pm_callback_end來擷取時間段,以及這時間段內的callgraph。

Phase名稱 ftrace關鍵詞
suspend_prepare dpm_prepare
suspend dpm_suspend
suspend_late dpm_suspend_late
suspend_noirq dpm_suspend_noirq
suspend_machine machine_suspend start
resume_machine machine_suspend end
resume_noirq dpm_resume_noirq
resume_early dpm_resume_early
resume dpm_resume
resume_complete dpm_complete

下面是一組suspend/resume執行ftrace log,我們將據此進行各階段程式碼分析,包括suspend_enter、suspend_prepare、suspend、suspend_late、suspend_noirq、suspend_machine、resume_machine、resume_noirq、resume_early、resume、resume_complete。

從這裡也可以看出freeze和mem/standby除了machine部分不同之外,還少了CPU開關和syscore suspend/resume操作。

suspend_resume: suspend_enter[1] begin
suspend_resume: sync_filesystems[0] begin
suspend_resume: sync_filesystems[0] end
suspend_resume: freeze_processes[0] begin
suspend_resume: freeze_processes[0] end
suspend_resume: suspend_enter[1] end
suspend_resume: dpm_prepare[2] begin
suspend_resume: dpm_prepare[2] end
suspend_resume: dpm_suspend[2] begin
suspend_resume: dpm_suspend[2] end
suspend_resume: dpm_suspend_late[2] begin
suspend_resume: dpm_suspend_late[2] end
suspend_resume: dpm_suspend_noirq[2] begin
suspend_resume: dpm_suspend_noirq[2] end
No CPU_OFF…
No syscore_suspend…

suspend_resume: machine_suspend[1] begin
suspend_resume: machine_suspend[1] end
No suscore_resume…
No CPU_ON…

suspend_resume: dpm_resume_noirq[16] begin
suspend_resume: dpm_resume_noirq[16] end
suspend_resume: dpm_resume_early[16] begin
suspend_resume: dpm_resume_early[16] end
suspend_resume: dpm_resume[16] begin
suspend_resume: dpm_resume[16] end
suspend_resume: dpm_complete[16] begin
suspend_resume: dpm_complete[16] end
suspend_resume: resume_console[1] begin
suspend_resume: resume_console[1] end
suspend_resume: thaw_processes[0] begin
suspend_resume: thaw_processes[0] end

suspend_resume: suspend_enter[3] begin
suspend_resume: sync_filesystems[0] begin
suspend_resume: sync_filesystems[0] end
suspend_resume: freeze_processes[0] begin
suspend_resume: freeze_processes[0] end
suspend_resume: suspend_enter[3] end
suspend_resume: dpm_prepare[2] begin
suspend_resume: dpm_prepare[2] end
suspend_resume: dpm_suspend[2] begin
suspend_resume: dpm_suspend[2] end
suspend_resume: dpm_suspend_late[2] begin
suspend_resume: dpm_suspend_late[2] end
suspend_resume: dpm_suspend_noirq[2] begin
suspend_resume: dpm_suspend_noirq[2] end
suspend_resume: CPU_OFF[1-7] begin/end
suspend_resume: syscore_suspend[0] begin/end
suspend_resume: machine_suspend[3] begin
suspend_resume: machine_suspend[3] end
suspend_resume: syscore_resume[0] begin/end
suspend_resume: CPU_ON[1-7] begin/end
suspend_resume: dpm_resume_noirq[16] begin
suspend_resume: dpm_resume_noirq[16] end
suspend_resume: dpm_resume_early[16] begin
suspend_resume: dpm_resume_early[16] end
suspend_resume: dpm_resume[16] begin
suspend_resume: dpm_resume[16] end
suspend_resume: dpm_complete[16] begin
suspend_resume: dpm_complete[16] end
suspend_resume: resume_console[3] begin
suspend_resume: resume_console[3] end
suspend_resume: thaw_processes[0] begin
suspend_resume: thaw_processes[0] end

在介紹相關程式碼之前,先介紹一下HiKey使用的platform_suspend_ops:

static const struct platform_suspend_ops psci_suspend_ops = {
    .valid          = suspend_valid_only_mem,  僅支援mem型別的suspend
    .enter          = psci_system_suspend_enter,  睡眠的CPU底層支援
};

freeze的platform_freeze_ops如下:

static const struct platform_freeze_ops acpi_freeze_ops = {
    .begin = acpi_freeze_begin,
    .prepare = acpi_freeze_prepare,
    .restore = acpi_freeze_restore,
    .end = acpi_freeze_end,
};

3.1 suspend_enter

enter_state作為suspend/resume的入口點,完成了絕大部分工作。首先確保系統沒有正在進入睡眠狀態;然後為suspend做一些準備,使系統進入睡眠並在喚醒後進行必要清理恢復工作。

下面分析一下suspend之前的準備工作,即suspend_enter階段:

static int enter_state(suspend_state_t state)
{
    int error;

    trace_suspend_resume(TPS("suspend_enter"), state, true);
    if (state == PM_SUSPEND_FREEZE) {--------------------------------------是否是freeze型別suspend
#ifdef CONFIG_PM_DEBUG
        if (pm_test_level != TEST_NONE && pm_test_level <= TEST_CPUS) {
            pr_warning("PM: Unsupported test mode for suspend to idle,"
                   "please choose none/freezer/devices/platform.\n");
            return -EAGAIN;
        }
#endif
    } else if (!valid_state(state)) {-------------------------------------目前只支援mem型別suspend
        return -EINVAL;
    }
    if (!mutex_trylock(&pm_mutex))
        return -EBUSY;

    if (state == PM_SUSPEND_FREEZE)
        freeze_begin();--------------------------------------------------初始化suspend_freeze_state為FREEZE_STATE_NONE

#ifndef CONFIG_SUSPEND_SKIP_SYNC
    trace_suspend_resume(TPS("sync_filesystems"), 0, true);
    printk(KERN_INFO "PM: Syncing filesystems ... ");
    sys_sync();----------------------------------------------------------sync檔案系統快取檔案,確保資料sync到硬碟。
    printk("done.\n");
    trace_suspend_resume(TPS("sync_filesystems"), 0, false);
#endif

    pr_debug("PM: Preparing system for sleep (%s)\n", pm_states[state]);
    pm_suspend_clear_flags();
    error = suspend_prepare(state);--------------------------------------注意這裡面的suspend_prepare和下面的suspend_prepare階段容易搞混。
    if (error)
        goto Unlock;

    if (suspend_test(TEST_FREEZER))
        goto Finish;

    trace_suspend_resume(TPS("suspend_enter"), state, false);
    pr_debug("PM: Suspending system (%s)\n", pm_states[state]);
    pm_restrict_gfp_mask();
    error = suspend_devices_and_enter(state);
    pm_restore_gfp_mask();

Finish:
    pr_debug("PM: Finishing wakeup.\n");
    suspend_finish();---------------------------------------------------解凍,重啟程序;傳送PM_POST_SUSPEND通知;釋放之前分配的console。
Unlock:
    mutex_unlock(&pm_mutex);
    return error;
}

接著分析一下suspend_prepare函式:

static int suspend_prepare(suspend_state_t state)
{
    int error;

    if (!sleep_state_supported(state))  驗證suspend狀態
        return -EPERM;

    pm_prepare_console();  分配一個suspend console

    error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);  傳送PM_SUSPEND_PREPARE通知訊息
    if (error)
        goto Finish;

    trace_suspend_resume(TPS("freeze_processes"), 0, true);
    error = suspend_freeze_processes();  凍結程序
    trace_suspend_resume(TPS("freeze_processes"), 0, false);
    if (!error)
        return 0;

    suspend_stats.failed_freeze++;
    dpm_save_failed_step(SUSPEND_FREEZE);
Finish:
    pm_notifier_call_chain(PM_POST_SUSPEND);
    pm_restore_console();
    return error;
}

suspend_freeze_process先處理使用者空間程序,然後處理核心程序:

static inline int suspend_freeze_processes(void)
{
    int error;

    error = freeze_processes();  觸發使用者空間程序進入freeze狀態。當前程序不會被凍結。因為凍結失敗的程序會自動被解凍,所以不需要進行錯誤處理。
    /*
     * freeze_processes() automatically thaws every task if freezing
     * fails. So we need not do anything extra upon error.
     */
    if (error)
        return error;

    error = freeze_kernel_threads();  凍結核心執行緒
    /*
     * freeze_kernel_threads() thaws only kernel threads upon freezing
     * failure. So we have to thaw the userspace tasks ourselves.
     */
    if (error)  由於freeze_kernel_threads凍結失敗,只會解凍核心執行緒。所以還需要對使用者空間程序進行解凍。
        thaw_processes();

    return error;
}

下面的階段都在suspend_devices_and_enter中,可以看出這是一個對稱的流程,每一階段的suspend,都有對應的resume。

int suspend_devices_and_enter(suspend_state_t state)
{
    int error;
    bool wakeup = false;

    if (!sleep_state_supported(state))
        return -ENOSYS;

    error = platform_suspend_begin(state);
    if (error)
        goto Close;

    suspend_console();  關閉console子系統,暫停printk列印
    suspend_test_start();
    error = dpm_suspend_start(PMSG_SUSPEND); suspend_prepare(dpm_prepare)、suspend(dpm_suspend)兩階段
    if (error) {
        pr_err("PM: Some devices failed to suspend, or early wake event detected\n");
        log_suspend_abort_reason("Some devices failed to suspend, or early wake event detected");
        goto Recover_platform;
    }
    suspend_test_finish("suspend devices");
    if (suspend_test(TEST_DEVICES))
        goto Recover_platform;

    do {
        error = suspend_enter(state, &wakeup);  suspend_late(dpm_suspend_late)、suspend_noirq(dpm_suspend_noirq)、suspend_machine、resume_machine、resume_noirq(dpm_resume_noirq)、resume_early(dpm_resume_early)
    } while (!error && !wakeup && platform_suspend_again(state));

Resume_devices:
    suspend_test_start();
    dpm_resume_end(PMSG_RESUME);  resume(dpm_resume)、resume_complete(dpm_complete)
    suspend_test_finish("resume devices");
    trace_suspend_resume(TPS("resume_console"), state, true);
    resume_console();  開啟console子系統,恢復printk列印。
    trace_suspend_resume(TPS("resume_console"), state, false);

Close:
    platform_resume_end(state);
    return error;

Recover_platform:
    platform_recover(state);
    goto Resume_devices;
}

還有必要過一下suspend_enter:

static int suspend_enter(suspend_state_t state, bool *wakeup)
{
    char suspend_abort[MAX_SUSPEND_ABORT_LEN];
    int error, last_dev;

    error = platform_suspend_prepare(state); 因為suspend_ops的prepare為空,所以返回0
    if (error)
        goto Platform_finish;

    error = dpm_suspend_late(PMSG_SUSPEND);  suspend_late
    if (error) {
        last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1;
        last_dev %= REC_FAILED_NUM;
        printk(KERN_ERR "PM: late suspend of devices failed\n");
        log_suspend_abort_reason("%s device failed to power down",
            suspend_stats.failed_devs[last_dev]);
        goto Platform_finish;
    }
    error = platform_suspend_prepare_late(state);  執行freeze_ops->prepare()
    if (error)
        goto Devices_early_resume;

    error = dpm_suspend_noirq(PMSG_SUSPEND);  suspend_noirq
    if (error) {
        last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1;
        last_dev %= REC_FAILED_NUM;
        printk(KERN_ERR "PM: noirq suspend of devices failed\n");
        log_suspend_abort_reason("noirq suspend of %s device failed",
            suspend_stats.failed_devs[last_dev]);
        goto Platform_early_resume;
    }
    error = platform_suspend_prepare_noirq(state);
    if (error)
        goto Platform_wake;

    if (suspend_test(TEST_PLATFORM))
        goto Platform_wake;

    /*
     * PM_SUSPEND_FREEZE equals
     * frozen processes + suspended devices + idle processors.
     * Thus we should invoke freeze_enter() soon after
     * all the devices are suspended.
     */
    if (state == PM_SUSPEND_FREEZE) {  這裡是freeze和mem/standy差別所在
        trace_suspend_resume(TPS("machine_suspend"), state, true);
        freeze_enter(); 
        trace_suspend_resume(TPS("machine_suspend"), state, false);
        goto Platform_wake;
    }

    error = disable_nonboot_cpus(); 關閉所有boot-CPU之外的CPU
    if (error || suspend_test(TEST_CPUS)) {
        log_suspend_abort_reason("Disabling non-boot cpus failed");
        goto Enable_cpus;
    }

    arch_suspend_disable_irqs();
    BUG_ON(!irqs_disabled());

    error = syscore_suspend();  執行syscore_ops_list上所有syscore_ops的suspend回撥函式
    if (!error) {
        *wakeup = pm_wakeup_pending();  檢查是否需要終止suspend流程?
        if (!(suspend_test(TEST_CORE) || *wakeup)) {
            trace_suspend_resume(TPS("machine_suspend"),
                state, true);
            error = suspend_ops->enter(state);  呼叫psci_suspend_ops的enter回撥函式,關閉machine
            trace_suspend_resume(TPS("machine_suspend"),
                state, false);  !!!!!!!!!!!!!!!!這裡即為喚醒之後的執行路徑了!!!!!!!!!!!!!!!!
            events_check_enabled = false;
        } else if (*wakeup) {
            pm_get_active_wakeup_sources(suspend_abort,
                MAX_SUSPEND_ABORT_LEN);
            log_suspend_abort_reason(suspend_abort);
            error = -EBUSY;
        }
        syscore_resume();  執行所有syscore_ops_list的resume回撥函式
    }

    arch_suspend_enable_irqs();
    BUG_ON(irqs_disabled());

Enable_cpus:
    enable_nonboot_cpus();  開啟所有non-boot CPU

Platform_wake:
    platform_resume_noirq(state);
    dpm_resume_noirq(PMSG_RESUME);  resume_noirq

Platform_early_resume:
    platform_resume_early(state);

Devices_early_resume:
    dpm_resume_early(PMSG_RESUME);  resume_early

Platform_finish:
    platform_resume_finish(state);
    return error;
}

3.2 suspend_prepare和suspend

DPM是Device Power Management的意思,這些操作都是針對非系統裝置(non-sysdev)進行的。那什麼是系統裝置呢?下面的machine應該就是所謂的sysdev了。

dpm_prepare實際上就是遍歷dpm_list上的所有裝置,執行->prepare回撥函式。如果裝置存在->prepare回電函式,會將裝置的prepare階段列印到ftrace。

int dpm_prepare(pm_message_t state)
{
    int error = 0;

    trace_suspend_resume(TPS("dpm_prepare"), state.event, true);
    might_sleep();

    mutex_lock(&dpm_list_mtx);
    while (!list_empty(&dpm_list)) {  遍歷dpm_list
        struct device *dev = to_device(dpm_list.next);

        get_device(dev);
        mutex_unlock(&dpm_list_mtx);

        trace_device_pm_callback_start(dev, "", state.event);
        error = device_prepare(dev, state);  執行->prepare回撥函式
        trace_device_pm_callback_end(dev, error);

        mutex_lock(&dpm_list_mtx);
        if (error) {
            if (error == -EAGAIN) {
                put_device(dev);
                error = 0;
                continue;
            }
            printk(KERN_INFO "PM: Device %s not prepared "
                "for power transition: code %d\n",
                dev_name(dev), error);
            put_device(dev);
            break;
        }
        dev->power.is_prepared = true;
        if (!list_empty(&dev->power.entry))
            list_move_tail(&dev->power.entry, &dpm_prepared_list);  移動裝置到dpm_prepared_list
        put_device(dev);
    }
    mutex_unlock(&dpm_list_mtx);
    trace_suspend_resume(TPS("dpm_prepare"), state.event, false);
    return error;
}

dpm_suspend遍歷dpm_prepared_list,這點和dpm_prepare有區別。然後執行裝置的->suspend回撥函式。

int dpm_suspend(pm_message_t state)
{
    ktime_t starttime = ktime_get();
    int error = 0;

    trace_suspend_resume(TPS("dpm_suspend"), state.event, true);
    might_sleep();

    cpufreq_suspend();

    mutex_lock(&dpm_list_mtx);
    pm_transition = state;
    async_error = 0;
    while (!list_empty(&dpm_prepared_list)) { 基於dpm_prepared_list遍歷裝置
        struct device *dev = to_device(dpm_prepared_list.prev);

        get_device(dev);
        mutex_unlock(&dpm_list_mtx);

        error = device_suspend(dev);  執行裝置->suspend回撥函式

        mutex_lock(&dpm_list_mtx);
        if (error) {
            pm_dev_err(dev, state, "", error);
            dpm_save_failed_dev(dev_name(dev));
            put_device(dev);
            break;
        }
        if (!list_empty(&dev->power.entry))
            list_move(&dev->power.entry, &dpm_suspended_list);  移動裝置到dpm_suspended_list
        put_device(dev);
        if (async_error)
            break;
    }
    mutex_unlock(&dpm_list_mtx);
    async_synchronize_full();
    if (!error)
        error = async_error;
    if (error) {
        suspend_stats.failed_suspend++;
        dpm_save_failed_step(SUSPEND_SUSPEND);
    } else
        dpm_show_time(starttime, state, NULL);
    trace_suspend_resume(TPS("dpm_suspend"), state.event, false);
    return error;
}

3.3 suspend_late和suspend_noirq

dpm_suspend_late基於dpm_suspended_list操作裝置,所以這也需要函式之間順序執行。

int dpm_suspend_late(pm_message_t state)
{
    ktime_t starttime = ktime_get();
    int error = 0;

    trace_suspend_resume(TPS("dpm_suspend_late"), state.event, true);
    mutex_lock(&dpm_list_mtx);
    pm_transition = state;
    async_error = 0;

    while (!list_empty(&dpm_suspended_list)) {  遍歷dpm_suspended_list列表
        struct device *dev = to_device(dpm_suspended_list.prev);

        get_device(dev);
        mutex_unlock(&dpm_list_mtx);

        error = device_suspend_late(dev);  執行->suspend_late回撥函式

        mutex_lock(&dpm_list_mtx);
        if (!list_empty(&dev->power.entry))
            list_move(&dev->power.entry, &dpm_late_early_list);  移動裝置到dpm_late_early_list

        if (error) {
            pm_dev_err(dev, state, " late", error);
            dpm_save_failed_dev(dev_name(dev));
            put_device(dev);
            break;
        }
        put_device(dev);

        if (async_error)
            break;
    }
    mutex_unlock(&dpm_list_mtx);
    async_synchronize_full();
    if (!error)
        error = async_error;
    if (error) {
        suspend_stats.failed_suspend_late++;
        dpm_save_failed_step(SUSPEND_SUSPEND_LATE);
        dpm_resume_early(resume_event(state));
    } else {
        dpm_show_time(starttime, state, "late");
    }
    trace_suspend_resume(TPS("dpm_suspend_late"), state.event, false);
    return error;
}

dpm_suspend_noirq基於dpm_late_early_list遍歷所有裝置。首先阻止裝置驅動接收中斷資訊,然後執行->suspend_noirq回撥函式。

int dpm_suspend_noirq(pm_message_t state)
{
    ktime_t starttime = ktime_get();
    int error = 0;

    trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, true);
    cpuidle_pause();  暫停cpuidle功能,退出idle的CPU
    device_wakeup_arm_wake_irqs();  將具有wakeirq的裝置設定成wakeup resource
    suspend_device_irqs();  關閉當前所有能夠關閉的irq,置成IRQS_SUSPENDED。IRQF_NO_SUSPEND型別的wakeup中斷不能被關閉,並且作為wakeup喚醒源的中斷不能被關閉。
    mutex_lock(&dpm_list_mtx);
    pm_transition = state;
    async_error = 0;

    while (!list_empty(&dpm_late_early_list)) {
        struct device *dev = to_device(dpm_late_early_list.prev);

        get_device(dev);
        mutex_unlock(&dpm_list_mtx);

        error = device_suspend_noirq(dev);  呼叫->suspend_noirq回撥函式

        mutex_lock(&dpm_list_mtx);
        if (error) {
            pm_dev_err(dev, state, " noirq", error);
            dpm_save_failed_dev(dev_name(dev));
            put_device(dev);
            break;
        }
        if (!list_empty(&dev->power.entry))
            list_move(&dev->power.entry, &dpm_noirq_list);  移動裝置到dpm_noirq_list
        put_device(dev);

        if (async_error)
            break;
    }
    mutex_unlock(&dpm_list_mtx);
    async_synchronize_full();
    if (!error)
        error = async_error;

    if (error) {
        suspend_stats.failed_suspend_noirq++;
        dpm_save_failed_step(SUSPEND_SUSPEND_NOIRQ);
        dpm_resume_noirq(resume_event(state));
    } else {
        dpm_show_time(starttime, state, "noirq");
    }
    trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, false);
    return error;
}

3.4 suspend_machine和resume_machine

freeze和mem/standby在這部分是不同的。

mem/standby直接呼叫suspend_ops->enter進入對應的睡眠模式。

而freeze就要稍微複雜了:

static void freeze_enter(void)
{
    spin_lock_irq(&suspend_freeze_lock);
    if (pm_wakeup_pending())  檢查是否有wakeup訊號在處理,如果有則退出當前流程。
        goto out;

    suspend_freeze_state = FREEZE_STATE_ENTER;
    spin_unlock_irq(&suspend_freeze_lock);

    get_online_cpus();
    cpuidle_resume();  允許使用cpuidle

    /* Push all the CPUs into the idle loop. */
    wake_up_all_idle_cpus();  強制所有CPU退出idle狀態
    pr_debug("PM: suspend-to-idle\n");
    /* Make the current CPU wait so it can enter the idle loop too. */
    wait_event(suspend_freeze_wait_head,
           suspend_freeze_state == FREEZE_STATE_WAKE);  等待FREEZE_STATE_WAKE事件,進入idle loop
    pr_debug("PM: resume from suspend-to-idle\n");  !!!!!!!!!!!!!!!!這裡即為喚醒之後的執行路徑了!!!!!!!!!!!!!!!!
    cpuidle_pause();  暫停使用cpuidle
    put_online_cpus();

    spin_lock_irq(&suspend_freeze_lock);

out:
    suspend_freeze_state = FREEZE_STATE_NONE;
    spin_unlock_irq(&suspend_freeze_lock);
}

3.5 resume_noirq

執行dpm_noirq_list上裝置的resume_noirq回撥函式。

void dpm_resume_noirq(pm_message_t state)
{
    struct device *dev;
    ktime_t starttime = ktime_get();

    trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, true);
    mutex_lock(&dpm_list_mtx);
    pm_transition = state;

    /*
     * Advanced the async threads upfront,
     * in case the starting of async threads is
     * delayed by non-async resuming devices.
     */
    list_for_each_entry(dev, &dpm_noirq_list, power.entry) {
        reinit_completion(&dev->power.completion);
        if (is_async(dev)) {
            get_device(dev);
            async_schedule(async_resume_noirq, dev);
        }
    }

    while (!list_empty(&dpm_noirq_list)) {  遍歷dpm_noirq_list
        dev = to_device(dpm_noirq_list.next);
        get_device(dev);
        list_move_tail(&dev->power.entry, &dpm_late_early_list);  移動裝置到下一級dpm_late_early_list
        mutex_unlock(&dpm_list_mtx);

        if (!is_async(dev)) {
            int error;

            error = device_resume_noirq(dev, state, false);
            if (error) {
                suspend_stats.failed_resume_noirq++;
                dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
                dpm_save_failed_dev(dev_name(dev));
                pm_dev_err(dev, state, " noirq", error);
            }
        }

        mutex_lock(&dpm_list_mtx);
        put_device(dev);
    }
    mutex_unlock(&dpm_list_mtx);
    async_synchronize_full();
    dpm_show_time(starttime, state, "noirq");
    resume_device_irqs();
    device_wakeup_disarm_wake_irqs();
    cpuidle_resume();
    trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, false);
}

3.6 resume_early

執行前述dpm_late_early_list裝置的resume_early回撥函式,移動裝置到dpm_suspended_list列表。

void dpm_resume_early(pm_message_t state)
{
    struct device *dev;
    ktime_t starttime = ktime_get();

    trace_suspend_resume(TPS("dpm_resume_early"), state.event, true);
    mutex_lock(&dpm_list_mtx);
    pm_transition = state;

    /*
     * Advanced the async threads upfront,
     * in case the starting of async threads is
     * delayed by non-async resuming devices.
     */
    list_for_each_entry(dev, &dpm_late_early_list, power.entry) {
        reinit_completion(&dev->power.completion);
        if (is_async(dev)) {
            get_device(dev);
            async_schedule(async_resume_early, dev);
        }
    }

    while (!list_empty(&dpm_late_early_list)) {
        dev = to_device(dpm_late_early_list.next);
        get_device(dev);
        list_move_tail(&dev->power.entry, &dpm_suspended_list);
        mutex_unlock(&dpm_list_mtx);

        if (!is_async(dev)) {
            int error;

            error = device_resume_early(dev, state, false);
            if (error) {
                suspend_stats.failed_resume_early++;
                dpm_save_failed_step(SUSPEND_RESUME_EARLY);
                dpm_save_failed_dev(dev_name(dev));
                pm_dev_err(dev, state, " early", error);
            }
        }
        mutex_lock(&dpm_list_mtx);
        put_device(dev);
    }
    mutex_unlock(&dpm_list_mtx);
    async_synchronize_full();
    dpm_show_time(starttime, state, "early");
    trace_suspend_resume(TPS("dpm_resume_early"), state.event, false);
}

3.7 resume

執行所有dpm_suspended_list上裝置的resume回撥函式。

void dpm_resume(pm_message_t state)
{
    struct device *dev;
    ktime_t starttime = ktime_get();

    trace_suspend_resume(TPS("dpm_resume"), state.event, true);
    might_sleep();

    mutex_lock(&dpm_list_mtx);
    pm_transition = state;
    async_error = 0;

    list_for_each_entry(dev, &dpm_suspended_list, power.entry) {
        reinit_completion(&dev->power.completion);
        if (is_async(dev)) {
            get_device(dev);
            async_schedule(async_resume, dev);
        }
    }

    while (!list_empty(&dpm_suspended_list)) {
        dev = to_device(dpm_suspended_list.next);
        get_device(dev);
        if (!is_async(dev)) {
            int error;

            mutex_unlock(&dpm_list_mtx);

            error = device_resume(dev, state, false);
            if (error) {
                suspend_stats.failed_resume++;
                dpm_save_failed_step(SUSPEND_RESUME);
                dpm_save_failed_dev(dev_name(dev));
                pm_dev_err(dev, state, "", error);
            }

            mutex_lock(&dpm_list_mtx);
        }
        if (!list_empty(&dev->power.entry))
            list_move_tail(&dev->power.entry, &dpm_prepared_list);
        put_device(dev);
    }
    mutex_unlock(&dpm_list_mtx);
    async_synchronize_full();
    dpm_show_time(starttime, state, NULL);

    cpufreq_resume();
    trace_suspend_resume(TPS("dpm_resume"), state.event, false);
}

3.8 resume_complete

執行所有dpm_prepared_list上裝置的complete回撥函式。至此dpm_complete結束所有非系統裝置的睡眠。

void dpm_complete(pm_message_t state)
{
    struct list_head list;

    trace_suspend_resume(TPS("dpm_complete"), state.event, true);
    might_sleep();

    INIT_LIST_HEAD(&list);
    mutex_lock(&dpm_list_mtx);
    while (!list_empty(&dpm_prepared_list)) {
        struct device *dev = to_device(dpm_prepared_list.prev);

        get_device(dev);
        dev->power.is_prepared = false;
        list_move(&dev->power.entry, &list);
        mutex_unlock(&dpm_list_mtx);

        trace_device_pm_callback_start(dev, "", state.event);
        device_complete(dev, state);
        trace_device_pm_callback_end(dev, 0);

        mutex_lock(&dpm_list_mtx);
        put_device(dev);
    }
    list_splice(&list, &dpm_list);
    mutex_unlock(&dpm_list_mtx);
    trace_suspend_resume(TPS("dpm_complete"), state.event, false);
}

4 如何讓HiKey進入STR/STI並喚醒?

可以通過配置GPIO作為喚醒源,或者通過RTC作為喚醒源,延時一定時間來喚醒。

檢查是否存在/sys/class/rtc/rtc0/wakealarm,入不存在則需要開啟CONFIG_RTC_DRV_PL031。

寫入wakealarm的引數,表示在多少秒之後resume喚醒,退出suspend。

寫mem進入state,是系統進入suspend流程。

adb root && adb remount
adb shell "echo +10 > /sys/class/rtc/rtc0/wakealarm && echo mem > /sys/power/state"

5. suspend/resume的latency分析手段

5.1 analyze_suspend.py v3.0

在kernel的scripts中,這個工具可以幫助核心和OS開發者優化suspend/resume時間。

在開啟一系列核心選項之後,此工具就可以執行suspend操作,然後抓取dmesg和ftrace資料知道resume結束。

這些資料會按照時間線顯示每個裝置,並且顯示佔用最多suspend/resume時間的裝置或者子系統的呼叫關係詳圖。

執行工具後,會根據時間生成一個子目錄,裡面包含:html、dmesg和原始ftrace檔案。

下面簡單看一下工具選項:

Options:
  [general]
    -h          Print this help text
    -v          Print the current tool version
    -verbose    Print extra information during execution and analysis
    -status     Test to see if the system is enabled to run this tool
    -modes      List available suspend modes  顯示當前支援的suspend模式
    -m mode     Mode to initiate for suspend ['freeze', 'mem', 'disk'] (default: mem)  設定進入何種模式的suspend
    -rtcwake t  Use rtcwake to autoresume after <t> seconds (default: disabled)  使用rtc來喚醒,引數是間隔時間
  [advanced]
    -f          Use ftrace to create device callgraphs (default: disabled)  基於ftrace生成呼叫關係圖
    -filter "d1 d2 ..." Filter out all but this list of dev names
    -x2         Run two suspend/resumes back to back (default: disabled)
    -x2delay t  Minimum millisecond delay <t> between the two test runs (default: 0 ms)
    -postres t  Time after resume completion to wait for post-resume events (default: 0 S)
    -multi n d  Execute <n> consecutive tests at <d> seconds intervals. The outputs will
                be created in a new subdirectory with a summary page.
  [utilities]
    -fpdt       Print out the contents of the ACPI Firmware Performance Data Table
    -usbtopo    Print out the current USB topology with power info
    -usbauto    Enable autosuspend for all connected USB devices
  [android testing]
    -adb binary Use the given adb binary to run the test on an android device.  引數需要給出adb路徑,工具就會對Android裝置進行測試,並將結果pull出來。有一點需要注意,在此之前確保adb具有root許可權。
                The device should already be connected and with root access.
                Commands will be executed on the device using "adb shell"
  [re-analyze data from previous runs] 針對之前測試資料重新分析
    -ftrace ftracefile  Create HTML output using ftrace input
    -dmesg dmesgfile    Create HTML output using dmesg (not needed for kernel >= 3.15)
    -summary directory  Create a summary of all test in this dir

在瞭解了工具使用方法之後,就可以進行相關測試了。

5.1.1 Android

./analysze_suspend.py –modes –adb /usr/bin/adb獲取當前系統支援的suspend狀態。

['freeze', 'mem']

1.Android上測試STR,suspend/resume共5次,每次間隔20秒。

./analyze_suspend.py -adb  /usr/bin/adb -rtcwake 10 -multi 5 20 -f -m mem

2.Android上測試STI,suspend/resume共10次,每次間隔5秒。

./analyze_suspend.py -adb  /usr/bin/adb -rtcwake 10 -multi 5 20 -f -m freeze

測試結果可以在如下獲得:

存在的問題:analyze_suspend.py不支援Android的rtcwakeup和callgraph。已經在如下fix:

5.1.1.1 總體對比

下面是HiKey上測試結果,可以看出兩個資料都不夠穩定。mem的suspend和resume平均值都比較高。

freeze相比mem的suspend/resume平均值提高了304.3ms/613.5ms。

image

image

5.1.1.2 是否suspend CPU

對比如下兩幅圖,明顯看出mem型別的suspend關閉了除CPU0之外的所有CPU;而freeze則沒有關閉任何CPU。

non-boot CPUs的suspend/resume時間就達到300ms/200ms。

image

image

同時從log中也可以看出mem和freeze的主要區別就在於是否disabling/enabling non-boot CPU。其他裝置和子系統的suspend/resume時間基本一致。

同時還可以看出mem的suspend後,系統的timestamp是停止的;而freeze的timestamp還是一直在執行的。可以得出freeze狀態持續的時間。

因為先寫rtcwake為10s,然後進入睡眠,再喚醒,所以freeze時間是小於10s的。

[ 3385.642962] PM: suspend entry 1970-01-01 00:57:30.580909763 UTC
[ 3385.649165] PM: Syncing filesystems ... done.
[ 3385.661349] Freezing user space processes ...
[ 3385.671207] dwc2 f72c0000.usb: dwc2_hsotg_ep_stop_xfr: timeout DOEPCTL.EPDisable
[ 3385.678933] dwc2 f72c0000.usb: GINNakEff triggered
[ 3385.685718] (elapsed 0.019 seconds) done.
[ 3385.689860] Freezing remaining freezable tasks ... (elapsed 0.002 seconds) done.
[ 3385.700092] Suspending console(s) (use no_console_suspend to debug)
[ 3385.736020] PM: suspend of devices complete after 27.195 msecs
[ 3385.740811] PM: late suspend of devices complete after 4.765 msecs
[ 3385.743919] PM: noirq suspend of devices complete after 3.090 msecs
Disabling and Enabling non-boot CPUs
[ 3386.209126] PM: noirq resume of devices complete after 1.865 msecs
[ 3386.212066] PM: early resume of devices complete after 2.460 msecs
[ 3386.234729] mmc_host mmc0: Bus speed (slot 0) = 24800000Hz (slot req 400000Hz, actual 400000HZ div = 31)
[ 3386.311480] mmc_host mmc0: Bus speed (slot 0) = 51756522Hz (slot req 52000000Hz, actual 51756522HZ div = 0)
[ 3386.410411] mmc_host mmc2: Bus speed (slot 0) = 24800000Hz (slot req 400000Hz, actual 400000HZ div = 31)
[ 3386.458232] mmc_host mmc2: Bus speed (slot 0) = 24800000Hz (slot req 25000000Hz, actual 24800000HZ div = 0)
[ 3386.458729] PM: resume of devices complete after 246.646 msecs
[ 3386.818770] Restarting tasks ...
[ 3386.827026] done.
[ 3386.844139] PM: suspend exit 1970-01-01 00:57:40.624589167 UTC


[ 3471.760265] PM: Syncing filesystems ... done.
[ 3471.771897] Freezing user space processes ...
[ 3471.780407] dwc2 f72c0000.usb: dwc2_hsotg_ep_stop_xfr: timeout DOEPCTL.EPDisable
[ 3471.788105] dwc2 f72c0000.usb: GINNakEff triggered
[ 3471.794916] (elapsed 0.018 seconds) done.
[ 3471.799078] Freezing remaining freezable tasks ... (elapsed 0.002 seconds) done.
[ 3471.809320] Suspending console(s) (use no_console_suspend to debug)
[ 3471.847947] PM: suspend of devices complete after 29.905 msecs
[ 3471.852473] PM: late suspend of devices complete after 4.497 msecs
[ 3471.855611] PM: noirq suspend of devices complete after 3.120 msecs

[ 3481.034722] PM: noirq resume of devices complete after 1.945 msecs
[ 3481.037992] PM: early resume of devices complete after 2.694 msecs
[ 3481.062803] mmc_host mmc0: Bus speed (slot 0) = 24800000Hz (slot req 400000Hz, actual 400000HZ div = 31)
[ 3481.137795] mmc_host mmc0: Bus speed (slot 0) = 51756522Hz (slot req 52000000Hz, actual 51756522HZ div = 0)
[ 3481.234796] mmc_host mmc2: Bus speed (slot 0) = 24800000Hz (slot req 400000Hz, actual 400000HZ div = 31)
[ 3481.278601] mmc_host mmc2: Bus speed (slot 0) = 24800000Hz (slot req 25000000Hz, actual 24800000HZ div = 0)
[ 3481.279396] PM: resume of devices complete after 241.388 msecs
[ 3481.358513] Restarting tasks ... done.
[ 3481.377766] PM: suspend exit 1970-01-01 00:59:15.332218333 UTC

5.1.1.3 resume_console節省時間

對比resume_console可以發現,mem要比freeze多210ms。

5.1.2 Ubuntu

此工具在Ubuntu上顯示了更強大的功能。

支援了callgraph功能之後,更能清晰地分析每個裝置或者子系統的suspend/resume佔用的時間。

sudo ./analyze_suspend.py -rtcwake 10 -multi 5 20 -f -m mem
sudo ./analyze_suspend.py -rtcwake 10 -multi 5 20 -f -m freeze

在對比兩種不同suspend模式後,發現freeze花費的時間要比mem少。這也符合預期,但是沒有功耗資料?_?。

相關推薦

Suspend to RAMSuspend to Idle分析以及HiKey效能對比

測試環境:AOSP 7.1.1+Kernel 4.4.17 HW:HiKey              Ubuntu 14.04+Kernel 4.4.0-31 1. Linux核心suspend狀態 Linux核心支援多種型別的睡眠狀態,通過設定不同的模組進入低功耗模式來達到省電功能。 目前存在四種模式:

JPA關係對映之one-to-manymany-to-one(另inverse=“true”解析)

one-to-many(一對多)和many-to-one(多對一)雙向關聯 假設部門與員工是一對多關係,反過來員工與部門就是多對一關係。 Dept.java類 public class Dept implements java.io.Serializable {

【java】xxx.hbm.xml檔案中的many-to-oneone-to-many理解

一、前言       在前幾天中自己在學習ssh的過程中,發現Hibernate需要配置一個名字叫xxx.hbm.xml的檔案,這個檔案主要是使用Hibernate對映到相應的表。自然在這個檔案裡面會

JPA關係對映系列三:one-to-manymany-to-one

SpringDataJPA是Spring Data的一個子專案,通過提供基於JPA的Repository極大的減少了JPA作為資料訪問方案的程式碼量,你僅僅需要編寫一個介面整合下SpringDat

關於Hibernate中many-to-oneone-to-many的一些理解

       一對多,主要從兩個實體出發,看強調的重點是一還是多的一端,也就是外來鍵所在的一端,如果外來鍵所在一端是一那麼對關係的維護就需要在一的一端。 如果外來鍵在多的一端那麼這個時候就應該是“多對一”維護端需要建立在多的一端。 如果兩端都有互相的主鍵作為外來鍵,那麼這個

一段C語言彙編的對應分析揭示函式呼叫的本質

一段C語言和彙編的對應分析,揭示函式呼叫的本質 2018年09月30日 13:32:19 sdulibh 閱讀數:17 本文作者周平,原創作品轉載請註明出處 首先對會涉及到的一些CPU暫存器和彙編的基礎知識羅列一下:   16位、32位、64

字元指標整形指標簡單分析*&的作用。

你始終記住 * 就是取值的, 例:*p,你看看p中存的是什麼是地址的話就是去這個地址中存的內容,如不是抵制就返回0; 程式碼如下:     string ww="zhj";     string * dizhi=&ww;//存的地址     cout <&

Jni程式設計(二)jni.h 標頭檔案定義分析以及c/c++呼叫java類的屬性方法

在第一篇部落格中 我們初步瞭解了jni程式設計的步驟,那接下來我認為極其重要的事情是搞清楚jni.h標頭檔案裡面的結構,包括資料型別和方法的定義等,這些是必須的,否則沒有辦法進行學習,就像寫文章一樣,要先學會寫字是一樣的道理。 首先來看一下jni.h標頭檔案的組成:ps下面

怎麼實現員工工資大資料分析echarts+js實現

前言  現如今市場上的人事系統五花八門,可做了大資料分析的人事系統少之又少,最近本人花了一個星期好好研究了大資料展示方面的內容,圖表主要用的是echarts來實現的,官網地址:https://echarts.apache.org/zh/index.html     下面兩張圖片

kubernetes中port、target port、node port的對比分析以及kube-proxy代理

ans toc contain exp red lec adb service 接口 轉:http://blog.csdn.net/xinghun_4/article/details/50492041 容器網絡實例 服務中的3個端口設置 這幾個port的概念很容易混淆,比

PHP yield 分析以及協程的實現超詳細版()

出錯 同時 分享圖片 spl 們的 是什麽 cti 接下來 版本 參考資料 http://www.laruence.com/2015/05/28/3038.html http://php.net/manual/zh/class.generator.php htt

Map四種獲取keyvalue值的方法以及對map中的元素排序(轉)

compareto map.entry 排序 區別 sta hashmap 得到 package log 獲取map的值主要有四種方法,這四種方法又分為兩類,一類是調用map.keySet()方法來獲取key和value的值,另一類則是通過map.entrySet()方法來

JS檔案的**.js**.min.js的區別以及js函式執行的順序

①**.js和**.min.js的區別 引用 Q: .js和.min.js檔案分別是什麼? A: .js是JavaScript 原始碼檔案, .min.js是壓縮版的js檔案。 Q:為什麼要壓縮為.min.js檔案? 減小體積 .min.js檔案經過壓縮,相對編譯前的js檔案體積較小

機器學習中的過擬合欠擬合現象以及通過正則化的方式解決。

過擬合: 過擬合(over-fitting)是所建的機器學習模型或者是深度學習模型在訓練樣本中表現得過於優越,導致在驗證資料集以及測試資料集中表現不佳的現象。就像上圖中右邊的情況。 過擬合的模型太過具體從而缺少泛化能力,過度的擬合了訓練集中的資料。出現的原因是模型將其中的不重要的變

json,dump()dump()區別以及如何序列化一個物件

json作用: 將python的資料型別轉化為字串,便於在網路或者各種程式語言中傳遞。 方法:dumps(),loads() 把python的資料直接放入括號內第一位引數即可,返回序列化之後的json字串,   - json字串就是一種字串,   - 字串很容易儲存和傳遞   - json字串可以

程序執行緒的區別以及應用場景

什麼是執行緒? Linux下執行緒用程序PCB模擬描述,也叫輕量級程序 執行緒是程序內部的一個執行流,也就是執行緒在程序的地址空間內執行。 一個程序內的所有執行緒共享程序資源 執行緒是CPU排程的基本單位(CPU排程是按照PCB進行排程的) 建立,銷燬一個執行緒相較

關於ftpssh修改埠之後連線不的問題

                我們可以先通過輸入netstat -an|grep,來檢視一下當前的總埠。               &nb

Rxjava2.x 原始碼分析以及手動實現Rxjava(一)

這兩年Rxjava火的一塌糊塗,不會點Rxjava+Okhttp+Retrofit+MVP+Dagger2架構都不好意思說自己混Android的。Rxjava 到底是什麼和Rxjava到底怎麼用,這裡就不講了,網上太多了,具體可以參考 這位大佬 和扔物線的。  Rxjava

redis的主從複製高可用、叢集以及用redis做mysql快取

一、redis的安裝先在下載安裝包解壓後進入目錄應為已經有Makefile了所以直接make編譯這裡會報錯,需要gcc編譯器安裝好gcc,再次make編譯,還是會報錯;這個錯誤根據 Readme 可知需要執行 make MALLOC=libcmake完成後,提示去到 src

JMETER 3.1版本 JMETER-Plugins 外掛資源以及各個外掛的用法

  JJMETER 3.1可以和最新版本的badboy匹配以及和最新版本的JMETER外掛匹配;連結:https://pan.baidu.com/s/1ah_5ewIKE-4X4XzwR0TcFA 密碼:getb  永久有效     雖然Jmeter功能相對完善,但基本可以滿