一:wakeup_source简介:linux 3.4内核PM使用了wakeup_source来保持唤醒状态,也就是keep awake。之前android一直是基于Linux加入了wake_lock机制来阻止系统休眠,后来Linux 3.4内核加入了wakeup_source来管理,安卓4.4跟着升级内核也就摒弃了自己的臃肿的wake_lock机制,在对上层接口并不改变,在内核wake_lock实现直接基于wakeup_source来实现的。当然也会带来debug上的一些问题,比如以前的wake_lock自身带有强大的debug信息,那么我们在调试的时候可以自己看见dmesg中默认打印active wake lock XXX,很直观来辨别需要休眠的时候那个wake lock有问题阻止了休眠。这个需要我们自己来完善。个人认为改进很大,现在使用了autosleep机制,只要不存在任何active wakeup_source了,系统自动休眠,当有active wake_source自动block住,个人认为休眠更及时,非休眠时间在减少,同时不会消耗额外的资源。使用基于queue work与进程block来管理suspend。还有这里的wakeup_source个人觉得应该叫keepawake_source或者stayawake_souce,毕竟系统的唤醒也就是cpu的再次运行是由中断唤醒的而不是wakeup_source。同时安卓4.4还有一个重大改变就是去除了early suspend机制改为fb event通知机制。那么现在就只有suspend与resume,runtime suspend与runtime resume了。/*** struct wakeup_source - Representation of wakeup sources** @total_time: Total time this wakeup source has been active.* @max_time: Maximum time this wakeup source has been continuously active.* @last_time: Monotonic clock when the wakeup source's was touched last time.* @prevent_sleep_time: Total time this source has been preventing autosleep.* @event_count: Number of signaled wakeup events.* @active_count: Number of times the wakeup sorce was activated.* @relax_count: Number of times the wakeup sorce was deactivated.* @expire_count: Number of times the wakeup source's timeout has expired.* @wakeup_count: Number of times the wakeup source might abort suspend.* @active: Status of the wakeup source.* @has_timeout: The wakeup source has been activated with a timeout.*/struct wakeup_source {const char         *name;struct list_head    entry;struct list_head    list;spinlock_t        lock;struct timer_list    timer;unsigned long        timer_expires; //超时时间,也就是wake_lock_timeout()里面的时间参数,超时后会执行deactivate函数ktime_t total_time;ktime_t max_time;ktime_t last_time;ktime_t start_prevent_time;ktime_t prevent_sleep_time;unsigned long        event_count; //event计数unsigned long        active_count;//active计数unsigned long        relax_count;unsigned long        expire_count;unsigned long        wakeup_count;bool            active:1; //用于判断是否是active状态bool            autosleep_enabled:1;//这个变量是来标记active等时间的};//active任何wakeup_source都会执行该函数,标记active为true/*** wakup_source_activate - Mark given wakeup source as active.* @ws: Wakeup source to handle.** Update the @ws' statistics and, if @ws has just been activated, notify the PM* core of the event by incrementing the counter of of wakeup events being* processed.*/static void wakeup_source_activate(struct wakeup_source *ws){unsigned int cec;ws->active = true;ws->active_count++;ws->last_time = ktime_get();if (ws->autosleep_enabled)ws->start_prevent_time = ws->last_time;/* Increment the counter of events in progress. */cec = atomic_inc_return(&combined_event_count);trace_wakeup_source_activate(ws->name, cec);}//deactivate任何wakeup_source都会执行该函数,标记active为false/*** wakup_source_deactivate - Mark given wakeup source as inactive.* @ws: Wakeup source to handle.** Update the @ws' statistics and notify the PM core that the wakeup source has* become inactive by decrementing the counter of wakeup events being processed* and incrementing the counter of registered wakeup events.*/static void wakeup_source_deactivate(struct wakeup_source *ws){unsigned int cnt, inpr, cec;ktime_t duration;ktime_t now;ws->relax_count++;/** __pm_relax() may be called directly or from a timer function.* If it is called directly right after the timer function has been* started, but before the timer function calls __pm_relax(), it is* possible that __pm_stay_awake() will be called in the meantime and* will set ws->active.  Then, ws->active may be cleared immediately* by the __pm_relax() called from the timer function, but in such a* case ws->relax_count will be different from ws->active_count.*/if (ws->relax_count != ws->active_count) {ws->relax_count--;return;}ws->active = false;now = ktime_get();duration = ktime_sub(now, ws->last_time);ws->total_time = ktime_add(ws->total_time, duration);if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time))ws->max_time = duration;ws->last_time = now;del_timer(&ws->timer);ws->timer_expires = 0;if (ws->autosleep_enabled)update_prevent_sleep_time(ws, now);/** Increment the counter of registered wakeup events and decrement the* couter of wakeup events in progress simultaneously.*/cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);trace_wakeup_source_deactivate(ws->name, cec);split_counters(&cnt, &inpr);if (!inpr && waitqueue_active(&wakeup_count_wait_queue)){wake_up(&wakeup_count_wait_queue); //当不存在任何active wake_up source的时候唤醒try_to_suspend进程。}}wakup_source的申请与释放:1:使用安卓的wake_lock接口:wake_lock(),wake_lock_timeout(),wake_unlock();2: 使用wakeup_source自带的接口:pm_stay_awake(),pm_relax();这里的name就是device name。二:autosleep分析:

sys接口:sys/power/autosleep

亮屏时libsuspend会写入off,灭屏写入memstatic ssize_t autosleep_show(struct kobject *kobj,struct kobj_attribute *attr,char *buf){suspend_state_t state = pm_autosleep_state();if (state == PM_SUSPEND_ON)return sprintf(buf, "off\n");#ifdef CONFIG_SUSPENDif (state < PM_SUSPEND_MAX)return sprintf(buf, "%s\n", valid_state(state) ?pm_states[state] : "error");#endif#ifdef CONFIG_HIBERNATIONreturn sprintf(buf, "disk\n");#elsereturn sprintf(buf, "error");#endif}static ssize_t autosleep_store(struct kobject *kobj,struct kobj_attribute *attr,const char *buf, size_t n){suspend_state_t state = decode_state(buf, n);int error;if (state == PM_SUSPEND_ON&& strcmp(buf, "off") && strcmp(buf, "off\n"))return -EINVAL;error = pm_autosleep_set_state(state);return error ? error : n;}power_attr(autosleep);int pm_autosleep_set_state(suspend_state_t state){#ifndef CONFIG_HIBERNATIONif (state >= PM_SUSPEND_MAX)return -EINVAL;#endif__pm_stay_awake(autosleep_ws); //防止系统休眠mutex_lock(&autosleep_lock);autosleep_state = state;__pm_relax(autosleep_ws); //释放上面的wake up sourceif (state > PM_SUSPEND_ON) {pm_wakep_autosleep_enabled(true); //设置所有wake up source里面的autosleep_enabled为真,这个变量不会对休眠有影响,但是会标记active的时间,使用debugfs可以看见queue_up_suspend_work();//调度工作队列,会执行try_to_suspend(),其实state mem执行try_to_suspend(),一次就可以了,后面再分析。} else {pm_wakep_autosleep_enabled(false);//设置所有wake up source里面的autosleep_enabled为假}mutex_unlock(&autosleep_lock);return 0;}重头戏:try_to_suspendstatic void try_to_suspend(struct work_struct *work){unsigned int initial_count, final_count;if (!pm_get_wakeup_count(&initial_count, true)) //获取initial_count,这个函数会block住,当存在active wakeup source的时候,直到wakeup source为detative状态goto out;mutex_lock(&autosleep_lock);if (!pm_save_wakeup_count(initial_count)) {//保存initial_count,不会block,当然也会检查是否有active wakeup source,当有active存在再次queue work。mutex_unlock(&autosleep_lock);goto out;}if (autosleep_state == PM_SUSPEND_ON) {//当为ON状态时,return。//在睡眠期间跟了很久没有遇见过这种情况mutex_unlock(&autosleep_lock);return;}if (autosleep_state >= PM_SUSPEND_MAX)hibernate();                  //hibernate高通平台目前不支持elsepm_suspend(autosleep_state); //进入pm_suspend,dmesg会有PM: suspend entry 与PM: suspend exit来标记,这里面会执行freeze task,suspend与resume,disable cpu的操作。内核PM最重要的函数。mutex_unlock(&autosleep_lock);if (!pm_get_wakeup_count(&final_count, false))//获取final_count,非block,当然也会检查是否有active wakeup source,当有active存在再次queue workgoto out;/** If the wakeup occured for an unknown reason, wait to prevent the* system from trying to suspend and waking up in a tight loop.*/if (final_count == initial_count)             //这里遇见未知原因,initial_count与final_count相等,超时500ms后继续往下执行。这种现象我也是跟了许久没有遇见过。schedule_timeout_uninterruptible(HZ / 2);out:queue_up_suspend_work(); //调度queue work会再次执行该函数,实际上只要一次echo mem > sys/power/autosleep后这个进程一直会在auto_sleep cycle。}pm_get_wakeup_count原型,个人感觉这个是仿__wait_event_interruptible()而写的。bool pm_get_wakeup_count(unsigned int *count, bool block){unsigned int cnt, inpr;if (block) { //当block为真时,该进程可能会block住DEFINE_WAIT(wait);for (;;) {prepare_to_wait(&wakeup_count_wait_queue, &wait,TASK_INTERRUPTIBLE);split_counters(&cnt, &inpr); //有active的wakeup_source存在就是block住,否则blockif (inpr == 0 || signal_pending(current))break;schedule();//在这里面block住,直到最后一个active的wakeup_source deactivate时会唤醒该进程,之后会break出来。}finish_wait(&wakeup_count_wait_queue, &wait);}split_counters(&cnt, &inpr);*count = cnt;return !inpr;}那么有2个问题,按power键唤醒系统是退出try_to_suspend了吗?首先系统是如何被唤醒的?这个是由硬件中断唤醒的,比如我们这里的power键,还有其他的比如alarm等其他硬件中断,只要我们在中断申请时enbale_irq_wake(),那么睡眠期间,只要触发该中断就可以唤醒系统。那么在try_to_suspend里面唤醒后pm_suspend()执行完resume后会退出来,接着会获取final_count,在这里是存在activewake_up source的(在out之前加添的打印active wakeup_source,下面的dmesg证明了存在的wakeup_source),之后执行out,后调度工作队列,再次进入try_to_suspend(),在第一次获取initial_count后便会遇见active wakeup_source,这里面更多的是系统上层加的wakeup_source,那么try_to_suspend()会一直block在pm_get_wakeup_count()里面直到灭屏和所有wakeup_source deactivate时会再次进入pm_suspend()休眠。这里也就解释了为什么只要执行一次ehco mem > sys/power/autosleep后自动可以休眠了的原因。看下面的dmesg:<6>[  928.536418] CPU0: msm_cpu_pm_enter_sleep mode 000000,00000000,00000000,00000000,00000000,00000020,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000<6>[  928.543066] PM: noirq resume of devices complete after 6.020 msecs<6>[  928.548512] PM: early resume of devices complete after 2.598 msecs<6>[  928.650793] PM: resume of devices complete after 102.266 msecs<6>[  928.660290] Restarting tasks ... done.<6>[  928.681208] PM: suspend exit 1970-01-05 05:23:32.206389881 UTC<6>[  928.681229] active wake lock KeyEvents<6>[  928.681267] active wake lock qpnp_soc_wake<6>[  928.681284] active wake lock alarm, time left 486<6>[  928.681342] active wake lock KeyEvents<6>[  928.681584] active wake lock qpnp_soc_wake<6>[  928.681600] active wake lock alarm, time left 486<6>[  928.696345] request_suspend_state: wakeup at 928691792356 (1970-01-05 05:23:32.221521704 UTC)<6>[  928.708608] mdss_dsi_panel_power on=1还有一个与wakeup source无关的问题,为什么suspend后就一直停留在那里不动了?这个是cpu停止运转了,下面再分析下代码。核心函数suspend_devices_and_enter():/*** suspend_devices_and_enter - Suspend devices and enter system sleep state.* @state: System sleep state to enter.*/int suspend_devices_and_enter(suspend_state_t state){int error;bool wakeup = false;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);//这里会执行所有driver的suspend函数,suspend里面有active wakeup_source或者return 为真的话,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;do {error = suspend_enter(state, &wakeup);//这里会diable cpu} while (!error && !wakeup&& suspend_ops->suspend_again && suspend_ops->suspend_again());Resume_devices:suspend_test_start();dpm_resume_end(PMSG_RESUME);suspend_test_finish("resume devices");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;}static int suspend_enter(suspend_state_t state, bool *wakeup){int error;if (suspend_ops->prepare) {error = suspend_ops->prepare();if (error)goto Platform_finish;}error = dpm_suspend_end(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();  //disable nonboot cpu注意还有cpu需要下面disable的if (error || suspend_test(TEST_CPUS))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)) {error = suspend_ops->enter(state); //在这里cpu会停止运行,直到中断唤醒//下面的全部是唤醒的操作了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_start(PMSG_RESUME);Platform_finish:if (suspend_ops->finish)suspend_ops->finish();return error;}平台赋值,这里debug平台是基于msm8974的:static const struct platform_suspend_ops lpm_suspend_ops = {.enter = lpm_suspend_enter,//在这里面disbale cpu,停止运行程序.valid = suspend_valid_only_mem,.prepare_late = lpm_suspend_prepare,.wake = lpm_suspend_wake,};添加显示wake_lock的dmesg:<6>[   90.964850] CPU0: msm_cpu_pm_enter_sleep mode 000000,00000000,00000000,00000000,00000000,00000020,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000<6>[   90.965302] Enabling non-boot CPUs ...<6>[   90.968303] CPU3 is up<6>[   90.970699] PM: noirq resume of devices complete after 2.382 msecs<6>[   90.973460] PM: early resume of devices complete after 1.443 msecs<6>[   91.097369] PM: resume of devices complete after 123.888 msecs<6>[   91.112858] Restarting tasks ... done.<6>[   91.141699] PM: suspend exit 1970-01-05 03:59:28.158266979 UTC<6>[   91.141776] PM: suspend entry 1970-01-05 03:59:28.158347917 UTC<6>[   91.141801] PM: Syncing filesystems ... done.<6>[   91.222299] Freezing user space processes ... (elapsed 0.03 seconds) done.<6>[   91.258126] Freezing remaining freezable tasks ... (elapsed 0.02 seconds) done.<6>[   91.278279] Suspending console(s) (use no_console_suspend to debug)<6>[   91.384933] PM: suspend of devices complete after 95.062 msecs<6>[   91.397910] PM: late suspend of devices complete after 12.934 msecs<6>[   91.413019] PM: noirq suspend of devices complete after 15.064 msecs<6>[   91.413059] Disabling non-boot CPUs ...<6>[   91.424477] CPU0: msm_cpu_pm_enter_sleep mode 000000,00000000,00000000,00000000,00000000,00000020,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000//这里cpu停止运行直到中断唤醒<6>[   91.425144] Enabling non-boot CPUs ...<6>[   91.432354] CPU3 is up<6>[   91.444416] PM: noirq resume of devices complete after 11.997 msecs<6>[   91.460948] PM: early resume of devices complete after 10.458 msecs<6>[   91.577213] PM: resume of devices complete after 116.231 msecs<6>[   91.584876] Restarting tasks ... done.<6>[   91.614571] PM: suspend exit 1970-01-05 03:59:35.550203849 UTC<6>[   91.614639] active wake lock rmt_storage_-1220268312<6>[   91.689912] PM: suspend entry 1970-01-05 03:59:35.625550620 UTC<6>[   91.689921] PM: Syncing filesystems ... done.<6>[   91.700706] Freezing user space processes ...<3>[   91.712870] Freezing of user space  aborted<6>[   91.712898]<6>[   91.712903] Restarting tasks ... done.<6>[   91.720540] PM: suspend exit 1970-01-05 03:59:35.656175256 UTC<6>[   91.720574] PM: suspend entry 1970-01-05 03:59:35.656214579 UTC<6>[   91.720586] PM: Syncing filesystems ... done.<6>[   91.801097] Freezing user space processes ...<3>[   91.815050] Freezing of user space  aborted<6>[   91.815075]<6>[   91.815083] Restarting tasks ... done.<6>[   91.823603] PM: suspend exit 1970-01-05 03:59:35.759241558 UTC<6>[   91.823633] PM: suspend entry 1970-01-05 03:59:35.759272964 UTC<6>[   91.823643] PM: Syncing filesystems ... done.<6>[   91.911985] Freezing user space processes ... (elapsed 0.03 seconds) done.<6>[   91.949378] Freezing remaining freezable tasks ... (elapsed 0.01 seconds) done.<6>[   91.969089] Suspending console(s) (use no_console_suspend to debug)<6>[   92.085594] PM: suspend of devices complete after 98.499 msecs<6>[   92.098615] PM: late suspend of devices complete after 12.975 msecs<6>[   92.113909] PM: noirq suspend of devices complete after 15.249 msecs<6>[   92.113951] Disabling non-boot CPUs ...<6>[   92.147320] CPU0: msm_cpu_pm_enter_sleep mode 000000,00000000,00000000,00000000,00000000,00000020,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000<6>[   92.148265] Enabling non-boot CPUs ...<6>[   92.155121] CPU1 is up<6>[   92.163797] CPU2 is up<6>[   92.174144] CPU3 is up<6>[   92.186556] PM: noirq resume of devices complete after 12.344 msecs<6>[   92.199945] PM: early resume of devices complete after 10.020 msecs<6>[   92.317528] PM: resume of devices complete after 117.548 msecs<6>[   92.326254] Restarting tasks ... done.<6>[   92.342025] PM: suspend exit 1970-01-05 04:00:48.122436614 UTC<6>[   92.342337] active wake lock qpnp-vadc-f611ac00

<6>[   92.342357] active wake lock alarm, time left 481<6>[   92.411428] PM: suspend entry 1970-01-05 04:00:48.191838905 UTC<6>[   92.411451] PM: Syncing filesystems ... done.<6>[   92.419086] Freezing user space processes ...<3>[   92.431173] Freezing of user space  aborted<6>[   92.431213]<6>[   92.431225] Restarting tasks ... done.<6>[   92.441575] PM: suspend exit 1970-01-05 04:00:48.221987864 UTC<6>[   92.441709] active wake lock qpnp-vadc-f611ac00<6>[   92.445004] PM: suspend entry 1970-01-05 04:00:48.225417551 UTC<6>[   92.445026] PM: Syncing filesystems ... done.<6>[   92.525764] Freezing user space processes ...<3>[   92.541465] Freezing of user space  aborted<6>[   92.541523]<6>[   92.541545] Restarting tasks ... done.<6>[   92.558768] PM: suspend exit 1970-01-05 04:00:48.339166145 UTC<6>[   92.558865] PM: suspend entry 1970-01-05 04:00:48.339268645 UTC<6>[   92.558898] PM: Syncing filesystems ... done.<6>[   92.656122] Freezing user space processes ... (elapsed 0.04 seconds) done.<6>[   92.699091] Freezing remaining freezable tasks ... (elapsed 0.01 seconds) done.<6>[   92.718891] Suspending console(s) (use no_console_suspend to debug)<6>[   92.832084] PM: suspend of devices complete after 97.905 msecs<6>[   92.845099] PM: late suspend of devices complete after 12.971 msecs<6>[   92.860407] PM: noirq suspend of devices complete after 15.264 msecs<6>[   92.860447] Disabling non-boot CPUs ...<6>[   92.895114] CPU0: msm_cpu_pm_enter_sleep mode 000000,00000000,00000000,00000000,00000000,00000020,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000

linux wakeup,linux内核3.4基于wakeup相关推荐

  1. linux内核3.4基于wakeup_source的autosleep机制分析

    点击打开链接 一:wakeup_source简介: linux 3.4内核PM使用了wakeup_source来保持唤醒状态,也就是keep awake.之前android一直是基于Linux加入了w ...

  2. Wakeup linux system from sleep mode

    Wakeup linux system from sleep mode v0.01 20190831 Init 1. wakeup souces Linux System进入sleep状态之后,需要有 ...

  3. grub2引导linux内核,一种基于grub2的linux系统启动bootloader的制作方法与流程

    技术领域 本发明涉及服务器应用技术领域,具体涉及一种基于grub2的linux系统启动bootloader的制作方法. 背景技术: 当前linux系统的内核版本已经升级至4.0以上,最新的linux系 ...

  4. Debian 10发布:基于Linux 4.19内核,包含5.9万软件包

    边策 发自 凹非寺  量子位 报道 | 公众号 QbitAI 在经历25个月的等待后,Linux最流行的发行版之一Debian终于迎来最新版本Debian 10 "buster", ...

  5. 国外linux内核视频播放器,基于Video for Linux内核的USB摄像头视频信号采集实现

    摘要:Video for Linux是Linux中关于视频设备的内核驱动,本文介绍了在Video for Linux内 >> 基于ARM9和USB摄像头的网络视频采集系统设计 基于嵌入式V ...

  6. 《Linux内核完全剖析-基于0.12内核》书评之陈莉君

    <Linux内核完全剖析-基于0.12内核>书评之陈莉君 <Linux内核完全剖析-基于0.12内核>一书出版之后,机械工业出版社编辑希望我就此书抽空写一个书评.在我拿到这本书 ...

  7. 基于S3C2410平台移植Linux 2.6内核指南

    安装交叉编译工具 Ø        下载交叉编译工具 (本文默认所有软件均下载在用户主目录下) arm-linux-gcc-3.4.1 -- 编译内核 URL: ftp://ftp.handhelds ...

  8. Linux内核源码分析:基于最新的Linux 4.0内核(学习路线总结)

    今天给大家分享的是基于最新的Linux 4.0内核学习路线总结,本文由8个专题组成,文末附上学习路线思维导图. 一.进程管理专题 1.进程原理 1.1 进程生命周期 1.2 task_struct结构 ...

  9. 《Linux设备驱动开发详解(第3版)》(即《Linux设备驱动开发详解:基于最新的Linux 4.0内核》)网购链接

    <Linux设备驱动开发详解:基于最新的Linux 4.0内核> china-pub   天猫     dangdang   京东 China-pub 8月新书销售榜 推荐序一 技术日新月 ...

  10. linux opensuse,又一linux发行版发布:openSUSE 15基于Linux 4.12内核正式发布

    导读 openSUSE 项目组于今日放出了 openSUSE Leap 15,其基于即将到来的 SUSE Enterprise Linux 15 系列,包含了专为重度用户而设计的更新组件和技术. SU ...

最新文章

  1. Ubuntu下安装Python3.6并在终端输入Python就能显示Python3.6
  2. Tag recommendaion... 论文中的小例子,使用HOSVD算法推荐
  3. java如何遍历combobox_如何通过COMBOBOX设置Java中的框架标题?
  4. 什么是标签?跟数据中台有什么关系?终于有人讲明白了
  5. linux-安装树形查看工具-tree
  6. 骁龙870对比天玑1200,到底谁更优秀?
  7. Android Studio一直显示Building“project name”Gradle project info问题详解
  8. QT之实现斗鱼直播PC客户端
  9. lcms质谱仪_常用质谱的优缺点_液质联用(LCMS)仪器社区_仪器信息网论坛
  10. 云计算入门:10个基本知识
  11. 各种格式的文件用什么软件打开
  12. gta5结局杀老崔我哭了_GTA5:每个主角都在故事里得到了自己想要的东西,但崔佛却是白给...
  13. Java——Shape类
  14. 分销代理商管理系统有哪些功能?
  15. input,checkbox启用禁用
  16. CPU的功能和基本结构
  17. c语言计算时钟的夹角不用if,C语言学习笔记——计算时钟的夹角
  18. CTF-攻防世界web新手入门篇
  19. 这篇文章告诉你音频降噪手机软件app有哪些?
  20. Python开发一个APP居然如此之简单?老王自学三月就独立开发了!

热门文章

  1. lisp 焊缝标注_焊接符号标注 图纸
  2. 自然语言处理之词移距离Word Mover's Distance
  3. Blend 混合模式
  4. 圣诞礼物|2020年送这些礼物你就能拥有一个程序员男朋友
  5. xctf crazy
  6. GaRy-Liang的linux成长日记2-系统安装
  7. 网站单页面优化的6个技巧
  8. iOS打开应用提示未受信任的企业级开发者
  9. 异常,Map,File
  10. 机器学习实战之信用卡诈骗(三)