【copy网页源码】【客户笔记源码】【jqueryui源码分析】源码解读内存

来源:宝塔面板置源码

1.Redis源码阅读(1)——zmalloc
2.Kswapd 源码解析
3.UE4源码剖析:MallocBinned(上)
4.开源即时通讯GGTalk源码剖析之:客户端全局缓存及本地存储
5.8086模拟器8086tiny源码分析(8)执行mov指令(五)段寄存器拾遗
6.Hermes源码分析(二)——解析字节码

源码解读内存

Redis源码阅读(1)——zmalloc

       zmalloc是源码一个简化内存分配的库,包含以下API函数:

       zmalloc

       zcalloc

       zrealloc

       zfree

       zstrdup

       zmalloc_used_memory

       zmalloc_set_oom_handler

       zmalloc_get_rss

       zmalloc_get_allocator_info

       zmalloc_get_private_dirty

       zmalloc_get_smap_bytes_by_field

       zmalloc_get_memory_size

       zlibc_free

       其中,解读zmalloc用于分配内存,内存zcalloc在分配内存的源码同时初始化为0,zrealloc用于重新分配内存,解读zfree用于释放内存,内存copy网页源码zstrdup用于复制字符串并分配内存,源码zmalloc_used_memory用于获取已分配内存的解读大小,zmalloc_set_oom_handler用于设置内存溢出处理器,内存zmalloc_get_rss用于获取当前进程的源码内存使用量,zmalloc_get_allocator_info用于获取分配器信息,解读zmalloc_get_private_dirty用于获取私有脏数据,内存zmalloc_get_smap_bytes_by_field用于获取指定字段的源码内存使用量,zmalloc_get_memory_size用于获取内存大小,解读zlibc_free用于释放内存。内存

       在zmalloc中,宏函数update_zmalloc_stat_alloc用于更新used_memory的值。这个宏函数中的if语句用于补齐分配的内存字节数到sizeof(long),但是我不太理解5.0源码中为什么atomicIncr使用的是__n而不是直接对_n进行操作。测试发现,used_memory的值并未对齐到8,那么if语句的存在意义何在呢?

       同样地,update_zmalloc_stat_free宏函数用于更新已释放内存的统计信息。与update_zmalloc_stat_alloc相比,虽然malloc_usable_size已经返回精确的字节数,但update_zmalloc_stat_alloc为何不直接使用atomicIncr更新used_memory呢?在Unstable分支中,已有开发者对此进行了优化。

Kswapd 源码解析

       kswapd是Linux内核中的一个内存回收线程,主要用于内存不足时回收内存。初始化函数为kswapd_init,内核为每个节点分配一个kswapd进程。每个节点的pg_data_t结构体中维护四个成员变量,用于管理kswapd线程。

       在初始化后,每个节点的kswapd线程进入睡眠状态。唤醒时机主要在被动唤醒和主动唤醒两种场景:被动唤醒是内存分配进程唤醒并完成异步内存回收后,对节点内存环境进行平衡度检查,若平衡则线程短暂休眠ms后主动唤醒。主动唤醒是内存回收策略调用kswapd,对节点进行异步内存回收,客户笔记源码让节点达到平衡状态。

       内存回收包括快速和直接两种方式,但系统周期性调用kswapd线程平衡不满足要求的节点,因为有些任务内存分配不允许阻塞或激活I/O访问,回收内存相当于亡羊补牢,系统利用空闲时间进行内存回收是必要的。

       kswapd线程通过module_init(kswapd_init)创建,一般处于睡眠状态等待被唤醒,当系统内存紧张时,会唤醒kswapd线程,调整不平衡节点至平衡状态。

       kswapd函数包含alloc_order、reclaim_order和classzone_idx三个变量,用于控制线程执行流程。kswapd_try_to_sleep函数判断是否睡眠并让出CPU控制权,同时是线程唤醒的入口。balance_pgdat函数是实际内存回收操作,涉及内存分配失败后唤醒kswapd线程,调用此函数对指定节点进行异步内存回收。

       kswapd_shrink_node函数通过shrink_node对低于sc->reclaim_idx的非平衡zone区域进行回收。

       总结kswapd执行流程,其生命周期与Linux操作系统相似,平时处于睡眠状态让出CPU控制权。在内存紧张时被唤醒,有被动唤醒和周期性主动唤醒两种时机。被动唤醒发生在内存分配任务获取不到内存时,表明系统内存环境紧张,主动唤醒则是内存回收策略的执行。线程周期性唤醒在被动唤醒后的短暂时间内,原因在于系统内存环境紧张,需要在这段时间内进行内存回收。

