linux sleep and wake

Brief introduction

Sleep / wake is a very important part in embedded Linux, the embedded device from sleeping as much as possible to extend battery life. This article will introduce Linux in detail in the sleep / wake is how it works, as well as Android Linux and how this part of the mechanism linking.

globalization

  • English Version: link
  • Chinese version:  Link

Author: zhangjiejing a Date: 2010-04-07,  http://www.thinksrc.com

Version Information

  • Linux Kernel: v2.6.28
  • Android: v2.0

For a brief hibernation (suspend) the

In Linux, sleep is divided into three main steps:

  1. Freeze user mode and kernel mode processes task
  2. Call the device registration of the suspend callback function
    • Order is in accordance with the registration order
  3. Sleep core equipment and the CPU into sleep mode to freeze the process is the kernel to process a list of the status of all processes are set to stop and save the context of all the processes. When these processes are thawed, they do not know they have been frozen off, simply continue. how to make Linux into hibernation it? users can read and write files sys / sys / power / state is to achieve control system goes to sleep. For example,
    # echo standby > /sys/power/state
    

    Command system goes to sleep. You can also use

    # cat /sys/power/state
    

    To get the kernel to support what kinds of sleep mode.

Linux Suspend process

Related documents:

You can access the  Linux kernel website  to get the source code, the following is the path to the file:

  • linux_soruce/kernel/power/main.c
  • linux_source/kernel/arch/xxx/mach-xxx/pm.c
  • linux_source/driver/base/power/main.c

Let's take a detailed look at how Linux is the sleep / wake. Let 's going to see how these happens.

For a user to read and write / sys / power / state of calls to the main.c state_store (), the user can write const char * const pm_state String [] as defined, such as "mem", "standby".

Then state_store () calls enter_state (), it first checks the status of some parameters, and then synchronize the file systems Here is the code:

/**
 *      enter_state - Do common work of entering low-power state.
 *      @state:         pm_state structure for state we're entering.
 *
 *      Make sure we're the only ones trying to enter a sleep state. Fail
 *      if someone has beat us to it, since we don't want anything weird to
 *      happen when we wake up.
 *      Then, do the setup for suspend, enter the state, and cleaup (after
 *      we've woken up).
 */
static int enter_state(suspend_state_t state)
{
        int error;

        if (!valid_state(state))
                return -ENODEV;

        if (!mutex_trylock(&pm_mutex))
                return -EBUSY;

        printk(KERN_INFO "PM: Syncing filesystems ... ");
        sys_sync();
        printk("done.\n");

        pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);
        error = suspend_prepare();
        if (error)
                goto Unlock;

        if (suspend_test(TEST_FREEZER))
                goto Finish;

        pr_debug("PM: Entering %s sleep\n", pm_states[state]);
        error = suspend_devices_and_enter(state);

 Finish:
        pr_debug("PM: Finishing wakeup.\n");
        suspend_finish();
 Unlock:
        mutex_unlock(&pm_mutex);
        return error;
}

Preparing, freezing process

When entering the suspend_prepare (), which will assign a virtual terminal to suspend output, then a broadcast system to enter the Notify suspend, shut off the helper process user mode, then the first call suspend_freeze_processes () to freeze all processes, here will save the current state of all processes, there may be some process will be denied access to frozen state, when there is such a process would lead to freezing of failure, this function will give up the process of freezing and thawing process all just frozen.

/**
 *      suspend_prepare - Do prep work before entering low-power state.
 *
 *      This is common code that is called for each state that we're entering.
 *      Run suspend notifiers, allocate a console and stop all processes.
 */
static int suspend_prepare(void)
{
        int error;
        unsigned int free_pages;

        if (!suspend_ops || !suspend_ops->enter)
                return -EPERM;

        pm_prepare_console();

        error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
        if (error)
                goto Finish;

        error = usermodehelper_disable();
        if (error)
                goto Finish;

        if (suspend_freeze_processes()) {
                error = -EAGAIN;
                goto Thaw;
        }

        free_pages = global_page_state(NR_FREE_PAGES);
        if (free_pages < FREE_PAGE_NUMBER) {
                pr_debug("PM: free some memory\n");
                shrink_all_memory(FREE_PAGE_NUMBER - free_pages);
                if (nr_free_pages() < FREE_PAGE_NUMBER) {
                        error = -ENOMEM;
                        printk(KERN_ERR "PM: No enough memory\n");
                }
        }
        if (!error)
                return 0;

 Thaw:
        suspend_thaw_processes();
        usermodehelper_enable();
 Finish:
        pm_notifier_call_chain(PM_POST_SUSPEND);
        pm_restore_console();
        return error;
}

