Bagaimana mungkin melakukan pembaruan langsung saat program sedang berjalan?

15

Saya bertanya-tanya bagaimana aplikasi pembunuh seperti Thunderbird atau Firefox dapat diperbarui melalui manajer paket sistem saat mereka masih berjalan. Apa yang terjadi dengan kode lama ketika sedang diperbarui? Apa yang harus saya lakukan ketika saya ingin menulis sebuah program a.out yang memperbarui sendiri saat sedang berjalan?

ubuplex
sumber
@derobert Tidak persis: utas itu tidak masuk ke kekhasan executable. Ini memberikan latar belakang yang relevan tetapi itu bukan duplikat.
Gilles 'SO- stop being evil'
@ Gilles Nah, jika Anda meninggalkan sisa barang itu terlalu luas. Ada dua (setidaknya) pertanyaan di sini. Dan pertanyaan lainnya cukup dekat, ia bertanya mengapa mengganti file untuk memperbarui berfungsi di Unix.
derobert
Jika Anda benar-benar ingin melakukan ini dengan kode Anda sendiri, Anda mungkin ingin melihat bahasa pemrograman Erlang, di mana pembaruan kode "panas" adalah fitur utama. learnyousomeerlang.com/relups
mattdm
1
@Gilles BTW: Setelah melihat esai yang Anda tambahkan sebagai tanggapan, saya telah mencabut pemungutan suara dekat saya. Anda telah mengubah pertanyaan ini menjadi tempat yang bagus untuk mengarahkan siapa pun yang ingin tahu cara kerja pembaruan.
derobert

Jawaban:

20

Mengganti file secara umum

Pertama, ada beberapa strategi untuk mengganti file:

  1. Buka file yang ada untuk ditulis, potong hingga 0 panjang, dan tulis konten baru. (Varian yang kurang umum adalah membuka file yang ada, menimpa konten lama dengan konten baru, memotong file ke panjang baru jika lebih pendek.) Dalam istilah shell:

    echo 'new content' >somefile
    
  2. Hapus file lama, dan buat file baru dengan nama yang sama. Dalam istilah shell:

    rm somefile
    echo 'new content' >somefile
    
  3. Tulis ke file baru dengan nama sementara, lalu pindahkan file baru ke nama yang ada. Langkah ini menghapus file lama. Dalam istilah shell:

    echo 'new content' >somefile.new
    mv somefile.new somefile
    

