当前位置: 首页 > news >正文

Android休眠唤醒驱动流程


Android休眠与唤醒

android是在传统的linux内核电源管理设计的基础上,结合手机设计的实际需求而进化出的一套电源管理系统,其核心内容有:wakelockearly_suspendlate_resume

wakelockAndroid的电源管理系统中扮演一个核心的角色。wakelock是一种锁的机制,只要有人拿着这个锁,系统就无法进入休眠,可以被用户态程序和内核获得。这个锁可以是有超时的或者是没有超时的,超时的锁会在时间过去以后自动解锁。如果没有锁了或者超时了,内核就会启动休眠的那套机制来进入休眠。

当系统在启动完毕后,会自己去加一把名为“main“的锁,而当系统有意愿去睡眠时则会先去释放这把“main锁,在android中,在early_suspend的最后一步会去释放“main锁(wake_unlock:main)。释放完后则会去检查是否还有其他存在的锁,如果没有则直接进入睡眠过程。

它的缺点是,如果有某一应用获锁而不释放或者因一直在执行某种操作而没时间来释放的话,则会导致系统一直进入不了睡眠状态,功耗过大。


early_suspend:先于linux内核的睡眠过程被调用。一般在手机系统的设计中对背光的操作等采用此类方法,因为背光需要的能耗过大。当然此操作与late_resume是配套使用的。一些在内核中要预先进行处理的事件可以先注册上early_suspend函数,当系统要进入睡眠之前会首先调用这些注册的函数。


本文中,linuxkernel版本为linux-3.0.21android版本为android 4.0.4

android休眠唤醒主要相关的文件主要有:

  • linux_source/kernel/power/main.c

  • linux_source/kernel/power/earlysuspend.c

  • linux_source/kernel/power/wakelock.c

  • linux_source/kernel/power/process.c

  • linux_source/driver/base/power/main.c

  • linux_source/arch/arm/mach-msm/pm-8x60.c



Android休眠过程如下:

当用户读写/sys/power/state时,/kernel/power/main.c中的state_store()函数会被调用。其中,androidearly_suspend会执行request_suspend_state(state);而标准的linux休眠则执行error= enter_state(state);

static ssize_tstate_store(struct kobject *kobj, struct kobj_attribute *attr,

const char *buf,size_t n)

{

#ifdef CONFIG_SUSPEND

#ifdef CONFIG_EARLYSUSPEND

suspend_state_t state =PM_SUSPEND_ON;

#else

suspend_state_t state =PM_SUSPEND_STANDBY;

#endif

const char * const *s;

#endif

char *p;

int len;

int error = -EINVAL;


p = memchr(buf, '\n', n);

len = p ? p - buf : n;


/* First, check if we arerequested to hibernate */

if (len == 4 &&!strncmp(buf, "disk", len)) {

error = hibernate();

goto Exit;

}


#ifdef CONFIG_SUSPEND

for (s =&pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) {

if (*s && len ==strlen(*s) && !strncmp(buf, *s, len))

break;

}

if (state <PM_SUSPEND_MAX && *s)

#ifdefCONFIG_EARLYSUSPEND

//androidlinux内核会定义该宏,首先进入eralysuspend模式

if (state ==PM_SUSPEND_ON || valid_state(state)) {

error = 0;

request_suspend_state(state);

}

#else //标准linux内核直接enter_state()函数

error=enter_state(state);

#endif

#endif

Exit:

return error ? error : n;

}


request_suspend_state(state)函数中,会调用early_suspend_work的工作队列,从而进入early_suspend()函数中。

staticDECLARE_WORK(early_suspend_work, early_suspend);

voidrequest_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()函数中,首先要判断当前请求的状态是否还是suspend,若不是,则直接退出了;若是,函数会调用已经注册的early_suspend的函数。然后同步文件系统,最后释放main_wake_lock

static voidearly_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");


suspend_sys_sync_queue();

abort:

spin_lock_irqsave(&state_lock,irqflags);

if(state ==SUSPEND_REQUESTED_AND_SUSPENDED)

wake_unlock(&main_wake_lock);

spin_unlock_irqrestore(&state_lock,irqflags);

}


wake_unlock(),删除链表中wake_lock节点,判断当前是否存在wake_lock,若wake_lock的数目为0,则调用工作队列suspend_work,进入suspend状态。

staticDECLARE_WORK(suspend_work, suspend);

void wake_unlock(structwake_lock *lock)

{

int type;

unsigned long irqflags;

spin_lock_irqsave(&list_lock,irqflags);

type = lock->flags &WAKE_LOCK_TYPE_MASK;

#ifdefCONFIG_WAKELOCK_STAT

wake_unlock_stat_locked(lock,0);

#endif

if (debug_mask &DEBUG_WAKE_LOCK)

pr_info("wake_unlock:%s\n", lock->name);

lock->flags &=~(WAKE_LOCK_ACTIVE | WAKE_LOCK_AUTO_EXPIRE);

list_del(&lock->link);

list_add(&lock->link,&inactive_locks);

if (type ==WAKE_LOCK_SUSPEND) {

longhas_lock =has_wake_lock_locked(type);

if (has_lock > 0) {

if (debug_mask &DEBUG_EXPIRE)

pr_info("wake_unlock:%s, start expire timer, "

"%ld\n",lock->name, has_lock);

mod_timer(&expire_timer,jiffies + has_lock);

} else {

if(del_timer(&expire_timer))

if (debug_mask &DEBUG_EXPIRE)

pr_info("wake_unlock:%s, stop expire "

"timer\n",lock->name);

if(has_lock== 0)

queue_work(suspend_work_queue,&suspend_work);

}

if (lock ==&main_wake_lock) {

if (debug_mask &DEBUG_SUSPEND)

print_active_locks(WAKE_LOCK_SUSPEND);

#ifdefCONFIG_WAKELOCK_STAT

update_sleep_wait_stats_locked(0);

#endif

}

}

spin_unlock_irqrestore(&list_lock,irqflags);

}


suspend()函数中,先判断当前是否有wake_lock,若有,则退出;然后同步文件系统,最后调用pm_suspend()函数。

