




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、Android的休眠唤醒主要基于wake_lock机制,只要系统中存在任一有效的wake_lock,系统就不能进入深度休眠,但可以进行设备的浅度休眠操作。wake_lock一般在关闭lcd、tp但系统仍然需要正常运行的情况下使用,比如听歌、传输很大的文件等。本文主要分析driver层wake_lock的实现。一、wake_lock 定义和接口cpp view plaincopy1. enum 2. WAKE_LOCK_SUSPEND, / 阻止进入深度休眠模式
2、3. WAKE_LOCK_IDLE, / 阻止进入空闲模式 4. WAKE_LOCK_TYPE_COUNT 5. ; 6. 7. struct wake_lock 8. #ifdef CONFIG_HAS_WAKELOCK 9. st
3、ruct list_head link; / 链表节点 10. int flags; / 标志 11.
4、; const char *name; / 名称 12. unsigned long expires; / 超时时间 13. #ifdef CONFIG_WAKELOC
5、K_STAT 14. struct 15. int count; / 使用计数 16.
6、0; int expire_count; / 超时计数 17. int
7、wakeup_count; / 唤醒计数 18. ktime_t total_time; / 锁使用时间 19. ktime_t
8、60; prevent_suspend_time; / 锁阻止休眠的时间 20. ktime_t max_time; / 锁使用时间最长的一次 21.
9、0; ktime_t last_time; / 锁上次操作时间 22. stat; 23. #endif 24. #endif 25. ; 可以看到wake_lock按功能分为休眠锁和空闲锁两种类型
10、,用于阻止系统进入深度休眠模式或者空闲模式。wake_lock的主要部件有锁名称、链表节点、标志位、超时时间,另外还有一个内嵌的结构用于统计锁的使用信息。接下来我们看看wake_lock对外提供的操作接口:1、内核空间接口cpp view plaincopy1. void wake_lock_init(struct wake_lock *lock, int type, const char *name); 2. void wake_lock_destroy(struct
11、160;wake_lock *lock); 3. void wake_lock(struct wake_lock *lock); 4. void wake_lock_timeout(struct wake_lock *lock, long timeout); 5. void wake_unlock(struct wake_lock *lock); 其中wake_lock_init()用于初
12、始化一个新锁,type参数指定了锁的类型;wake_lock_destroy()则注销一个锁;wake_lock()和wake_lock_timeout()用于将初始化完成的锁激活,使之成为有效的永久锁或者超时锁;wake_unlock()用于解锁使之成为无效锁。另外还有两个接口:cpp view plaincopy1. int wake_lock_active(struct wake_lock *lock); 2. long has_wake_lock(int type); 其中wake_
13、lock_active()用于判断锁当前是否有效,如果有效则返回非0值;has_wake_lock()用于判断系统中是否还存在有效的type型锁,如果存在超时锁则返回最长的一个锁的超时时间,如果存在永久锁则返回-1,如果系统中不存在有效锁则返回0。2、用户空间接口wake_lock向用户空间提供了两个文件节点用于申请锁和解锁:cpp view plaincopy1. / wack_lock文件的读函数,显示用户空间定义的有效锁 2. ssize_t wake_lock_show( 3.
14、; struct kobject *kobj, struct kobj_attribute *attr, char *buf) 4. 5. char *s = buf; 6. char *end = buf + PAGE_SIZE; 7. &
15、#160; struct rb_node *n; 8. struct user_wake_lock *l; 9. 10. mutex_lock(&tree_lock); 11. 12. for (n = rb_first(&user_
16、wake_locks); n != NULL; n = rb_next(n) 13. l = rb_entry(n, struct user_wake_lock, node); 14. if (wake_lock_active(&a
17、mp;l->wake_lock) 15. s += scnprintf(s, end - s, "%s ", l->name); 16. 17. s += sc
18、nprintf(s, end - s, "n"); 18. 19. mutex_unlock(&tree_lock); 20. return (s - buf); 21. 22. 23. / wack_lock文件的写函数,初始化并激活用户空间定义的锁
19、60; 24. ssize_t wake_lock_store( 25. struct kobject *kobj, struct kobj_attribute *attr, 26. const char *buf, size_t n) 27. 28. l
20、ong timeout; 29. struct user_wake_lock *l; 30. 31. mutex_lock(&tree_lock); 32. l = lookup_wake_lock_name(buf, 1, &timeout); 33. &
21、#160; if (IS_ERR(l) 34. n = PTR_ERR(l); 35. goto bad_name; 36. 37. 38.
22、60; if (debug_mask & DEBUG_ACCESS) 39. pr_info("wake_lock_store: %s, timeout %ldn", l->name, timeout); 40. 41. if (timeou
23、t) 42. wake_lock_timeout(&l->wake_lock, timeout); 43. else 44. wake_lock(&l->wake_lock); 45. bad_name: 46.
24、 mutex_unlock(&tree_lock); 47. return n; 48. 49. 50. / wack_unlock文件的读函数,显示用户空间的无效锁 51. ssize_t wake_unlock_show( 52. struct kobject
25、0;*kobj, struct kobj_attribute *attr, char *buf) 53. 54. char *s = buf; 55. char *end = buf + PAGE_SIZE; 56. struct
26、60;rb_node *n; 57. struct user_wake_lock *l; 58. 59. mutex_lock(&tree_lock); 60. 61. for (n = rb_first(&user_wake_locks); n
27、;!= NULL; n = rb_next(n) 62. l = rb_entry(n, struct user_wake_lock, node); 63. if (!wake_lock_active(&l->wake_lock)
28、0; 64. s += scnprintf(s, end - s, "%s ", l->name); 65. 66. s += scnprintf(s, end
29、0;- s, "n"); 67. 68. mutex_unlock(&tree_lock); 69. return (s - buf); 70. 71. 72. / wack_unlock文件的写函数,用于用户空间解锁 73. ssize_t w
30、ake_unlock_store( 74. struct kobject *kobj, struct kobj_attribute *attr, 75. const char *buf, size_t n) 76. 77. struct user_wake_lock
31、 *l; 78. 79. mutex_lock(&tree_lock); 80. l = lookup_wake_lock_name(buf, 0, NULL); 81. if (IS_ERR(l) 82.
32、0; n = PTR_ERR(l); 83. goto not_found; 84. 85. 86. if (debug_mask & DEBUG_ACCESS) 87.
33、0; pr_info("wake_unlock_store: %sn", l->name); 88. 89. wake_unlock(&l->wake_lock); 90. not_found: 91. mutex_unlock(&tree_lock);
34、;92. return n; 93. 94. 95. power_attr(wake_lock); 96. power_attr(wake_unlock); 这两个文件节点分别为"/sys/power/wake_lock"和"/sys/power/wake_unlock",应用程序可以根据HAL层的接口读写这两个节点。二、wake_lock 实现在linux/kernel/power/
35、wakelock.c中我们可以看到wake_lock的实现代码,首先看看其定义的一些初始化信息:cpp view plaincopy1. #define WAKE_LOCK_TYPE_MASK (0x0f) / 锁类型标志掩码 2. #define WAKE_LOCK_INITIALIZED
36、; (1U << 8) / 锁已经初始化标志 3. #define WAKE_LOCK_ACTIVE (1U << 9) /
37、60;锁有效标志 4. #define WAKE_LOCK_AUTO_EXPIRE (1U << 10) / 超时锁标志 5. #define WAKE_LOCK_PREVENTING_SUSPEND (1U << 11) / 正在
38、阻止休眠标志 6. 7. static DEFINE_SPINLOCK(list_lock); / 读写锁链表的自旋锁 8. static LIST_HEAD(inactive_locks); / 内核维护的无效锁链表 9. static struct list_head active_wake_locksWAKE_LOCK_TYPE_COUNT; /
39、0;有效锁链表 10. static int current_event_num; / 休眠锁使用计数器 11. struct workqueue_struct *suspend_work_queue; / 执行系统休眠的工作队列 12. struct workqueue_struct *sys_sync_work_queue; /
40、60;执行系统同步的工作队列 13. struct wake_lock main_wake_lock; / 内核休眠锁 14. struct wake_lock sys_sync_wake_lock; / 缓存同
41、步锁 15. suspend_state_t requested_suspend_state = PM_SUSPEND_MEM; / 系统休眠状态 16. static struct wake_lock unknown_wakeup; / 未知锁 在后面的分析中我们会看到这些变量的具体用途。1、wake_lock系统初始化cpp view
42、 plaincopy1. static int _init wakelocks_init(void) 2. 3. int ret; 4. int i; 5. / 初始化有效锁链表,内核维护了2个有效锁链表 6. / WAKE_
43、LOCK_SUSPEND 用于阻止进入深度休眠模式 7. / WAKE_LOCK_IDLE 用于阻止进入空闲模式 8. for (i = 0; i < ARRAY_SIZE(active_wake_locks); i+) 9.
44、0; INIT_LIST_HEAD(&active_wake_locksi); 10. 11. #ifdef CONFIG_WAKELOCK_STAT 12. / 初始化deleted_wake_locks 13. wake_lock_init(&deleted_wake_locks, WAKE_LOCK_SUSPEND,
45、;14. "deleted_wake_locks"); 15. #endif 16. / 初始化内核休眠锁 17. wake_lock_init(&main_wake_lock, WAKE_LOCK_SUSPEND, "mai
46、n"); 18. / 初始化同步锁 19. wake_lock_init(&sys_sync_wake_lock, WAKE_LOCK_SUSPEND, "sys_sync"); 20. / 激活内核休眠锁 21. wake_lock(&am
47、p;main_wake_lock); 22. / 初始化未知锁 23. wake_lock_init(&unknown_wakeup, WAKE_LOCK_SUSPEND, "unknown_wakeups"); 24. 25. / 注册power_device,power_driver
48、; 26. ret = platform_device_register(&power_device); 27. if (ret) 28. pr_err("wakelocks_init: platform_device_register failedn");
49、60; 29. goto err_platform_device_register; 30. 31. ret = platform_driver_register(&power_driver); 32. if (ret) &
50、#160; 33. pr_err("wakelocks_init: platform_driver_register failedn"); 34. goto err_platform_driver_register; 35. 36.
51、 / 创建fs_sync内核进程 37. sys_sync_work_queue = create_singlethread_workqueue("fs_sync"); 38. if (sys_sync_work_queue = NULL) 39.
52、 pr_err ("fs_sync workqueue create failed.n"); 40. 41. / 创建suspend内核进程 42. suspend_work_queue = create_singlethread_workqueue(
53、"suspend"); 43. if (suspend_work_queue = NULL) 44. ret = -ENOMEM; 45. goto err_suspend_work_queue;
54、60; 46. 47. 48. #ifdef CONFIG_WAKELOCK_STAT 49. / 在proc下创建wakelocks文件 50. proc_create("wakelocks", S_IRUGO, NULL, &wakelock_stats_fops);
55、160; 51. #endif 52. 53. return 0; 54. 55. err_suspend_work_queue: 56. platform_driver_unregister(&power_driver); 57. err_platform_driver_register: 58.
56、160; platform_device_unregister(&power_device); 59. err_platform_device_register: 60. wake_lock_destroy(&unknown_wakeup); 61. wake_lock_destroy(&main_wake_lock); 62. #ifdef CONFIG_
57、WAKELOCK_STAT 63. wake_lock_destroy(&deleted_wake_locks); 64. #endif 65. return ret; 66. 67. core_initcall(wakelocks_init); 可以看到内核通过core_initcall调用了wake_lock系统的初始化函数,函数首先初始化了两个
58、有效锁的链表,用于管理系统中的有效锁;接下来初始化了deleted_wake_locks用于处理统计信息,main_wake_lock用于锁定内核(系统启动时会激活这个锁,深度休眠时需要释放这个锁),sys_sync_wake_lock用于浅度休眠阶段同步缓存时阻止内核进入深度休眠,unknown_wakeup用于唤醒时延迟0.5s进入下一次可能的深度休眠;还注册了一个platform_device用于深度休眠阶段检测是否存在有效锁;后面创建了内核进程fs_sync用于浅度休眠阶段同步缓存,内核进程suspend用于进行浅度休眠和深度休眠;还在/proc下面创建了wakelocks节点用于显示
59、wake_lock的统计信息。2、wake_lock初始化cpp view plaincopy1. void wake_lock_init(struct wake_lock *lock, int type, const char *name) 2. 3. unsigned long irqflags = 0; 4.
60、 / 初始化名称 5. if (name) 6. lock->name = name; 7. BUG_ON(!lock->name); 8. 9. if (debug_mask
61、 & DEBUG_WAKE_LOCK) 10. pr_info("wake_lock_init name=%sn", lock->name); 11. #ifdef CONFIG_WAKELOCK_STAT 12. lock->stat.count = 0;
62、;13. lock->stat.expire_count = 0; 14. lock->stat.wakeup_count = 0; 15. lock->stat.total_time = ktime_set(0, 0); 16. lock->st
63、at.prevent_suspend_time = ktime_set(0, 0); 17. lock->stat.max_time = ktime_set(0, 0); 18. lock->stat.last_time = ktime_set(0, 0); 19. #endif 20.
64、 / 初始化flag 21. lock->flags = (type & WAKE_LOCK_TYPE_MASK) | WAKE_LOCK_INITIALIZED; 22. / 初始化链表节点 23. INIT_LIST_HEAD(&lock->link);&
65、#160; 24. spin_lock_irqsave(&list_lock, irqflags); 25. / 将锁加入无效锁链表 26. list_add(&lock->link, &inactive_locks); 27. spin_unlock_irqrestor
66、e(&list_lock, irqflags); 28. 29. EXPORT_SYMBOL(wake_lock_init); 其中参数lock为被初始化对象,type代表锁的类型,name表示锁的名称, 函数主要初始化锁的名称并设置 WAKE_LOCK_INITIALIZED 标志位,并将锁加入无效锁链表inactive_locks,当需要使用锁的时候通过wake_lock()或者wake_lock_timeout()激活该锁:cpp view plaincopy1.
67、 / 根据参数激活锁 2. static void wake_lock_internal( 3. struct wake_lock *lock, long timeout, int has_timeout) 4. 5. int type; 6.
68、;unsigned long irqflags; 7. long expire_in; 8. 9. spin_lock_irqsave(&list_lock, irqflags); 10. / 获取锁的类型 11. type =
69、 lock->flags & WAKE_LOCK_TYPE_MASK; 12. BUG_ON(type >= WAKE_LOCK_TYPE_COUNT); 13. BUG_ON(!(lock->flags & WAKE_LOCK_INITIALIZED); 14. #ifdef CONFIG_WAKELOCK_STAT
70、160; 15. if (type = WAKE_LOCK_SUSPEND && wait_for_wakeup) 16. if (debug_mask & DEBUG_WAKEUP) 17.
71、0; pr_info("wakeup wake lock: %sn", lock->name); 18. wait_for_wakeup = 0; 19. lock->stat.wakeup_count+;
72、0;20. 21. if (lock->flags & WAKE_LOCK_AUTO_EXPIRE) && 22. (long)(lock->expires - jiffies) <= 0) 23.
73、60; wake_unlock_stat_locked(lock, 0); 24. lock->stat.last_time = ktime_get(); 25. 26. #endif 27. /
74、160;设置锁有效的标志位 28. if (!(lock->flags & WAKE_LOCK_ACTIVE) 29. lock->flags |= WAKE_LOCK_ACTIVE; 30. #ifdef CONFIG_WAKELOCK_STAT 31.
75、60; lock->stat.last_time = ktime_get(); 32. #endif 33. 34. / 将锁从无效锁链表中删除 35. list_del(&lock->link); 36.
76、160; / 如果是超时锁 37. if (has_timeout) 38. if (debug_mask & DEBUG_WAKE_LOCK) 39.
77、pr_info("wake_lock: %s, type %d, timeout %ld.%03lun", 40. lock->name, type, timeout / HZ, 41.
78、 (timeout % HZ) * MSEC_PER_SEC / HZ); 42. / 设置锁超时时间,以当前jiffies为基准 43. lock->exp
79、ires = jiffies + timeout; 44. / 设置锁的超时锁标志 45. lock->flags |= WAKE_LOCK_AUTO_EXPIRE; 46.
80、60;/ 将锁加入有效锁链表 47. list_add_tail(&lock->link, &active_wake_lockstype); 48. else / 如果是永久锁 49. if
81、60;(debug_mask & DEBUG_WAKE_LOCK) 50. pr_info("wake_lock: %s, type %dn", lock->name, type); 51. / 设置超
82、时时间为极限 52. lock->expires = LONG_MAX; 53. / 清除超时锁标志 54. lock->flags &= WAKE_LOCK_AUTO_EXP
83、IRE; 55. / 将锁加入有效锁链表 56. list_add(&lock->link, &active_wake_lockstype); 57. 58. / 如果是
84、休眠锁 59. if (type = WAKE_LOCK_SUSPEND) 60. current_event_num+; / 休眠锁使用计数器加1 61. #ifdef CONFIG_WAKELOCK_STAT 62. &
85、#160; / 如果是内核休眠锁 63. if (lock = &main_wake_lock) 64. update_sleep_wait_stats_locked(1); 65.
86、160; / 如果内核休眠锁无效 66. else if (!wake_lock_active(&main_wake_lock) 67. update_sleep_wait_stats_locked(0); 6
87、8. #endif 69. / 如果是超时锁 70. if (has_timeout) 71. expire_in = has_wake_lock_locke
88、d(type); 72. else 73. expire_in = -1; 74. / 当前存在有效超时锁,并且最长的一个到期时间间隔为expire_in &
89、#160;75. if (expire_in > 0) 76. if (debug_mask & DEBUG_EXPIRE) 77.
90、 pr_info("wake_lock: %s, start expire timer, " 78. "%ldn", lock->name
91、, expire_in); 79. mod_timer(&expire_timer, jiffies + expire_in); 80. else / 如果有永久锁或者无有效锁 81.
92、160; if (del_timer(&expire_timer) 82. if (debug_mask & DEBUG_EXPIRE) 83.
93、0; pr_info("wake_lock: %s, stop expire timern", 84.
94、0; lock->name); 85. if (expire_in = 0) / 无有效锁 86.
95、60; queue_work(suspend_work_queue, &suspend_work); 87. 88. 89. spin_unlock_irqrestore(&list_lock, irqflags); 90. 91.
96、 92. / 激活永久锁 93. void wake_lock(struct wake_lock *lock) 94. 95. wake_lock_internal(lock, 0, 0); 96. 97. EXPORT_SYMBOL(wake_lock); 98. 99. / 激活超时锁
97、;100. void wake_lock_timeout(struct wake_lock *lock, long timeout) 101. 102. wake_lock_internal(lock, timeout, 1); 103. 104. EXPORT_SYMBOL(wake_lock_timeout); 可以看到激活过程都是通过调用wake_lock_i
98、nternal()完成的,该函数首先完成一些统计信息的初始化,设置 WAKE_LOCK_ACTIVE 标志位并将锁从无效锁链表中移除;然后根据是否是超时锁设置 WAKE_LOCK_AUTO_EXPIRE 标志位,并设置超时锁的超时时间,再将锁加入有效锁链表;最后再根据锁的类型判断是否为休眠锁,如果是休眠锁且为超时锁则通过has_wake_lock_locked()获取系统中存在的超时锁中时间最长的到期时间值,并以此值设置expire_timer,has_wake_lock_locked()返回0则表示系统中不存在有效锁则启动suspend进程开始进入深度
99、休眠状态。3、expire_timercpp view plaincopy1. static void expire_wake_locks(unsigned long data) 2. 3. long has_lock; 4. unsigned long irqflags; 5. if
100、 (debug_mask & DEBUG_EXPIRE) 6. pr_info("expire_wake_locks: startn"); 7. spin_lock_irqsave(&list_lock, irqflags); 8. / 打印当前
101、的有效锁 9. if (debug_mask & DEBUG_SUSPEND) 10. print_active_locks(WAKE_LOCK_SUSPEND); 11. / 检测系统是否持有休眠锁 12. has_loc
102、k = has_wake_lock_locked(WAKE_LOCK_SUSPEND); 13. if (debug_mask & DEBUG_EXPIRE) 14. pr_info("expire_wake_locks: done, has_lock %ldn", has_lock);
103、60; 15. / 如果系统当前没有持有有效地休眠锁 16. if (has_lock = 0) 17. / 则启动深度休眠工作队列 18. queue_work(suspend_wor
104、k_queue, &suspend_work); 19. spin_unlock_irqrestore(&list_lock, irqflags); 20. 21. / 定义timer,运行函数为expire_wake_locks 22. static DEFINE_TIMER(expire_timer, expire_wake_locks, 0, 0);
105、60;该timer会在多个地方用到,在激活锁的函数中注册用于超时锁到期后检测系统的有效锁状态,如果系统不存在有效锁了则启动suspend进程。4、suspend_workcpp view plaincopy1. static void suspend(struct work_struct *work) 2. 3. int ret; 4. int entry_eve
106、nt_num; 5. 6. / 判断系统是否还持有有效锁,如果有则直接返回 7. if (has_wake_lock(WAKE_LOCK_SUSPEND) 8. if (debug_mask & DEBUG_SUSPEND)
107、9. pr_info("suspend: abort suspendn"); 10. return; 11. 12. 13. /
108、160;记录函数进入时休眠锁的使用次数 14. entry_event_num = current_event_num; 15. sys_sync(); / 将缓存中的数据写入磁盘 16. if (debug_mask & DEBUG_SUSPEND) 17.
109、160; pr_info("suspend: enter suspendn"); 18. / 开始深度休眠 19. ret = pm_suspend(requested_suspend_state); 20. / 退出深度休眠,打印信息
110、160; 21. if (debug_mask & DEBUG_EXIT_SUSPEND) 22. struct timespec ts; 23. struct rtc_time tm; 24.
111、 getnstimeofday(&ts); 25. rtc_time_to_tm(ts.tv_sec, &tm); 26. pr_info("suspend: exit suspend, ret
112、0;= %d " 27. "(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)n", ret, 28. tm.tm_year + 1900,
113、 tm.tm_mon + 1, tm.tm_mday, 29. tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec); 30. 31. / 如果深度休眠前和深度休眠后锁的使用
114、次数一致,即唤醒过程中没有激活新的锁 32. if (current_event_num = entry_event_num) 33. if (debug_mask & DEBUG_SUSPEND) 34.
115、60; pr_info("suspend: pm_suspend returned with no eventn"); 35. / 激活unknown_wakeup,0.5s超时 36. wake_lock_timeout(&unknown_
116、wakeup, HZ / 2); 37. 38. 39. / 声明工作队列,运行函数为suspend 40. static DECLARE_WORK(suspend_work, suspend); 声明工作队列用于内核深度休眠,可以看到一个正常的休眠流程会三次调用sys_sync()用于同步缓存(之前一次在浅度休眠,之后一次在深度休眠),然后调用pm_suspend()开始执
117、行深度休眠流程。5、has_wake_lockcpp view plaincopy1. / 移除过期超时锁 2. static void expire_wake_lock(struct wake_lock *lock) 3. 4. #ifdef CONFIG_WAKELOCK_STAT 5. wake_unlock_stat_locked(lock, 1);
118、;6. #endif 7. / 清除锁有效和超时锁标志 8. lock->flags &= (WAKE_LOCK_ACTIVE | WAKE_LOCK_AUTO_EXPIRE); 9. / 从当前链表中删除 10. list_del(&l
119、ock->link); 11. / 加入无效锁链表 12. list_add(&lock->link, &inactive_locks); 13. if (debug_mask & (DEBUG_WAKE_LOCK | DEBUG_EXPIRE) 14.
120、60; pr_info("expired wake lock %sn", lock->name); 15. 16. 17. / 打印有效锁信息,调用者需持有list_lock 18. static void print_active_locks(int type) 19.
121、20. struct wake_lock *lock; 21. bool print_expired = true; 22. 23. BUG_ON(type >= WAKE_LOCK_TYPE_COUNT); 24. / 遍历有效锁链表 25. list_for_each_entry(lock, &active_wake_lockstype, link) 26. / 如果是超时锁 27. &
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 学年第一学期幼儿教学工作总结模版
- 创先争优个人学习心得体会模版
- 新生儿单纯疱疹病毒感染的临床护理
- 社保委托代表协议
- 重力教学设计
- 上学期八年级语文教学工作总结模版
- 某精密模具有限公司品质管理系统
- 猫咪输液护理常规
- 部编本大小多少教学设计
- 7S管理培训体系精要
- 2025年全国国家版图知识竞赛(中小学组)题库
- 汉中汉源电力招聘试题及答案
- 驻外员工报销管理制度
- 批评不可怕课件
- 金蝶K3-ERP系统管理制度
- 厨房用电安全知识
- 通信工程项目管理流程
- 具身智能项目建议书(参考)
- AI系列培训课件-人工智能技术及应用课件第1章
- 云南省昆明市盘龙区2024-2025学年八年级上学期期末质量监测英语试题(含答案)
- DBJT13-369-2021 福建省装配式建筑非砌筑内隔墙技术标准
评论
0/150
提交评论