Saya tidak akan mencantumkan semua perbedaan antara strategi, saya hanya akan menyebutkan beberapa yang penting di sini. Dengan stategy 1, jika ada proses saat ini menggunakan file, proses melihat konten baru saat sedang diperbarui. Ini dapat menyebabkan beberapa kebingungan jika proses mengharapkan konten file tetap sama. Perhatikan bahwa ini hanya tentang proses yang membuka file (seperti terlihat di dalam lsofatau di ; aplikasi interaktif yang memiliki dokumen terbuka (misalnya membuka file di editor) biasanya tidak membuat file tetap terbuka, mereka memuat konten file selama Operasi "buka dokumen" dan mereka mengganti file (menggunakan salah satu strategi di atas) selama operasi "simpan dokumen"./proc/PID/fd/

Dengan strategi 2 dan 3, jika beberapa proses somefilemembuka file , file lama tetap terbuka selama peningkatan konten. Dengan strategi 2, langkah menghapus file sebenarnya hanya menghapus entri file di direktori. File itu sendiri hanya dihapus ketika tidak memiliki entri direktori yang mengarah ke sana (pada sistem file Unix khas, mungkin ada lebih dari satu entri direktori untuk file yang sama ) dan tidak ada proses yang terbuka. Inilah cara untuk mengamati ini - file hanya dihapus ketika sleepprosesnya dimatikan ( rmhanya menghapus entri direktori-nya).

echo 'old content' >somefile
sleep 9999999 <somefile &
df .
rm somefile
df .
cat /proc/$!/fd/0
kill $!
df .

Dengan strategi 3, langkah memindahkan file baru ke nama yang ada menghapus entri direktori yang mengarah ke konten lama dan membuat entri direktori yang mengarah ke konten baru. Ini dilakukan dalam satu operasi atom, jadi strategi ini memiliki keuntungan besar: jika suatu proses membuka file kapan saja, ia akan melihat konten lama atau konten baru - tidak ada risiko mendapatkan konten campuran atau file tidak ada.

Mengganti executable

Jika Anda mencoba strategi 1 dengan executable yang berjalan di Linux, Anda akan mendapatkan kesalahan.

cp /bin/sleep .
./sleep 999999 &
echo oops >|sleep
bash: sleep: Text file busy

“File teks” berarti file yang berisi kode yang dapat dieksekusi untuk alasan historis yang tidak jelas . Linux, seperti banyak varian unix lainnya, menolak untuk menimpa kode program yang sedang berjalan; beberapa varian unix memungkinkan ini, menyebabkan crash kecuali kode baru itu modifikasi yang sangat baik dari kode lama.

Di Linux, Anda dapat menimpa kode perpustakaan yang dimuat secara dinamis. Kemungkinan akan menyebabkan crash pada program yang menggunakannya. (Anda mungkin tidak dapat mengamati ini dengan sleepkarena memuat semua kode perpustakaan yang dibutuhkan ketika dimulai. Cobalah program yang lebih kompleks yang melakukan sesuatu yang berguna setelah tidur, misalnya perl -e 'sleep 9; print lc $ARGV[0]'.)

Jika penerjemah menjalankan skrip, file skrip dibuka dengan cara biasa oleh penerjemah, sehingga tidak ada perlindungan terhadap penulisan ulang skrip. Beberapa penerjemah membaca dan mem-parsing seluruh skrip sebelum mereka mulai mengeksekusi baris pertama, yang lain membaca skrip sesuai kebutuhan. Lihat Apa yang terjadi jika Anda mengedit skrip selama eksekusi? dan Bagaimana Linux menangani skrip shell? untuk lebih jelasnya.

Strategi 2 dan 3 juga aman untuk executable: walaupun menjalankan executable (dan perpustakaan yang dimuat secara dinamis) tidak membuka file dalam arti memiliki deskriptor file, mereka berperilaku dengan cara yang sangat mirip. Selama beberapa program menjalankan kode, file tetap di disk bahkan tanpa entri direktori.

Memutakhirkan aplikasi

Sebagian besar manajer paket menggunakan strategi 3 untuk mengganti file, karena keunggulan utama yang disebutkan di atas - kapan saja, membuka file mengarah ke versi yang valid.

Di mana pemutakhiran aplikasi dapat rusak adalah bahwa sementara memutakhirkan satu file bersifat atomik, memutakhirkan aplikasi secara keseluruhan tidak terjadi jika aplikasi terdiri dari banyak file (program, perpustakaan, data, ...). Pertimbangkan urutan kejadian berikut:

  1. Sebuah instance dari aplikasi dimulai.
  2. Aplikasi ditingkatkan.
  3. Aplikasi instance yang berjalan membuka salah satu file datanya.

Pada langkah 3, instance berjalan dari versi lama aplikasi membuka file data dari versi baru. Apakah ini berfungsi atau tidak tergantung pada aplikasi, dari file mana itu dan seberapa banyak file tersebut telah dimodifikasi.

Setelah peningkatan, Anda akan perhatikan bahwa program lama masih berjalan. Jika Anda ingin menjalankan versi baru, Anda harus keluar dari program lama dan menjalankan versi baru. Manajer paket biasanya membunuh dan memulai ulang daemon pada pembaruan, tetapi membiarkan aplikasi pengguna akhir saja.

Beberapa daemon memiliki prosedur khusus untuk menangani peningkatan tanpa harus mematikan daemon dan menunggu instance baru untuk restart (yang menyebabkan gangguan layanan). Ini diperlukan dalam kasus init , yang tidak dapat dibunuh; sistem init menyediakan cara untuk meminta panggilan instance yang berjalan execveuntuk menggantikan dirinya dengan versi baru.

Gilles 'SO- berhenti menjadi jahat'
sumber
"lalu pindahkan file baru ke nama yang ada. Langkah itu menghapus file lama." Itu sedikit membingungkan, karena itu benar-benar hanya sebuah unlink, seperti yang Anda bahas nanti. Mungkin "mengganti nama yang ada", tapi itu masih agak membingungkan.
derobert
@derobert Saya tidak ingin terlalu berat ke terminologi "unlink". Saya menggunakan "delete" mengacu pada entri direktori, kehalusan yang dijelaskan nanti. Apakah ini membingungkan pada tahap itu?
Gilles 'SANGAT berhenti menjadi jahat'
Mungkin tidak cukup membingungkan untuk menjamin satu atau dua paragraf tambahan yang menjelaskan pembatalan tautan. Saya berharap beberapa kata yang tidak membingungkan, tetapi juga secara teknis benar. Mungkin hanya menggunakan "hapus" lagi, yang sudah Anda beri tautan untuk menjelaskan?
derobert
3

Pembaruan dapat dijalankan saat program berjalan, tetapi program yang berjalan yang Anda lihat sebenarnya adalah versi lama. Biner lama tetap di disk sampai Anda menutup program.

Penjelasan: pada sistem Linux, file hanyalah sebuah inode, yang dapat memiliki beberapa tautan. Misalnya. yang /bin/bashAnda lihat hanyalah tautan ke inode 3932163sistem saya. Anda dapat menemukan inode mana yang melakukan tautan ke sesuatu dengan menerbitkan ls --inode /pathtautan tersebut. File (inode) hanya dihapus jika tidak ada tautan yang mengarah ke sana dan tidak digunakan oleh program apa pun. Ketika pengelola paket melakukan upgrade, mis. /usr/bin/firefox, pertama-tama menghapus tautan (menghapus tautan keras /usr/bin/firefox), kemudian membuat file baru bernama /usr/bin/firefoxhardlink ke inode yang berbeda (yang berisi firefoxversi baru ). Inode lama sekarang ditandai sebagai bebas dan dapat digunakan kembali untuk menyimpan data baru tetapi tetap pada disk (inode hanya dibuat ketika Anda membangun sistem file Anda dan tidak pernah dihapus). Di awal berikutnyafirefox, yang baru akan digunakan.

Jika Anda ingin menulis sebuah program yang "memutakhirkan" dirinya saat berjalan, satu-satunya solusi yang dapat saya pikirkan adalah dengan memeriksa secara berkala stempel waktu file binernya sendiri, dan jika itu lebih baru daripada waktu mulai program, kemudian muat ulang.

psimon
sumber
1
Sebenarnya, itu karena cara menghapus (membatalkan tautan) file bekerja di Unix; lihat unix.stackexchange.com/questions/49299/... Juga, setidaknya di Linux, Anda tidak bisa benar-benar menulis ke biner yang sedang berjalan, Anda akan mendapatkan kesalahan "text file busy".
derobert
Aneh ... Lalu bagaimana caranya misalnya. aptPekerjaan peningkatan Debian ? Saya dapat memutakhirkan program yang sedang berjalan tanpa masalah termasuk Iceweasel( Firefox).
psimon
2
APT (atau, lebih tepatnya, dpkg) tidak menimpa file. Alih-alih memutuskan tautan mereka dan menempatkan yang baru di bawah nama yang sama. Lihat pertanyaan & jawaban yang saya tautkan untuk penjelasan.
derobert
2
Ini bukan hanya karena masih dalam RAM, itu masih pada disk. File tidak benar-benar dihapus sampai contoh terakhir dari program keluar.
derobert
2
Situs tidak akan membiarkan saya menyarankan edit (setelah perwakilan Anda cukup tinggi, Anda cukup mengedit, Anda tidak dapat lagi menyarankannya). Jadi, sebagai komentar: file (inode) pada sistem Unix biasanya memiliki satu nama. Tetapi dapat memiliki lebih banyak jika Anda menambahkan nama dengan ln(tautan keras). Anda dapat menghapus nama dengan rm(batalkan tautan). Anda sebenarnya tidak bisa langsung menghapus file, hanya menghapus namanya. Ketika sebuah file tidak memiliki nama dan tambahan tidak terbuka, kernel akan menghapusnya. Program yang sedang berjalan memiliki file yang dijalankannya dari buka, jadi bahkan setelah menghapus semua nama, file tersebut masih ada.
derobert
0

Saya ingin tahu bagaimana aplikasi pembunuh seperti Thunderbird atau Firefox dapat diperbarui melalui manajer paket sistem ketika masih berjalan? Yah, saya dapat memberi tahu Anda bahwa ini tidak benar-benar berfungsi dengan baik ... Firefox saya rusak cukup parah jika dibiarkan terbuka saat pembaruan paket sedang berjalan. Saya kadang-kadang harus membunuhnya dengan paksa dan menyalakannya kembali, karena sangat rusak sehingga saya bahkan tidak bisa menutupnya dengan benar.

Apa yang terjadi dengan kode lama ketika sedang diperbarui? Biasanya di Linux suatu program dimuat ke dalam memori, sehingga executable pada disk tidak diperlukan atau digunakan saat program sedang berjalan. Bahkan Anda bahkan dapat menghapus file yang dapat dieksekusi dan program tidak perlu ... Namun, beberapa program mungkin memerlukan file yang dapat dieksekusi, dan OS tertentu (seperti Windows) akan mengunci file yang dapat dieksekusi, mencegah penghapusan atau bahkan mengubah nama / memindahkan, sedangkan program sedang berjalan. Firefox rusak, karena sebenarnya cukup rumit dan menggunakan banyak file data yang memberi tahu cara membangun GUI (antarmuka pengguna). Selama paket pembaruan, file-file ini ditimpa (diperbarui), jadi ketika Firefox yang lebih lama dapat dieksekusi (dalam memori) mencoba menggunakan file GUI baru, hal-hal aneh dapat terjadi ...

Apa yang harus saya lakukan ketika saya ingin menulis sebuah program a.out yang memperbarui sendiri saat sedang berjalan? Sudah banyak jawaban untuk pertanyaan Anda. Lihat ini: /programming/232347/how-should-i-implement-an-auto-updater By the way, pertanyaan tentang pemrograman lebih baik di StackOverflow.

Rouben Tchakhmakhtchian
sumber
2
Sebenarnya executable paged-demand (swapped). Mereka tidak sepenuhnya dimuat ke dalam memori, dan dapat dikeluarkan dari memori setiap kali sistem menginginkan RAM untuk sesuatu yang lain. Versi lama sebenarnya tersisa di disk; lihat unix.stackexchange.com/questions/49299/… . Di Linux setidaknya, Anda tidak bisa benar-benar menulis ke executable yang sedang berjalan, Anda akan mendapatkan kesalahan "file teks sibuk". Bahkan root tidak bisa melakukannya. (Anda benar tentang Firefox).
derobert