名如其特色,JIT —— just in time,即时编译。
把它详细化点讲,就是
一个程序在它运行的时候建立而且运行了全新的代码,而并不是那些最初做为这个程序的一部分保存在硬盘上的固有的代码。就叫 JIT。shell
这里有几点要看的:函数
第1阶段是JITing 99%的挑战所在,但它也是这个过程当中不那么神秘的部分,由于这正是编译器所作的。众所周知的编译器,如gcc和clang,将C/C++源代码转换为机器代码。机器代码被发送到输出流中,但它极可能只保存在内存中(实际上,gcc和clang / llvm都有构建块用于将代码保存在内存中以便执行JIT)。第2阶段,看下去 ::twemoji👅:编码
现代操做系统对于容许程序在运行时执行的操做能够说是很是挑剔。过去“海阔凭鱼跃,天高任鸟飞”的日子随着保护模式的出现而不复存在,保护模式容许操做系统以各类权限对虚拟内存块的使用作出限制。所以,在“普通”代码中,你能够在堆上动态建立新数据,可是你不能在没有操做系统明确容许的状况下从堆中运行其内容。spa
在这一点上,我但愿机器代码只是数据 - 一个字节流,好比:操作系统
unsigned char[] code = {0x48, 0x89, 0xf8};
不一样的人会有不一样的视角,对某些人而言,0x48, 0x89, 0xf8只是一些能够表明任何事物的数据。 对于其余人来讲,它是真实有效的机器代码的二进制编码,其对应的x86-64汇编代码以下:指针
mov %rdi, %rax
其实能够看出机器码就是比特流,因此将它加载进内存并不困难。而问题是应该如何执行。code
好啦。下面咱们就模拟一下执行新生成的机器码的过程。假设JIT已经为咱们编译出了新的机器码,是一个求和函数的机器码:内存
//求和函数 long add4(long num) { return num + 4; } //对应的机器码 0x48, 0x83, 0xc0, 0x01, 0xc3
首先,动态的在内存上建立函数以前,咱们须要在内存上分配空间。具体到模拟动态建立函数,其实就是将对应的机器码映射到内存空间中。这里咱们使用c语言作实验,利用 mmap函数 来实现这一点。 ::twemoji🆗:
因此,咱们就须要这些:编译器
//头文件 #include <unistd.h> #include <sys/mman.h> //定义函数 void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize) /*函数说明: mmap()用来将某个文件内容映射到内存中,对该内存区域的存取便是直接对该文件内容的读写。*/
由于咱们想要把已是 比特流的“求和函数”在内存中建立出来,同时还要运行它。因此mmap有几个参数须要注意一下。
而表明映射区域的保护方式,有下列组合:string
PROT_EXEC //映射区域可被执行;
PROT_READ //映射区域可被读取;
PROT_WRITE //映射区域可被写入;
因此,咱们的程序能够像是这个样子:
#include<stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h> //分配内存 void* create_space(size_t size) { void* ptr = mmap(0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0); return ptr; }
经过这一段代码咱们能够得到一块分配给咱们存放代码的空间。下一步就是实现一个方法将机器码拷贝到分配给咱们的那块空间上去。使用 函数 memcpy 便可。
//在内存中建立函数 void copy_code_2_space(unsigned char* m) { unsigned char macCode[] = { 0x48, 0x83, 0xc0, 0x01, c3 }; memcpy(m, macCode, sizeof(macCode)); }
咱们再整理一下,最后程序就变成这个样子
#include<stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h> //分配内存 void* create_space(size_t size) { void* ptr = mmap(0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0); return ptr; } //在内存中建立函数 void copy_code_2_space(unsigned char* addr) { unsigned char macCode[] = { 0x48, 0x83, 0xc0, 0x01, 0xc3 }; memcpy(addr, macCode, sizeof(macCode)); } //main 声明一个函数指针TestFun用来指向咱们的求和函数在内存中的地址 int main(int argc, char** argv) { const size_t SIZE = 1024; typedef long (*TestFun)(long); void* addr = create_space(SIZE); copy_code_2_space(addr); TestFun test = addr; int result = test(1); printf("result = %d\n", result); return 0; }
咱们经过
gcc a.c ./a.out 1
咱们能够获得 result=2 因此这就是JIT在编译的做用以及最后的结果了