Apa alasan melakukan garpu ganda saat membuat daemon?

165

Saya mencoba membuat daemon dengan python. Saya telah menemukan pertanyaan berikut , yang memiliki beberapa sumber daya bagus di dalamnya yang sedang saya ikuti, tetapi saya ingin tahu mengapa garpu ganda diperlukan. Saya telah menjelajahi Google dan menemukan banyak sumber daya yang menyatakan bahwa itu perlu, tetapi tidak mengapa.

Beberapa menyebutkan bahwa itu untuk mencegah daemon dari memperoleh terminal pengendali. Bagaimana cara melakukan ini tanpa garpu kedua? Apa akibatnya?

Pakaian Shabby
sumber
2
Satu kesulitan dengan melakukan garpu ganda adalah bahwa orang tua tidak dapat dengan mudah mendapatkan PID dari proses cucu ( fork()panggilan mengembalikan PID anak ke orang tua, sehingga mudah untuk mendapatkan PID dari proses anak, tetapi tidak begitu mudah untuk dapatkan PID dari proses cucu ).
Craig McQueen

Jawaban:

105

Melihat kode yang dirujuk dalam pertanyaan, pembenarannya adalah:

Garpu anak kedua dan segera keluar untuk mencegah zombie. Ini menyebabkan proses anak kedua menjadi yatim piatu, membuat proses init bertanggung jawab atas pembersihannya. Dan, karena anak pertama adalah pemimpin sesi tanpa terminal pengendali, dimungkinkan untuk memperolehnya dengan membuka terminal di masa mendatang (sistem berbasis sistem V). Garpu kedua ini menjamin bahwa anak itu tidak lagi menjadi pemimpin sesi, mencegah daemon dari mendapatkan terminal pengendali.

Jadi itu adalah untuk memastikan bahwa daemon di-parented kembali ke init (kalau-kalau proses menendang daemon itu berumur panjang), dan menghilangkan kemungkinan daemon mendapatkan kembali tty kontrol. Jadi, jika tidak satu pun dari kasus ini berlaku, maka satu garpu harus cukup. " Pemrograman Jaringan Unix - Stevens " memiliki bagian yang bagus tentang ini.

Pesta
sumber
28
Ini tidak sepenuhnya benar. Cara standar untuk membuat daemon adalah cukup lakukan p=fork(); if(p) exit(); setsid(). Dalam hal ini, orang tua juga keluar dan proses anak pertama diperbaiki. Sihir garpu ganda hanya diperlukan untuk mencegah daemon memperoleh tty.
parasietje
1
Jadi, seperti yang saya pahami, jika program saya memulai dan forkssuatu childproses, proses anak pertama ini akan menjadi session leaderdan akan dapat membuka terminal TTY. Tetapi jika saya bercabang lagi dari anak ini dan mengakhiri anak pertama ini, anak bercabang kedua tidak akan menjadi session leaderdan tidak akan dapat membuka terminal TTY. Apakah pernyataan ini benar?
tonix
2
@tonix: hanya forking tidak membuat pemimpin sesi. Itu dilakukan oleh setsid(). Jadi, proses bercabang pertama menjadi pemimpin sesi setelah memanggil setsid()dan kemudian kami bercabang lagi sehingga proses, bercabang ganda terakhir tidak lagi menjadi pemimpin sesi. Selain persyaratan setsid()untuk menjadi pemimpin sesi, Anda tepat.
dbmikus
169

Saya mencoba memahami garpu ganda dan menemukan pertanyaan ini di sini. Setelah banyak penelitian, inilah yang saya temukan. Semoga ini akan membantu memperjelas hal-hal yang lebih baik bagi siapa pun yang memiliki pertanyaan yang sama.

Di Unix setiap proses milik grup yang pada gilirannya milik sesi. Inilah hierarki ...

Sesi (SID) → Grup Proses (PGID) → Proses (PID)

