在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
这是win32下的结构 从ruby_setup开始阅读 Ruby对象内存结构 RVALUE是一个union,内含ruby所有结构体(RBasic RObject RClass RFloat RString RArray RRegexp RHash RData RTypedData RStruct RBignum RFile RNode RMatch RRational RComplex)以及用于gc的free结构。RVALUE是object space heap的最小单元。ruby及C扩展通常用VALUE引用RVALUE(或者具体的Rxxx结构),使用这些结构时,先对VALUE转型。
ruby _pioinfo()和msvc _pioinfo() __pioinfo[]关系 ruby使用自己实现的_pioinfo()函数去查全局变量__pioinfo[],这个函数与msvcrt的实现_pioinfo()有区别
由于ioinfo是msvcrt内部数据结构,没有公开到头文件也没有函数返回这个结构,但ruby要使用这些结构的头几个字段,所以ruby内定义了一个不完整的ioinfo结构,这个结构比msvcrt的ioinfo小。
ruby rb_thread_t结构 rb_thread_t结构很复杂,先解析目前已阅读的部分,之后慢慢补充 Init_BareVM用malloc分配主线程rb_thread_t结构。 GET_THREAD()永远返回当前线程。 每条系统线程都可以从tls index ruby_native_thread_key 获取自己的rb_thread_t结构,由Init_native_thread维护。 rb_thread_t是一个双向链表,由vmlt_node表明
rb_thread_t.rb_vm_t结构很复杂 rb_global_vm_lock_t rb_nativethread_lock_t main_thread,主线程的rb_thread_t结构 running_thread,正在运行的rb_thread_t结构 living_threads,一些10年前的资料表明,ruby用双向循环链表记录解析器内所有线程rb_thread_t,多线程调度从这个链表内根据各种条件选择一条线程执行,具体细节需要继续阅读代码 default_params,分别设置脚本栈,机器栈,纤程栈的大小,粗略看来,机器栈相关的大小,在win32下然并卵。脚本栈将成为rb_thread_t stack_size的依据。注意,这堆数据ruby通过取环境变量得到,所以,可以通过环境变量来改变,RUBY_THREAD_VM_STACK_SIZE之类的。在vm.c vm_default_params_setup内设置。
rb_thread_t.machine结构 这个结构就记录的数据就是线程teb关于线程栈基址及长度的数据,ruby记录栈长度时做了些计算,暂时不清楚为什么要这样做(space = size / 5),具体源代码可参考thread_win32.c native_thread_init_stack。目前来看,stack_start的这貌似在某些xp机器上会算错,算小了,可以考虑直接取teb.StackBase。 之所以叫machine是因为这堆数据真是反映操作系统线程栈(机器栈,系统线程栈),ruby用这些数据检查ruby解析器函数调用时,是否存在栈溢出情况,源代码参考gc.c stack_check。machine stack为gc提供参考,在ruby发起gc mark时,会扫描machine stack上是否有引用object space内的对象(例如ruby自身和一些C扩展),如果有,就mark。
rb_thread_t stack stack_size cfp(rb_control_frame_t)字段 这些字段记录ruby解析器用于脚本解析的脚本栈,当脚本包含函数调用时,这些字段会发生变化。 在vm.c th_init()内初始化,观察得知,这个stack来自object space。 stack是栈顶,cfp是栈底。观察vm_push_frame(),cfp记录每一个frame的属性,cfp由栈底向上增长,而frame分配的储存空间,则由stack向下分配。如下图: [...] <-stack 所以,检查ruby脚本栈是否溢出就是检查cfp.sp + 分配空间 后是否大于cfp,这也是CHECK_VM_STACK_OVERFLOW0的逻辑。压栈由PUSH(x)宏实现,在vm_insnhelper.h内。 cfp内的iseq就是与这个脚本栈关联的虚拟机指令序列,cfg.pc就是虚拟机当前指令,虚拟机指令结构及定义,务必仔细阅读insns.def,insns.def会翻译成C函数,成为vm.inc文件。虚拟机指令主要由指令id标识,根据指令id选择对应的C函数执行,这个过程可在vm_exec_core()内 INSN_DISPATCH(); #include "vm.inc" END_INSNS_DISPATCH(); 虚拟机指令id在insns.inc内定义,接下来看一个具体的例子: 1.假设rb_thread_t.cfp.pc = 0x00d3c4f0 2.在windbg内执行 dd 0x00d3c4f0得到一下内容 00d3c4f0 00000028 00000001 00000010 00000003 观察第一个dword是0x28,在insns.inc内查找对应的指令,是trace,在vm.inc内INSN_ENTRY(trace)对应这条指令的逻辑。
rb_thread_t.native_thread_data 目前只有interrupt_event,由Init_native_thread创建事件
rb_thread_t.self 主线程是0,这个貌似是线程对应的ruby对象
rb_objspace_t 目前来说,只能呵呵,下图是10年前的资料,仅供参考 https://ruby-hacking-guide.github.io/gc.html
由函数Init_heap初始化。 唯一一个rb_objspace实例,用于管理对象gc,内存分配,大小限制有宏GC_MALLOC_LIMIT_MIN以及GC_MALLOC_LIMIT_MAX控制,体现在rb_objspace_t.malloc_params.limit。 以下只是猜测,观察代码,rb_objspace_t.heap_pages就是对应上图结构。sorted是heaps表,sorted_length是heaps_length,page就是上图的heap了。观察heap_pages_expand_sorted(),是增加sorted表,用于增加page。当expand完成后,使用heap_assign_page()增加page。 heap_assign_page()先从tomb_heap内取page,如果没有tomb,就新建page(包括page head和page body),按body地址排序,插入sorted表,标记为free page,这个时候貌似会触发gc,释放page上引用到的ruby对象(例如这个page是从tomb来的)。 上述过程在heap_add_pages()内触发,Init_heap会把pages都添加到eden_heap内。 init_mark_stack()初始化了4块stack_chunk_t,mark_stack,跟gc mark有关。 objspace->finalizer_table,不清楚用途。 objspace->flags.gc_stressful,用于控制gc的频繁度,如果大于0,在newobj的时候,也会触发gc,参考gc.c newobj_of()。 garbage_collect(),先使用ready_to_gc()执行heap_assign_page(),然后执行gc_marks(),分为mark_start和mark_rest两个步骤:1)vm,finalizer_table,线程的rb_thread_t ,encoding等等,计算对象代龄,收集到mark_stack内,其中rb_thread_t是mark机器栈上由局部变量引用的ruby对象,特别需要注意,局部变量有可能被优化到寄存器,所以,写ruby c扩展时,局部变量需要用volatile保护;另外,ruby源码到处可见RB_GC_GUARD,它的作用也类似,避免调用栈末端函数折叠优化,从而避免栈变量优化(末端函数被折叠,栈就消失了,gc mark机器栈时就会误判对象生命周期);2)遍历mark_stack,marking,把eden heap内所有sweep page都移动到tomb heap内。 所以,ruby内所有对象都有rvalue引用,gc就是针对object space内的rvalue进行。
rb_iseq_t rb_iseq_struct 虚拟机指令序列对象。 type序列类型 location应该是序列对应的ruby模块吧,path是个RString,强转之后能看到具体名字。
PUSH_TAG POP_TAG EXEC_TAG JUMP_TAG ruby使用c setjmp longjmp实现各种跳转,例如异常跳转,解析器循环求值跳出等等。 tag结构体,tag结构体是rb_thread_t的成员,用链表纪录的栈结构tag。ruby在机器栈上构造tag,放到rb_thread_t tag栈顶部,用这个tag执行setjmp。当setjmp返回,从rb_thread_t.tag弹出。jump_tag就是用rb_thread_t.tag作为longjmp参数,longjmp目的由rb_thread_t.state表明,在eval_intern.h内RUBY_TAG_xxx定义。 下面是整个过程: PUSH_TAG ---- 构造tag实例,压rb_thread_t.tag EXEC_TAG ---- setjmp POP_TAG ---- 从rb_thread_t.tag弹出 下面是循环跳出过程: rb_eval(break) >---- rb_eval(if) | longjmp state=RUBY_TAG_BREAK rb_eval(block) | rb_eval(while) <---- |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论