Các chính xác mối quan hệ thay đổi một chút. Để bắt đầu, tôi sẽ xem xét (gần) mô hình đơn giản nhất có thể, được sử dụng bởi một cái gì đó như MS-DOS, nơi một tệp thực thi sẽ luôn được liên kết tĩnh. Vì lợi ích của ví dụ, chúng ta hãy xem xét kinh điển "Xin chào, thế giới!" chương trình, mà chúng tôi sẽ giả sử được viết bằng C.
Trình biên dịch
Trình biên dịch sẽ biên dịch điều này thành một vài phần. Nó sẽ lấy chuỗi ký tự "Xin chào, Thế giới!" Và đặt nó vào một phần được đánh dấu là dữ liệu không đổi và nó sẽ tổng hợp một tên cho chuỗi cụ thể đó (ví dụ: "$ L1"). Nó sẽ biên dịch cuộc gọi printf
sang một phần khác được đánh dấu là mã. Trong trường hợp này, nó sẽ nói tên là main
(hoặc, thường xuyên, _main
). Nó cũng sẽ có một cái gì đó để nói đoạn mã này dài N byte và (quan trọng) có chứa một lệnh gọi đến printf
offset M trong mã đó.
Trình liên kết
Khi trình biên dịch được thực hiện xong, trình liên kết sẽ chạy. Nó thường được coi là một phần của chuỗi công cụ phát triển (mặc dù có trường hợp ngoại lệ - MS-DOS được sử dụng để bao gồm một trình liên kết, mặc dù nó hiếm khi được sử dụng). Mặc dù nó thường không hiển thị bên ngoài, nhưng thông thường nó sẽ được thông qua một số đối số dòng lệnh, một chỉ định một tệp đối tượng chứa một số mã khởi động và một chỉ định khác bất kỳ tệp nào chứa thư viện chuẩn C.
Sau đó, trình liên kết sẽ xem xét tệp đối tượng chứa mã khởi động và thấy rằng nó dài 1112 byte và có một lệnh gọi _main
ở offset 784 trong đó.
Dựa vào đó, nó sẽ bắt đầu xây dựng một bảng biểu tượng. Nó sẽ có một mục ghi ".startup" (hoặc bất kỳ tên nào) dài 1112 byte và (cho đến nay) không có gì đề cập đến tên đó. Nó sẽ có một mục khác nói "printf" là một độ dài không xác định hiện tại, nhưng nó được gọi từ ".startup + 784".
Sau đó, nó sẽ quét qua thư viện được chỉ định (hoặc thư viện) để cố gắng tìm các định nghĩa về tên trong bảng ký hiệu hiện chưa được xác định - trong trường hợp này printf
. Nó sẽ tìm thấy tệp đối tượng cho printf nói rằng nó dài 4087 byte và có các tham chiếu đến các thói quen khác để thực hiện những việc như chuyển đổi một int thành một chuỗi, cũng như những thứ như putchar
(hoặc có thể fputc
) để viết chuỗi kết quả vào đầu ra tập tin.
Trình liên kết sẽ quét lại để cố gắng tìm các định nghĩa của các ký hiệu đó, theo cách đệ quy, cho đến khi đạt được một trong hai kết luận: đó là tìm các định nghĩa của tất cả các ký hiệu, hoặc nếu không có ký hiệu mà nó không thể tìm thấy định nghĩa.
Nếu nó tìm thấy một tham chiếu nhưng không có định nghĩa, nó sẽ dừng lại và đưa ra một thông báo lỗi thường nói điều gì đó về "XXX bên ngoài không xác định", và bạn sẽ phải tìm ra thư viện hoặc tệp đối tượng nào khác mà bạn cần liên kết .
Nếu nó tìm thấy định nghĩa của tất cả các biểu tượng, nó sẽ chuyển sang giai đoạn tiếp theo: nó đi qua danh sách các địa điểm đề cập đến từng biểu tượng và nó sẽ điền vào địa chỉ nơi biểu tượng đó được đưa vào bộ nhớ, vì vậy (ví dụ: ) nơi mã khởi động gọi main
, nó sẽ điền địa chỉ 1112
làm địa chỉ chính. Khi hoàn thành tất cả, nó sẽ ghi tất cả mã và dữ liệu vào một tệp thực thi.
Có một vài chi tiết nhỏ khác có thể được đề cập: thông thường sẽ giữ riêng mã và dữ liệu và sau khi hoàn tất, chúng sẽ đặt tất cả chúng lại với nhau tại (nhiều hoặc ít) địa chỉ liên tiếp (ví dụ: tất cả các phần mã, sau đó tất cả các phần của dữ liệu). Thông thường cũng sẽ có một số quy tắc về cách kết hợp các định nghĩa cho phần / phân đoạn - ví dụ: nếu các tệp đối tượng khác nhau đều có phân đoạn mã, thì nó sẽ chỉ sắp xếp các đoạn mã lần lượt. Nếu hai hoặc nhiều chuỗi ký tự giống hệt nhau (hoặc các hằng số khác) được xác định, thông thường nó sẽ hợp nhất các chuỗi đó lại với nhau để tất cả chúng tham chiếu đến cùng một vị trí. Cũng có một vài quy tắc phải làm gì khi / nếu nó tìm thấy các định nghĩa trùng lặp của cùng một biểu tượng. Trong một trường hợp điển hình, đây đơn giản sẽ là một lỗi. Trong một vài trường hợp, nó sẽ có những thứ như "nếu ai đó cũng định nghĩa nó, đừng coi đó là lỗi - chỉ cần sử dụng định nghĩa đó thay vì định nghĩa này.
Khi nó có các mục cho tất cả các biểu tượng, trình liên kết phải sắp xếp các "mảnh" và gán địa chỉ cho chúng. Thứ tự sắp xếp các mảnh sẽ thay đổi phần nào - thông thường sẽ có một số cờ về các loại mảnh khác nhau, vì vậy (ví dụ) tất cả các dữ liệu không đổi kết thúc cạnh nhau, tất cả các đoạn mã bên cạnh nhau và như vậy. Trong hệ thống đơn giản giống như MS-DOS của chúng tôi, hầu hết điều này sẽ không thành vấn đề.
Máy xúc lật
Điều đó đưa chúng ta đến giai đoạn tiếp theo: bộ nạp. bộ nạp thường là một phần của hệ điều hành, tải bộ thực thi. Trong các phiên bản cổ (ví dụ: tệp CP / M, MS_DOS .com, trình tải chỉ đọc dữ liệu từ tệp thực thi vào bộ nhớ, sau đó bắt đầu thực thi tại một số địa chỉ. Các trình tải gần đây hơn (ví dụ: đối với tệp MS-DOS .exe) sẽ bắt đầu (nhiều hơn hoặc ít hơn) theo cùng một cách: đọc một tệp vào bộ nhớ. Tuy nhiên, trong trường hợp này, dựa trên các mục được đặt bởi trình liên kết, nó sẽ "sửa" mọi tham chiếu tuyệt đối trong tệp thực thi để tham chiếu đến đúng địa chỉ. Trong ví dụ trên, mã khởi động của chúng tôi được đề cập đếnmain
tại địa chỉ 1112, nhưng tệp thực thi đang được tải tại địa chỉ cơ sở của (giả sử) 4000. Trong trường hợp này, trình tải sẽ sửa địa chỉ đó để tham khảo 5112. Tuy nhiên, trong hệ thống đơn giản này, trình tải vẫn là một đoạn mã khá đơn giản - về cơ bản chỉ cần duyệt qua danh sách các địa điểm và thêm địa chỉ cơ sở vào từng địa chỉ.
Bây giờ, hãy xem xét một hệ điều hành hiện đại hơn một chút hỗ trợ một cái gì đó như các tệp đối tượng được chia sẻ hoặc DLL. Điều này về cơ bản chuyển một số công việc từ trình liên kết sang trình tải. Cụ thể, đối với một biểu tượng được xác định trong .so / DLL, trình liên kết sẽ không cố tự gán địa chỉ.
Thay vào đó, nó sẽ tạo ra một mục nhập bảng biểu tượng về cơ bản là "được xác định trong tệp .so / DLL XXX". Khi trình liên kết ghi tệp thực thi, hầu hết các mục trong bảng ký hiệu này về cơ bản sẽ chỉ được sao chép vào tệp thực thi, nói rằng "ký hiệu XXX được xác định trong tệp YYY". Sau đó, đến bộ tải để tìm tệp YYY và địa chỉ ký hiệu XXX trong tệp đó và điền địa chỉ chính xác vào bất cứ nơi nào nó được sử dụng trong tệp thực thi. Giống như trong trình liên kết, điều này sẽ được đệ quy, vì vậy DLL A có thể đề cập đến các ký hiệu trong DLL B, có thể đề cập đến DLL C, v.v. Mặc dù chuỗi từ thực thi đến tất cả các định nghĩa có thể dài, ý tưởng cơ bản của quy trình này khá đơn giản - quét qua danh sách các tham chiếu bên ngoài và tìm định nghĩa cho từng định nghĩa. Cũng lưu ý rằng trong hầu hết các trường hợp,
Một lần nữa, có một số bit và mảnh linh tinh để xem xét. Ví dụ, việc chia sẻ thường sẽ chỉ xảy ra trên cơ sở từng phần, chứ không phải theo từng tệp. Ví dụ, nếu một tệp có một số mã và một số dữ liệu (không cố định), tất cả các quy trình sẽ chia sẻ cùng một phần mã, nhưng mỗi quy trình sẽ có bản sao dữ liệu riêng.