版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、 在上一文章Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划中,我们简要介绍了Android系统的匿名共享内存机制,其中,简要提到了它具有辅助内存管理系统来有效地管理内存的特点,但是没有进一步去了解它是如何实现的。在本文中,我们将通过分析Android系统的匿名共享内存Ashmem驱动程序的源代码,来深入了解它是如何辅助内存管理系 Android系统的匿名共享内存Ashmem机制并没有自立山头,从头搞一套自己的共享内存机制,而是建立在Linux内核实现的共享内存的基础上的。与此同时,它又向Linux内存管理系统的内存回收算法注册接口
2、,告诉Linux内存管理系统它的某些内存块不再使用了,可以被回收了,不过,这些不再使用的内存需要由它的使用者来告诉Ashmem驱动程序。通过这种用户-Ashmem驱动程序-内存管理系统三者的紧密合作,实现有效的内存管理机制,适合移动设备小内存的特点。 Android系统的匿名共享内存Ashmem驱动程序利用了Linux的共享内存子系统导出的接口来实现自己的功能,因此,它的实现非常小巧,总共代码不到700行。虽然代码很少,但是这里不打算机械式地一行一行地阅读和分析Ashmem驱动程序的源代码,而是通过使用情景来分析,这样可以帮助我们清晰地理解它的实现原理。我们这里所说的使用情景,将从Androi
3、d系统的应用程序框架层提供的匿名共享内存接口开始,经过系统运行时库层,最终到达驱动程序层,通过这样一个完整的过程来理解Android系统的匿名共享内存Ashmem机制。这里,我们将从上一篇文章Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划介绍的Android应用程序框架层提供MemoryFile接口开始,分别介绍Android系统匿名共享内存的创建(open)、映射(mmap)、读写(read/write)以及锁定和解锁(pin/unpin)四个使用情景。 在进入到这个四个使用情景前,我们先来看一下Ashmem驱动程序模块的初始化
4、函数,看看它给用户空间暴露了什么接口,即它创建了什么样的设备文件,以及提供了什么函数来操作这个设备文件。Ashmem驱动程序实现在kernel/common/mm/ashmem.c文件中,它的模块初始化函数定义为ashmem_init:view plain1. staticstructfile_operationsashmem_fops=2. .owner=THIS_MODULE,3. .open=ashmem_open,4. .release=ashmem_release,5. .mmap=ashmem_mmap,6. .unlocked_ioctl=ashmem_ioctl,7. .com
5、pat_ioctl=ashmem_ioctl,8. ;9. 10. staticstructmiscdeviceashmem_misc=11. .minor=MISC_DYNAMIC_MINOR,12. .name=ashmem,13. .fops=&ashmem_fops,14. ;15. 16. staticint_initashmem_init(void)17. 18. intret;19. 20. .21. 22. ret=misc_register(&ashmem_misc);23. if(unlikely(ret)24. printk(KERN_ERRashmem:failedto
6、registermiscdevice!n);25. returnret;26. 27. 28. .29. 30. return0;31. 这里,我们可以看到,Ahshmem驱动程序在加载时,会创建一个/dev/ashmem的设备文件,这是一个misc类型的设备。注册misc设备是通过misc_register函数进行的,关于这个函数的详细实现,可以参考前面Android日志系统驱动程序Logger源代码分析一文,调用这个函数成功后,就会在/dev目录下生成一个ashmem设备文件了。同时,我们还可以看到,这个设备文件提供了open、mmap、release和ioctl四种操作。为什么没有rea
7、d和write操作呢?这是因为读写共享内存的方法是通过内存映射地址来进行的,即通过mmap系统调用把这个设备文件映射到进程地址空间中,然后就直接对内存进行读写了,不需要通过read 和write文件操作,后面我们将会具体分析是如何实现的。 有了这个基础之后,下面我们就分四个部分来分别介绍匿名共享内存的创建(open)、映射(mmap)、读写(read/write)以及锁定和解锁(pin/unpin)使用情景。 一. 匿名共享内存的创建操作 在Android应用程序框架层提供MemoryFile类的构造函数中,进行了匿名共享内存的创建操作,我们先来看一下这个构造函数的实现,它位于framewor
8、ks/base/core/java/android/os/MemoryFile.java文件中:view plain1. publicclassMemoryFile2. 3. .4. 5. privatestaticnativeFileDescriptornative_open(Stringname,intlength)throwsIOException;6. 7. .8. 9. privateFileDescriptormFD;/ashmemfiledescriptor10. .11. privateintmLength;/totallengthofourashmemregion12. 13
9、. .14. 15. /*16. *Allocatesanewashmemregion.Theregionisinitiallynotpurgable.17. *18. *paramnameoptionalnameforthefile(canbenull).19. *paramlengthofthememoryfileinbytes.20. *throwsIOExceptionifthememoryfilecouldnotbecreated.21. */22. publicMemoryFile(Stringname,intlength)throwsIOException23. mLength=
10、length;24. mFD=native_open(name,length);25. .26. 27. 28. .29. 这里我们看到,这个构造函数最终是通过JNI方法native_open来创建匿名内存共享文件。这个JNI方法native_open实现在frameworks/base/core/jni/adroid_os_MemoryFile.cpp文件中:view plain1. staticjobjectandroid_os_MemoryFile_open(JNIEnv*env,jobjectclazz,jstringname,jintlength)2. 3. constchar*na
11、mestr=(name?env-GetStringUTFChars(name,NULL):NULL);4. 5. intresult=ashmem_create_region(namestr,length);6. 7. if(name)8. env-ReleaseStringUTFChars(name,namestr);9. 10. if(result0)11. jniThrowException(env,java/io/IOException,ashmem_create_regionfailed);12. returnNULL;13. 14. 15. returnjniCreateFileD
12、escriptor(env,result);16. 这个函数又通过运行时库提供的接口ashmem_create_region来创建匿名共享内存,这个函数实现在system/core/libcutils/ashmem-dev.c文件中:view plain1. /*2. *ashmem_create_region-createsanewashmemregionandreturnsthefile3. *descriptor,or0onerror4. *5. *nameisanoptionallabeltogivetheregion(visiblein/proc/pid/maps)6. *sizei
13、sthesizeoftheregion,inpage-alignedbytes7. */8. intashmem_create_region(constchar*name,size_tsize)9. 10. intfd,ret;11. 12. fd=open(ASHMEM_DEVICE,O_RDWR);13. if(fd0)14. returnfd;15. 16. if(name)17. charbufASHMEM_NAME_LEN;18. 19. strlcpy(buf,name,sizeof(buf);20. ret=ioctl(fd,ASHMEM_SET_NAME,buf);21. if
14、(ret0)22. gotoerror;23. 24. 25. ret=ioctl(fd,ASHMEM_SET_SIZE,size);26. if(ret0)27. gotoerror;28. 29. returnfd;30. 31. error:32. close(fd);33. returnret;34. 这里,一共通过执行三个文件操作系统调用来和Ashmem驱动程序进行交互,分虽是一个open和两个ioctl操作,前者是打开设备文件ASHMEM_DEVICE,后者分别是设置匿名共享内存的名称和大小。 在介绍这三个文件操作之前,我们先来了解一下Ashmem驱动程序的一个相关数据结构stru
15、ct ashmem_area,这个数据结构就是用来表示一块共享内存的,它定义在kernel/common/mm/ashmem.c文件中:view plain1. /*2. *ashmem_area-anonymoussharedmemoryarea3. *Lifecycle:Fromourparentfilesopen()untilitsrelease()4. *Locking:Protectedbyashmem_mutex5. *BigNote:MappingsdoNOTpinthisstructure;itdiesonclose()6. */7. structashmem_area8. c
16、harnameASHMEM_FULL_NAME_LEN;/*optionalnamefor/proc/pid/maps*/9. structlist_headunpinned_list;/*listofallashmemareas*/10. structfile*file;/*theshmem-basedbackingfile*/11. size_tsize;/*sizeofthemapping,inbytes*/12. unsignedlongprot_mask;/*allowedprotbits,asvm_flags*/13. ; 域name表示这块共享内存的名字,这个名字会显示/proc
17、/maps文件中,表示打开这个共享内存文件的进程ID;域unpinned_list是一个列表头,它把这块共享内存中所有被解锁的内存块连接在一起,下面我们讲内存块的锁定和解锁操作时会看到它的用法;域file表示这个共享内存在临时文件系统tmpfs中对应的文件,在内核决定要把这块共享内存对应的物理页面回收时,就会把它的内容交换到这个临时文件中去;域size表示这块共享内存的大小;域prot_mask表示这块共享内存的访问保护位。 在Ashmem驱动程中,所有的ashmem_area实例都是从自定义的一个slab缓冲区创建的。这个slab缓冲区是在驱动程序模块初始化函数创建的,我们来看一个这个初始化
18、函数的相关实现:view plain1. staticint_initashmem_init(void)2. 3. intret;4. 5. ashmem_area_cachep=kmem_cache_create(ashmem_area_cache,6. sizeof(structashmem_area),7. 0,0,NULL);8. if(unlikely(!ashmem_area_cachep)9. printk(KERN_ERRashmem:failedtocreateslabcachen);10. return-ENOMEM;11. 12. 13. .14. 15. return0
19、;16. 全局变量定义在文件开头的地方:view plain1. staticstructkmem_cache*ashmem_area_cachep_read_mostly; 它的类型是struct kmem_cache,表示这是一个slab缓冲区,由内核中的内存管理系统进行管理。 这里就是通过kmem_cache_create函数来创建一个名为ashmem_area_cache、对象大小为sizeof(struct ashmem_area)的缓冲区了。缓冲区创建了以后,就可以每次从它分配一个struct ashmem_area对象了。关于Linux内核的slab缓冲区的相关知识,可以参考前面
20、Android学习启动篇一文中提到的一本参考书籍Understanding the Linux Kernel的第八章Memory Managerment。 有了这些基础知识后,我们回到前面的ashmem_create_region函数中。 首先是执行打开文件的操作:view plain1. fd=open(ASHMEM_DEVICE,O_RDWR); ASHMEM_DEVICE是一个宏,定义为:view plain1. #defineASHMEM_DEVICE/dev/ashmem 这里就是匿名共享内存设备文件/dev/ashmem了。 从上面的描述我们可以知道,调用这个open函数最终会进入
21、到Ashmem驱动程序中的ashmem_open函数中去:view plain1. staticintashmem_open(structinode*inode,structfile*file)2. 3. structashmem_area*asma;4. intret;5. 6. ret=nonseekable_open(inode,file);7. if(unlikely(ret)8. returnret;9. 10. asma=kmem_cache_zalloc(ashmem_area_cachep,GFP_KERNEL);11. if(unlikely(!asma)12. return
22、-ENOMEM;13. 14. INIT_LIST_HEAD(&asma-unpinned_list);15. memcpy(asma-name,ASHMEM_NAME_PREFIX,ASHMEM_NAME_PREFIX_LEN);16. asma-prot_mask=PROT_MASK;17. file-private_data=asma;18. 19. return0;20. 首先是通过nonseekable_open函数来设备这个文件不可以执行定位操作,即不可以执行seek文件操作。接着就是通过kmem_cache_zalloc函数从刚才我们创建的slab缓冲区ashmem_area_c
23、achep来创建一个ashmem_area结构体了,并且保存在本地变量asma中。再接下去就是初始化变量asma的其它域,其中,域name初始为ASHMEM_NAME_PREFIX,这是一个宏,定义为:view plain1. #defineASHMEM_NAME_PREFIXdev/ashmem/2. #defineASHMEM_NAME_PREFIX_LEN(sizeof(ASHMEM_NAME_PREFIX)-1) 函数的最后是把这个ashmem_area结构保存在打开文件结构体的private_data域中,这样,Ashmem驱动程序就可以在其它地方通过这个private_data域来
24、取回这个ashmem_area结构了。 到这里,设备文件/dev/ashmem的打开操作就完成了,它实际上就是在Ashmem驱动程序中创建了一个ashmem_area结构,表示一块新的共享内存。 再回到ashmem_create_region函数中,又调用了两次ioctl文件操作分别来设备这块新建的匿名共享内存的名字和大小。在kernel/comon/mm/include/ashmem.h文件中,ASHMEM_SET_NAME和ASHMEM_SET_SIZE的定义为:view plain1. #defineASHMEM_NAME_LEN2562. 3. #define_ASHMEMIOC0x7
25、74. 5. #defineASHMEM_SET_NAME_IOW(_ASHMEMIOC,1,charASHMEM_NAME_LEN)6. #defineASHMEM_SET_SIZE_IOW(_ASHMEMIOC,3,size_t) 先来看ASHMEM_SET_NAME命令的ioctl调用,它最终进入到Ashmem驱动程序的ashmem_ioctl函数中:view plain1. staticlongashmem_ioctl(structfile*file,unsignedintcmd,unsignedlongarg)2. 3. structashmem_area*asma=file-pri
26、vate_data;4. longret=-ENOTTY;5. 6. switch(cmd)7. caseASHMEM_SET_NAME:8. ret=set_name(asma,(void_user*)arg);9. break;10. .11. 12. 13. returnret;14. 这里通过set_name函数来进行实际操作:view plain1. staticintset_name(structashmem_area*asma,void_user*name)2. 3. intret=0;4. 5. mutex_lock(&ashmem_mutex);6. 7. /*cannotc
27、hangeanexistingmappingsname*/8. if(unlikely(asma-file)9. ret=-EINVAL;10. gotoout;11. 12. 13. if(unlikely(copy_from_user(asma-name+ASHMEM_NAME_PREFIX_LEN,14. name,ASHMEM_NAME_LEN)15. ret=-EFAULT;16. asma-nameASHMEM_FULL_NAME_LEN-1=0;17. 18. out:19. mutex_unlock(&ashmem_mutex);20. 21. returnret;22. 这个
28、函数实现很简单,把用户空间传进来的匿名共享内存的名字设备到asma-name域中去。注意,匿名共享内存块的名字的内容分两部分,前一部分是前缀,这是在open操作时,由驱动程序默认设置的,固定为ASHMEM_NAME_PREFIX,即dev/ashmem/;后一部分由用户指定,这一部分是可选的,即用户可以不调用ASHMEM_SET_NAME命令来设置匿名共享内存块的名字。 再来看ASHMEM_SET_SIZE命令的ioctl调用,它最终也是进入到Ashmem驱动程序的ashmem_ioctl函数中:view plain1. staticlongashmem_ioctl(structfile*fi
29、le,unsignedintcmd,unsignedlongarg)2. 3. structashmem_area*asma=file-private_data;4. longret=-ENOTTY;5. 6. switch(cmd)7. .8. caseASHMEM_SET_SIZE:9. ret=-EINVAL;10. if(!asma-file)11. ret=0;12. asma-size=(size_t)arg;13. 14. break;15. .16. 17. 18. returnret;19. 这个实现很简单,只是把用户空间传进来的匿名共享内存的大小值保存在对应的asma-si
30、ze域中。 这样,ashmem_create_region函数就执先完成了,层层返回,最后回到应用程序框架层提供的接口Memory的构造函数中,整个匿名共享内存的创建过程就完成了。前面我们说过过,Ashmem驱动程序不提供read和write文件操作,进程若要访问这个共享内存,必须要把这个设备文件映射到自己的进程空间中,然后进行直接内存访问,这就是我们下面要介绍的匿名共享内存设备文件的内存映射操作了。 二.匿名共享内存设备文件的内存映射操作 在MemoryFile类的构造函数中,进行了匿名共享内存的创建操作后,下一步就是要把匿名共享内存设备文件映射到进程空间来了:view plain1. pu
31、blicclassMemoryFile2. 3. .4. 5. /returnsmemoryaddressforashmemregion6. privatestaticnativeintnative_mmap(FileDescriptorfd,intlength,intmode)7. throwsIOException;8. 9. .10. 11. privateintmAddress;/addressofashmemmemory12. 13. .14. 15. /*16. *Allocatesanewashmemregion.Theregionisinitiallynotpurgable.1
32、7. *18. *paramnameoptionalnameforthefile(canbenull).19. *paramlengthofthememoryfileinbytes.20. *throwsIOExceptionifthememoryfilecouldnotbecreated.21. */22. publicMemoryFile(Stringname,intlength)throwsIOException23. .24. mAddress=native_mmap(mFD,length,PROT_READ|PROT_WRITE);25. .26. 27. 映射匿名共享内存设备文件到
33、进程空间是通过JNI方法native_mmap来进行的。这个JNI方法实现在frameworks/base/core/jni/adroid_os_MemoryFile.cpp文件中:view plain1. staticjintandroid_os_MemoryFile_mmap(JNIEnv*env,jobjectclazz,jobjectfileDescriptor,2. jintlength,jintprot)3. 4. intfd=jniGetFDFromFileDescriptor(env,fileDescriptor);5. jintresult=(jint)mmap(NULL,l
34、ength,prot,MAP_SHARED,fd,0);6. if(!result)7. jniThrowException(env,java/io/IOException,mmapfailed);8. returnresult;9. 这里的文件描述符fd是在前面open匿名设备文件/dev/ashmem获得的,有个这个文件描述符后,就可以直接通过mmap来执行内存映射操作了。这个mmap系统调用最终进入到Ashmem驱动程序的ashmem_mmap函数中:view plain1. staticintashmem_mmap(structfile*file,structvm_area_struc
35、t*vma)2. 3. structashmem_area*asma=file-private_data;4. intret=0;5. 6. mutex_lock(&ashmem_mutex);7. 8. /*userneedstoSET_SIZEbeforemapping*/9. if(unlikely(!asma-size)10. ret=-EINVAL;11. gotoout;12. 13. 14. /*requestedprotectionbitsmustmatchourallowedprotectionmask*/15. if(unlikely(vma-vm_flags&asma-p
36、rot_mask)&PROT_MASK)16. ret=-EPERM;17. gotoout;18. 19. 20. if(!asma-file)21. char*name=ASHMEM_NAME_DEF;22. structfile*vmfile;23. 24. if(asma-nameASHMEM_NAME_PREFIX_LEN!=0)25. name=asma-name;26. 27. /*.andallocatethebackingshmemfile*/28. vmfile=shmem_file_setup(name,asma-size,vma-vm_flags);29. if(unl
37、ikely(IS_ERR(vmfile)30. ret=PTR_ERR(vmfile);31. gotoout;32. 33. asma-file=vmfile;34. 35. get_file(asma-file);36. 37. if(vma-vm_flags&VM_SHARED)38. shmem_set_file(vma,asma-file);39. else40. if(vma-vm_file)41. fput(vma-vm_file);42. vma-vm_file=asma-file;43. 44. vma-vm_flags|=VM_CAN_NONLINEAR;45. 46. o
38、ut:47. mutex_unlock(&ashmem_mutex);48. returnret;49. 这个函数的实现也很简单,它调用了Linux内核提供的shmem_file_setup函数来在临时文件系统tmpfs中创建一个临时文件,这个临时文件与Ashmem驱动程序创建的匿名共享内存对应。函数shmem_file_setup是Linux内核中用来创建共享内存文件的方法,而Linux内核中的共享内存机制其实是一种进程间通信(IPC)机制,它的实现相对也是比较复杂,Android系统的匿名共享内存机制正是由于直接使用了Linux内核共享内存机制,它才会很小巧,它站在巨人的肩膀上了。关于Li
39、nux内核中的共享内存的相关知识,可以参考前面Android学习启动篇一文中提到的一本参考书籍Linux内核源代码情景分析的第六章传统的Unix进程间通信第七小节共享内存。 通过shmem_file_setup函数创建的临时文件vmfile最终就保存在vma-file中了。这里的vma是由Linux内核的文件系统层传进来的,它的类型为struct vm_area_struct,它表示的是当前进程空间中一块连续的虚拟地址空间,它的起始地址可以由用户来指定,也可以由内核自己来分配,这里我们从JNI方法native_mmap调用的mmap的第一个参数为NULL可以看出,这块连续的虚拟地址空间的起始地
40、址是由内核来指定的。文件内存映射操作完成后,用户访问这个范围的地址空间就相当于是访问对应的文件的内容了。有关Linux文件的内存映射操作,同样可以参考前面Android学习启动篇一文中提到的一本参考书籍Linux内核源代码情景分析的第二章内存管理第十三小节系统调用mmap。从这里我们也可以看出,Android系统的匿名共享内存是在虚拟地址空间连续的,但是在物理地址空间就不一定是连续的了。 同时,这个临时文件vmfile也会保存asma-file域中,这样,Ashmem驱动程序后面就可以通过在asma-file来操作这个匿名内存共享文件了。 函数ashmem_mmap执行完成后,经过层层返回到J
41、NI方法native_mmap中去,就从mmap函数的返回值中得到了这块虚拟空间的起始地址了,这个起始地址最终返回到应用程序框架层的MemoryFile类的构造函数中,并且保存在成员变量mAddress中,后面,共享内存的读写操作就是对这个地址空间进行操作了。 三.匿名共享内存的读写操作 因为前面对匿名共享内存文件进行内存映射操作,这里对匿名内存文件内容的读写操作就比较简单了,就像访问内存变量一样就行了。 我们来看一下MemoryFile类的读写操作函数:view plain1. publicclassMemoryFile2. 3. .4. 5. privatestaticnativeintn
42、ative_read(FileDescriptorfd,intaddress,bytebuffer,6. intsrcOffset,intdestOffset,intcount,booleanisUnpinned)throwsIOException;7. privatestaticnativevoidnative_write(FileDescriptorfd,intaddress,bytebuffer,8. intsrcOffset,intdestOffset,intcount,booleanisUnpinned)throwsIOException;9. 10. .11. 12. privat
43、eFileDescriptormFD;/ashmemfiledescriptor13. privateintmAddress;/addressofashmemmemory14. privateintmLength;/totallengthofourashmemregion15. privatebooleanmAllowPurging=false;/trueifourashmemregionisunpinned16. 17. .18. 19. /*20. *Readsbytesfromthememoryfile.21. *WillthrowanIOExceptionifthefilehasbee
44、npurged.22. *23. *parambufferbytearraytoreadbytesinto.24. *paramsrcOffsetoffsetintothememoryfiletoreadfrom.25. *paramdestOffsetoffsetintothebytearraybuffertoreadinto.26. *paramcountnumberofbytestoread.27. *returnnumberofbytesread.28. *throwsIOExceptionifthememoryfilehasbeenpurgedordeactivated.29. */30. publicintreadBytes(bytebuffer,intsrcOffset,intdestOffset,intcount)31. throwsIOException32. if(isDeactivated()33. thrownewIOException(Cantreadfromdeactivatedmemoryfile.);34. 35. if(destOffsetbuffer.length|countbuffer.length-destOffset37. |srcOffsetmLength38. |countmLeng
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 棉花加工工安全实践竞赛考核试卷含答案
- 八年级物理下册计算综合复习专题教学设计
- 钨钼冶炼工岗前班组评比考核试卷含答案
- 小学四年级心理健康帮助他人收获快乐教学设计
- 高三英语高考听力标准训练(第十七套)听前预测与听中抓点教学设计
- 2026届初中道德与法治中考冲刺三轮复习教学设计
- 高一数学《集合与常用逻辑用语》复习课教学设计:核心素养视域下逻辑推理与集合运算的深度融合
- 浆纱机操作工安全强化竞赛考核试卷含答案
- 骨灰管理员诚信道德能力考核试卷含答案
- 焦炉炉前工安全意识知识考核试卷含答案
- 2026年强制性产品认证应知应会培训考核卷(十一)
- 检维修作业:安全要点与应急处置
- 2025年江苏泰州市中考语文试卷真题及答案详解(精校打印)
- 婴幼儿伤害预防与处理配套教材电子课件(完整版)
- 2026年殡葬系统版遗体火化师技能知识试题
- 美国金融硕士申请书范文
- 2025年出租汽车驾驶员从业资格考试(公共科目)综合能力测试题及答案一
- DCS报警管理制度
- 中外航海文化知到课后答案智慧树章节测试答案2025年春中国人民解放军海军大连舰艇学院
- G-T 35273-2020 《信息安全技术 个人信息安全规范》
- 工业生态学课件
评论
0/150
提交评论