Sepertinya saya tidak dapat menemukan informasi mengenai hal ini selain dari "MMU CPU mengirimkan sinyal" dan "kernel mengarahkannya ke program yang menyinggung, menghentikannya".
Saya berasumsi bahwa itu mungkin mengirim sinyal ke shell dan shell menanganinya dengan menghentikan proses yang menyinggung dan mencetak "Segmentation fault"
. Jadi saya menguji asumsi itu dengan menulis shell yang sangat minim yang saya sebut crsh (crap shell). Shell ini tidak melakukan apa pun kecuali mengambil input pengguna dan memasukkannya ke system()
metode.
#include <stdio.h>
#include <stdlib.h>
int main(){
char cmdbuf[1000];
while (1){
printf("Crap Shell> ");
fgets(cmdbuf, 1000, stdin);
system(cmdbuf);
}
}
Jadi saya menjalankan shell ini di terminal telanjang (tanpa bash
berjalan di bawah). Kemudian saya melanjutkan untuk menjalankan program yang menghasilkan segfault. Jika asumsi saya benar, ini akan a) crash crsh
, menutup xterm, b) tidak mencetak "Segmentation fault"
, atau c) keduanya.
braden@system ~/code/crsh/ $ xterm -e ./crsh
Crap Shell> ./segfault
Segmentation fault
Crap Shell> [still running]
Kembali ke titik awal, kurasa. Saya baru saja menunjukkan bahwa bukan shell yang melakukan ini, tetapi sistem di bawahnya. Bagaimana "Kesalahan segmentasi" dicetak? "Siapa" yang melakukannya? Kernel? Sesuatu yang lain Bagaimana sinyal dan semua efek sampingnya menyebar dari perangkat keras ke penghentian program pada akhirnya?
sumber
crsh
adalah ide bagus untuk eksperimen semacam ini. Terima kasih telah memberi tahu kami tentang hal itu dan gagasan di baliknya.crsh
, saya pikir itu akan diucapkan "crash." Saya tidak yakin apakah itu nama yang pas.system()
fungsinya di bawah tenda. Ternyata itusystem()
akan menelurkan proses shell! Jadi proses shell Anda memunculkan proses shell lain dan bahwa proses shell (mungkin/bin/sh
atau sesuatu seperti itu) adalah salah satu yang menjalankan program. Cara/bin/sh
ataubash
kerjanya adalah dengan menggunakanfork()
danexec()
(atau fungsi lain dalamexecve()
keluarga).man 2 wait
, itu akan termasuk makroWIFSIGNALED()
danWTERMSIG()
.(WIFSIGNALED(status) && WTERMSIG(status) == 11)
mencetak sesuatu yang konyol ("YOU DUN GOOFED AND TRIGGERED A SEGFAULT"
). Ketika saya menjalankansegfault
program dari dalamcrsh
, itu dicetak persis seperti itu. Sementara itu, perintah yang keluar biasanya tidak menghasilkan pesan kesalahan.Jawaban:
Semua CPU modern memiliki kapasitas untuk menginterupsi instruksi mesin yang sedang dijalankan. Mereka menyimpan cukup keadaan (biasanya, tetapi tidak selalu, di tumpukan) untuk memungkinkan untuk melanjutkan eksekusi nanti, seolah-olah tidak ada yang terjadi (biasanya instruksi yang terputus akan dimulai dari awal). Kemudian mereka mulai mengeksekusi pengendali interupsi , yang hanya kode mesin lebih banyak, tetapi ditempatkan di lokasi khusus sehingga CPU tahu di mana itu di muka. Interrupt handler selalu menjadi bagian dari kernel sistem operasi: komponen yang berjalan dengan hak istimewa terbesar dan bertanggung jawab untuk mengawasi pelaksanaan semua komponen lainnya. 1,2
Interupsi dapat bersifat sinkron , artinya dipicu oleh CPU itu sendiri sebagai respons langsung terhadap sesuatu yang dilakukan oleh instruksi yang saat ini dijalankan, atau asinkron , yang berarti bahwa itu terjadi pada waktu yang tidak dapat diprediksi karena peristiwa eksternal, seperti data yang tiba di jaringan Pelabuhan. Beberapa orang mencadangkan istilah "interupsi" untuk interupsi asinkron, dan memanggil interupsi sinkron "traps", "fault", atau "exception" sebagai gantinya, tetapi kata-kata itu semuanya memiliki arti lain sehingga saya akan tetap menggunakan "interrupt sinkron".
Sekarang, sebagian besar sistem operasi modern memiliki gagasan tentang proses . Pada dasarnya, ini adalah mekanisme di mana komputer dapat menjalankan lebih dari satu program pada saat yang sama, tetapi juga merupakan aspek kunci tentang bagaimana sistem operasi mengkonfigurasi perlindungan memori , yang merupakan fitur dari sebagian besar (tetapi, sayangnya, masih belum semua ) CPU modern. Ini berjalan bersama dengan memori virtual, yang merupakan kemampuan untuk mengubah pemetaan antara alamat memori dan lokasi aktual dalam RAM. Perlindungan memori memungkinkan sistem operasi untuk memberikan masing-masing proses potongan RAM sendiri, yang hanya dapat diakses olehnya. Hal ini juga memungkinkan sistem operasi (bertindak atas nama beberapa proses) untuk menetapkan wilayah RAM sebagai read-only, executable, dibagi di antara sekelompok proses yang bekerja sama, dll. Juga akan ada sejumlah memori yang hanya dapat diakses oleh inti. 3
Selama setiap proses mengakses memori hanya dengan cara yang dikonfigurasi CPU untuk memungkinkan, perlindungan memori tidak terlihat. Ketika suatu proses melanggar aturan, CPU akan menghasilkan interupsi sinkron, meminta kernel untuk menyelesaikan masalah. Secara teratur terjadi bahwa proses tidak benar - benar melanggar aturan, hanya kernel yang perlu melakukan beberapa pekerjaan sebelum proses dapat diizinkan untuk melanjutkan. Sebagai contoh, jika halaman memori proses perlu "diusir" ke file swap untuk membebaskan ruang dalam RAM untuk sesuatu yang lain, kernel akan menandai halaman itu tidak dapat diakses. Saat berikutnya proses mencoba menggunakannya, CPU akan menghasilkan interupsi perlindungan memori; kernel akan mengambil halaman dari swap, meletakkannya kembali di tempatnya, tandai itu dapat diakses kembali, dan melanjutkan eksekusi.
Tapi anggaplah proses itu benar-benar melanggar aturan. Itu mencoba mengakses halaman yang tidak pernah memiliki RAM yang dipetakan ke sana, atau mencoba untuk mengeksekusi halaman yang ditandai sebagai tidak mengandung kode mesin, atau apa pun. Keluarga sistem operasi umumnya dikenal sebagai "Unix" semua menggunakan sinyal untuk menghadapi situasi ini. 4 Sinyal mirip dengan interupsi, tetapi dihasilkan oleh kernel dan diteruskan oleh proses, bukannya dihasilkan oleh perangkat keras dan diteruskan oleh kernel. Proses dapat menentukan penangan sinyaldi kode mereka sendiri, dan beri tahu kernel di mana mereka berada. Penangan sinyal tersebut kemudian akan mengeksekusi, mengganggu aliran kontrol normal, bila perlu. Semua sinyal memiliki nomor dan dua nama, satu di antaranya adalah akronim samar dan yang lain frase sedikit lebih samar. Sinyal yang dihasilkan ketika proses melanggar aturan perlindungan memori adalah (dengan konvensi) nomor 11, dan namanya adalah
SIGSEGV
"Segmentasi kesalahan". 5,6Perbedaan penting antara sinyal dan interupsi adalah bahwa ada perilaku default untuk setiap sinyal. Jika sistem operasi gagal menentukan handler untuk semua interupsi, itu adalah bug di OS, dan seluruh komputer akan macet ketika CPU mencoba memanggil handler yang hilang. Tetapi proses tidak berkewajiban untuk mendefinisikan penangan sinyal untuk semua sinyal. Jika kernel menghasilkan sinyal untuk suatu proses, dan sinyal itu telah dibiarkan pada perilaku standarnya, kernel hanya akan melanjutkan dan melakukan apa pun defaultnya dan tidak mengganggu proses. Sebagian besar perilaku default sinyal adalah "tidak melakukan apa-apa" atau "menghentikan proses ini dan mungkin juga menghasilkan dump inti."
SIGSEGV
adalah salah satu yang terakhir.Jadi, untuk rekap, kami memiliki proses yang melanggar aturan perlindungan memori. CPU menghentikan proses dan menghasilkan interupsi sinkron. Kernel menerjunkan yang mengganggu dan menghasilkan
SIGSEGV
sinyal untuk proses. Mari kita asumsikan prosesnya tidak mengatur penangan sinyalSIGSEGV
, jadi kernel melakukan perilaku default, yaitu untuk menghentikan proses. Ini memiliki semua efek yang sama dengan_exit
panggilan sistem: file yang terbuka ditutup, memori tidak dapat dialokasikan, dll.Sampai saat ini tidak ada yang mencetak pesan yang dapat dilihat manusia, dan shell (atau, lebih umum, proses induk dari proses yang baru saja dihentikan) belum terlibat sama sekali.
SIGSEGV
pergi ke proses yang melanggar aturan, bukan induknya. Namun, langkah selanjutnya dalam urutan ini adalah memberi tahu proses induk bahwa anaknya telah dihentikan. Hal ini dapat terjadi dalam beberapa cara yang berbeda, dari yang paling sederhana adalah ketika orang tua sudah menunggu pemberitahuan ini, menggunakan salah satuwait
panggilan sistem (wait
,waitpid
,wait4
, dll). Dalam hal ini, kernel hanya akan menyebabkan panggilan sistem kembali, dan menyediakan proses induk dengan nomor kode yang disebut status keluar. 7 Status keluar memberitahu orang tua mengapa proses anak dihentikan; dalam hal ini, ia akan belajar bahwa anak itu dihentikan karena perilaku default dari suatuSIGSEGV
sinyal.Proses induk kemudian dapat melaporkan acara tersebut ke manusia dengan mencetak pesan; program shell hampir selalu melakukan ini. Anda
crsh
tidak menyertakan kode untuk melakukan itu, tetapi itu tetap terjadi, karena rutin pustaka Csystem
menjalankan shell berfitur lengkap/bin/sh
,, "di bawah tenda".crsh
adalah kakek - nenek dalam skenario ini; notifikasi proses induk dipotong oleh/bin/sh
, yang mencetak pesannya yang biasa. Kemudian/bin/sh
itu sendiri keluar, karena itu tidak ada lagi yang harus dilakukan, dan implementasi C perpustakaan darisystem
menerima bahwa pemberitahuan keluar. Anda dapat melihat pemberitahuan keluar itu dalam kode Anda, dengan memeriksa nilai balik darisystem
; tetapi itu tidak akan memberi tahu Anda bahwa proses cucu meninggal dengan segfault, karena itu dikonsumsi oleh proses cangkang perantara.Catatan kaki
Beberapa sistem operasi tidak mengimplementasikan driver perangkat sebagai bagian dari kernel; namun, semua penangan interupsi masih harus menjadi bagian dari kernel, dan begitu juga kode yang mengkonfigurasi perlindungan memori, karena perangkat keras tidak mengizinkan apa pun kecuali kernel untuk melakukan hal-hal ini.
Mungkin ada program yang disebut "hypervisor" atau "manajer mesin virtual" yang bahkan lebih istimewa daripada kernel, tetapi untuk keperluan jawaban ini dapat dianggap sebagai bagian dari perangkat keras .
Kernel adalah sebuah program , tetapi itu bukan proses; itu lebih seperti perpustakaan. Semua proses menjalankan bagian dari kode kernel, dari waktu ke waktu, selain kode mereka sendiri. Mungkin ada sejumlah "utas kernel" yang hanya mengeksekusi kode kernel, tetapi mereka tidak menjadi perhatian kami di sini.
Satu-satunya OS yang mungkin harus Anda hadapi lagi yang tidak dapat dianggap sebagai implementasi Unix, tentu saja, Windows. Itu tidak menggunakan sinyal dalam situasi ini. (Memang, itu tidak memiliki sinyal; pada Windows
<signal.h>
antarmuka sepenuhnya dipalsukan oleh perpustakaan C.) Ia menggunakan sesuatu yang disebut " penanganan pengecualian terstruktur " sebagai gantinya.Beberapa pelanggaran perlindungan memori menghasilkan
SIGBUS
("Bus error") alih-alihSIGSEGV
. Garis antara keduanya tidak ditentukan dan bervariasi dari satu sistem ke sistem lainnya. Jika Anda telah menulis sebuah program untuk mendefinisikan handlerSIGSEGV
, mungkin ide yang baik untuk mendefinisikan handler yang samaSIGBUS
."Segmentasi fault" adalah nama interupsi yang dihasilkan untuk pelanggaran perlindungan memori oleh salah satu komputer yang menjalankan Unix asli , mungkin PDP-11 . " Segmentasi " adalah jenis perlindungan memori, tetapi saat ini istilah " kesalahan segmentasi " mengacu secara umum pada segala jenis pelanggaran perlindungan memori.
Semua cara lain proses orang tua mungkin diberitahu tentang anak yang telah diakhiri, berakhir dengan panggilan orang tua
wait
dan menerima status keluar. Hanya saja sesuatu terjadi lebih dulu.sumber
mmap
file ke wilayah memori yang lebih besar dari file, dan kemudian mengakses "seluruh halaman" di luar akhir file. (POSIX dinyatakan tidak jelas tentang kapan SIGSEGV / SIGBUS / SIGILL / etc terjadi.)Shell memang ada hubungannya dengan pesan itu, dan
crsh
secara tidak langsung memanggil shell, yang mungkinbash
.Saya menulis sebuah program C kecil yang selalu kesalahan:
Ketika saya menjalankannya dari shell default saya
zsh
,, saya mendapatkan ini:Saat saya menjalankannya
bash
, saya mendapatkan apa yang Anda catat dalam pertanyaan Anda:Saya akan menulis penangan sinyal dalam kode saya, kemudian saya menyadari bahwa
system()
panggilan perpustakaan yang digunakan olehcrsh
shell eksekutif,/bin/sh
menurutman 3 system
. Itu/bin/sh
hampir pasti mencetak "kesalahan Segmentasi", karenacrsh
tentu saja tidak.Jika Anda menulis ulang
crsh
untuk menggunakanexecve()
system call untuk menjalankan program, Anda tidak akan melihat string "Kesalahan segmentasi". Itu berasal dari shell yang dipanggil olehsystem()
.sumber
execvp
dan melakukan tes lagi untuk menemukan bahwa sementara shell masih tidak crash (artinya SIGSEGV tidak pernah dikirim ke shell), ia tidak mencetak "Segmentasi Fault". Tidak ada yang dicetak sama sekali. Ini tampaknya menunjukkan bahwa shell mendeteksi ketika proses anaknya terbunuh dan bertanggung jawab untuk mencetak "kesalahan segmentasi" (atau beberapa varian darinya).waitpid()
setiap fork / exec, dan mengembalikan nilai yang berbeda untuk proses yang memiliki kesalahan segmentasi, daripada proses yang keluar dengan status 0.Ini sedikit ringkasan yang kacau. Mekanisme sinyal Unix sepenuhnya berbeda dari peristiwa khusus CPU yang memulai proses.
Secara umum, ketika alamat yang buruk diakses (atau ditulis ke area baca-saja, upaya untuk mengeksekusi bagian yang tidak dapat dieksekusi, dll.), CPU akan menghasilkan beberapa peristiwa khusus-CPU (pada arsitektur non-VM tradisional ini adalah disebut pelanggaran segmentasi, karena setiap "segmen" (secara tradisional, "teks" yang dapat dieksekusi hanya baca, "data" yang dapat ditulis dan panjang variabel, dan tumpukan yang secara tradisional berada di ujung memori) memiliki rentang alamat yang tetap - pada arsitektur modern, itu lebih cenderung menjadi kesalahan halaman [untuk memori yang belum dipetakan] atau pelanggaran akses [untuk membaca, menulis, dan mengeksekusi masalah izin], dan saya akan fokus pada hal ini untuk sisa jawabannya).
Sekarang, pada titik ini, kernel dapat melakukan beberapa hal. Kesalahan halaman juga dihasilkan untuk memori yang valid tetapi tidak dimuat (mis. Swap, atau dalam file yang di-mmaps, dll.), Dan dalam hal ini kernel akan memetakan memori dan kemudian memulai kembali program pengguna dari instruksi yang menyebabkan kesalahan. Kalau tidak, ia mengirim sinyal. Ini tidak persis "mengarahkan [acara asli] ke program yang menyinggung", karena proses untuk memasang penangan sinyal berbeda dan sebagian besar tidak bergantung pada arsitektur, vs. jika program tersebut diharapkan untuk mensimulasikan pemasangan penangan interrupt.
Jika program pengguna telah memasang penangan sinyal, ini berarti membuat bingkai tumpukan dan mengatur posisi eksekusi program pengguna ke penangan sinyal. Hal yang sama dilakukan untuk semua sinyal, tetapi dalam kasus pelanggaran segmentasi hal-hal umumnya diatur sehingga jika penangan sinyal kembali itu akan memulai kembali instruksi yang menyebabkan kesalahan. Program pengguna mungkin telah memperbaiki kesalahan, misalnya dengan memetakan memori ke alamat yang menyinggung - tergantung arsitektur apakah ini mungkin). Penangan sinyal juga dapat melompat ke lokasi yang berbeda dalam program (biasanya melalui longjmp atau dengan melemparkan pengecualian), untuk membatalkan operasi apa pun yang menyebabkan akses memori buruk.
Jika program pengguna tidak memiliki penangan sinyal yang diinstal, itu hanya dihentikan. Pada beberapa arsitektur, jika sinyal diabaikan itu dapat memulai kembali instruksi berulang-ulang, menyebabkan loop tak terbatas.
sumber
#PF(fault-code)
(kesalahan halaman) atau#GP(0)
("Jika alamat efektif operan memori berada di luar CS, Batas segmen DS, ES, FS, atau GS. "). Mode 64bit menjatuhkan pemeriksaan batas segmen, karena OS hanya menggunakan paging saja, dan model memori datar untuk ruang pengguna.Kesalahan segmentasi adalah akses ke alamat memori yang tidak diizinkan (bukan bagian dari proses, atau mencoba untuk menulis data hanya baca, atau mengeksekusi data yang tidak dapat dieksekusi, ...). Ini ditangkap oleh MMU (Memory Management Unit, sekarang bagian dari CPU), menyebabkan gangguan. Interupsi ditangani oleh kernel, yang mengirimkan
SIGSEGFAULT
sinyal (lihatsignal(2)
misalnya) ke proses yang menyinggung. Penangan default untuk sinyal ini membuang inti (lihatcore(5)
) dan mengakhiri proses.Shell sama sekali tidak memiliki tangan dalam hal ini.
sumber