Proses pertama dalam grup proses menjadi pemimpin grup proses dan proses pertama dalam sesi menjadi pemimpin sesi. Setiap sesi dapat memiliki satu TTY yang terkait dengannya. Hanya pemimpin sesi yang bisa mengendalikan TTY. Agar suatu proses benar-benar di-daemonisasi (dijalankan di latar belakang) kita harus memastikan bahwa ketua sesi terbunuh sehingga tidak ada kemungkinan sesi tersebut pernah mengambil kendali atas TTY.

Saya menjalankan program contoh daemon python Sander Marechal dari situs ini di Ubuntu saya. Inilah hasilnya dengan komentar saya.

1. `Parent`    = PID: 28084, PGID: 28084, SID: 28046
2. `Fork#1`    = PID: 28085, PGID: 28084, SID: 28046
3. `Decouple#1`= PID: 28085, PGID: 28085, SID: 28085
4. `Fork#2`    = PID: 28086, PGID: 28085, SID: 28085

Perhatikan bahwa prosesnya adalah pemimpin sesi setelah itu Decouple#1, karena itu PID = SID. Itu masih bisa mengendalikan TTY.

Perhatikan bahwa Fork#2tidak lagi pemimpin sesi PID != SID. Proses ini tidak pernah bisa mengendalikan TTY. Benar-benar dianemonisasi.

Saya pribadi menemukan istilah garpu-dua kali membingungkan. Ungkapan yang lebih baik mungkin fork-decouple-fork.

Tautan minat tambahan:

Praveen Gollakota
sumber
Forking dua kali juga mencegah pembuatan zombie ketika proses induk berjalan untuk waktu yang lebih lama dan karena alasan tertentu menghapus handler default untuk sinyal yang memberitahukan bahwa proses mati.
Trismegistos
Tetapi kedua untuk juga dapat memanggil decouple dan menjadi pemimpin sesi dan kemudian memperoleh terminal.
Trismegistos
2
Ini tidak benar. Yang pertama fork()sudah mencegah pembuatan zombie, asalkan Anda menutup induknya.
parasietje
1
Contoh minimal untuk menghasilkan hasil yang dikutip di atas: gist.github.com/cannium/7aa58f13c834920bb32c
can.
1
Apakah ada gunanya menelepon setsid() sebelum satu fork()? Sebenarnya saya kira jawaban dari pertanyaan ini adalah jawabannya.
Craig McQueen
118

Sebenarnya, garpu ganda tidak ada hubungannya dengan mengasuh kembali daemon sebagai anak init. Semua yang diperlukan untuk menjadi orang tua kembali anak adalah bahwa orang tua harus keluar. Ini dapat dilakukan dengan hanya satu garpu. Selain itu, melakukan double-fork dengan sendirinya tidak menjadi proses ulang daemon init; orang tua daemon harus keluar. Dengan kata lain, orang tua selalu keluar saat forking daemon yang tepat sehingga proses daemon di-parent kembali init.

Jadi mengapa garpu ganda? POSIX.1-2008 Bagian 11.1.3, " The Controlling Terminal ", memiliki jawabannya (penekanan ditambahkan):

Terminal pengendali untuk suatu sesi dialokasikan oleh pemimpin sesi dengan cara yang ditentukan implementasi. Jika pemimpin sesi tidak memiliki terminal pengendali, dan membuka file perangkat terminal yang belum dikaitkan dengan sesi tanpa menggunakan O_NOCTTYopsi (lihat open()), itu ditentukan implementasi apakah terminal menjadi terminal pengendali pemimpin sesi. Jika suatu proses yang bukan pemimpin sesi membuka file terminal, atau O_NOCTTYopsi digunakan open(), maka terminal itu tidak akan menjadi terminal pengendali dari proses panggilan .

Ini memberitahu kita bahwa jika proses daemon melakukan sesuatu seperti ini ...

int fd = open("/dev/console", O_RDWR);

