1.generator 执行机制分析
2.Linux内核源码分析:Linux内核版本号和源码目录结构
3.JDK源码分析Timer/TimerTask 源码分析
4.ART 深入浅出 - 为何 Thread.getStackTrace() 会崩溃?
generator 执行机制分析
本文以下面代码为例,源码分析 generator 执行机制相关的分析源码,版本为 V8 7.7.1。源码
首先,分析当 let iterator = test() 开始执行时,源码V8 调用 Runtime_CreateJSGeneratorObject,分析rapidscada源码创建一个生成器对象。源码此函数逻辑是分析创建 JSGeneratorObject 的实例,设置相关属性后返回生成器对象 generator。源码此时生成器对象 generator 被保存在累加器中。分析在字节码 SuspendGenerator 的源码处理函数中,该函数暂停当前函数的分析执行,并多次调用 StoreObjectField 来保存生成器函数当前运行的源码状态。最后返回累加器中的分析值,即生成器对象 generator。源码因此,生成器函数在执行到“第一次暂停”的位置时,处于暂停状态。
在有了生成器对象后,可以调用其 next 方法让生成器函数继续执行。当 JavaScript 代码继续执行 iterator.next() 时,生成器对象的 next 方法被调用。生成器函数恢复执行需要 CPU 的寄存器操作。在笔者的 Mac 下,调用链路为GeneratorBuiltinsAssembler::GeneratorPrototypeResume-> CodeFactory::ResumeGenerator-> Builtins::Generate_ResumeGeneratorTrampoline。shopx5源码之后,调用 X 汇编,使生成器函数在暂停处恢复执行。此过程通过 Builtins::Generate_ResumeGeneratorTrampoline 函数完成,函数通过将未来要返回的地址压栈,并跳转到生成器函数 test 暂停的地方,继续执行。
生成器函数从暂停处继续执行后,字节码一行一行往下执行,直到遇到下一个 SuspendGenerator,即“第二次暂停”。这是由 yield 带来的。yield 被 V8 编译成 SuspendGenerator 和 ResumeGenerator 两条字节码,分别表示保存状态暂停和恢复状态继续执行。
async/await 与 generator 的关系分析:async/await 和 generator 都有暂停当前函数执行并从暂停处恢复执行的能力。await 和 yield 对应的字节码都是 SuspendGenerator 和 ResumeGenerator。生成器函数暂停时,需要调用生成器对象的 next 方法来从暂停处恢复执行。async 函数依赖 Promise 和 microtask,当 V8 在执行 microtask 队列时,已经暂停的 async 函数恢复执行。async 函数通过 Generator 和 Promise 获得保存状态暂停和恢复状态执行的能力,以及自我驱动向下继续执行的能力,从而避免调用 next 方法。荒野过检测源码
JavaScript 中的函数类型较为复杂。虽然在 JavaScript 中,1 和 0.1 都是 number,但在 V8 中它们是不同的类型,内存表示和 CPU 运算指令也有所不同。因此,即使在 JavaScript 中 typeof 都返回 function 的 test、test1、test2,在 V8 中是不同的类型。日常开发中,当一个组件/方法需要一个函数做为参数时,需要确保正确传递 ES6 之前的函数、async 函数或生成器函数,以避免运行时错误。
原生 generator 与 babel 转译的区别:在日常开发中,生成器/async 函数会被 babel 转译成类似下面的代码。这段代码中,test 函数被多次调用,但由于闭包保存了函数执行的状态,每次调用 test 都是新的 test。这种实现非常巧妙,但与 V8 中生成器函数的原理有较大区别。Babel 转译的带源码的dll代码无法生成字节码 SuspendGenerator 和 ResumeGenerator。
总结:生成器函数被调用时,开始执行并返回生成器对象后暂停。调用 iterator.next() 后,生成器函数从第一次暂停的位置恢复执行,遇到 yield(SuspendGenerator)后第二次暂停。
Linux内核源码分析:Linux内核版本号和源码目录结构
深入探索Linux内核世界:版本号与源码结构剖析
Linux内核以其卓越的稳定性和灵活性著称,版本号的精心设计彰显其功能定位。Linux采用xxx.yyy.zzz的格式,其中yy代表驱动和bug修复,zz则是修订次数的递增。主版本号(xx)与次版本号(yy)共同描绘了核心功能的大致轮廓,而修订版(zz)则确保了系统的稳定性与可靠性。
Linux源码的结构犹如一座精密的城堡,由多个功能强大的模块构成。首先,arch目录下包含针对不同体系结构的代码,比如RISC-V和x的虚拟地址翻译,是内核与硬件之间的重要桥梁。接着,block与drivers的区别在于,前者封装了通用的块设备操作,如读写,而后者则根据特定硬件设备分布在各自的子目录中,如GPIO设备在drivers/gpio。枭雄帝王online源码
为了保证组件来源的可信度和系统安全,certs目录存放认证和签名相关的代码,预先装载了必要的证书。从Linux 2.2版本开始,内核引入动态加载模块机制,fs和net目录下的代码分别支持虚拟文件系统和网络协议,这大大提升了灵活性,但同时也对组件验证提出了更高要求,以防止恶意代码的入侵。
内核的安全性得到了进一步加强,crypto目录包含了各种加密算法,如AES和DES,它们为硬件驱动提供了性能优化。同时,内核还采用了压缩算法,如LZO和LZ4,以减小映像大小,提升启动速度和内存利用效率。
文档是理解内核运作的关键,《strong>Documentation目录详尽地记录了模块的功能和规范。此外,include存储内核头文件,init负责初始化过程,IPC负责进程间通信,kernel核心代码涵盖了进程和中断管理,lib提供了通用库函数,而mm则专注于内存管理。网络功能则在net目录下,支持IPv4和TCP/IPv6等协议。
内核的实用工具和示例代码在scripts和samples目录下,而security则关注安全机制,sound负责音频驱动,tools则存放开发和调试工具,如perf和kconfig。用户内核源码在usr目录,虚拟化支持在virt,而LICENSE目录保证了源码的开放和透明。
最后,Makefile是编译内核的关键,README文件则包含了版本信息、硬件支持、安装配置指南,以及已知问题、限制和BUG修复等重要细节。这份详尽的指南是新用户快速入门Linux内核的绝佳起点。
通过深入研究这些目录,开发者和爱好者可以更全面地理解Linux内核的运作机制,从而更好地开发、维护和优化这个强大的操作系统。[原文链接已移除,以保护版权]
JDK源码分析Timer/TimerTask 源码分析
在Java中,Timer 类是实现定时任务的常见工具,配合TimerTask 实现定时、延迟或周期性执行。本文将深入剖析其源码结构和工作原理。 Timer 的核心机制涉及关键类,包括TimerThread、Timer、TimerQueue 和 TimerTask。一个Timer 实例对应一个TimerThread,负责执行任务;Timer拥有一个TimerThread和一个TimerQueue,而TimerQueue中存储了多个TimerTask。这样的关系可以总结为:1个 TimerThread -> 1个线程
1个 Timer -> 持有 TimerThread 和 TimerQueue
1个 TimerQueue -> 持有多个 TimerTask
源码分析时,首先创建Timer时,thread和queue会在声明时初始化为final类型,确保它们与Timer的生命周期绑定。接着,任务通过schedule方法进行调度,这个过程会根据TimerTask类型设置不同的period参数。 TimerTask 是一个实现了Runnable接口的抽象类,子类需实现run方法。TimerTask的类型决定了其执行周期。TimerThread的run方法包含一个死循环,类似Android的Handler机制。 TimerQueue作为队列,内部使用完全二叉树结构,add和fixUp方法用于维护最小执行时间的节点在队列前端。purge方法执行后,会调用fixDown方法进行调整。 总之,每个Timer实例由一个线程和一个二叉堆(通过TimerQueue实现)组成,用于管理定时任务的执行顺序。理解这些核心组件的交互,有助于深入理解Timer的工作机制。ART 深入浅出 - 为何 Thread.getStackTrace() 会崩溃?
前言
Thread 类的 getStackTrace() 方法是日常开发中常用的工具,特别是用于卡顿检测方案,如周期性调用 Thread.getStackTrace() 或 Thread.getAllStackTraces 获取主线程调用栈。然而,在频繁调用时,有时会引发崩溃现象。崩溃栈显示关键调用链路涉及 VMStack_getThreadStackTrace()、ThreadList::SuspendThreadByPeer()、ThreadSuspendByPeerWarning()、~LogMessage() 和 Runtime::Abort() 等。接下来,我们将逐步分析这一过程及其原因。
Thread.getStackTrace 源码分析
在 ART 源码版本 Android 中,核心调用在于 VMStack.cc 文件的 GetThreadStack 方法。关键步骤已用注释标记。GetThreadStack() 内部逻辑包括挂起线程、调用回调函数生成调用栈以及恢复线程。挂起线程的主要方法是 SuspendThreadByPeer(),该函数包含多步骤,但主要涉及初始化变量、循环检查目标线程状态、设置挂起标志位以及循环判断目标线程是否挂起,直至超时。
关键点之一在于,当超时时调用 ThreadSuspendByPeerWarning() 函数,其内部 LOG 调用会在严重级别为 FATAL 时直接触发 Abort。这就是文章开头提到的崩溃栈的原因。通常,为避免此崩溃,可以使用 ThreadList::SuspendThreadByThreadId() 函数,该函数在超时时仅产生 WARNING 级别的 LOG,并不会终止运行。
超时时间由 thread_suspend_timeout_ns_ 变量决定,此变量在 Runtime 初始化时传入 ThreadList,若未指定,则默认值在 thread_list.h 文件中。默认值为 秒,即时间单位为纳秒。因此, 秒的默认超时时间是导致问题的原因之一。
另一个关键点涉及 ART 如何实际挂起线程。关键代码是 suspended_thread->ModifySuspendCount(),它设置挂起标志位。该函数的原理已通过注释解释。此外,从检查点的角度出发,Java 中的 Check Point 概念在解释执行和机器码执行过程中起到暂停当前指令执行的作用,从而挂起当前线程。检查点存在于 Java 指令执行过程中的特定位置,如 switch/case 语句。
总结
通过深入分析,我们知道 Java 层的 Thread.getStackTrace() 方法本质上是将目标线程设置为请求挂起的状态,然后循环判断线程是否挂起。这一过程依赖于各个检查点的执行,从而在调用栈生成过程中引发超时。因此,目标线程迟迟未能执行到检查点是 Thread.getStackTrace() 方法超时的根本原因。