Saya mencoba memahami hal-hal seperti penghubung dan pemuat yang lebih baik.
Apa bidang ilmu komputer milik mereka? Kompiler, Sistem Operasi, Arsitektur Komputer?
Di mana linker dan loader berperan selama pengembangan?
Saya mencoba memahami hal-hal seperti penghubung dan pemuat yang lebih baik.
Apa bidang ilmu komputer milik mereka? Kompiler, Sistem Operasi, Arsitektur Komputer?
Di mana linker dan loader berperan selama pengembangan?
Jawaban:
The tepat hubungan bervariasi agak. Untuk memulainya, saya akan mempertimbangkan (hampir) model paling sederhana yang mungkin, digunakan oleh sesuatu seperti MS-DOS, di mana executable akan selalu terhubung secara statis. Sebagai contoh, mari kita pertimbangkan kanonik "Halo, Dunia!" program, yang akan kita asumsikan ditulis dalam C.
Kompiler akan mengkompilasi ini menjadi beberapa bagian. Ini akan mengambil string literal "Halo, Dunia!", Dan memasukkannya ke dalam satu bagian yang ditandai sebagai data konstan, dan itu akan mensintesis nama untuk string tertentu (misalnya, "$ L1"). Ini akan mengkompilasi panggilan printf
ke bagian lain yang ditandai sebagai kode. Dalam hal ini, itu akan mengatakan nama itu main
(atau, sering, _main
). Itu juga akan memiliki sesuatu untuk mengatakan potongan kode ini panjangnya N byte, dan (penting) berisi panggilan ke printf
pada offset M dalam kode itu.
Setelah kompiler selesai memproduksinya, tautan akan dijalankan. Ini biasanya dianggap sebagai bagian dari rantai alat pengembangan (meskipun ada pengecualian - MS-DOS digunakan untuk menyertakan penghubung, meskipun jarang jika pernah digunakan). Meskipun biasanya tidak terlihat secara eksternal, biasanya akan melewati beberapa argumen baris perintah, satu menetapkan file objek yang berisi beberapa kode startup, dan yang lain menentukan file apa pun yang berisi pustaka standar C.
Linker kemudian akan melihat file objek yang berisi kode startup dan menemukan bahwa itu adalah, katakanlah, panjangnya 1112 byte, dan memiliki panggilan ke _main
offset 784 di dalamnya .
Berdasarkan itu, itu akan mulai membangun tabel simbol. Ini akan memiliki satu entri yang mengatakan ".startup" (atau nama apa pun) panjangnya 1112 byte, dan (sejauh ini) tidak ada yang merujuk pada nama itu. Ini akan memiliki entri lain yang mengatakan "printf" adalah panjang yang tidak diketahui saat ini, tetapi itu dirujuk dari ".startup + 784".
Kemudian akan memindai melalui perpustakaan yang ditentukan (atau perpustakaan) untuk mencoba menemukan definisi nama-nama dalam tabel simbol yang saat ini tidak didefinisikan - dalam hal ini printf
. Ini akan menemukan file objek untuk printf yang mengatakan bahwa panjangnya 4087 byte, dan memiliki referensi ke rutin lain untuk melakukan hal-hal seperti mengubah int ke string, serta hal-hal seperti putchar
(atau mungkin fputc
) untuk menulis string yang dihasilkan ke output mengajukan.
Linker akan memindai ulang untuk mencoba menemukan definisi simbol-simbol itu, secara rekursif, hingga mencapai salah satu dari dua kesimpulan: itu baik ditemukan definisi semua simbol, atau ada simbol yang tidak dapat menemukan definisi.
Jika ditemukan referensi tetapi tidak ada definisi, itu akan berhenti dan memberikan pesan kesalahan yang biasanya mengatakan sesuatu tentang "XXX eksternal yang tidak ditentukan", dan terserah Anda untuk mencari tahu apa perpustakaan lain atau file objek yang perlu Anda tautkan .
Jika menemukan definisi semua simbol, ia bergerak ke fase berikutnya: ia berjalan melalui daftar tempat yang merujuk ke setiap simbol, dan itu akan mengisi alamat di mana simbol itu dimasukkan ke dalam memori, jadi (misalnya ) di mana kode startup memanggil main
, itu akan mengisi alamat 1112
sebagai alamat utama. Setelah selesai semua itu, itu akan menulis semua kode dan data ke file yang dapat dieksekusi.
Ada beberapa detail kecil lain yang mungkin perlu disebutkan: biasanya akan memisahkan kode dan data, dan setelah masing-masing selesai, itu akan menempatkan mereka bersama-sama di (kurang lebih) alamat berurutan (misalnya, semua bagian kode, lalu semua bagian data). Biasanya juga akan ada beberapa aturan tentang cara menggabungkan definisi untuk bagian / segmen - misalnya, jika file objek yang berbeda semuanya memiliki segmen kode, itu hanya akan mengatur potongan kode satu demi satu. Jika dua atau lebih string literal yang identik (atau konstanta lain) didefinisikan, biasanya akan menggabungkan keduanya sehingga semuanya merujuk ke tempat yang sama. Ada juga beberapa aturan untuk apa yang harus dilakukan ketika / jika menemukan definisi duplikat dari simbol yang sama. Dalam kasus khusus, ini hanya akan menjadi kesalahan. Dalam beberapa kasus, ia akan memiliki hal-hal seperti "jika orang lain juga mendefinisikannya, jangan menganggapnya sebagai kesalahan - cukup gunakan definisi itu daripada yang ini.
Setelah memiliki entri untuk semua simbol, linker harus mengatur "potongan" dan memberikan alamat kepada mereka. Urutan pengaturan potongan-potongannya akan sedikit berbeda - biasanya akan ada beberapa tanda tentang jenis-jenis potongan yang berbeda, jadi (misalnya) semua data konstan berakhir bersebelahan, semua potongan kode di sebelah satu sama lain dan seterusnya. Dalam sistem mirip-MS-DOS kami yang sederhana, sebagian besar dari ini tidak akan terlalu berarti.
Itu membawa kita ke fase berikutnya: loader. loader biasanya merupakan bagian dari sistem operasi, yang memuat executable. Dalam versi kuno (mis., CP / M, file MS_DOS .com, loader hanya membaca data dari file yang dapat dieksekusi ke dalam memori, kemudian mulai mengeksekusi di beberapa alamat. Loader yang sedikit lebih baru (mis., Untuk file MS-DOS .exe) akan memulai (kurang lebih) dengan cara yang sama: membaca file ke dalam memori. Namun, dalam hal ini, berdasarkan entri yang diletakkan di sana oleh linker, itu akan "memperbaiki" setiap referensi absolut dalam executable untuk merujuk ke alamat yang benar. Dalam contoh di atas, kode startup kami disebutmain
di alamat 1112, tetapi executable sedang dimuat di alamat dasar (katakanlah) 4000. Dalam hal ini, loader akan memperbaiki alamat itu hingga merujuk ke 5112. Namun demikian, dalam sistem yang sederhana ini, loader masih merupakan sepotong kode yang cukup sederhana - pada dasarnya hanya berjalan melalui daftar relokasi, dan menambahkan alamat pangkalan untuk masing-masing.
Sekarang mari kita pertimbangkan OS yang sedikit lebih modern yang mendukung sesuatu seperti file objek bersama atau DLL. Ini pada dasarnya menggeser beberapa pekerjaan dari linker ke loader. Khususnya, untuk simbol yang didefinisikan dalam .so / DLL, penghubung tidak akan mencoba untuk menetapkan alamat itu sendiri.
Sebagai gantinya itu akan membuat entri tabel simbol yang pada dasarnya mengatakan "didefinisikan dalam file .so / DLL XXX". Ketika linker menulis executable, sebagian besar entri tabel simbol ini pada dasarnya hanya akan disalin ke executable, mengatakan "simbol XXX didefinisikan dalam file YYY". Kemudian tergantung pada loader untuk menemukan file YYY, dan alamat simbol XXX dalam file itu, dan isi alamat yang benar di mana pun file itu digunakan dalam executable. Sama seperti di linker ini akan bersifat rekursif, jadi DLL A dapat merujuk ke simbol di DLL B, yang mungkin merujuk ke DLL C, dan seterusnya. Meskipun rantai dari yang dapat dieksekusi ke semua definisi mungkin panjang, ide dasar dari proses ini cukup sederhana - memindai melalui daftar referensi eksternal, dan menemukan definisi untuk masing-masing. Perhatikan juga bahwa dalam kebanyakan kasus,
Sekali lagi, ada beberapa potongan-potongan lain untuk dipertimbangkan. Misalnya, berbagi biasanya hanya akan terjadi berdasarkan bagian-demi-bagian, bukan file-demi-file. Jika suatu file memiliki beberapa kode dan beberapa data (tidak konstan), misalnya, semua proses akan berbagi bagian kode yang sama, tetapi masing-masing akan mendapatkan salinan datanya sendiri.
Untuk mengetahui lebih lanjut tentang tautan, saya pikir mereka umumnya akan dibahas dalam kombinasi dengan kompiler. Mereka adalah untuk merajut berbagai modul Anda bersama-sama menjadi unit kohesif, menyelesaikan alamat dalam kode itu. Beberapa bahkan mungkin mencoba melakukan optimasi.
Untuk mengetahui lebih lanjut tentang loader, saya pikir mereka umumnya akan dibahas dalam kombinasi dengan menulis kompiler untuk arsitektur tertentu kecuali jika Anda berarti loader sebagai sinonim untuk tautan. Saya sedang memikirkan loader sebagai bagian dari header file yang dapat dieksekusi yang memberi tahu sistem operasi cara membuka dan menjalankan perangkat lunak yang dikompilasi.
Saya setuju bahwa membaca artikel Wikipedia mungkin akan memberikan lebih banyak informasi daripada yang Anda cari. Ke mana mereka datang ke pengembangan ... umumnya mereka berada di luar kendali proyek, dan merupakan bagian dari pemilihan sistem operasi dan paket pengembangan yang Anda pilih untuk digunakan. Sangat jarang Anda menggunakan (misalnya) MSVC tetapi ingin menjalankan penghubung berbasis GCC ... bahkan mungkin tidak dapat dilakukan. Tempat HANYA yang pernah saya gunakan penghubung non-standar adalah di IBM ketika kami menggunakan salinan pengembangan.
Jika Anda memiliki pertanyaan yang lebih khusus dan spesifik tentang topik ini, saya pikir Anda akan menemukan respons yang jauh lebih baik.
Linker dan loader adalah dua konsep yang terkait tetapi terpisah.
Linker adalah bagian dari teori kompiler. Ketika Anda mengkompilasi proyek yang terdiri dari lebih dari satu modul (file kode sumber), biasanya kompilator mengeluarkan file perantara tunggal untuk setiap modul sumber. Ini memiliki beberapa manfaat, salah satunya adalah jika Anda hanya membuat perubahan pada satu file dan kemudian harus melakukan kompilasi ulang, Anda tidak harus membangun kembali seluruh proyek ketika Anda hanya membuat satu perubahan lokal.
Tetapi ini berarti bahwa jika Anda memiliki kode dalam satu modul yang memanggil fungsi dalam modul yang berbeda, kompiler tidak dapat menghasilkan CALL
instruksi padanya, karena ia tidak memiliki lokasi fungsi lainnya. Ada dalam file perantara yang berbeda, dan lokasi persis fungsi dapat berubah jika Anda membuat perubahan lokal ke file sumber perantara itu dan mengkompilasi ulang. Jadi sebagai gantinya, ia memasukkan "token referensi eksternal" (persis apa itu atau apa yang tampak tidak masalah, anggap saja sebagai konsep abstrak) yang mengatakan "Saya perlu fungsi ini yang alamat persisnya saya tidak tahu saat ini."
Setelah semuanya dikompilasi menjadi file perantara, penghubung adalah yang menyelesaikan pekerjaan. Itu pergi melalui semua file perantara dan menghubungkannya bersama-sama ke dalam biner akhir. Karena ia menyatukan berbagai hal, ia mengetahui alamat sebenarnya dari semua fungsi, sehingga ia dapat mengganti token referensi eksternal dengan CALL
instruksi aktual ke lokasi yang benar dalam biner.
Loader, di sisi lain, milik sistem operasi, bukan kompiler. Tugasnya adalah memuat biner ke dalam memori sehingga dapat dijalankan, dan untuk menyelesaikan proses penautan, karena penghubung hanya dapat menyelesaikan kode yang diketahuinya. Jika program Anda menggunakan DLL apa pun, itu bersifat eksternal bahkan untuk biner yang dikompilasi, sehingga tautan tidak mengetahui alamatnya. Ia meninggalkan token referensi eksternal dalam biner terakhir dalam format yang diketahui oleh loader OS, dan kemudian loader melewati dan mencocokkan token ini dengan alamat fungsi aktual dalam DLL setelah semuanya dimuat ke dalam memori.
Komputer pada dasarnya bekerja dengan angka biner.
Orang berbicara bahasa asli mereka.
Apakah, bahasa pemrograman untuk komunikasi antara orang dan komputer.
Jika Anda mengatakan: Tambahkan 2 dan 3 dan kemudian kurangi 1 dari itu, saya ragu komputer akan mengerti apa pun (mungkin dalam beberapa bahasa pemrograman akan).
Jadi, Anda perlu menerjemahkan kode sumber Anda ke dalam format yang dimengerti komputer, jadi Anda memerlukan kompiler, yang menerjemahkan bahasa pemrograman ke kode objek yang disebut co. Tetapi kode objek belum bahasa yang dipahami dan dieksekusi langsung oleh komputer. Jadi diperlukan tautan yang akan membuat file yang dapat dieksekusi yang berisi instruksi dalam apa yang disebut bahasa mesin; bahasa mesin adalah sekumpulan operasi yang dikodekan ke dalam angka-angka biner yang dimengerti oleh prosesor. Semua instruksi biner memiliki struktur dan diterbitkan oleh produsen prosesor. Anda dapat mencarinya di situs Intel misalnya dan lihat seperti apa bentuknya.