static void suspend(structwork_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;

suspend_sys_sync_queue();

if (debug_mask &DEBUG_SUSPEND)

pr_info("suspend:enter suspend\n");

ret=pm_suspend(requested_suspend_state);

if (debug_mask &DEBUG_EXIT_SUSPEND) {

struct timespec ts;

struct rtc_time tm;

getnstimeofday(&ts);

rtc_time_to_tm(ts.tv_sec,&tm);

pr_info("suspend:exit suspend, ret = %d "

"(%d-%02d-%02d%02d:%02d:%02d.%09lu UTC)\n", ret,

tm.tm_year + 1900,tm.tm_mon + 1, tm.tm_mday,

tm.tm_hour, tm.tm_min,tm.tm_sec, ts.tv_nsec);

}

if (current_event_num ==entry_event_num) {

if (debug_mask &DEBUG_SUSPEND)

pr_info("suspend:pm_suspend returned with no event\n");

wake_lock_timeout(&unknown_wakeup,HZ / 2);

}

}



pm_suspend()函数中,enter_state()函数被调用,从而进入标准linux休眠过程。

intpm_suspend(suspend_state_t state)

{

if (state >PM_SUSPEND_ON && state <= PM_SUSPEND_MAX)

returnenter_state(state);

return -EINVAL;

}


enter_state()函数中,首先检查一些状态参数,再同步文件系统,然后调用suspend_prepare()来冻结进程,最后调用suspend_devices_and_enter()让外设进入休眠。

static intenter_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 ... ");

suspend_sys_sync_queue();

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;

}


suspend_prepare()函数中,先通过pm_prepare_console();suspend分配一个虚拟终端来输出信息,再广播一个系统进入suspend的通报,关闭用户态的helper进程,然后调用suspend_freeze_processes()来冻结进程,最后会尝试释放一些内存。

static intsuspend_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: freesome 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;

}


suspend_freeze_processes()函数中调用了freeze_processes()函数,而freeze_processes()函数中又调用了try_to_freeze_tasks()来完成冻结任务。在冻结过程中,会判断当前进程是否有wake_lock,若有,则冻结失败,函数会放弃冻结。

static inttry_to_freeze_tasks(bool sig_only)

{

struct task_struct *g,*p;

unsigned long end_time;

unsigned int todo;

struct timeval start,end;

u64 elapsed_csecs64;

unsigned intelapsed_csecs;

unsigned int wakeup = 0;


do_gettimeofday(&start);


end_time = jiffies +TIMEOUT;

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;


/*

* Now that we've doneset_freeze_flag, don't

* perturb a task inTASK_STOPPED or TASK_TRACED.

* It is "frozenenough". If the task does wake

* up, it willimmediately call try_to_freeze.

*/

if(!task_is_stopped_or_traced(p) &&

!freezer_should_skip(p))

todo++;

} while_each_thread(g,p);

read_unlock(&tasklist_lock);

yield(); /* Yield isokay here */

if(todo &&has_wake_lock(WAKE_LOCK_SUSPEND)){

wakeup= 1;

break;

}

if (time_after(jiffies,end_time))

break;

} while (todo);


do_gettimeofday(&end);

elapsed_csecs64 =timeval_to_ns(&end) - timeval_to_ns(&start);

do_div(elapsed_csecs64,NSEC_PER_SEC / 100);

elapsed_csecs =elapsed_csecs64;


if (todo) {

/* This does notunfreeze processes that are already frozen

* (we have slightlyugly calling convention in that respect,

* and caller must callthaw_processes() if something fails),

* but it cleans upleftover PF_FREEZE requests.

*/

if(wakeup) {

printk("\n");

printk(KERN_ERR"Freezing of %s aborted\n",

sig_only ? "userspace " : "tasks ");

}

else {

printk("\n");

printk(KERN_ERR"Freezing of tasks failed after %d.%02d seconds "

"(%d tasksrefusing to freeze):\n",

elapsed_csecs / 100,elapsed_csecs % 100, todo);

show_state();

}

read_lock(&tasklist_lock);

do_each_thread(g, p) {

task_lock(p);

if (freezing(p) &&!freezer_should_skip(p))

printk(KERN_ERR "%s\n", p->comm);

cancel_freezing(p);

task_unlock(p);

} while_each_thread(g,p);

read_unlock(&tasklist_lock);

} else {

printk("(elapsed%d.%02d seconds) ", elapsed_csecs / 100,

elapsed_csecs % 100);

}

return todo ? -EBUSY : 0;

}


到现在,所有的进程(也包括workqueue/kthread)都已经停止了,内核态进程有可能在停止的时候握有一些信号量,所以如果这时候在外设里面去解锁这个信号量有可能会发生死锁,所以在外设suspend()函数里面作lock/unlock锁要非常小心,建议不要在外设的suspend()里面等待锁。而且suspend的过程中,有一些log是无法输出的,所以一旦出现问题,非常难调试。


回到enter_state()函数中,再冻结进程完成后,调用suspend_devices_and_enter()函数让外设进入休眠。该函数中,首先休眠串口(之后不能再显示log,解决方法为在kernel配置选项的cmd_line中,添加no_console_suspend”选项),再通过device_suspend()函数调用各驱动的suspend函数。

当外设进入休眠后,suspend_ops->prepare()被调用,suspend_ops是板级的PM操作(本文中粉红色的函数,依赖于具体的平台),以高通8960为例,其注册在kernel/arch/arm/mach-msm/pm-8x60.c中,只定义了suspend_ops->enter()函数。

static structplatform_suspend_ops msm_pm_ops = {

.enter = msm_pm_enter,

.valid =suspend_valid_only_mem,

};

接下来,

intsuspend_devices_and_enter(suspend_state_t state)

{

int error;


if (!suspend_ops)

return -ENOSYS;


trace_machine_suspend(state);

if (suspend_ops->begin){

error =suspend_ops->begin(state);

if (error)

goto Close;

}

suspend_console();

suspend_test_start();

error =dpm_suspend_start(PMSG_SUSPEND);

if (error) {

printk(KERN_ERR "PM:Some devices failed to suspend\n");

goto Recover_platform;

}

suspend_test_finish("suspenddevices");

if(suspend_test(TEST_DEVICES))

goto Recover_platform;


error =suspend_enter(state);


Resume_devices:

suspend_test_start();

dpm_resume_end(PMSG_RESUME);

suspend_test_finish("resumedevices");

resume_console();

Close:

if (suspend_ops->end)

suspend_ops->end();

trace_machine_suspend(PWR_EVENT_EXIT);

return error;


Recover_platform:

if (suspend_ops->recover)

suspend_ops->recover();

goto Resume_devices; }