Let peripherals into hibernation

Now, all the processes (including workqueue / kthread) have been stopped, it is possible to hold the kernel mode character semaphores in time to stop, so if this time the peripheral inside to unlock the semaphore deadlock is likely to occur , so suspend peripherals () function inside for lock / unlock the lock to be very careful here proposed design when they should not wait for a lock suspend () inside. and because when suspend, some Log is not output, so once problem, very difficult to debug.

Here then kernel will try to free some memory.

Finally calls suspend_devices_and_enter () all the peripherals to sleep, in this function, if the platform is registered suspend_pos (usually at the board level as defined in the definition and registration), where it will call suspend_ops-> begin (), then driver /base/power/main.c in device_suspend () -> dpm_suspend () is called, they will in turn call the driver suspend () callback to sleep off all the equipment.

When all the equipment hibernation, suspend_ops-> prepare () is called, this function is usually to make some preparations to let the trigger go to sleep. Next Linux, in a multi-core CPU in a non-boot CPU is turned off, by avoiding these comments to see other CPU caused by race condion, next only after a CPU running.

suspend_ops power management board is operating normally registered in the file arch / xxx / mach-xxx / pm.c in.

Next, suspend_enter () is called, this function will be closed arch irq, call device_power_down (), it calls suspend_late () function, which is the system really into the function of sleep last call, usually make the final in this function check. If the check is no problem, the next sleep all the devices and bus systems, and call suspend_pos-> enter () to the CPU into a power saving state. at this time, it has been dormant. also stopped executing code here a.

/**
 *      suspend_devices_and_enter - suspend devices and enter the desired system
 *                                  sleep state.
 *      @state:           state to enter
 */
int suspend_devices_and_enter(suspend_state_t state)
{
        int error, ftrace_save;

        if (!suspend_ops)
                return -ENOSYS;

        if (suspend_ops->begin) {
                error = suspend_ops->begin(state);
                if (error)
                        goto Close;
        }
        suspend_console();
        ftrace_save = __ftrace_enabled_save();
        suspend_test_start();
        error = device_suspend(PMSG_SUSPEND);
        if (error) {
                printk(KERN_ERR "PM: Some devices failed to suspend\n");
                goto Recover_platform;
        }
        suspend_test_finish("suspend devices");
        if (suspend_test(TEST_DEVICES))
                goto Recover_platform;

        if (suspend_ops->prepare) {
                error = suspend_ops->prepare();
                if (error)
                        goto Resume_devices;
        }

        if (suspend_test(TEST_PLATFORM))
                goto Finish;

        error = disable_nonboot_cpus();
        if (!error && !suspend_test(TEST_CPUS))
                suspend_enter(state);

        enable_nonboot_cpus();
 Finish:
        if (suspend_ops->finish)
                suspend_ops->finish();
 Resume_devices:
        suspend_test_start();
        device_resume(PMSG_RESUME);
        suspend_test_finish("resume devices");
        __ftrace_enabled_restore(ftrace_save);
        resume_console();
 Close:
        if (suspend_ops->end)
                suspend_ops->end();
        return error;

 Recover_platform:
        if (suspend_ops->recover)
                suspend_ops->recover();
        goto Resume_devices;
}

Resume

If the system is interrupted sleep or wake other events, following code will begin execution of this order is to wake up and sleep sequential opposite, so the system equipment and the bus will first wake up, enable system interrupt enable hibernation when non-stop out of the starting CPU, and calls suspend_ops-> finish (), and in suspend_devices_and_enter () function will continue to wake up each device, enabling virtual terminal, the last call suspend_ops-> end ().

Upon returning to enter_state () function when after suspend_devices_and_enter () returns, the peripheral has woken up, but the processes and tasks are still frozen state, where calls suspend_finish () to unfreeze these processes and tasks, and issued Notify expressed the system has been withdrawn from the suspend state, wake terminal.

Here, all the sleep and wake has been finished, the system continues to run.

Android hibernation (suspend)

Android played in a patch of the kernel, state_store () function will go the other way, will enter into request_suspend_state (), the file in earlysuspend.c in. These features are added android system, and later will earlysuspend late resume introduced.