UE4源码剖析:MallocBinned(上)

       近期着手UE4项目开发,对UnrealEngine已久仰慕,终于得此机会深入探索。鉴于项目内存性能问题,决定从内存分配器着手,深入研读UE4源码。虽个人水平有限,尚不能全面理解,jqueryui源码分析但愿借此机会揭开源码神秘面纱,让新手朋友们不再感到陌生。

       UE4内存分配器位于硬件抽象层HAL(Hardware Abstraction Layer)中。具体装箱内存分配器代码位于VS项目目录:UE4/Source/Runtime/Core/Private/HAL/MallocBinned。

       分析从ApplePlatformMemory::BaseAllocator开始,可发现Mac平台的默认分配器为MallocBinned,iOS的默认分配器为MallocAnsi。以下将重点分析MallocBinned。

       一、确定对齐方式

       FScopeLock用于局部线程锁,确保线程同步。关于Alignment的确定,通常使用默认值。默认值取决于内存对齐方式,此处默认对齐为8字节。

       二、确定有足够空间来内存对齐

       代码中,SpareBytesCount用于确认空间足够。若分配内存小于8字节,则按Alignment大小匹配箱体;若大于8字节,则按Size + Alignment - sizeof(FFreeMem)匹配箱体。

       三、确定箱体大小

       根据Size的大小,有三种不同的处理方式。k以下的内存分配采用装箱分配,PoolTable中包含个不同大小的池子。

       四、初始化内存池

       分析内存池初始化过程,主要工作包括:确定内存大小,分配内存块,设置内存池基本信息。

       五、内存装箱

       AllocateBlockFromPool从内存池中分配一个Block,实现内存装箱过程。

开源即时通讯GGTalk源码剖析之:客户端全局缓存及本地存储

       继上篇详细介绍了 GGTalk 内置的虚拟数据库,本文将深入探讨 GGTalk 客户端的全局缓存及本地存储机制。对于还没有获取GGTalk源码的朋友,文章底部附有下载链接。

       一. GGTalk 客户端缓存设计

       核心在于ClientGlobalCache类,apktool源码错误它在内存中保存用户和群组数据。此类接受泛型参数TUser和TGroup,且限定TUser和TGroup需实现特定接口,还继承自BaseGlobalCache类。三个私有字段分别用于存储用户、群组和缓存信息。

       构造函数接收五个参数,用于初始化私有字段,并调用父类BaseGlobalCache的Initialize方法,实现缓存初始化逻辑。

       二. GGTalk 客户端本地持久化存储

       BaseGlobalCache类中,originUserLocalPersistence字段负责本地文件存储。它包含四个属性,代表好友列表、群组列表、快捷回复列表和最近联系人/群列表。

       Load和Save方法用于读写本地文件,将数据存入或从文件加载。在了解本地缓存的核心概念后,回到Initialize方法,读取本地文件数据,缓存到内存中。

       三. 更新本地缓存

       在用户登录或断线重连时,系统会比较本地缓存与服务器数据,更新缺失或过时的信息。当缓存中只有用户自己时,会从服务器加载所有联系人;当存在其他数据时,会更新本地缓存以反映服务器最新状态。

       四. 总结

       GGTalk客户端缓存流程包括读取本地缓存、从服务器加载更新数据,以及在窗口关闭时将当前用户数据缓存。下篇将解析消息收发及处理机制。

       敬请期待:《GGTalk 开源即时通讯系统源码剖析之:消息收发及处理》。底部链接提供下载GGTalk源码。

模拟器tiny源码分析(8)执行mov指令(五)段寄存器拾遗

       分析模拟器tiny源码中关于mov指令与内存访问的处理

       在分析mov指令时,我们关注到了指令可能访问内存,这自然引出了CPU内存地址的结构问题。内存地址通常由两部分组成:段寄存器和位偏移地址。

       在我们的源码陈丹丹分析中,大部分关注的都是偏移地址,但事实上,段寄存器通常默认为DS(数据段寄存器),除非通过段跨越前缀修改。

       以mov [bx],h为例,编译后指令序列显示为:0xc7,0x,0x,0x。而如果我们修改段前缀为ss,即mov ss:[bx],h,则指令序列变为:0x,0xc7,0x,0x,0x,这里多出了一字节。

       那么,tiny在处理段前缀时是如何操作的呢?答案是通过宏SEGREG。如果使用了段跨越前缀,参数1会决定使用哪个段寄存器,通常默认为DS;而参数2则决定偏移寄存器1的使用。

       参数3由两部分组成:一部分是偏移寄存器2,另一部分则是内存地址。最终,地址计算方式为:段寄存器* + 偏移寄存器1 + 偏移寄存器2 + 内存地址。这使得指令能够准确指向内存位置。