... maka proses daemon mungkin diperoleh /dev/consolesebagai terminal pengendali, tergantung pada apakah proses daemon adalah pemimpin sesi, dan tergantung pada implementasi sistem. Program dapat menjamin bahwa panggilan di atas tidak akan mendapatkan terminal pengendali jika program pertama memastikan bahwa itu bukan pemimpin sesi.

Biasanya, ketika meluncurkan daemon, setsiddipanggil (dari proses anak setelah memanggil fork) untuk memisahkan daemon dari terminal pengendali. Namun, panggilan setsidjuga berarti bahwa proses panggilan akan menjadi pemimpin sesi sesi baru, yang membuka kemungkinan bahwa daemon dapat memperoleh kembali terminal pengendali. Teknik garpu ganda memastikan bahwa proses daemon bukan pemimpin sesi, yang kemudian menjamin bahwa panggilan untuk open, seperti dalam contoh di atas, tidak akan mengakibatkan proses daemon mendapatkan kembali terminal pengendali.

Teknik garpu ganda agak paranoid. Mungkin tidak perlu jika Anda tahu bahwa daemon tidak akan pernah membuka file perangkat terminal. Juga, pada beberapa sistem mungkin tidak diperlukan bahkan jika daemon benar-benar membuka file perangkat terminal, karena perilaku itu didefinisikan oleh implementasi. Namun, satu hal yang tidak didefinisikan implementasi adalah bahwa hanya pemimpin sesi yang dapat mengalokasikan terminal pengendali. Jika suatu proses bukan pemimpin sesi, itu tidak dapat mengalokasikan terminal pengendali. Oleh karena itu, jika Anda ingin menjadi paranoid dan memastikan bahwa proses daemon tidak dapat secara tidak sengaja memperoleh terminal pengendali, terlepas dari implementasi spesifik yang ditentukan, maka teknik garpu ganda sangat penting.

Dan Moulding
sumber
3
+1 Sayang sekali jawaban ini datang ~ empat tahun setelah pertanyaan diajukan.
Tim Seguine
12
Tapi itu masih belum menjelaskan mengapa itu sangat penting bahwa daemon tidak dapat memperoleh kembali terminal pengendali
UloPe
7
Kata kuncinya adalah "secara tidak sengaja" memperoleh terminal pengendali. Jika proses tersebut terjadi untuk membuka terminal, dan itu menjadi proses yang mengendalikan terminal, daripada jika seseorang mengeluarkan a ^ C dari terminal itu, ia dapat menghentikan proses tersebut. Jadi mungkin bagus untuk melindungi suatu proses agar tidak terjadi tanpa disengaja. Secara pribadi saya akan tetap menggunakan satu garpu dan setsid () untuk kode yang saya tulis yang saya tahu tidak akan membuka terminal.
BobDoolittle
1
@ BobDoolittle bagaimana ini bisa terjadi "secara tidak sengaja"? Suatu proses tidak hanya akan dengan mudah berakhir membuka terminal jika tidak ditulis untuk melakukannya. Mungkin double-forking bermanfaat jika Anda programmer tidak tahu kode dan tidak tahu apakah itu mungkin membuka tty.
Marius
10
@Marius Bayangkan apa yang mungkin terjadi jika Anda menambahkan baris seperti ini untuk file konfigurasi daemon Anda: LogFile=/dev/console. Program tidak selalu memiliki kontrol waktu kompilasi atas file mana yang mungkin dibuka;)
Dan Moulding
11

Diambil dari Bad CTK :

"Pada beberapa rasa Unix, kamu terpaksa melakukan double-fork saat startup, untuk masuk ke mode daemon. Ini karena forking tunggal tidak dijamin untuk terlepas dari terminal pengendali."