接下来suspend_enter()被调用,其中多CPU中的非启动CPU被关闭。关闭IRQ。最后调用suspend_pos->enter()来使CPU进入省电状态。这时候,整个休眠过程完成,代码的执行也就停在这里了。

static intsuspend_enter(suspend_state_t state)

{

int error;


if (suspend_ops->prepare){

error =suspend_ops->prepare();

if (error)

goto Platform_finish;

}


error =dpm_suspend_noirq(PMSG_SUSPEND);

if (error) {

printk(KERN_ERR "PM:Some devices failed to power down\n");

goto Platform_finish;

}


if(suspend_ops->prepare_late) {

error =suspend_ops->prepare_late();

if (error)

goto Platform_wake;

}


if(suspend_test(TEST_PLATFORM))

goto Platform_wake;


error= disable_nonboot_cpus();

if (error ||suspend_test(TEST_CPUS))

goto Enable_cpus;


arch_suspend_disable_irqs();

BUG_ON(!irqs_disabled());


error =syscore_suspend();

if (!error) {

if(!(suspend_test(TEST_CORE) || pm_wakeup_pending())) {

error =suspend_ops->enter(state);

events_check_enabled =false;

}

syscore_resume();

}


arch_suspend_enable_irqs();

BUG_ON(irqs_disabled());


Enable_cpus:

enable_nonboot_cpus();


Platform_wake:

if (suspend_ops->wake)

suspend_ops->wake();


dpm_resume_noirq(PMSG_RESUME);


Platform_finish:

if (suspend_ops->finish)

suspend_ops->finish();


return error;

}

suspend_pos->enter()所对应的函数中,对共享资源的操作后,调用msm_pm_power_collapse

static intmsm_pm_enter(suspend_state_t state)

{

boolallow[MSM_PM_SLEEP_MODE_NR];

int i;

int64_t period = 0;

int64_t time =msm_pm_timer_enter_suspend(&period);


if (MSM_PM_DEBUG_SUSPEND& msm_pm_debug_mask)

pr_info("%s\n",__func__);


if (smp_processor_id()) {

__WARN();

goto enter_exit;

}



for (i = 0; i <MSM_PM_SLEEP_MODE_NR; i++) {

structmsm_pm_platform_data *mode;


mode =&msm_pm_sleep_modes[MSM_PM_MODE(0, i)];

allow[i] =mode->suspend_supported && mode->suspend_enabled;

}


#ifdef CONFIG_SHSYS_CUST

if (sh_pm_debug_mask &SH_PM_DEBUG_SUSPEND_WFI) {

allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE]= false;

allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE]= false;

}

#endif


if(allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE]) {

struct msm_rpmrs_limits*rs_limits;

int ret;


if (MSM_PM_DEBUG_SUSPEND& msm_pm_debug_mask)

pr_info("%s: powercollapse\n", __func__);


#ifdefCONFIG_SHSYS_CUST_DEBUG

if (sh_pm_debug_mask &SH_PM_DEBUG_IDLE_CLK)

pr_info( "%s():Execute clock_debug_print_enabled().\n", __func__ );

#endif

clock_debug_print_enabled();


#ifdefCONFIG_MSM_SLEEP_TIME_OVERRIDE

if(msm_pm_sleep_time_override > 0) {

int64_t ns =NSEC_PER_SEC *

(int64_t)msm_pm_sleep_time_override;

msm_pm_set_max_sleep_time(ns);

msm_pm_sleep_time_override= 0;

}

#endif /*CONFIG_MSM_SLEEP_TIME_OVERRIDE */


if(MSM_PM_DEBUG_SUSPEND_LIMITS & msm_pm_debug_mask)

msm_rpmrs_show_resources();


rs_limits= msm_rpmrs_lowest_limits(false,

MSM_PM_SLEEP_MODE_POWER_COLLAPSE,-1, -1);


if((MSM_PM_DEBUG_SUSPEND_LIMITS & msm_pm_debug_mask) &&

rs_limits)

pr_info("%s: limit%p: pxo %d, l2_cache %d, "

"vdd_mem %d,vdd_dig %d\n",

__func__, rs_limits,

rs_limits->pxo,rs_limits->l2_cache,

rs_limits->vdd_mem,rs_limits->vdd_dig);


if (rs_limits) {

#ifdef CONFIG_SHSYS_CUST

if(sleep_test_is_enabled()== true)

{

pm_bms_masked_disable();

}

#endif

ret= msm_rpmrs_enter_sleep(

msm_pm_max_sleep_time,rs_limits, false, true);

if (!ret) {

int collapsed =msm_pm_power_collapse(false);

msm_rpmrs_exit_sleep(rs_limits,false, true,

collapsed);

}

} else {

pr_err("%s: cannotfind the lowest power limit\n",

__func__);

}

time =msm_pm_timer_exit_suspend(time, period);

msm_pm_add_stat(MSM_PM_STAT_SUSPEND,time);

} else if(allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE]) {

if (MSM_PM_DEBUG_SUSPEND& msm_pm_debug_mask)

pr_info("%s:standalone power collapse\n", __func__);

msm_pm_power_collapse_standalone(false);

} else if(allow[MSM_PM_SLEEP_MODE_RETENTION]) {

if (MSM_PM_DEBUG_SUSPEND& msm_pm_debug_mask)

pr_info("%s:retention\n", __func__);

msm_pm_retention();

} else if(allow[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT]) {

if (MSM_PM_DEBUG_SUSPEND& msm_pm_debug_mask)

pr_info("%s:swfi\n", __func__);

msm_pm_swfi();

}


msm_pm_power_collapse函数中,保存睡眠前的硬件信息,更改CLOCK,调用msm_pm_spm_power_collapse,向SPM寄存器写入相关信息。最主要调用到了msm_pm_l2x0_power_collapse,执行msm_pm_collapse

staticbool __ref msm_pm_spm_power_collapse(

unsignedint cpu, bool from_idle, bool notify_rpm)