Related to the file:

  • linux_source/kernel/power/main.c
  • linux_source/kernel/power/earlysuspend.c
  • linux_source/kernel/power/wakelock.c

Features introduced

Early Suspend

Early suspend the introduction of a mechanism for android, this mechanism controversial upstream, do not make comments. The mechanism of action when the display is turned off, at this time, some of the display and related equipment, such as LCD backlighting, such as gravity sensors, touch screens, these devices will be turned off, but the system may still be processing tasks in running state (this time also wake lock), for example, files on the SD card scanning in embedded devices, the backlight is a a lot of power consumption, the android will join such a mechanism.

Late Resume

Late Resume and suspend matching is a mechanism in the kernel to wake up completed started execution. Mainly wakes up on sleep Early Suspend the device.

Wake Lock

Wake Lock to play a central role in Android's power management system. Wake Lock is a lock mechanism, as long as someone holding the lock, the system can not go to sleep, may be obtained user mode and kernel. This lock can be there is no overtime or overtime, overtime lock will automatically unlock after a time in the past. If you do not lock or times out, the kernel will start the set of mechanisms to sleep go to sleep.

Android Suspend

When the user is written to mem or standby / sys / power / state in time, state_store () is called, then calls request_suspend_state here Android () is the standard Linux will here to ENTER_STATE () function. If the request is dormant, so early_suspend this workqueue will be called, and enter early_suspend state.

void request_suspend_state(suspend_state_t new_state)
{
        unsigned long irqflags;
        int old_sleep;

        spin_lock_irqsave(&state_lock, irqflags);
        old_sleep = state & SUSPEND_REQUESTED;
        if (debug_mask & DEBUG_USER_STATE) {
                struct timespec ts;
                struct rtc_time tm;
                getnstimeofday(&ts);
                rtc_time_to_tm(ts.tv_sec, &tm);
                pr_info("request_suspend_state: %s (%d->%d) at %lld "
                        "(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n",
                        new_state != PM_SUSPEND_ON ? "sleep" : "wakeup",
                        requested_suspend_state, new_state,
                        ktime_to_ns(ktime_get()),
                        tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
                        tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);
        }
        if (!old_sleep && new_state != PM_SUSPEND_ON) {
                state |= SUSPEND_REQUESTED;
                queue_work(suspend_work_queue, &early_suspend_work);
        } else if (old_sleep && new_state == PM_SUSPEND_ON) {
                state &= ~SUSPEND_REQUESTED;
                wake_lock(&main_wake_lock);
                queue_work(suspend_work_queue, &late_resume_work);
        }
        requested_suspend_state = new_state;
        spin_unlock_irqrestore(&state_lock, irqflags);
}

Early Suspend

In early_suspend () function, the first checks whether the current status of the request is also suspend, to suspend the request would prevent cancel at this time (because this time the user process is still running), if you need to exit, simply exit the If not, this function will be registered in the early suspend a series of callbacks are called once, and then synchronize the file system, and then give up main_wake_lock, this is a wake lock does not lock timeout, if this does not release the lock, then the system I can not go to sleep.

   static void early_suspend(struct work_struct *work)
{
        struct early_suspend *pos;
        unsigned long irqflags;
        int abort = 0;

        mutex_lock(&early_suspend_lock);
        spin_lock_irqsave(&state_lock, irqflags);
        if (state == SUSPEND_REQUESTED)
                state |= SUSPENDED;
        else
                abort = 1;
        spin_unlock_irqrestore(&state_lock, irqflags);

        if (abort) {
                if (debug_mask & DEBUG_SUSPEND)
                        pr_info("early_suspend: abort, state %d\n", state);
                mutex_unlock(&early_suspend_lock);
                goto abort;
        }

        if (debug_mask & DEBUG_SUSPEND)
                pr_info("early_suspend: call handlers\n");
        list_for_each_entry(pos, &early_suspend_handlers, link) {
                if (pos->suspend != NULL)
                        pos->suspend(pos);
        }
        mutex_unlock(&early_suspend_lock);

        if (debug_mask & DEBUG_SUSPEND)
                pr_info("early_suspend: sync\n");

        sys_sync();
abort:
        spin_lock_irqsave(&state_lock, irqflags);
        if (state == SUSPEND_REQUESTED_AND_SUSPENDED)
                wake_unlock(&main_wake_lock);
        spin_unlock_irqrestore(&state_lock, irqflags);
}

