在这一部分中我们来重点分析Erlang虚拟机的调度策略。
由第一分部的介绍可以得知,在ERTS_SMP模式中,erl_start()函数在创建好第一个进程后最后调用的两个函数分别为erts_start_schedulers()和erts_sys_main_thread()。在单核模式下,erl_start()函数在调用set_main_stack_size()进行一些栈区设置后调用了process_main()函数,然后进入了单核模式下的进程切换调度。这里我们重点讨论在ERTS_SMP模式下Erlang虚拟机的调度策略。
进入erts_start_schedulers()函数的定义(该函数的定义在otp_src_R15B02/erts/emulator/beam/erl_process.c文件中)。erts_start__schedulers()函数通过前面early_init()函数获取的cpu数目来为每个cpu创建一个调度线程,然后创建一个附属线程。调度线程的入口函数是sched_thread_func(),这个函数的定义也在erl_process.c文件中,该函数设置调度器初始化时使用的一系列回调函数,然后进行初始化并等待其他调度线程完成初始化,然后每个调度线程最终也会调用process_main()。而process_main()函数在整个erlang虚拟机的运行过程中会被调用两次,第一次调用是进行emulator的初始化工作,首先被init_emulator()调用,init_emulator()又被erl_init()调用,erl_init的调用关系已经在第一部分中被说明,在该次调用中process_main()在进行一些指令集所需的寄存器参数定义后跳转到init_emulator程序段中,如图3.1:
图3.1
在这部分代码中,process_main()首先初始化了emulator调用的error_handler和一些其他的BIF的入口函数,然后初始化了所有BIF的函数导出表,当这些工作都完成后,process_main()函数的第一次调用结束并return;如图3.2所示:
图3.2
process_main()函数的第二次调用是被sched_thread_func()调用,在这次调用中process_main()函数进行真正的调度工作,这部分的代码十分的长,大概有4000多行的代码量,因此这个函数是调度的核心,在这次调用中process_main()将进入一个死循环而永远不会返回,因此在真正的调度工作中,process_main()函数没有返回值。
process_main()函数最先调用的是schedule()函数,schedule()通过传入的进程信息c_p和进程已经执行了的reds来调度选择下一个要执行的进程(有可能还是原来的那个c_p,也可能会发生切换)。
schedule()函数的定义和process_main()位于同一个文件,该函数首先通过传递的进程信息获取到对应的调度器(如果c_p = NULL,说明是进行第一次调度,schedule()通过调用erts_get_scheduler_data()从异步队列里获取一个调度器,并且获取与调度器绑定的运行队列,如果c_p != NULL,说明不是第一次调度,那么直接从进程中获取与之绑定的调度器和运行队列)。获取了调度器后,schedule()将计算每次reds的增量以及整个队列的reds增量,然后通过profile_runnable_proc()函数来分析当前的执行进程是继续执行还是需要被调度。
当profile_runnable_proc()分析好进程的执行情况后会设置c_p的status,接着进入一个swith(p->status)的程序段中。如果当前进程执行状态经过分析变成退出状态,将调用handle_pending_exit()进行进程的善后处理并退出。除此之外即为reds用完但是进程尚未退出的情况,调度器会将当前进程调用handle_pending_suspend()函数进行挂起。
接下来调度器需要选择一个新的进程用于执行,首先,schedule()函数会先调用check_balance(rq)检查该调度器绑定的runqueue是否是平衡状态,如果进程数的低于或者高于了迁移的阀值,就调用immigrate()函数进行迁移,Erlang的进程共有四个优先级,如下所示:
/* process priorities */
#define PRIORITY_MAX 0
#define PRIORITY_HIGH 1
#define PRIORITY_NORMAL 2
#define ERTS_NO_PROC_PRIO_LEVELS 4
其中PRIORITY_MAX和PRIORITY_HIGH各有一个优先级队列,PRIORITY_NORMAL和PRIORITY_LOW共用一个优先级队列,一般情况下,PRIORITY_LOW优先级进程只有在调度特定个数的PRIORITY_NORMAL后才会被调度,这种机制保证了PRIORITY_NORMAL优先级高于PRIORITY_LOW优先级被执行,但在某些情况下会引起优先级反转。
每个runqueue和一个scheduler进行绑定,每个runqueue又包含了三个优先级队列:一个PRIORITY_MAX队列,一个PRIORITY_HIGH队列,一个PRIORITY_NORMAL和PRIORITY_LOW共用的混合队列,在进行任务迁移的时候,immigrate()中有个for结构,分别对每种优先级队列进行任务迁移。关于时间片调度算法和Erlang任务迁移算法,这里不做详细说明,请参见论文:Characterizing the Scalability of Erlang VM on Many-core Processors。平衡运行队列中的进程数量后检查运行队列的状态,如果运行队列状态变为ERTS_QUNQ_FLG_SUSPENDED,将调用suspend_scheduler()函数将当前的调度器挂起。如果调度器未被挂起,接着检查runqueue是否有任务,如果没有任务,将调用empty_runq()清除runqueue的状态标志,然后调用try_steal_tast()函数拉取任务,如果拉取成功则调用goto continue_check_activites_to_run跳转到上一个检查点,重新检查runqueue的运行状态,然后按状态调度runqueue。如果没有拉取成功,则调用scheduler_wait()等待系统IO任务,scheduler_wait()函数将调用erl_sys_schedule()阻塞在系统IO上,其实erl_sys_schedule()在unix系列平台下的定义就是一个poll模型的封装,定义如下:
void
erl_sys_schedule(int runnable)
{
#ifdef ERTS_SMP
ERTS_CHK_IO(!runnable);
#else
ERTS_CHK_IO(runnable ? 0 : !check_children());
#endif
ERTS_SMP_LC_ASSERT(!erts_thr_progress_is_blocking());
(void) check_children();
}
如果runqueue的len != 0,那么scheduler先调度runqueue中的PRIORITY_MAX优先队列(通常为系统任务),然后调度PRIORITY_HIGH优先级队列(通常为port任务),然后调度一般任务。当选择好一个任务进行调度后,schedule()函数返回选择的新进程的指针给process_main()。
这时,process_main()函数通过schedule()计算得到了下一个需要执行的process的指针,接下来需要将进程的中断现场恢复到当前的上下文环境中,首先将进程的寄存器参数恢复到每个寄存器中,然后调用SET_I(c_p->i)这个宏定义,来设置register变量I,I代表的是下一条即将执行的Erlang虚拟机指令threaded-code。然后用next指针指向I寄存器中保存的threaded-code地址,经过一系列的跟踪调用后调用Goto(next)这个宏来执行下一条threaded-code。
threaded-code指令段的定义在process_main()中,是以宏OpCase(OpCode)开始的程序段,经过调度的进程后调用Goto(next)将跳转到具体的threaded-code段中继续执行。这里以send原语来举例说明,当Erlang进程调用Pid ! MsgObj原语的时候,将跳转到lb_send这一threaded-code段中,在这个内建函数中首先计算进程的reds消耗(c_p->fcalls = FCALLS - 1),然后将寄存器r0的值赋给了reg[0],register变量r0此时保存的值为要发送到的进程id,x(1)寄存器保存的是要发送的消息体的地址,然后将调用erl_send()。消息在进程间发送的形式和在不同node上的发送形式不同,进程间,是通过Erlang自定义的消息队列,每个进程都有一个公有的消息队列和私有消息队列,消息队列位于进程的堆空间中。当erl_send()执行结束后,lb_send接着执行PreFetch(0, next)这个宏结构,这个宏结构将I指向的地址做+1操作后赋予next指针,然后对进程的消息队列进行检查,调用erts_gc_after_bif_call对发送的消息数据进行内存回收,然后调用goto find_func_info跳转到该程序段中,在该程序段中对执行现场进行错误检查,并将handle_error()结果返回给register变量I,然后跳转到post_error_handling,在这段程序中,如果I == 0,说明执行成功,没有错误产生,将调用goto do_schedule进行跳转,至此一次完整的调度执行流程就完成了,程序将进入下一次调度。如果I != 0那说明指令执行失败,产生了错误,将调用erts_garbage_collect()对执行过程中产生的堆数据进行回收,然后调用Goto(*I)跳转到具体的错误处理函数中。
总结:在Erlang虚拟机的指令集(threaded-code)中提供了send和receive原语,所以erlang语言屏蔽了网络编程开发和进程通信等很多琐碎的问题,更利于高效率的开发出分布式结构的程序。send和receive原语的提供,为进程间消息通信机制也提供了基础支持。
Erlang虚拟机有自己的指令集系统,更方便的设计出适合分布式程序的调度算法和调度粒度。
Erlang虚拟机对系统IO的响应采用了事件驱动模式,这是一种异步模式,而不是同步模式,而Erlang语言中所涉及到的同步语法都是使用异步模式模拟的(即在用户态模式下由虚拟机进行阻塞操作,而不是由操作系统内核来进行同步调用的阻塞操作),虽然逻辑编写会变得更复杂,但这种模式对繁重的IO处理有很大的性能提升,所以Erlang不会出现某个调度线程由于调度内核函数而阻塞在内核态中不能及时切换到用户态。
Erlang虚拟机的每一条虚拟机指令(threaded-code)的执行都会进行错误检查,使erlang程序的容错性更高,也为Erlang的容错性提供了最底层的支持,同时Erlang的进程设计等方面也会考虑容错机制。
相关推荐
Erlang核心开发者Lukas Larsson在2014年3月份Erlang Factory上的一个演讲详细介绍了Erlang内存体系的原理以及调优案例 根据siyao zheng博客上听写的资源进行的翻译,大致只翻译了80%但核心部分已经完整,希望对大家...
hex, Erlang虚拟机的软件包管理器 十六进制 Hex是Erlang虚拟机的软件包管理器。这个项目目前提供了与混合。tcm 工具构建的任务。有关安装说明和其他文档,请参阅 hex.pm 。在本地安装十六进制以进行开发: mix ...
源码中可能包含了`erts`(Erlang Run-Time System)的相关部分,它负责进程的创建、调度和通信。了解这些内部实现有助于理解Erlang如何高效地处理并发。 2. **BEAM虚拟机** BEAM是Erlang虚拟机的缩写,它是Erlang...
Erlang虚拟机(Erlang VM,也称为BEAM虚拟机)是Erlang编程语言的核心组成部分,它为Erlang提供了强大的并发特性和故障容错能力。基于Erlang VM的语言充分利用了这些优势,同时也引入了不同语法和编程范式的创新。 ...
7. **学习路径**:初学者可以通过阅读和调试这些源码,了解Erlang的基本语法、模块结构、进程通信以及数据库操作。同时,也可以研究其错误处理和日志记录机制,以增强对Erlang实际开发流程的理解。 8. **实践经验**...
Erlang是一种面向并发的、动态类型的编程语言,主要用于构建分布式、容错性强的系统。OTP(Open Telecom ...同时,对于研究分布式系统、并发编程或者虚拟机设计的人来说,Erlang 21.0的源码也是一个宝贵的教育资源。
5. **BEAM虚拟机**:Erlang运行在BEAM虚拟机上,BEAM提供了高效的内存管理、垃圾回收和并发调度。 6. **OTP(Open Telecom Platform)**:OTP是Erlang的标准库,提供了一系列的设计原则、库和工具,用于构建可靠、...
Erlang是运行于虚拟机的解释性语言,但是现在也包含有乌普萨拉大学高性能Erlang计划(HiPE)开发的本地代码编译器,自R11B-4版本开始,Erlang也开始支持脚本式解释器。在编程范型上,Erlang属于多重范型编程语言,...
Erlang虽然不是专门的安全性语言,但可以通过合理的设计和使用第三方库来加强安全性。 7. **日志与监控**:记录用户活动、错误日志和系统状态对于调试和优化非常重要。Erlang提供了`observer`工具和其他日志库来...
Erlang源码揭示了这些模块如何设计和实现,包括如何使用Erlang的模块化特性来组织代码,以及如何通过进程间的通信来协调各个部分的工作。 5. **热代码升级** Erlang的热代码升级机制允许在不中断服务的情况下更新...
- **BEAM虚拟机**:Erlang程序运行在BEAM(Berkeley ERLang)虚拟机上,它设计为高效处理并发和容错。 - **进程模型**:Erlang采用轻量级进程模型,进程间通信快速且高效,支持分布式计算。 - **模式匹配**:Erlang...
erlang 服务端代码 例子,演示了如何管理角色
【标题】:“英雄远征erlang源码”指的是一个基于Erlang编程语言开发的服务器端源代码,用于支持网络游戏“英雄远征”的运行。Erlang是一种并发性极强、容错性好的函数式编程语言,常用于构建高可用性的分布式系统,...
它们是独立的执行流,有自己的堆栈和局部变量,但共享全局的Erlang虚拟机(VM)。每个进程都有一个唯一的进程ID(PID),用于标识和通信。创建进程的成本非常低,因此Erlang可以支持大量的并发进程。 2. 进程创建 ...
在源码中,你可能会看到Erlang的并发特性如`spawn`和`receive`表达式,它们允许创建和管理并发运行的进程。此外,`gen_server`行为模式可能被用来实现状态管理和服务的生命周期控制。`rabbitMQ`的灵感可能体现在消息...
Erlang Server源码分析与详解 Erlang是一种面向并发的、函数式编程语言,以其在分布式系统、高可用性和容错性方面的优势而受到广泛关注。尤其在构建大规模聊天室服务器这样的实时通信系统中,Erlang的性能表现突出...
对于初学者,可以通过阅读《Erlang程序设计》这本书了解基本语法、数据类型、控制结构、模式匹配以及Erlang的并发特性。同时,结合源码分析,可以提升实战能力。 总之,这份资源为Erlang初学者提供了全面的学习...
三、Erlang与MongoDB的结合 在"远古封神Server"项目中,Erlang服务器通过Erlang的MongoDB驱动程序(如mongodb-erlang)与MongoDB进行交互。这种结合使得服务器能够高效地执行以下操作: 1. 数据存取:Erlang进程...
源码中可能包含BEAM虚拟机的部分实现,帮助我们理解Erlang程序的执行过程。 7. OTP(开放电信平台)框架:OTP提供了许多预定义的行为和库,如gen_server、gen_event等,用于构建可靠的服务。通过源码,我们可以看到...