百篇博客系列篇.本篇为:

内存管理相关篇为:

MMU的本质

虚拟地址(VA): 就是线性地址, 鸿蒙内存部分全是VA的身影, 是由编译器和链接器在定位程序时分配的,每个应用程序都使用相同的虚拟内存地址空间,而这些虚拟内存地址空间实际上分别映射到不同的实际物理内存空间上。CPU只知道虚拟地址,向虚拟地址要数据,但在其保护模式下很悲催地址信号在路上被MMU拦截了,MMU把虚拟地址换成了物理地址,从而拿到了真正的数据。

物理地址(PA):程序的指令和常量数据,全局变量数据以及运行时动态申请内存所分配的实际物理内存存放位置。

MMU采用页表(page table)来实现虚实地址转换,页表项除了描述虚拟页到物理页直接的转换外,还提供了页的访问权限(读,写,可执行)和存储属性。 MMU的本质是拿虚拟地址的高位(20位)做文章,低12位是页内偏移地址不会变。也就是说虚拟地址和物理地址的低12位是一样的,本篇详细讲述MMU是如何变戏法的。

MMU是通过两级页表结构:L1和L2来实现映射功能的,鸿蒙内核当然也实现了这两级页表转换的实现。本篇是系列篇关于内存部分最满意的一篇,也是最不好理解的一篇, 强烈建议结合源码看.鸿蒙内核源码注释 < G| G| C| C >

一级页表L1

L1页表将全部的4G地址空间划分为4096个1M的节,页表中每一项(页表项)32位,其内容是L2页表基地址或某个1M物理内存的基地址。虚拟地址的高12位用于对页表项定位,也就是4096个页面项的索引,L1页表的基地址,也叫转换表基地址,存放在CP15的C2(TTB)寄存器中,鸿蒙内核源码分析(内存汇编篇)中有详细的描述,自行翻看。

L1页表项有三种描述格式,鸿蒙源码如下。

  1. /* L1 descriptor type */
  2. #define MMU_DESCRIPTOR_L1_TYPE_INVALID (0x0 << 0)
  3. #define MMU_DESCRIPTOR_L1_TYPE_PAGE_TABLE (0x1 << 0)
  4. #define MMU_DESCRIPTOR_L1_TYPE_SECTION (0x2 << 0)
  5. #define MMU_DESCRIPTOR_L1_TYPE_MASK (0x3 << 0)

第一种:Fault(INVALID)页表项,表示对应虚拟地址未被映射,访问将产生一个数据中止异常。

第二种:PAGE_TABLE页表项,指向L2页表的页表项,意思就是把1M分成更多的页(256*4K)

第三种:SECTION页表项 ,指向1M节的页表项

页表项的最低二位[1:0],用于定义页表项的类型,section页表项对应1M的节,直接使用页表项的最高12位替代虚拟地址的高12位即可得到物理地址。还是直接看鸿蒙源码来的清晰,每一行都加了详细的注释。