Late Resume

When all of the wake is over, user processes are already running, and wake normally be several of the following reasons:

  • Incoming telegram

If an incoming call, then Modem will give rild by sending commands to make a call rild notification WindowManager response, which would be a remote call PowerManagerService write "on" to / sys / power / state to perform late resume of equipment, such as light up the screen Wait.

  • Users will be sent to key user key event in WindowManager, WindowManager will deal with these key events, key divided into several cases, if the case is not a wake-up keys (keys can wake up the system) then WindowManager will take the initiative to give up wakeLock to make the system go to sleep again, If the key is a wake-key, WindowManger will call PowerManagerService the interface to execute Late Resume.
  • Late Resume in turn had previously been called Early Suspend wake of the device.
static void late_resume(struct work_struct *work)
{
        struct early_suspend *pos;
        unsigned long irqflags;
        int abort = 0;

        mutex_lock(&early_suspend_lock);
        spin_lock_irqsave(&state_lock, irqflags);
        if (state == SUSPENDED)
                state &= ~SUSPENDED;
        else
                abort = 1;
        spin_unlock_irqrestore(&state_lock, irqflags);

        if (abort) {
                if (debug_mask & DEBUG_SUSPEND)
                        pr_info("late_resume: abort, state %d\n", state);
                goto abort;
        }
        if (debug_mask & DEBUG_SUSPEND)
                pr_info("late_resume: call handlers\n");
        list_for_each_entry_reverse(pos, &early_suspend_handlers, link)
                if (pos->resume != NULL)
                        pos->resume(pos);
        if (debug_mask & DEBUG_SUSPEND)
                pr_info("late_resume: done\n");
abort:
        mutex_unlock(&early_suspend_lock);
}

Wake Lock

Then take a look at our wake lock mechanism is how to run and work, focusing wakelock.c file on it.

wake lock and unlock the lock has two states, locked in two ways, one is permanently locked, this lock is released unless the display is not unlocked, so the use of this lock is very careful. the second is the lock timeout, this lock will lock the system wake-up time, if this time has passed, the lock will be automatically removed.

There are two types of locks:

  1. WAKE_LOCK_SUSPEND This lock prevents the system goes to sleep
  2. WAKE_LOCK_IDLE This lock does not affect the sleep system, the role I was not very clear.

In the wake lock in place so that there will be three systems just start suspend (), they are:

  1. In wake_unlock (), if found later unlocked without any other wake lock, and began to sleep
  2. After timers to time, the timer callback function to see if there are other wake lock, and if not, where the system goes to sleep.
  3. In wake_lock (), the future of a wake lock lock, will check out if there is a lock again, I want to check here is not necessary, a better approach is to make this operation atomic locked, not burdensome examination. and such a check may also be missing.

Suspend

When wake_lock run suspend (), in the wakelock.c suspend () function is called, the function first sync the file system, and then call pm_suspend (request_suspend_state), followed by pm_suspend () is called enter_state () to access the Linux sleep procedure ..

      static void suspend(struct work_struct *work)
{
        int ret;
        int entry_event_num;

        if (has_wake_lock(WAKE_LOCK_SUSPEND)) {
                if (debug_mask & DEBUG_SUSPEND)
                        pr_info("suspend: abort suspend\n");
                return;
        }

        entry_event_num = current_event_num;
        sys_sync();
        if (debug_mask & DEBUG_SUSPEND)
                pr_info("suspend: enter suspend\n");
        ret = pm_suspend(requested_suspend_state);
        if (current_event_num == entry_event_num) {
                wake_lock_timeout(&unknown_wakeup, HZ / 2);
        }
}

Android difference in the standard Linux dormant

pm_suspend () although it will call enter_state () to access standard Linux sleep procedure, but there are some differences:

  • When entering the freezing process, android will first check there is no wake lock, and if not, will stop these processes, because it is possible to suspend and freeze during the beginning of the process of an application for a wake lock, if so, to freeze the process will be interrupted .
  • In suspend_late (), the last check time there will wake lock, this may be some kind of rapid application wake lock, and lock the quick release of the process which, if this is the case, here returns an error, the entire suspend it will all give up. If pm_suspend () succeeds, LOG output by adding "no_console_suspend" in kernel cmd inside to see the log output suspend and resume process.
Published 17 original articles · won praise 2 · views 20000 +

Guess you like

Origin blog.csdn.net/toove/article/details/83346375