{

void*entry;

boolcollapsed = 0;

intret;

unsignedint saved_gic_cpu_ctrl;


saved_gic_cpu_ctrl= readl_relaxed(MSM_QGIC_CPU_BASE + GIC_CPU_CTRL);

mb();

if(MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask)

pr_info("CPU%u:%s: notify_rpm %d\n",

cpu,__func__, (int) notify_rpm);

ret= msm_spm_set_low_power_mode(

MSM_SPM_MODE_POWER_COLLAPSE,notify_rpm);

WARN_ON(ret);

entry= (!cpu || from_idle) ?

msm_pm_collapse_exit: msm_secondary_startup;

msm_pm_boot_config_before_pc(cpu,virt_to_phys(entry));

if(MSM_PM_DEBUG_RESET_VECTOR & msm_pm_debug_mask)

pr_info("CPU%u:%s: program vector to %p\n",

cpu,__func__, entry);

#ifdefCONFIG_VFP

vfp_flush_context();

#endif

#ifdefCONFIG_SHSYS_CUST_DEBUG

if(!from_idle && notify_rpm)

{

if((cpu== 0) || ((cpu == 1) && (sh_pm_debug_mask &SH_PM_DEBUG_CPU1_PC)))

{

pr_info("%s(): [CPU%u] Enter suspend power collapse.\n",__func__, cpu );

}

}

#endif

collapsed=msm_pm_l2x0_power_collapse();

#ifdefCONFIG_SHSYS_CUST_DEBUG

if(!from_idle && notify_rpm)

{

if((cpu== 0) || ((cpu == 1) && (sh_pm_debug_mask &SH_PM_DEBUG_CPU1_PC)))

{

pr_info("%s(): [CPU%u] Exit suspend power collapse. ret = %d \n",__func__, cpu, collapsed );

}

}

#endif

msm_pm_boot_config_after_pc(cpu);

if(collapsed) {

#ifdefCONFIG_VFP

vfp_reinit();

#endif

cpu_init();

writel(0xF0,MSM_QGIC_CPU_BASE + GIC_CPU_PRIMASK);

writel_relaxed(saved_gic_cpu_ctrl,

MSM_QGIC_CPU_BASE+ GIC_CPU_CTRL);

mb();

local_fiq_enable();

}

if(MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask)

pr_info("CPU%u:%s: msm_pm_collapse returned, collapsed %d\n",

cpu,__func__, collapsed);

ret= msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false);

WARN_ON(ret);

returncollapsed;

}

msm_pm_collapse的定义是在kernel/arch/arm/mach-msm/idle-v7.s.

函数中最终分两种模式进入睡眠,一种是进入trustzone;另一种是直接执行WFI指令。

至此,手机进入了睡眠。

ENTRY(msm_pm_collapse)

#ifdefined(CONFIG_MSM_FIQ_SUPPORT)

cpsid f

#endif


ldr r0, =msm_saved_state /* address of msm_saved_state ptr */

ldr r0,[r0] /* load ptr */

#if(NR_CPUS >= 2)

mrc p15,0, r1, c0, c0, 5 /* MPIDR */

ands r1,r1, #15 /* What CPU am I */

mov r2,#CPU_SAVED_STATE_SIZE

mul r1,r1, r2

add r0,r0, r1

#endif


stmia r0!, {r4-r14}

mrc p15, 0, r1, c1, c0, 0 /* MMU control */

mrc p15, 0, r2, c2, c0, 0 /* TTBR0 */

mrc p15, 0, r3, c3, c0, 0 /* dacr */

#ifdefCONFIG_ARCH_MSM_SCORPION

/*This instruction is not valid for non scorpion processors */

mrc p15, 3, r4, c15, c0, 3 /* L2CR1 is the L2 cache control reg 1*/

#endif

mrc p15, 0, r5, c10, c2, 0 /* PRRR */

mrc p15, 0, r6, c10, c2, 1 /* NMRR */

mrc p15, 0, r7, c1, c0, 1 /* ACTLR */

mrc p15, 0, r8, c2, c0, 1 /* TTBR1 */

mrc p15, 0, r9, c13, c0, 3 /* TPIDRURO */

mrc p15, 0, ip, c13, c0, 1 /* context ID */

stmia r0!, {r1-r9, ip}

#ifdefCONFIG_MSM_CPU_AVS

mrc p15, 7, r1, c15, c1, 7 /* AVSCSR is the Adaptive VoltageScaling

* Control and Status Register */

mrc p15, 7, r2, c15, c0, 6 /* AVSDSCR is the Adaptive Voltage

* Scaling Delay Synthesizer Control

*Register */

#ifndefCONFIG_ARCH_MSM_KRAIT

mrc p15, 7, r3, c15, c1, 0 /* TSCSR is the Temperature Status and

* Control Register

*/

#endif


stmia r0!, {r1-r3}

#endif


#ifdefCONFIG_MSM_JTAG

bl msm_jtag_save_state

#endif


ldr r0,=msm_pm_flush_l2_flag

ldr r0,[r0]

mov r1,#0

mcr p15,2, r1, c0, c0, 0 /*CCSELR*/

isb

mrc p15,1, r1, c0, c0, 0 /*CCSIDR*/

mov r2,#1

and r1,r2, r1, ASR #30 /* Check if the cache is write back */

orr r1,r0, r1

cmp r1,#1

bne skip

bl v7_flush_dcache_all

skip:

#ifdefCONFIG_ARCH_MSM_KRAIT

ldr r0,=SCM_SVC_BOOT

ldr r1,=SCM_CMD_TERMINATE_PC

ldr r2,=msm_pm_flush_l2_flag

ldr r2,[r2]

bl scm_call_atomic1

#else

mrc p15, 0, r4, c1, c0, 0 /* read current CR */

bic r0, r4, #(1 << 2) /* clear dcache bit */

bic r0, r0, #(1 << 12) /* clear icache bit */

mcr p15, 0, r0, c1, c0, 0 /* disable d/i cache */

isb


SUSPEND_8x25_L2

SET_SMP_COHERENCYOFF

wfi

DELAY_8x25300


mcr p15, 0, r4, c1, c0, 0 /* restore d/i cache */

isb

ENABLE_8x25_L2/* enable only l2, no need to restore the reg back */

SET_SMP_COHERENCYON

#endif


#ifdefined(CONFIG_MSM_FIQ_SUPPORT)

cpsie f

#endif

#ifdefCONFIG_MSM_JTAG

bl msm_jtag_restore_state

#endif

ldr r0, =msm_saved_state /* address of msm_saved_state ptr */