LOS_ArchMmuQuery

  1. //通过虚拟地址查询物理地址
  2. STATUS_T LOS_ArchMmuQuery(const LosArchMmu *archMmu VADDR_T vaddr PADDR_T *paddr UINT32 *flags)
  3. {//archMmu->virtTtb:转换表基地址
  4. PTE_T l1Entry = OsGetPte1(archMmu->virtTtb vaddr);//获取PTE vaddr右移20位 得到L1描述子地址
  5. PTE_T l2Entry;
  6. PTE_T* l2Base = NULL;
  7. if (OsIsPte1Invalid(l1Entry)) {//判断L1描述子地址是否有效
  8. return LOS_ERRNO_VM_NOT_FOUND;//无效返回虚拟地址未查询到
  9. } else if (OsIsPte1Section(l1Entry)) {// section页表项: l1Entry低二位是否为 10
  10. if (paddr != NULL) {//物理地址 = 节基地址(section页表项的高12位) + 虚拟地址低20位
  11. *paddr = MMU_DESCRIPTOR_L1_SECTION_ADDR(l1Entry) + (vaddr & (MMU_DESCRIPTOR_L1_SMALL_SIZE - 1));
  12. }
  13. if (flags != NULL) {
  14. OsCvtSecAttsToFlags(l1Entry flags);//获取虚拟内存的flag信息
  15. }
  16. } else if (OsIsPte1PageTable(l1Entry)) {//PAGE_TABLE页表项: l1Entry低二位是否为 01
  17. l2Base = OsGetPte2BasePtr(l1Entry);//获取L2转换表基地址
  18. if (l2Base == NULL) {
  19. return LOS_ERRNO_VM_NOT_FOUND;
  20. }
  21. l2Entry = OsGetPte2(l2Base vaddr);//获取L2描述子地址
  22. if (OsIsPte2SmallPage(l2Entry) || OsIsPte2SmallPageXN(l2Entry)) {
  23. if (paddr != NULL) {//物理地址 = 小页基地址(L2页表项的高20位) + 虚拟地址低12位
  24. *paddr = MMU_DESCRIPTOR_L2_SMALL_PAGE_ADDR(l2Entry) + (vaddr & (MMU_DESCRIPTOR_L2_SMALL_SIZE - 1));
  25. }
  26. if (flags != NULL) {
  27. OsCvtPte2AttsToFlags(l1Entry l2Entry flags);//获取虚拟内存的flag信息
  28. }
  29. } else if (OsIsPte2LargePage(l2Entry)) {//鸿蒙目前暂不支持64K大页,未来手机版应该会支持。
  30. LOS_Panic("%s %d, large page unimplemented\n" __FUNCTION__ __LINE__);
  31. } else {
  32. return LOS_ERRNO_VM_NOT_FOUND;
  33. }
  34. }
  35. return LOS_OK;
  36. }

这是鸿蒙内核对地址使用最频繁的功能,通过虚拟地址得到物理地址和flag信息,看下哪些地方会调用到它。

二级页表L2

L1页表项表示1M的地址范围,L2把1M分成更多的小页,鸿蒙内核 一页按4K算,所以被分成 256个小页。

L2页表中包含256个页表项,每个32位(4个字节),L2页表需要 256*4 = 1K的空间,必须按1K对齐,每个L2页表项将4K的虚拟内存地址转换为物理地址,每个L2页面项都给出了一个4K的页基地址。

L2页表项有三种格式:

  1. /* L2 descriptor type */
  2. #define MMU_DESCRIPTOR_L2_TYPE_INVALID (0x0 << 0)
  3. #define MMU_DESCRIPTOR_L2_TYPE_LARGE_PAGE (0x1 << 0)
  4. #define MMU_DESCRIPTOR_L2_TYPE_SMALL_PAGE (0x2 << 0)
  5. #define MMU_DESCRIPTOR_L2_TYPE_SMALL_PAGE_XN (0x3 << 0)
  6. #define MMU_DESCRIPTOR_L2_TYPE_MASK (0x3 << 0)

第一种:Fault(INVALID)页表项,表示对应虚拟地址未被映射,访问将产生一个数据中止异常。

第二种:大页表项,包含一个指向64K页的指针,但鸿蒙内核并没有实现大页表的支持,给出了未实现的提示

  1. if (OsIsPte2LargePage(l2Entry)) {
  2. LOS_Panic("%s %d, large page unimplemented\n" __FUNCTION__ __LINE__);
  3. }

第三种:小页表项,包含一个指向4K页的指针。

映射初始化的过程

先看调用和被调用的关系

  1. //启动映射初始化
  2. VOID OsInitMappingStartUp(VOID)
  3. {
  4. OsArmInvalidateTlbBarrier();//使TLB失效
  5. OsSwitchTmpTTB();//切换到临时TTB
  6. OsSetKSectionAttr();//设置内核段(text,rodata,bss)映射
  7. OsArchMmuInitPerCPU();//初始化CPU与mmu相关信息
  8. }

干脆利落,调用了四个函数,其中三个在鸿蒙内核源码分析(内存汇编篇)有涉及,不展开讲,这里说OsSetKSectionAttr