Stefan Thyberg
sumber
3
Bagaimana garpu tunggal tidak terlepas dari terminal pengendali tetapi garpu ganda melakukannya? Unix apa ini terjadi?
bdonlan
12
Daemon harus menutup input dan output file deskriptor (fds), jika tidak, ia akan tetap melekat pada terminal tempat dimulainya. Proses bercabang mewarisi yang dari orangtua. Rupanya, anak pertama menutup FDS tetapi itu tidak membersihkan segalanya. Pada garpu kedua, fds tidak ada, sehingga anak kedua tidak dapat terhubung ke apa pun lagi.
Aaron Digulla
4
@ Harun: Tidak, daemon dengan benar "melepaskan" dirinya dari terminal pengendali dengan memanggil setsidsetelah fork awal. Ini kemudian memastikan bahwa ia tetap terlepas dari terminal pengendali dengan bercabang lagi dan meminta ketua sesi (proses yang disebut setsid) keluar.
Dan Moulding
2
@ Bdonlan: Bukan forkitu terlepas dari terminal pengendali. Itu setsidyang melakukannya. Tetapi setsidakan gagal jika dipanggil dari pemimpin grup proses. Jadi inisial forkharus dilakukan sebelumnya setsiduntuk memastikan yang setsiddipanggil dari proses yang bukan pemimpin grup proses. Yang kedua forkmemastikan bahwa proses akhir (yang akan menjadi daemon) bukan pemimpin sesi. Hanya pemimpin sesi yang dapat memperoleh terminal pengendali, jadi garpu kedua ini menjamin bahwa daemon tidak akan secara tidak sengaja mendapatkan kembali terminal pengendali. Ini berlaku untuk OS POSIX apa pun.
Dan Moulding
@DanMoulding Ini tidak menjamin bahwa anak kedua tidak akan mendapatkan terminal pengendali karena ia dapat memanggil setsid dan menjadi pemimpin sesi dan kemudian mendapatkan terminal kontrol.
Trismegistos
7

Menurut "Pemrograman Lanjutan di Lingkungan Unix", oleh Stephens dan Rago, garpu kedua lebih merupakan rekomendasi, dan itu dilakukan untuk menjamin bahwa daemon tidak memperoleh terminal pengendali pada sistem berbasis sistem V.

Paolo Tedesco
sumber
3

Salah satu alasannya adalah bahwa proses induk dapat segera wait_pid () untuk anak, dan kemudian melupakannya. Ketika cucu itu meninggal, orang tuanya adalah init, dan ia akan menunggu () untuk itu - dan membawanya keluar dari keadaan zombie.

Hasilnya adalah bahwa proses induk tidak perlu mewaspadai anak-anak bercabang, dan itu juga memungkinkan untuk memotong proses yang berjalan lama dari libs dll.

KarlP
sumber
2

Panggilan daemon () memiliki panggilan induk _exit () jika berhasil. Motivasi asli mungkin untuk memungkinkan orang tua melakukan beberapa pekerjaan ekstra saat anak melakukan dasemonisasi.

Ini juga mungkin didasarkan pada kepercayaan yang salah bahwa itu diperlukan untuk memastikan daemon tidak memiliki proses induk dan diulangi untuk init - tetapi ini akan tetap terjadi begitu orang tua meninggal dalam kasus garpu tunggal.

Jadi saya kira itu semua hanya bermuara pada tradisi pada akhirnya - garpu tunggal sudah cukup selama orang tua meninggal dalam waktu singkat.

omong kosong
sumber
2

Diskusi yang layak tampaknya di http://www.developerweb.net/forum/showthread.php?t=3025

Mengutip mlampkin dari sana:

... anggap panggilan setsid () sebagai cara "baru" untuk melakukan sesuatu (lepas dari terminal) dan garpu [kedua] () menyebutnya sebagai redundansi untuk berurusan dengan SVr4 ...

Stobor
sumber
-1

Mungkin lebih mudah dipahami dengan cara ini:

  • Garpu dan setid pertama akan membuat sesi baru (tetapi ID proses == ID sesi).
  • Garpu kedua memastikan ID proses! = ID sesi.
pesolek
sumber