Hermes源码分析(二)——解析字节码

        前面一节 讲到字节码序列化为二进制是有固定的格式的,这里我们分析一下源码里面是怎么处理的

        这里可以看到首先写入的是魔数,他的值为

        对应的二进制见下图,注意是小端字节序

        第二项是字节码的版本,笔者的版本是,也即 上图中的4a

        第三项是源码的hash,这里采用的是SHA1算法,生成的哈希值是位,因此占用了个字节

        第四项是文件长度,这个字段是位的,也就是下图中的为0aa,转换成十进制就是,实际文件大小也是这么多

        后面的字段类似,就不一一分析了,头部所有字段的类型都可以在BytecodeFileHeader.h中看到,Hermes按照既定的内存布局把字段写入后再序列化,就得到了我们看到的字节码文件。

        这里写入的数据很多,以函数头的写入为例,我们调用了visitFunctionHeader方法,并通过byteCodeModule拿到函数的签名,将其写入函数表(存疑,在实际的文件中并没有看到这一部分)。注意这些数据必须按顺序写入,因为读出的时候也是按对应顺序来的。

        我们知道react-native 在加载字节码的时候需要调用hermes的prepareJavaScript方法, 那这个方法做了些什么事呢?

        这里做了两件事情:

        1. 判断是否是字节码,如果是则调用createBCProviderFromBuffer,否则调用createBCProviderFromSrc,我们这里只关注createBCProviderFromBuffer

        2.通过BCProviderFromBuffer的构造方法得到文件头和函数头的信息(populateFromBuffer方法),下面是这个方法的实现。

        BytecodeFileFields的populateFromBuffer方法也是一个模版方法,注意这里调用populateFromBuffer方法的是一个 ConstBytecodeFileFields对象,他代表的是不可变的字节码字段。

        细心的读者会发现这里也有visitFunctionHeaders方法, 这里主要为了复用visitBytecodeSegmentsInOrder的逻辑,把populator当作一个visitor来按顺序读取buffer的内容,并提前加载到BytecodeFileFields里面,以减少后面执行字节码时解析的时间。

        Hermes引擎在读取了字节码之后会通过解析BytecodeFileHeader这个结构体中的字段来获取一些关键信息,例如bundle是否是字节码格式,是否包含了函数,字节码的版本是否匹配等。注意这里我们只是解析了头部,没有解析整个字节码,后面执行字节码时才会解析剩余的部分。

        evaluatePreparedJavaScript这个方法,主要是调用了HermesRuntime的 runBytecode方法,这里hermesPrep时上一步解析头部时获取的BCProviderFromBuffer实例。

        runBytecode这个方法比较长,主要做了几件事情:

        这里说明一下,Domain是用于垃圾回收的运行时模块的代理, Domain被创建时是空的,并跟随着运行时模块进行传播, 在运行时模块的整个生命周期内都一直存在。在某个Domain下创建的所有函数都会保持着对这个Domain的强引用。当Domain被回收的时候,这个Domain下的所有函数都不能使用。

        未完待续。。。

