JIT是什麼,它將怎樣運行?

什麼是 JIT?

名如其特色,JIT —— just in time,即時編譯。
把它詳細化點講,就是
一個程序在它運行的時候建立而且運行了全新的代碼,而並不是那些最初做爲這個程序的一部分保存在硬盤上的固有的代碼。就叫 JIT。shell

這裏有幾點要看的:函數

  1. 程序須要運行
  2. 生成的代碼是新的代碼,並不是做爲原始程序的一部分被存在磁盤上的那些代碼
  3. 不光生成代碼,還要運行。
    須要提醒的是第三點,也就是 JIT不光是生成新的代碼,它還會運行新生成的代碼,而這些代碼在存儲於磁盤上時不屬於該程序的一部分,它就是一個JIT。

JIT的兩個階段

我把JIT分爲了兩個階段
階段1:在程序運行時建立機器代碼。
階段2:在程序運行時也執行該機器代碼。

第1階段是JITing 99%的挑戰所在,但它也是這個過程當中不那麼神祕的部分,由於這正是編譯器所作的。衆所周知的編譯器,如gcc和clang,將C/C++源代碼轉換爲機器代碼。機器代碼被髮送到輸出流中,但它極可能只保存在內存中(實際上,gcc和clang / llvm都有構建塊用於將代碼保存在內存中以便執行JIT)。第2階段,看下去 ::twemoji👅:編碼

模擬一下JIT運行的過程

現代操做系統對於容許程序在運行時執行的操做能夠說是很是挑剔。過去「海闊憑魚躍,天高任鳥飛」的日子隨着保護模式的出現而不復存在,保護模式容許操做系統以各類權限對虛擬內存塊的使用作出限制。所以,在「普通」代碼中,你能夠在堆上動態建立新數據,可是你不能在沒有操做系統明確容許的狀況下從堆中運行其內容。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在編譯的做用以及最後的結果了

相關文章
相關標籤/搜索