ldr r0,[r0] /* load ptr */

#if(NR_CPUS >= 2)

mrc p15,0, r1, c0, c0, 5 /* MPIDR */

ands r1,r1, #15 /* What CPU am I */

mov r2,#CPU_SAVED_STATE_SIZE

mul r2,r2, r1

add r0,r0, r2

#endif

ldmfd r0, {r4-r14} /* restore registers */

mov r0, #0 /* return power collapse failed */

bx lr


通过调用接口scm_call_atomic1发送SCM命令进入trust_zone

s32scm_call_atomic1(u32 svc, u32 cmd, u32 arg1)

{

intcontext_id;

registeru32 r0 asm("r0") = SCM_ATOMIC(svc, cmd, 1);

registeru32 r1 asm("r1") = (u32)&context_id;

registeru32 r2 asm("r2") = arg1;


asmvolatile(

__asmeq("%0","r0")

__asmeq("%1","r0")

__asmeq("%2","r1")

__asmeq("%3","r2")

"smc #0 @switch to secure world\n"

:"=r" (r0)

:"r" (r0), "r" (r1), "r" (r2)

:"r3");

returnr0;

}





Android唤醒过程如下:

如果在休眠中系统被中断或者其他事件唤醒,接下来的代码就从suspend完成的地方开始执行,即pm-8x60.c中的msm_pm_spm_power_collapse()中的cpu_init(),然后执行suspend_enter()sysdev_resume()函数,唤醒系统设备和总线,使能系统中断。

static intsuspend_enter(suspend_state_t state)

{

int error = 0;


device_pm_lock();

#ifdef CONFIG_CPU_FREQ

cpufreq_get_cpufreq_name(0);

strcpy(governor_name,cpufreq_governor_name);

if(strnicmp(governor_name,userspace_governor, CPUFREQ_NAME_LEN)) {

cpufreq_set_policy(0,"performance");

}

#endif /* CONFIG_CPU_FREQ*/

arch_suspend_disable_irqs();

BUG_ON(!irqs_disabled());


if ((error =device_power_down(PMSG_SUSPEND))) {

printk(KERN_ERR "PM:Some devices failed to power down\n");

goto Done;

}


error =sysdev_suspend(PMSG_SUSPEND);

if (!error) {

if(!suspend_test(TEST_CORE))

error=suspend_ops->enter(state); //suspend过程完成处

sysdev_resume();

}


device_power_up(PMSG_RESUME);

Done:

arch_suspend_enable_irqs();

#ifdef CONFIG_CPU_FREQ

if(strnicmp(governor_name,userspace_governor, CPUFREQ_NAME_LEN)) {

cpufreq_set_policy(0,governor_name);

}

#endif /* CONFIG_CPU_FREQ*/

BUG_ON(irqs_disabled());

device_pm_unlock();

return error;

}


然后回到suspend_devices_and_enter()函数中,使能休眠时候停止掉的非启动CPU,继续唤醒每个设备,使能终端。

intsuspend_devices_and_enter(suspend_state_t state)

{

int error;


if (!suspend_ops)

return -ENOSYS;


if (suspend_ops->begin){

error=suspend_ops->begin(state);

if (error)

goto Close;

}

suspend_console();

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("suspenddevices");

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); //suspend过程完成处


enable_nonboot_cpus();

Finish:

if (suspend_ops->finish)

suspend_ops->finish();

Resume_devices:

suspend_test_start();

device_resume(PMSG_RESUME);

suspend_test_finish("resumedevices");

resume_console();

Close:

if (suspend_ops->end)

suspend_ops->end();

return error;


Recover_platform:

if (suspend_ops->recover)

suspend_ops->recover();

goto Resume_devices;

}


suspend_devices_and_enter()执行完成后,系统外设已经唤醒,但进程依然是冻结的状态,返回到enter_state函数中,调用suspend_finish()函数。

static intenter_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); //suspend过程完成处


Finish:

pr_debug("PM:Finishing wakeup.\n");

suspend_finish();

Unlock:

mutex_unlock(&pm_mutex);

return error;

}


suspend_finish()函数中,解冻进程和任务,使能用户空间helper进程,广播一个系统从suspend状态退出的notify,唤醒终端。

static voidsuspend_finish(void)

{

suspend_thaw_processes();

usermodehelper_enable();

pm_notifier_call_chain(PM_POST_SUSPEND);

pm_restore_console();

}


当所有的唤醒已经结束以后,用户进程都已经开始运行了,但没点亮屏幕,唤醒通常会是以下的几种原因:

如果是来电,那么Modem会通过发送命令给rild来让rild通知WindowManager有来电响应,这样就会远程调用PowerManagerService来写on”/sys/power/state来调用lateresume(),执行点亮屏幕等操作。

用户按键事件会送到WindowManager中,WindowManager会处理这些按键事件,按键分为几种情况,如果按键不是唤醒键,那么WindowManager会主动放弃wakeLock来使系统进入再次休眠;如果按键是唤醒键,那么WindowManger就会调用PowerManagerService中的接口来执行lateResume


on”被写入到/sys/power/state之后,同early_suspend过程,request_suspend_state()被调用,只是执行的工作队列变为late_resume_work。在late_resume函数中,唤醒调用了early_suspend的设备。

staticDECLARE_WORK(late_resume_work, late_resume);

static voidlate_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);

}


相关文章:

Android休眠唤醒驱动流程

Android休眠与唤醒 android是在传统的linux内核电源管理设计的基础上&#xff0c;结合手机设计的实际需求而进化出的一套电源管理系统&#xff0c;其核心内容有&#xff1a;wakelock、early_suspend与late_resume。 wakelock在Android的电源管理系统中扮演一个核心的角色。wa…...

QQ游戏外挂mouse_event不起作用的原因,及我的对对碰外挂程序.

/// 作者 : 阿东// 主页 : http://adong2008.512j.com// 邮箱 : dongfayeah.net// MSN : codelivehotmail.com// 日期 : 2005.02.06/ 关于QQ游戏外挂mouse_event不起作用的原因,及我的对对碰外挂程序. 相信很多朋友都使用过QQ游戏的外挂和自己也开发过.但可能你会发现,mouse…...

Linux中搭建Apache服务器