linux内核源码:内存管理——内存分配和释放关键函数分析&ZGC垃圾回收

       本文深入剖析了Linux内核源码中的内存管理机制,重点关注内存分配与释放的关键函数,通过分析4.9版本的源码,详细介绍了slab算法及其核心代码实现。在内存管理中,slab算法通过kmem_cache结构体进行管理,利用数组的形式统一处理所有的kmem_cache实例,通过size_index数组实现对象大小与kmem_cache结构体之间的映射,从而实现高效内存分配。其中,关键的计算方法是通过查找输入参数的最高有效位序号,这与常规的0起始序号不同,从1开始计数。

       在找到合适的kmem_cache实例后,下一步是通过数组缓存(array_cache)获取或填充slab对象。若缓存中有可用对象,则直接从缓存分配;若缓存已空,会调用cache_alloc_refill函数从三个slabs(free/partial/full)中查找并填充可用对象至缓存。在对象分配过程中,array_cache结构体发挥了关键作用,它不仅简化了内存管理,还优化了内存使用效率。

       对象释放流程与分配流程类似,涉及数组缓存的管理和slab对象的回收。在cache_alloc_refill函数中,关键操作是检查slab_partial和slab_free队列,寻找空闲的对象以供释放。整个过程确保了内存资源的高效利用,避免了资源浪费。

       总结内存操作函数概览,栈与堆的区别是显而易见的。栈主要存储函数调用参数、局部变量等,而堆用于存放new出来的对象实例、全局变量、静态变量等。由于堆的动态分配特性,它无法像栈一样精准预测内存使用情况,导致内存碎片问题。为了应对这一挑战,Linux内核引入了buddy和slab等内存管理算法,以提高内存分配效率和减少碎片。

       然而,即便使用了高效的内存管理算法,内存碎片问题仍难以彻底解决。在C/C++中,没有像Java那样的自动垃圾回收机制,导致程序员需要手动管理内存分配与释放。如果忘记释放内存,将导致资源泄漏,影响系统性能。为此,业界开发了如ZGC和Shenandoah等垃圾回收算法,以提高内存管理效率和减少内存碎片。

       ZGC算法通过分页策略对内存进行管理,并利用“初始标记”阶段识别GC根节点(如线程栈变量、静态变量等),并查找这些节点引用的直接对象。此阶段采用“stop the world”(STW)策略暂停所有线程,确保标记过程的准确性。接着,通过“并发标记”阶段识别间接引用的对象,并利用多个GC线程与业务线程协作提高效率。在这一过程中,ZGC采用“三色标记”法和“remember set”机制来避免误回收正常引用的对象,确保内存管理的精准性。

       接下来,ZGC通过“复制算法”实现内存回收,将正常引用的对象复制到新页面,将旧页面的数据擦除,从而实现内存的高效管理。此外,通过“初始转移”和“并发转移”阶段进一步优化内存管理过程。最后,在“对象重定位”阶段,完成引用关系的更新,确保内存管理过程的完整性和一致性。

       通过实测,ZGC算法在各个阶段展现出高效的内存管理能力,尤其是标记阶段的效率,使得系统能够在保证性能的同时,有效地管理内存资源。总之,内存管理是系统性能的关键因素,Linux内核通过先进的算法和策略,实现了高效、灵活的内存管理,为现代操作系统提供稳定、可靠的服务。

RocksDb 源码剖析 (1) | 如何混合 new 、mmap 设计高效内存分配器 arena ?

       本文旨在深入剖析RocksDb源码,从内存分配器角度着手。RocksDb内包含MemoryAllocator和Allocator两大类内存分配器。MemoryAllocator作为基类,提供MemkindKmemAllocator和JemallocNodumpAllocator两个子类,分别集成memkind和jemalloc库的功能,实现内存分配与释放。

       接着,重点解析Allocator类及其子类Arena的实现。基类Allocator提供两个关键接口:内存分配与对齐。Arena类采用block为单位进行内存分配,先分配一个block大小的内存,后续满足需求时,优先从block中划取,以减少内存浪费。一个block的大小由kBlockSize参数决定。分配策略中,Arena通过两个指针(aligned_alloc_ptr_和unaligned_alloc_ptr_)分别管理对齐与非对齐内存,提高内存利用效率。

       分配内存时,Arena通过构造函数初始化成员变量,包括block大小、内存在栈上的分配与mmap机制的使用。构造函数内使用OptimizeBlockSize函数确保block大小合理,减少内存对齐浪费。Arena中的内存管理逻辑清晰,尤其在分配新block时,仅使用new操作,无需额外内存对齐处理。

       分配内存流程中,AllocateNewBlock函数直接调用new分配内存,而AllocateFromHugePage和AllocateFallback函数则涉及mmap机制的使用与内存分配策略的统一。这些函数共同构成了Arena内存管理的核心逻辑,实现了灵活高效地内存分配。

       此外,Arena还提供AllocateAligned函数,针对特定对齐需求分配内存。这一函数在使用mmap分配内存时,允许用户自定义对齐大小,优化内存使用效率。在处理对齐逻辑时,Arena巧妙地利用位运算优化计算过程,提高了代码效率。

       总结而言,RocksDb的内存管理机制通过Arena类实现了高效、灵活的内存分配与管理。通过深入解析其源码,可以深入了解内存对齐、内存分配与多线程安全性的实现细节,为开发者提供宝贵的内存管理实践指导。未来,将深入探讨多线程内存分配器的设计,敬请期待后续更新。

文章所属分类:娱乐频道,点击进入>>