Saya sedang mempelajari CPU dan saya tahu cara membaca program dari memori dan menjalankan instruksinya. Saya juga mengerti bahwa sebuah OS memisahkan program dalam proses, dan kemudian bergantian antara masing-masing begitu cepat sehingga Anda berpikir bahwa mereka berjalan pada waktu yang sama, tetapi pada kenyataannya setiap program berjalan sendiri dalam CPU. Tetapi, jika OS ini juga merupakan kumpulan kode yang berjalan di dalam CPU, bagaimana ia dapat mengatur prosesnya?
Saya telah memikirkan dan satu-satunya penjelasan yang dapat saya pikirkan adalah: ketika OS memuat sebuah program dari memori eksternal ke RAM, ia menambahkan instruksi sendiri di tengah instruksi program asli, sehingga program dijalankan, program dapat memanggil OS dan melakukan beberapa hal. Saya percaya ada instruksi bahwa OS akan menambahkan ke program, yang akan memungkinkan CPU untuk kembali ke kode OS beberapa waktu. Dan juga, saya percaya bahwa ketika OS memuat sebuah program, ia memeriksa apakah ada beberapa instruksi yang dilarang (yang akan melompat ke alamat terlarang dalam memori) dan menghilangkannya kemudian.
Apakah saya berpikir tegar? Saya bukan seorang siswa CS, tetapi pada kenyataannya, seorang siswa matematika. Jika memungkinkan, saya ingin buku yang bagus tentang ini, karena saya tidak menemukan orang yang menjelaskan bagaimana OS dapat mengatur proses jika OS juga merupakan kumpulan kode yang berjalan di CPU, dan tidak dapat berjalan pada saat yang sama waktu program. Buku-buku hanya mengatakan bahwa OS dapat mengatur berbagai hal, tetapi sekarang caranya.
sumber
Jawaban:
Tidak. Sistem operasi tidak main-main dengan kode program yang menyuntikkan kode baru ke dalamnya. Itu akan memiliki sejumlah kelemahan.
Ini akan memakan waktu, karena OS harus memindai seluruh yang dapat dieksekusi untuk membuat perubahan. Biasanya, bagian dari executable hanya dimuat sesuai kebutuhan. Selain itu, memasukkannya mahal karena Anda harus memindahkan banyak barang.
Karena ketidakpastian masalah penghentian, tidak mungkin untuk mengetahui di mana memasukkan instruksi "Langsung kembali ke OS" Anda. Misalnya, jika kode mencakup sesuatu seperti
while (true) {i++;}
, Anda pasti perlu memasukkan kait di dalam loop itu tetapi kondisi pada loop (true
, di sini) bisa rumit, jadi Anda tidak bisa memutuskan berapa lama loopnya. Di sisi lain, akan sangat tidak efisien untuk memasukkan kait ke dalam setiap loop: misalnya, melompat kembali ke OS selamafor (i=0; i<3; i++) {j=j+i;}
akan banyak memperlambat proses. Dan, untuk alasan yang sama, Anda tidak dapat mendeteksi loop pendek untuk membiarkannya sendirian.Karena ketidakpastian masalah penghentian, tidak mungkin untuk mengetahui apakah suntikan kode mengubah arti program. Sebagai contoh, misalkan Anda menggunakan pointer fungsi dalam program C. Menyuntikkan kode baru akan memindahkan lokasi fungsi jadi, ketika Anda memanggil satu melalui pointer, Anda akan melompat ke tempat yang salah. Jika programmer cukup sakit untuk menggunakan lompatan terkomputasi, itu akan gagal juga.
Itu akan sangat menyenangkan dengan sistem anti-virus, karena itu akan mengubah kode virus juga, dan membereskan semua checksum Anda.
Anda bisa menyiasati masalah penghentian masalah dengan mensimulasikan kode dan memasukkan kait dalam loop apa pun yang mengeksekusi lebih dari jumlah tetap tertentu. Namun, itu akan memerlukan simulasi yang sangat mahal dari keseluruhan program sebelum diizinkan untuk dijalankan.
Sebenarnya, jika Anda ingin menyuntikkan kode, kompiler akan menjadi tempat alami untuk melakukannya. Dengan begitu, Anda hanya perlu melakukannya sekali tetapi tetap tidak berhasil karena alasan kedua dan ketiga yang diberikan di atas. (Dan seseorang bisa menulis kompiler yang tidak cocok.)
Ada tiga cara utama bahwa OS mendapatkan kembali kendali dari proses.
Dalam sistem kooperatif (atau non-preemptive), ada
yield
fungsi yang dapat dipanggil proses untuk memberikan kontrol kembali ke OS. Tentu saja, jika itu satu-satunya mekanisme Anda, Anda bergantung pada proses berperilaku baik dan proses yang tidak menghasilkan akan memakan CPU sampai berakhir.Untuk menghindari masalah itu, interupsi timer digunakan. CPU memungkinkan OS untuk mendaftarkan panggilan balik untuk semua jenis interupsi yang diterapkan CPU. OS menggunakan mekanisme ini untuk mendaftarkan panggilan balik untuk penghenti waktu yang diaktifkan secara berkala, yang memungkinkannya untuk mengeksekusi kodenya sendiri.
Setiap kali suatu proses mencoba membaca dari suatu file atau berinteraksi dengan perangkat keras dengan cara lain, itu meminta OS untuk melakukan pekerjaan untuk itu. Ketika OS diminta untuk melakukan sesuatu dengan suatu proses, ia dapat memutuskan untuk menunda proses itu dan mulai menjalankan yang lain. Ini mungkin terdengar agak Machiavellian tapi itu hal yang tepat untuk dilakukan: disk I / O lambat sehingga Anda mungkin juga membiarkan proses B berjalan saat proses A sedang menunggu gumpalan logam berputar untuk pindah ke tempat yang tepat. Jaringan I / O bahkan lebih lambat. Keyboard I / O glasial karena manusia bukan makhluk gigahertz.
sumber
1
, CPU menghentikan apa pun yang dilakukannya, dan mulai memproses interupsi (yang pada dasarnya berarti "mempertahankan status dan lompat ke alamat dalam memori"). Penanganan interupsi itu sendiri bukanx86
atau kode apa pun, itu benar-benar bawaan. Setelah melompat, ia kembali mengeksekusix86
kode (apa saja) . Thread adalah abstraksi yang jauh lebih tinggi.Sementara jawaban David Richerby adalah jawaban yang bagus, jawaban itu agak membingungkan tentang bagaimana sistem operasi modern menghentikan program yang ada. Jawaban saya harus akurat untuk arsitektur x86 atau x86_64, yang merupakan satu-satunya yang umum digunakan untuk desktop dan laptop. Arsitektur lain harus memiliki metode serupa untuk mencapai ini.
Ketika sistem operasi mulai, itu mengatur tabel interupsi. Setiap entri tabel menunjuk ke sedikit kode di dalam sistem operasi. Ketika interupsi terjadi, yang dikendalikan oleh CPU, ia melihat tabel ini dan memanggil kode. Ada berbagai interupsi, seperti membaginya dengan nol, kode tidak valid, dan beberapa yang didefinisikan sistem operasi.
Ini adalah bagaimana proses pengguna berbicara dengan kernel, seperti jika ingin membaca / menulis ke disk atau sesuatu yang lain yang dikendalikan oleh kernel sistem operasi. Sistem operasi juga akan mengatur timer yang memanggil interupsi ketika selesai, sehingga kode yang berjalan diubah secara paksa dari program pengguna ke kernel sistem operasi, dan kernel dapat melakukan hal-hal lain seperti mengantre program lain untuk dijalankan.
Dari memori, ketika ini terjadi kernel sistem operasi harus menyimpan di mana kode itu, dan ketika kernel selesai melakukan apa yang perlu dilakukan itu mengembalikan keadaan program sebelumnya. Dengan demikian program bahkan tidak tahu bahwa itu terganggu.
Proses tidak dapat mengubah tabel interupsi karena dua alasan, yang pertama berjalan di lingkungan yang dilindungi jadi jika ia mencoba memanggil kode rakitan tertentu yang dilindungi maka cpu akan memicu interupsi lainnya. Alasan kedua adalah memori virtual. Lokasi tabel interupsi berada pada 0x0 hingga 0x3FF dalam memori nyata, tetapi dengan proses pengguna lokasi itu biasanya tidak dipetakan, dan mencoba membaca memori yang tidak dipetakan akan memicu interupsi lain, jadi tanpa fungsi yang dilindungi dan kemampuan untuk menulis ke RAM nyata , proses pengguna tidak dapat mengubahnya.
sumber
Kernel OS mendapatkan kendali kembali dari proses yang berjalan karena penangan interupsi jam CPU, bukan dengan menyuntikkan kode ke dalam proses.
Anda harus membaca tentang interupsi untuk mendapatkan lebih banyak klarifikasi tentang cara kerjanya dan bagaimana kernel OS menanganinya dan mengimplementasikan fitur yang berbeda.
sumber
Ada adalah metode yang mirip dengan apa yang Anda menggambarkan: multitasking koperasi . OS tidak memasukkan instruksi, tetapi setiap program harus ditulis untuk memanggil fungsi OS yang dapat memilih untuk menjalankan proses koperasi lainnya. Ini memiliki kekurangan yang Anda jelaskan: satu program mogok membuat seluruh sistem. Windows hingga dan termasuk 3.0 bekerja seperti ini; 3.0 dalam "mode terproteksi" dan di atas tidak.
Pre-emptive multitasking (yang normal hari ini) bergantung pada sumber interupsi eksternal. Interupsi mengabaikan aliran kontrol normal dan biasanya menyimpan register di suatu tempat, sehingga CPU dapat melakukan sesuatu yang lain dan kemudian melanjutkan program secara transparan. Tentu saja, sistem operasi dapat mengubah register "ketika Anda meninggalkan interupsi melanjutkan di sini", sehingga ia melanjutkan dalam proses yang berbeda.
(Beberapa sistem lakukan petunjuk penulisan ulang dalam cara yang terbatas pada beban program, yang disebut "thunking", dan prosesor Transmeta dinamis dikompilasi ulang untuk set instruksi sendiri)
sumber
Multi-tasking tidak memerlukan injeksi kode. Dalam sistem operasi seperti Windows, ada komponen kode sistem operasi yang disebut scheduler yang bergantung pada interupsi perangkat keras yang dipicu oleh pengatur waktu perangkat keras. Ini digunakan oleh sistem operasi untuk beralih di antara berbagai program dan program itu sendiri, sehingga semuanya tampak bagi persepsi manusia kita untuk terjadi secara bersamaan.
Pada dasarnya, sistem operasi memprogram timer perangkat keras untuk mati sesering mungkin ... mungkin 100 kali per detik. Ketika timer mati, itu menghasilkan interupsi perangkat keras - sinyal yang memberitahu CPU untuk menghentikan apa yang dilakukannya, menyimpan statusnya di stack, mengubah modenya ke sesuatu yang lebih istimewa, dan mengeksekusi kode yang akan ditemukannya di tempat yang ditunjuk secara khusus tempatkan di memori. Kode itu kebetulan merupakan bagian dari penjadwal, yang memutuskan apa yang harus dilakukan selanjutnya. Mungkin untuk melanjutkan beberapa proses lain, dalam hal ini ia harus melakukan apa yang dikenal sebagai "saklar konteks" - menggantikan keseluruhan keadaan saat ini (termasuk tabel memori virtual) dengan yang dari proses lainnya. Dalam kembali ke suatu proses, ia harus mengembalikan semua konteks proses itu,
Tempat "yang ditunjuk secara khusus" dalam memori tidak harus diketahui oleh apa pun kecuali sistem operasi. Implementasinya bervariasi, tetapi intinya adalah bahwa CPU akan merespons berbagai gangguan dengan melakukan pencarian tabel; lokasi tabel berada pada tempat tertentu dalam memori (ditentukan oleh desain perangkat keras CPU), isi tabel diatur oleh sistem operasi (umumnya saat boot), dan "tipe" interupsi akan menentukan entri mana yang dalam tabel akan digunakan sebagai "layanan rutin interupsi".
Semua ini tidak melibatkan "injeksi kode" ... ini didasarkan pada kode yang terdapat dalam sistem operasi yang bekerja sama dengan fitur perangkat keras CPU dan sirkuit pendukungnya.
sumber
Saya pikir contoh dunia nyata yang paling dekat dengan apa yang Anda gambarkan adalah salah satu teknik yang digunakan oleh VMware , virtualisasi penuh menggunakan terjemahan biner .
VMware bertindak sebagai lapisan di bawah satu atau lebih yang secara bersamaan menjalankan sistem operasi pada perangkat keras yang sama.
Sebagian besar instruksi yang dieksekusi (misalnya dalam aplikasi biasa) dapat divirtualisasi menggunakan perangkat keras, tetapi kernel OS itu sendiri menggunakan instruksi yang tidak dapat divirtualisasi, karena jika kode mesin dari menebak OS dieksekusi tidak dimodifikasi itu akan "pecah "dari kendali host VMware. Misalnya, OS tamu perlu dijalankan di cincin perlindungan yang paling istimewa, dan mengatur tabel interupsi. Jika diizinkan untuk melakukan itu, VMware akan kehilangan kendali atas perangkat keras.
VMware menulis ulang instruksi-instruksi tersebut dalam kode OS sebelum menjalankannya, menggantikannya dengan lompatan ke dalam kode VMware yang mensimulasikan efek yang diinginkan.
Jadi teknik ini agak analog dengan apa yang Anda gambarkan.
sumber
Ada berbagai kasus di mana sistem operasi mungkin "menyuntikkan kode" ke dalam program. Versi 68000 berbasis sistem Apple Macintosh membangun tabel dari semua titik masuk segmen (terletak tepat sebelum variabel global statis, IIRC). Ketika sebuah program dimulai, setiap entri dalam tabel terdiri dari instruksi trap diikuti oleh nomor segmen dan diimbangi ke dalam segmen. Jika jebakan dieksekusi, sistem akan melihat kata-kata setelah instruksi jebakan untuk melihat segmen dan offset apa yang diperlukan, memuat segmen (jika belum), tambahkan alamat awal segmen ke offset, dan lalu ganti perangkap dengan lompatan ke alamat yang baru dihitung tersebut.
Pada perangkat lunak PC lama, meskipun ini tidak secara teknis dilakukan oleh "OS", itu adalah umum untuk kode yang akan dibangun dengan instruksi trap daripada instruksi matematika coprocessor. Jika tidak ada coprocessor matematika yang dipasang, trap handler akan meniru itu. Jika coprocessor dipasang, pertama kali jebakan diambil, pawang akan mengganti instruksi trap dengan instruksi coprocessor; eksekusi masa depan dari kode yang sama akan menggunakan instruksi coprocessor secara langsung.
sumber