它实现了内核空间各个区的映射,内核本身也是程序,鸿蒙把内核空间在物理内存上就独立开来了,也就是说在物理内存上有一段区域是只给内核空间享用的,从根上就把内核和APP 空间隔离了,里面放的是内核的重要数据(包括代码,常量和全局变量),具体看代码,代码很长,整个函数全贴出来了,都加上了注释。

OsSetKSectionAttr 内核空间的设置和映射

  1. typedef struct ArchMmuInitMapping {
  2. PADDR_T phys;//物理地址
  3. VADDR_T virt;//虚拟地址
  4. size_t size;//大小
  5. unsigned int flags;//标识 读/写/.. VM_MAP_REGION_FLAG_PERM_*
  6. const char *name;//名称
  7. } LosArchMmuInitMapping;
  8. VADDR_T *OsGFirstTableGet()
  9. {
  10. return (VADDR_T *)g_firstPageTable;//UINT8 g_firstPageTable[MMU_DESCRIPTOR_L1_SMALL_ENTRY_NUMBERS]
  11. }
  12. //设置内核空间段属性,可看出内核空间是固定映射到物理地址
  13. STATIC VOID OsSetKSectionAttr(VOID)
  14. {
  15. /* every section should be page aligned */
  16. UINTPTR textStart = (UINTPTR)&__text_start;//代码段开始位置
  17. UINTPTR textEnd = (UINTPTR)&__text_end;//代码段结束位置
  18. UINTPTR rodataStart = (UINTPTR)&__rodata_start;//常量只读段开始位置
  19. UINTPTR rodataEnd = (UINTPTR)&__rodata_end;//常量只读段结束位置
  20. UINTPTR ramDataStart = (UINTPTR)&__ram_data_start;//全局变量段开始位置
  21. UINTPTR bssEnd = (UINTPTR)&__bss_end;//bss结束位置
  22. UINT32 bssEndBoundary = ROUNDUP(bssEnd MB);
  23. LosArchMmuInitMapping mmuKernelMappings[] = {
  24. {
  25. .phys = SYS_MEM_BASE + textStart - KERNEL_VMM_BASE//映射物理内存位置
  26. .virt = textStart//内核代码区
  27. .size = ROUNDUP(textEnd - textStart MMU_DESCRIPTOR_L2_SMALL_SIZE),//代码区大小
  28. .flags = VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_EXECUTE//代码段可读,可执行
  29. .name = "kernel_text"
  30. },
  31. {
  32. .phys = SYS_MEM_BASE + rodataStart - KERNEL_VMM_BASE//映射物理内存位置
  33. .virt = rodataStart//内核常量区
  34. .size = ROUNDUP(rodataEnd - rodataStart MMU_DESCRIPTOR_L2_SMALL_SIZE),//4K对齐
  35. .flags = VM_MAP_REGION_FLAG_PERM_READ//常量段只读
  36. .name = "kernel_rodata"
  37. },
  38. {
  39. .phys = SYS_MEM_BASE + ramDataStart - KERNEL_VMM_BASE//映射物理内存位置
  40. .virt = ramDataStart
  41. .size = ROUNDUP(bssEndBoundary - ramDataStart MMU_DESCRIPTOR_L2_SMALL_SIZE),
  42. .flags = VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE//全局变量区可读可写
  43. .name = "kernel_data_bss"
  44. }
  45. };
  46. LosVmSpace *kSpace = LOS_GetKVmSpace();//获取内核空间
  47. status_t status;
  48. UINT32 length;
  49. paddr_t oldTtPhyBase;
  50. int i;
  51. LosArchMmuInitMapping *kernelMap = NULL;//内核映射
  52. UINT32 kmallocLength;
  53. /* use second-level mapping of default READ and WRITE */
  54. kSpace->archMmu.virtTtb = (PTE_T *)g_firstPageTable;//__attribute__((section(".bss.prebss.translation_table"))) UINT8 g_firstPageTable[MMU_DESCRIPTOR_L1_SMALL_ENTRY_NUMBERS];
  55. kSpace->archMmu.physTtb = LOS_PaddrQuery(kSpace->archMmu.virtTtb);//通过TTB虚拟地址查询TTB物理地址
  56. status = LOS_ArchMmuUnmap(&kSpace->archMmu KERNEL_VMM_BASE
  57. (bssEndBoundary - KERNEL_VMM_BASE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT);//解绑 bssEndBoundary - KERNEL_VMM_BASE 映射
  58. if (status != ((bssEndBoundary - KERNEL_VMM_BASE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) {//解绑失败
  59. VM_ERR("unmap failed, status: %d" status);
  60. return;
  61. }
  62. //映射 textStart - KERNEL_VMM_BASE 区
  63. status = LOS_ArchMmuMap(&kSpace->archMmu KERNEL_VMM_BASE SYS_MEM_BASE
  64. (textStart - KERNEL_VMM_BASE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT
  65. VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE |
  66. VM_MAP_REGION_FLAG_PERM_EXECUTE);
  67. if (status != ((textStart - KERNEL_VMM_BASE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) {
  68. VM_ERR("mmap failed, status: %d" status);
  69. return;
  70. }
  71. length = sizeof(mmuKernelMappings) / sizeof(LosArchMmuInitMapping);
  72. for (i = 0; i < length; i++) {//对mmuKernelMappings一一映射好
  73. kernelMap = &mmuKernelMappings[i];
  74. status = LOS_ArchMmuMap(&kSpace->archMmu kernelMap->virt kernelMap->phys
  75. kernelMap->size >> MMU_DESCRIPTOR_L2_SMALL_SHIFT kernelMap->flags);
  76. if (status != (kernelMap->size >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) {
  77. VM_ERR("mmap failed, status: %d" status);
  78. return;
  79. }
  80. LOS_VmSpaceReserve(kSpace kernelMap->size kernelMap->virt);//保留区
  81. }
  82. //将剩余空间映射好
  83. kmallocLength = KERNEL_VMM_BASE + SYS_MEM_SIZE_DEFAULT - bssEndBoundary;
  84. status = LOS_ArchMmuMap(&kSpace->archMmu bssEndBoundary
  85. SYS_MEM_BASE + bssEndBoundary - KERNEL_VMM_BASE
  86. kmallocLength >> MMU_DESCRIPTOR_L2_SMALL_SHIFT
  87. VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE);
  88. if (status != (kmallocLength >> MMU_DESCRIPTOR_L2_SMALL_SHIFT)) {
  89. VM_ERR("unmap failed, status: %d" status);
  90. return;
  91. }
  92. LOS_VmSpaceReserve(kSpace kmallocLength bssEndBoundary);
  93. /* we need free tmp ttbase */
  94. oldTtPhyBase = OsArmReadTtbr0();//读取TTB值
  95. oldTtPhyBase = oldTtPhyBase & MMU_DESCRIPTOR_L2_SMALL_FRAME;
  96. OsArmWriteTtbr0(kSpace->archMmu.physTtb | MMU_TTBRx_FLAGS);//内核页表基地址写入CP15 c2(TTB寄存器)
  97. ISB;
  98. /* we changed page table entry, so we need to clean TLB here */
  99. OsCleanTLB();//清空TLB缓冲区
  100. (VOID)LOS_MemFree(m_aucSysMem0 (VOID *)(UINTPTR)(oldTtPhyBase - SYS_MEM_BASE + KERNEL_VMM_BASE));//释放内存池
  101. }

LOS_ArchMmuMap

mmu的map 就是生成L1,L2页表项的过程,以供虚实地址的转换使用,还是直接看代码吧,代码说明一切!

  1. //所谓的 map 就是 生成L1,L2页表项的过程
  2. status_t LOS_ArchMmuMap(LosArchMmu *archMmu VADDR_T vaddr PADDR_T paddr size_t count UINT32 flags)
  3. {
  4. PTE_T l1Entry;
  5. UINT32 saveCounts = 0;
  6. INT32 mapped = 0;
  7. INT32 checkRst;
  8. checkRst = OsMapParamCheck(flags vaddr paddr);//检查参数
  9. if (checkRst < 0) {
  10. return checkRst;
  11. }
  12. /* see what kind of mapping we can use */
  13. while (count > 0) {
  14. if (MMU_DESCRIPTOR_IS_L1_SIZE_ALIGNED(vaddr) && //虚拟地址和物理地址对齐 0x100000(1M)时采用
  15. MMU_DESCRIPTOR_IS_L1_SIZE_ALIGNED(paddr) && //section页表项格式
  16. count >= MMU_DESCRIPTOR_L2_NUMBERS_PER_L1) { //MMU_DESCRIPTOR_L2_NUMBERS_PER_L1 = 0x100
  17. /* compute the arch flags for L1 sections cache, r ,w ,x, domain and type */
  18. saveCounts = OsMapSection(archMmu flags &vaddr &paddr &count);//生成L1 section类型页表项并保存
  19. } else {
  20. /* have to use a L2 mapping, we only allocate 4KB for L1, support 0 ~ 1GB */
  21. l1Entry = OsGetPte1(archMmu->virtTtb vaddr);//获取L1页面项
  22. if (OsIsPte1Invalid(l1Entry)) {//L1 fault页面项类型
  23. OsMapL1PTE(archMmu &l1Entry vaddr flags);//生成L1 page table类型页表项并保存
  24. saveCounts = OsMapL2PageContinous(l1Entry flags &vaddr &paddr &count);//生成L2 页表项目并保存
  25. } else if (OsIsPte1PageTable(l1Entry)) {//L1 page table页面项类型
  26. saveCounts = OsMapL2PageContinous(l1Entry flags &vaddr &paddr &count);//生成L2 页表项目并保存
  27. } else {
  28. LOS_Panic("%s %d, unimplemented tt_entry %x\n" __FUNCTION__ __LINE__ l1Entry);
  29. }
  30. }
  31. mapped += saveCounts;
  32. }
  33. return mapped;
  34. }
  35. STATIC UINT32 OsMapL2PageContinous(PTE_T pte1 UINT32 flags VADDR_T *vaddr PADDR_T *paddr UINT32 *count)
  36. {
  37. PTE_T *pte2BasePtr = NULL;
  38. UINT32 archFlags;
  39. UINT32 saveCounts;
  40. pte2BasePtr = OsGetPte2BasePtr(pte1);
  41. if (pte2BasePtr == NULL) {
  42. LOS_Panic("%s %d, pte1 %#x error\n" __FUNCTION__ __LINE__ pte1);
  43. }
  44. /* compute the arch flags for L2 4K pages */
  45. archFlags = OsCvtPte2FlagsToAttrs(flags);
  46. saveCounts = OsSavePte2Continuous(pte2BasePtr OsGetPte2Index(*vaddr), *paddr | archFlags *count);
  47. *paddr += (saveCounts << MMU_DESCRIPTOR_L2_SMALL_SHIFT);
  48. *vaddr += (saveCounts << MMU_DESCRIPTOR_L2_SMALL_SHIFT);
  49. *count -= saveCounts;
  50. return saveCounts;
  51. }

OsMapL2PageContinous 没有加注释,希望你别太懒,赶紧动起来,到这里应该都能看懂了!最好能结合 鸿蒙内核源码分析(内存汇编篇)一起看理解会更深透。

鸿蒙内核源码分析.总目录

v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51.c.h .o

百万汉字注解.百篇博客分析

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee| github| csdn| coding >

百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< 51cto| csdn| harmony| osc >

关注不迷路.代码即人生

QQ群:790015635 | 入群密码: 666

原创不易,欢迎转载,但请注明出处.

鸿蒙内核源码分析(内存映射篇) | 虚拟内存虚在哪里 | 百篇博客分析OpenHarmony源码 | v15.03的更多相关文章

  1. 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 百篇博客分析OpenHarmony源码 | v16.02

    百篇博客系列篇.本篇为: v16.xx 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪 ...

  2. 鸿蒙内核源码分析(内存汇编篇) | 谁是虚拟内存实现的基础 | 百篇博客分析OpenHarmony源码 | v14.14

    百篇博客系列篇.本篇为: v14.xx 鸿蒙内核源码分析(内存汇编篇) | 谁是虚拟内存实现的基础 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有 ...

  3. 鸿蒙内核源码分析(内存管理篇) | 虚拟内存全景图是怎样的 | 百篇博客分析OpenHarmony源码 | v12.04

    百篇博客系列篇.本篇为: v12.xx 鸿蒙内核源码分析(内存管理篇) | 虚拟内存全景图是怎样的 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有 ...

  4. 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分配方式  | 百篇博客分析OpenHarmony源码 | v11.02

    百篇博客系列篇.本篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分配方式 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪些 ...

  5. 鸿蒙内核源码分析(内存主奴篇) | 皇上和奴才如何相处 | 百篇博客分析OpenHarmony源码 | v10.04

    百篇博客系列篇.本篇为: v10.xx 鸿蒙内核源码分析(内存主奴篇) | 皇上和奴才如何相处 | 51.c.h .o 前因后果相关篇为: v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 ...

  6. v76.01 鸿蒙内核源码分析(共享内存) | 进程间最快通讯方式 | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(共享内存篇) | 进程间最快通讯方式 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) | 同样 ...

  7. v78.01 鸿蒙内核源码分析(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析( ...

  8. 鸿蒙内核源码分析(文件句柄篇) | 深挖应用操作文件的细节 | 百篇博客分析OpenHarmony源码 | v69.01

    百篇博客系列篇.本篇为: v69.xx 鸿蒙内核源码分析(文件句柄篇) | 深挖应用操作文件的细节 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说 ...

  9. 鸿蒙内核源码分析(VFS篇) | 文件系统和谐共处的基础 | 百篇博客分析OpenHarmony源码 | v68.01

    子曰:"质胜文则野,文胜质则史.文质彬彬,然后君子." <论语>:雍也篇 百篇博客系列篇.本篇为: v68.xx 鸿蒙内核源码分析(VFS篇) | 文件系统和谐共处的基 ...

随机推荐

  1. SpringBoot集成websocket(java注解方式)

    第一种:SpringBoot官网提供了一种websocket的集成方式 第二种:javax.websocket中提供了元注解的方式 下面讲解简单的第二种 添加依赖 <dependency> ...

  2. 定时执行的任务Quartz.net

  3. js中使用function定义类、实例化,函数的调用方法

    function Test002(name, age){ name, age, this.printInfo = function(){ //定义的公有方法 console.log(name, age ...

  4. WPF学习笔记一 依赖属性及其数据绑定

    本文想通过由浅入深的讲解让读者比较深的理解依赖属性.  首先,我们回顾一下依赖属性的发展历史. 最初,人们提出面向对象编程时,并没有属性这个说法,当时叫做成员变量.一个对象由成员变量和成员函数组成,如 ...

  5. LeetCoded第20题题解--有效的括号

    有效的括号 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效. 有效字符串需满足: 左括号必须用相同类型的右括号闭合. 左括号必须以正确的顺序闭合. 注意空 ...

  6. docker安装与配置nginx详细过程

    注:大鸟飞过,此方式只用于快速搭建使用 第一步 pull nginx 命令:docker pull nginx 第二步 启动nginx 命令:docker run --name nginx -p 80 ...

  7. 在Raspberry Pi 3B+上安装Windows 10 IoT

    下载 进入树莓派下载页面,当前网址https://www.raspberrypi.org/downloads/ 选择Windows 10 IoT Core,当前网址https://docs.micro ...

  8. 用C++实现的增强Eratosthenes筛法程序

    运行示例 PS H:\Read\num\x64\Release> .\eSievePro Eratosthenes sieve: a method to find out all primes ...

  9. 使用ECharts绘制网址径向树状图

    an.rustfisher.com有很多内容,很多页面.如果用一个树状图把所有页面展示出来会是什么效果? 第一时间想到了ECharts. 最后效果: https://an.rustfisher.com ...

  10. AQS实现原理

    AQS实现原理 AQS中维护了一个volatile int state(共享资源)和一个CLH队列.当state=1时代表当前对象锁已经被占用,其他线程来加锁时则会失败,失败的线程被放入一个FIFO的 ...