在Linux中搭建Apache服务器&#xff1a;修改默认目录&#xff1b;设置用户个人主页&#xff1b;设置默认主页&#xff1b;配置虚拟主机 首先安装该服务 yum -y install httpd 查看安装了多少个包&#xff0c;包括依赖包应该有三个 rpm -qa|grep http 设置防火墙&#xff0c;…...

基金投资入门与技巧——阅读笔记

基金投资 程国强 基金投资——间接地证券投资的理财方式 基金的种类 广义&#xff1a;基金是机构投资者的统称&#xff0c;包括信托投资基金、单位信托基金、公积金、保险基金、退休基金和各种基金会的基金。 狭义&#xff1a;基金指的是具有特定目的和用途的资金。 基金单…...

【每天学习一点新知识】sqlmap的使用

这个大佬的教程太详细了&#xff0c;这边选取了一部分理论的sqlmap详细使用教程_星落.的博客-CSDN博客_sqlmap sqlmap常用命令 -h 显示基本帮助信息 -hh 显示高级帮助信息 --version …...

如何发布一篇博客?(入门保姆级)

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要内容是如何发布一篇博客超级详细&#xff08;入门保姆级&#xff09; &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主收将持续更新学习记录获&#xff0c;友友们有任何…...

企业邮箱托管外包后安全吗?企业邮箱安全须知

目前各主流企业邮箱网易、TOM、腾讯等均采用SSL加密传输的方式&#xff0c;为企业提供高级别安全服务平台。但这只是在第一道入口加以防范&#xff0c;想更有效的保障企业安全&#xff0c;以下几点你需要知道~ 邮箱密码安全 企业邮箱管理员创建好邮箱后&#xff0c;一定要告知…...

一些IT段子,娱乐一下

1、我是个程序员&#xff0c;一天我坐在路边一边喝水一边苦苦检查bug。这时一个乞丐在我边上坐下了&#xff0c;开始要饭&#xff0c;我觉得可怜&#xff0c;就给了他1块钱&#xff0c;然后接着调试程序。他可能生意不好&#xff0c;就无聊的看看我在干什么&#xff0c;然后过了…...

Android游戏开发大全

查看书籍详细信息&#xff1a; Android游戏开发大全 编辑推荐 帮助读者掌握Android游戏项目的开发流程 和项目驱动的好书&#xff01; 内容简介 《Android游戏开发大全》以Android手机游戏的开发为主题&#xff0c;结合真实的游戏案例向读者详细介绍了Android平台下游戏开发…...

51nod-1437 迈克步(单调栈)

原题链接 1437 迈克步 题目来源&#xff1a; CodeForces 基准时间限制&#xff1a;1 秒 空间限制&#xff1a;131072 KB 分值: 80 难度&#xff1a;5级算法题 收藏 关注 有n只熊。他们站成一排队伍&#xff0c;从左到右依次1到n编号。第i只熊的高度是ai。 一组熊指的队伍中连…...

Dr.COM宽带认证客户端网络环境使用路由器上网

声明&#xff1a;本文所有内容均为兴趣研究&#xff0c;请勿作为商用用途&#xff01;如侵犯权利&#xff0c;联系qq1291936771删除&#xff01; 温馨提示&#xff1a;本文针对的是Dr.COM(x)版本客户端&#xff0c;其他版本客户端请勿尝试(其他版本能不能成功&#xff0c;我也…...

爱是一种遇见

终于明白爱情是一种遇见&#xff0c;不能制造也不能预期&#xff0c;一个人的时候&#xff0c;爱情会寂寞&#xff0c; 两个人的时候&#xff0c;爱情会麻烦。爱与不爱&#xff0c;与结局无关&#xff0c;今天就是永远。 爱情是一种遇见&#xff0c;朋友是一种遇见&#xff0c;…...

android 仿头条 微信大图预览动画 双击缩放 保存至相册

GalleryView 项目地址&#xff1a;cedear/GalleryView 简介&#xff1a; android 仿头条 微信大图预览动画 双击缩放 保存至相册 更多&#xff1a;作者 提 Bug 标签&#xff1a; 在我现在的项目当中&#xff0c;也存在大图预览的功能&#xff0c;但其实现过于繁重&…...

windows系统搭建FTP服务

1、安装FTP服务 ① 在Cortana中搜索“控制面板”打开 ② 在控制面板-程序中&#xff0c;点击“启用或关闭Windows”功能 ③ 找到“Internet Information Services勾选“FTP服务器、Web管理工具”等相关功能&#xff08;如下图所示&#xff09;&#xff0c;点击确定&#xff0c…...

windows XP中的IE6.0修复方法

如何在Win XP中重新安装或修复IE6.0 方法一&#xff1a;修复Internet Explorer 6.0 执行以下步骤修复Internet Explorer 6.0&#xff1a; 1. 使用系统文件检查工具扫描电脑上所有写保护的文件&#xff1a; a. 单击开始->运行&#xff1b;弹出运行文本框&#xff1b…...

【转帖】源的添加管理和Cydia使用教程

源的添加管理和Cydia使用教程 作者&#xff1a;莫溢 来源&#xff1a;apple.178.com 发布时间&#xff1a;2010-11-14 20:40:23 转帖&#xff1a;云在青天&#xff1a;对作者辛苦劳作深表感激&#xff0c;此处转帖仅作方便自己查阅用&#xff0c;如有冒犯作者权益请告知。 源是…...

windows下Npoint虚拟主机安装配置及心得

N点虚拟主机管理系统&#xff08;v1.96&#xff09;安装及设置说明 一、N点虚拟主机管理系统简介&#xff1a; N点虚拟主机管理系统是针对销售主机、邮局、数据库等产品时效率低、管理难等问题而自主研发的集产品自动化开通、管理、续费、升级等功能为一体的软件系统。通过一年…...

微博平台StatusNet研究(4):快速安装

StatusNet研究系列 StatusNet研究&#xff08;1&#xff09;&#xff1a;介绍 StatusNet研究&#xff08;2&#xff09;&#xff1a;基本安装 StatusNet研究&#xff08;3&#xff09;&#xff1a;友好URL与OpenID支持 StatusNet研究&#xff08;4&#xff09;&#xff1a;快速…...

DHCP配置的三种方式

1.DHCP中继 2.配置命令:sys int g0/0/0 ip add 192.168.2.2 24 dhcp enable ip pool pool1 net 192.168.1.0 mask 24 dns-list 8.8.8.8 gateway-list 192.168.1.1 dhcp select global //启用全局地址池 ip route-static 192.168.1.0 24 192.168.2.254 //配置一条去1.…...

