Compilation process của 1 chương trình .NET
Keyword cần chú ý
runtime | (n) là execution environment mà quản lý và thực thi “đoạn code đã được compile”. .NET framework sử dụng Common Language Runtime (CLR), .NET Core/6+ sử dụng CoreCLR |
managed code | (n) những đoạn code mà “việc thực thi chúng” được quản lý bởi runtime |
Tổng quan về compilation process
Lập trình viên viết 1 đoạn chương trình C#
C# compiler kiểm tra cú pháp và phân tích source code
Microsoft Intermediate Languages (MSIL) là kết quả của quá trình (2)
Nếu source code chứa hàm main() → compile thành file .exe
Nếu source code không chứa hàm main() → compile thành file .dll
Common Language Runtime (CLR) được khởi tạo bên trong 1 process (tiến trình) và thực hiện chạy entry-point-method (trong trường hợp này là hàm Main)
MSIL được chuyển thành native code bởi JIT compiler (Just-In-Time compiler)
CLR là gì
Theo định nghĩa của Microsoft
Hãy thử xem 1 ví dụ về cách mà CLR hoạt động
Như vậy, ta có thể viết 1 chương trình với bất kì ngôn ngữ lập trình nào, miễn là compiler (C++, C#, F#,..) ta dùng cho chúng nhắm tới runtime ta đang dùng. Ví dụ, VBC (VB Complier) và CSC (CSharp Compiler) đều sẽ compile VB.NET và C#.NET thành MSIL và chạy được với CLR (Common Language Runtime)
Compile source code
Ở bước (2), C# compiler sẽ dịch source code thành MSIL - là 1 bộ tập lệnh độc lập với CPU (CPU-independent set of instructions) mà có thể được chuyển thành native code 1 cách hiệu quả. Kết quả là 1 managed module
- standard 32-bit Windows portable executable file hoặc 64-bit Windows portable executable file mà cần runtime để thực thi
Có gì bên trong managed module?
Ngoài phần managed code
, 1 file PE sẽ có thêm phần metadata table - chứa thông tin về những thứ được định nghĩa trong module
Nếu coi PE giống như 1 cuốn sách công thức nấu ăn thì IL code sẽ là phần hướng dẫn nấu từng món và Metadata là danh sách nguyên liệu và thông tin của từng nguyên liệu (giá trị dinh dưỡng, khối lượng,…)
PE header:
Nếu header sử dụng
PE32 format
, file PE này có thể chạy trên phiên bản Windows 32-bit hoặc 64-bit.Nếu header sử dụng
PE32+ format
, file PE này yêu cầu phiên bản Windows 64-bit để chạy. This header also indicates the type of file: , and contains a timestamp indicating when the file was built.Header chứa 1 số thông tin như: loại file (GUI - Graphical User Interface, CUI - Console User Interface, or DLL - Dynamic Link Lybrary), ngày tạo,..
CLR header:
- Cho biết thông tin như: phiên bản CLR cần dùng, entry point method (là hàm Main), vị trí/kích thước của module metadata,…
Metadata table
- Describes the types in your code, including the definition of each type, the signatures of each type's members, the members that your code references, and other data that the runtime uses at execution time.
Managed code (MSIL)
- Code the compiler produced as it compiled the source code
Loading CLR
Khi chạy 1 executable file, Windows sẽ kiểm tra header của file .exe này để xác định sẽ khởi tạo tiến trình 32-bit hoặc 64-bit. Sau đó, Windows kiểm tra thông tin về kiến trúc CPU (CPU architecture information) được nhúng (embedded) trong phần header và theo đó load MSCorEE.dll
vào không gian địa của tiến trình
Tùy vào CPU type của máy tính mà Windows sẽ load phiên bản x86, x64 hoặc ARM của MSCorEE.dll
Thread chính của tiến trình sẽ gọi 1 method bên trong MSCorEE.dll mà sẽ: khởi tạo CLR, load EXE assembly, và gọi tới entry point method
của nó (Main). Tại thời điểm đó, managed application
sẽ bắt đầu chạy
JIT and execution
Tưởng tượng ta có 1 cuốn sách dạy nấu ăn (PE file) được viết ở 1 ngôn ngữ mà mình không biết hoàn toàn (MSIL). Và ta chỉ cần sử dụng 1 số công thức nhất định trong cuốn sách đó (chỉ thực thi 1 số methods)
Nếu không có JIT: Ta sẽ cần dịch toàn bộ cuốn sách sang ngôn ngữ mà ta biết (native code) trước khi bắt đầu nấu ăn. Việc này sẽ làm tốn rất nhiều thời gian, trong khi ta chỉ sử dụng 1 số công thức trong đó
Nếu có JIT: Ta chỉ dịch những công thức khi ta cần dùng đến. Và ta sẽ luôn giữ lại 1 bản dịch của công thức để khi ta cần dùng lại, ta không phải dịch nữa
Execution example
Once the Main
method of Program.exe
is called, and WriteLine
is set to be executed next, the JITCompiler
function searches assembly's (Console.dll
) metadata for the called method's (WriteLine
) MSIL. JITCompiler
next verifies and compiles the MSIL into native code and saves it in a dynamically allocated block of memory.
Then, JIT replaces the reference of the called method (WriteLine
) with an address of the block in memory containing the native code it previously compiled.
Finally, the JIT jumps to the code (this code is the actual implementation of WriteLine(string)
method) in the memory block, executes it and returns to Main
from where it continues execution as normal.
The second time WriteLine
is executed, code for the WriteLine
method has already been verified and compiled, meaning the call goes directly to the block of memory where native code is stored.
Subsequent calls to the JIT-compiled method go directly to the native code.
Verification
Trong khi compile MSIL sang native code
, CLR thực hiện 1 tiến trình gọi là verification
để đảm bảo những việc mà code thực hiện là an toàn. Ví dụ, verification
kiểm tra mọi method có được gọi với số lượng parameters cần thiết hay không, mỗi parameter có được truyền vào method với type đúng hay không, mỗi method có return đúng với type được định nghĩa hay không,…