盘点国内外十大免费CDN网站加速服务

盘点国内外十大免费CDN网站加速服务 核心提示&#xff1a; 除了传统的CDN厂商之外&#xff0c;云服务商也开始进驻该领域&#xff0c;市场上可供选择的免费的CDN还是比较多的&#xff0c;在此罗列出十大免费CDN&#xff0c;供寻找免费CDN加速服务的朋友参考一二。 CDN的全称是…...

万能DOS启动盘制作全攻略!(软盘+光盘+U盘+硬盘+NTFS+应急实用工具)

万能DOS启动盘制作全攻略&#xff01;&#xff08;软盘&#xff0b;光盘&#xff0b;U盘&#xff0b;硬盘&#xff0b;NTFS&#xff0b;应急实用工具&#xff09; 本文转自阿榕&#xff0c;文章版权归阿榕软件论坛管理员nnmm所有 首先说明一下各种操作系统启动到DOS的途径&…...

C语言从放弃到入门,C语言,从放弃到入门

零基础入门C语言&#xff0c;王桂林老师编写的。比较浅显、易懂&#xff0c;适合初学者以及复习。 课程收益&#xff1a; 所有对C语言有入门恐惧的人。 讲师介绍&#xff1a; 王桂林 能众软件&#xff0c;能众教育创始人&#xff0c;毕业于山东大学&#xff0c;曾工作于世界500…...

网站集成支付宝

本人是公司注册号支付宝 及时到帐服务&#xff0c;去下载支付宝提供的代码&#xff0c;其里面有如何在自己的页面中集成支付宝接口的例子。你自己先把这个例子在自己的机器上调试成功之后&#xff0c;在将这些代码引入到你的项目中去。&#xff08;下面是他人的一些做法&#x…...

​CRM系统如何选型?

不少企业都想要使用CRM客户管理系统&#xff0c;但往往在CRM选型阶段就被折腾的五迷三道。CRM系统选型难在哪里&#xff1f;下面我们从企业用户和CRM厂商两方面进行分析&#xff0c;来说说关于CRM系统选型的那些事。 企业自身原因&#xff1a; 1、认知偏差 看到一个观点&…...

Android HTTP客户端:实现HTTPS访问方式

随着移动应用程序的普及,安全性变得越来越重要。在Android开发中,使用HTTP协议进行网络通信是很常见的需求。然而,为了确保通信的安全性,在Android中使用HTTPS协议进行加密通信是必要的。本文将介绍如何在Android应用中使用HTTP客户端实现HTTPS访问。 Android提供了许多方…...

如果找活跃IP段!抓肉鸡必须的!

很多新手抓鸡的时候&#xff0c;为了IP段很头疼。有一种方法大家可以试试&#xff0c;我试验过还是可以的。首先选择一个你要扫的城市&#xff0c;比如&#xff1a;浙江打开百度&#xff0c;在里面输入浙江&#xff0c;然后出现一个某某网站&#xff0c;你把网站的地址复制一下…...

榜单!全年或超150万辆!行泊一体系统方案供应商TOP10出炉

作为域控集中架构下的产物&#xff0c;智能驾驶赛道的行泊一体方案正在成为市场的主流配置&#xff0c;同时&#xff0c;各类计算&#xff08;芯片&#xff09;方案也都在发力这个细分赛道。 高工智能汽车研究院认为&#xff0c;和NOA不同&#xff0c;作为高低速组合功能的行泊…...

QQ堂3.3外挂

QQ堂3.3可用外挂&#xff0c;功能如图、 下载地址&#xff1a;http://bbs.12tg.cn/read-htm-tid-145156.html...

超级玛丽全通关图文攻略

【第一关】 1-1:   呵呵&#xff01;这当然是上手关了。你在一开始就会遇到一只goomba&#xff0c;不要踩他。跳过去就可以了。还有三枚金币和一只红色蘑菇可吃&#xff0c;吃下它你的个子就会长高&#xff0c;并具备顶砖块的能力(平时不要乱顶&#xff0c;因为那是goomba的床…...

HDMI、DVI、VGA、RGB、分量、S端子)高清接口图片说明

各种视频输出端口(HDMI、DVI、VGA、RGB、分量、S端子)图片说明 1.S端子 标准S端子 标准S端子连接线 音频复合视频S端子色差常规连接示意图 S端子&#xff08;S-Video&#xff09;是应用最普遍的视频接口之一&#xff0c;是一种视频信号专用输出接口。常见的S端子是一个5芯接…...

MATLAB数字图像处理详细总结

前言 给一个算法如何写程序https://blog.csdn.net/baidu_38205880/article/details/80241655先在网上找一些参考再写 算法的一般步骤自顶向下&#xff0c;考虑输入输出&#xff0c;有点像信号中的响应的思想&#xff0c;还要考虑数据结构&#xff0c;特殊输入&#xff0c;增加…...

为什么数组下标越界要检查

如下定义一个数组&#xff1a; int[] ints new int[100];此时就会在堆中开辟一个对应的空间&#xff0c;ints也被分配了相应的内存空间。 这里从JVM的角度说下自己的理解&#xff0c;不一定是对的哈&#xff0c;比如现在只在堆中给ints分配了相对应它长度100的内存空间&…...

vbs整人代码大集合 多年的代码收集

vbs整人代码大集合&#xff0c;收集的比较全&#xff0c;喜欢的朋友可以参考下。不要搞破坏&#xff0c;学习vbs的朋友非常有帮助&#xff0c;死循环的使用比较多。 一、你打开好友的聊天对话框,然后记下在你QQ里好友的昵称,把下面代码里的xx替换一下,就可以自定义发送QQ信息到…...

javascript语言入门教程,javascript教程完整版

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;javascript语言入门教程&#xff0c;javascript教程完整版&#xff0c;今天让我们一起来看看吧&#xff01; 前言 教你学会JavaScript分成四大部分&#xff1a; 教你完全学会JavaScript&#xff08;基础篇--更新中&…...

七款开源ERP评估比较

前言 一、Compiere ERP 二、Openbravo ERP 三、Nseer ERP &#xff08;信恩&#xff09; 四、Open ERP 五、Web ERP 六、Sequoia ERP 七、Opentaps 前言 有一定规模的企业&#xff0c;会思考引入ERP系统用于改善自己的运营模式&#xff0c;但有许多企业在ERP实施与使用过…...

android8 保卫萝卜,保卫萝卜挑战8攻略图解,学会这些,轻松过第八关

要阻止怪物吃到萝卜&#xff0c;还是有一定的难度的。大家对游戏的操作水平不同&#xff0c;会卡在不同的关卡上&#xff0c;下面就让小编来给大家讲解保卫萝卜挑战8攻略图解。 首先第一个关键点就在于找准怪兽的一个入口。我们可以看到金币有五百多个&#xff0c;所以可以购买…...

恶搞中国足球大汇总

郭德纲在他的相声段子里曾经说&#xff0c;他准备练习踢足球&#xff0c;教练看了他之后非常惊讶&#xff0c;告诉他说&#xff1a;“你呀&#xff0c;有比马拉多纳还优秀的素质&#xff0c;但只有两样东西不行&#xff0c;一个是你的左腿&#xff0c;一个是你的右腿” “咋整&…...

。IBM ThinkPad T60P 全面评测

45000元都能买到些什么东西呢&#xff1f; 如果去吃麦当劳&#xff0c;50元吃一顿的话&#xff0c;每天每顿吃的话&#xff0c;差不多够吃1年&#xff1b; 如果拿来买球鞋&#xff0c;1000元1双的耐克鞋可以买45双&#xff0c;每天穿1双的话也足够一个半月不重复&#xff1b;…...

ubuntu实用工具

转至http://www.oschina.net/question/12_515 抛开Windows&#xff0c;其实在任何一款Linux发行版本中&#xff0c;我们都有超级大量的软件来安装&#xff0c;使用。这次的教程&#xff0c;我就以Ubuntu为例&#xff0c;来给大家推荐一些我认为不错的软件 声明&#xff1a; 1.本…...

赛效:在线查询QQ号价格评估的方法是什么

QQ是非常经典的聊天工具&#xff0c;在很多时候我们上班也要登着QQ号&#xff0c;除了可以交流、传递资料、 登录体平台外&#xff0c;有时候还要用QQ邮箱进行接收验证码之类的操作。QQ用久了&#xff0c;难免会有一个想法&#xff0c;就是我们的QQ号究竟值多少钱呢&#xff1f…...

查看文件的MD5值得方法 (校验完整性)

MAC查看MD5值 1.打开终端 2.输入 md5 filename 3.将需要校验的文件拖入终端窗口(这一步就是相当于输入文件的目录地址) 4.按下回车,等待返回结果(文件越大,校验越慢) WIN查看MD5值 certutil -hashfile filename MD5 certutil -hashfile filename SHA1 certutil -hash…...

盘点:恋爱一族约会英语词汇

美语中有些字眼在中文里面似乎找不到可代换的字, 其中感情方面的字好象就占了不少。也许有一天你会跟老外交往, 也或许你只是有兴趣认识这些字, 这个单元是恋爱一族不可不看的喔! 1. have a crush on 迷恋某人 A: Im having this huge crush on Ted. Im going to try and s…...

职业价值观测评(舒伯修订版)

职业价值观测评&#xff0c;简称为WVI&#xff0c;最早由心理学家舒伯在1970年提出&#xff0c;主要用于测评个体的职业价值观&#xff0c;表明个体对职业的认识、接受、追求和向往的目标。 职业价值观&#xff0c;是从业者的职业期望&#xff0c;职业价值观决定了员工在岗的工…...

JAVA 基于J2ME的手机游戏设计与开发(论文+源码)_Nueve

摘要&#xff1a;随着通信技术的发展和手机的普及&#xff0c;手机游戏的开发技术越来越为人们所关注。以J2ME为开发平台&#xff0c;利用Java提供强大工具&#xff0c;不但可以在手机上实现静态HTML技术所无法实现的计算处理、数据存储、与服务器的通信等功能&#xff0c;而且…...

抢先体验Windows Technical Preview(Windows 10)和Windows Server Technical Preview

抢先体验Windows Server Technical Preview Windows 10 是微软公司新一代操作系统&#xff0c;即传说中的Windows Technical&#xff0c;NT内核为6.4。该系统于2014年9月30日&#xff08;美国东部时间&#xff0c;北京时间2014年10月1日&#xff09;发布开技术预览版。北京时间…...

portraiture 4参数设置多少比较好,portraiture怎么批量磨皮

品牌型号&#xff1a;联想GeekPro 2020 系统&#xff1a;Windows 10 64位专业版 软件版本&#xff1a;portraiture v4 portraiture 4是一款智能化水平高的磨皮插件&#xff0c;可安装在photoshop、light room两款主流图像处理软件中使用。刚开始接触portraiture&#xff0c;…...

网线制作,集线器、交换机、路由器的介绍以及路由器的设置

目录 一. 网线制作 1.1 制作材料 1.2 网线标准 1.3 网线做法 二. 集线器、交换机、路由器介绍 前言 简介 简单来说 三. 路由器的设置 设置1 设置2 设置3 设置4 无线设置 一. 网线制作 1.1 制作材料 网线 …...

新华网论坛

新华网论坛 链接:http://forum.xinhuanet.com/index.html 来自 “ ITPUB博客 ” &#xff0c;链接&#xff1a;http://blog.itpub.net/5718/viewspace-167650/&#xff0c;如需转载&#xff0c;请注明出处&#xff0c;否则将追究法律责任。 转载于:http://blog.itpub.net/5718…...

C#下AxShockwaveFlash的成员函数

C#下AxShockwaveFlash的成员函数 1.AlignMode &#xff08;读写&#xff09; 语法&#xff1a;AlignMode As Long 说明&#xff1a;对齐方式&#xff08;与 SAlign 属性联动&#xff09;。当控件的 长宽比例与影片不一致且 WMode 不为 ExactFit 时&#xff0c;影片&#xff0…...

C++句柄类

假设有一个父类base&#xff0c;然后从base继承了多个子类base1&#xff0c;base2等等&#xff0c;C句柄类主要是用来管理多个子类&#xff0c;统一个的接口&#xff0c;不同的操作&#xff0e; 句柄类需要智能指针的基础知识和多态的知识&#xff0c;句柄类其实就是智能指针&…...