यूनिक्स डेमॉन ऐसे प्रोग्राम हैं जो बैकग्राउंड में चलते हैं। Nginx, Postgres और OpenSSH इसके कुछ उदाहरण हैं। वे अपनी प्रक्रियाओं को "अलग" करने के लिए कुछ विशेष तरकीबों का उपयोग करते हैं, और उन्हें किसी भी टर्मिनल से स्वतंत्र रूप से चलने देते हैं।
मैं हमेशा डेमॉन से मोहित रहा हूं - शायद यह नाम है - और मैंने सोचा कि यह एक पोस्ट करने में मजेदार होगा कि वे कैसे काम करते हैं। और विशेष रूप से, आप उन्हें रूबी में कैसे बना सकते हैं।
...लेकिन पहले।
इसे घर पर न आजमाएं!
आप शायद एक डेमॉन नहीं बनाना चाहते हैं। काम पूरा करने के और भी आसान तरीके हैं।
आप एक प्रोग्राम बनाना चाह सकते हैं जो पृष्ठभूमि में चलता है। कोई बात नहीं। आपका OS आपको पृष्ठभूमि में सामान्य प्रोग्राम चलाने देने के लिए एक सिस्टम प्रदान करता है।
उबंटू पर, यह सिस्टमड के अपस्टार्ट के माध्यम से पूरा किया जाता है। OSX पर इसे लॉन्च किया गया है। अन्य हैं। लेकिन वे सभी एक ही अवधारणा के अनुसार काम करते हैं। आप एक कॉन्फ़िगरेशन फ़ाइल प्रदान करते हैं जो सिस्टम को बताती है कि लंबे समय से चल रहे प्रोग्राम को कैसे शुरू और बंद करना है। फिर... ठीक है, बस इतना ही। आप service my_app start
. जैसे सिस्टम कमांड का उपयोग करके प्रोग्राम शुरू कर सकते हैं और यह बैकग्राउंड में चलता है।
संक्षेप में, अपस्टार्ट सरल और विश्वसनीय होता है जबकि पुराने जमाने के डेमॉन रहस्यमय होते हैं और उन्हें ठीक करना बहुत कठिन होता है।
...लेकिन अगर ऐसा है, तो हमें डेमॉन के बारे में क्यों सीखना चाहिए? अच्छा, क्योंकि मज़ा! और हम रास्ते में यूनिक्स प्रक्रियाओं के बारे में कुछ दिलचस्प तथ्य सीखेंगे।
सबसे सरल डेमॉन
अब जबकि आपसे कहा गया है कि कभी भी डेमॉन न बनाएं, आइए कुछ डेमॉन बनाएं! रूबी 1.9 के रूप में, यह अविश्वसनीय रूप से सरल है। आपको बस इतना करना है कि Process.daemon विधि का उपयोग करें।
# Optional: set the process name to something easy to type<br>$PROGRAM_NAME = "rubydaemon"<br>
# Make the current process into a daemon
Process.daemon()
# Once per second, log the current time to a file
loop do
File.open("/tmp/rubydaemon.log", "a") { |f| f.puts(Time.now) }
sleep(1)
end
अब, जब मैं इस स्क्रिप्ट को चलाता हूं, तो नियंत्रण कंसोल पर वापस चला जाता है। अगर मैं अपना लॉग पूंछता हूं, तो मैं देख सकता हूं कि टाइमस्टैम्प हर सेकेंड में जोड़ा जा रहा है, जैसा कि मैंने उम्मीद की थी।
तो यह आसान था। लेकिन यह अभी भी यह नहीं समझाता है कि डिमन्स कैसे काम करते हैं। करने के लिए वास्तव में समझें कि, हमें मैन्युअल रूप से डीमोनाइजेशन करने की आवश्यकता है।
पैरेंट प्रक्रिया बदलना
यदि आप सामान्य प्रोग्राम चलाने के लिए बैश का उपयोग करते हैं, तो उस प्रोग्राम की प्रक्रिया बैश का बच्चा है। लेकिन डेमॉन के साथ, इससे कोई फर्क नहीं पड़ता कि आप उन्हें कैसे लॉन्च करते हैं। उनकी मूल प्रक्रिया हमेशा OS द्वारा प्रदान की गई "रूट" प्रक्रिया होती है।
आप इसे डेमॉन की पैरेंट आईडी देखकर बता सकते हैं। एक डेमॉन की पैरेंट आईडी हमेशा 1 होती है। नीचे दिए गए उदाहरण में हम इसे दिखाने के लिए pstree का उपयोग करते हैं:
$ pstree
-+= 00001 root /sbin/launchd
|--- 72314 snhorne rubydaemon
दिलचस्प बात यह है कि "अनाथ प्रक्रियाएं" भी यही दिखती हैं। एक अनाथ प्रक्रिया एक बाल प्रक्रिया है जिसके माता-पिता ने समाप्त कर दिया है।
इसलिए, एक डेमॉन बनाने के लिए हमें जानबूझकर एक प्रक्रिया को अनाथ करना होगा। नीचे दिया गया कोड ऐसा करता है।
# Optional: set the process name to something easy to type
$PROGRAM_NAME = "rubydaemon"
# Create a new child process and exit the parent. This "orphans"
# our process and creates a daemon.
exit if fork()
# Once per second, log the current time to a file
loop do
File.open("/tmp/rubydaemon.log", "a") { |f| f.puts(Time.now) }
sleep(1)
end
फोर्क के लिए कॉल एक ही कोड चलाने वाली दो प्रक्रियाओं में परिणाम देता है। मूल प्रक्रिया नई प्रक्रिया का जनक है। कांटा माता-पिता के लिए एक सच्चा मूल्य और बच्चे के लिए एक गलत मूल्य देता है। तो exit if fork()
केवल माता-पिता से बाहर निकलता है।
वर्तमान सत्र से अलग करना
हमारे "डिमोनाइजेशन" कोड में कुछ समस्याएं हैं। हालांकि यह प्रक्रिया को सफलतापूर्वक अनाथ कर देता है, यह अभी भी टर्मिनल के सत्र का हिस्सा है। इसका मतलब है कि यदि आप टर्मिनल को मारते हैं, तो आप डेमॉन को मार देते हैं। इसे ठीक करने के लिए, हमें एक नया सत्र और फिर से कांटा बनाने की जरूरत है। यूनिक्स सत्र समूहों से परिचित नहीं हैं? यहां एक अच्छी स्टैक ओवरफ्लो पोस्ट है।
# Optional: set the process name to something easy to type
$PROGRAM_NAME = "rubydaemon"
# Create a new child process and exit the parent. This "orphans"
# our process and creates a daemon.
exit if fork
# Create a new session, create a new child process in it and
# exit the current process.
Process.setsid
exit if fork
# Once per second, log the current time to a file
loop do
File.open("/tmp/rubydaemon.log", "a") { |f| f.puts(Time.now) }
sleep(1)
end
रि-रूटिंग STDIN, STDOUT और STDERR
उपरोक्त कोड में एक और समस्या यह है कि यह मौजूदा एसटीडीओयूटी, आदि को जगह में छोड़ देता है। इसका मतलब है कि यदि आप टर्मिनल से डेमॉन को लॉन्च करते हैं, तो जो कुछ भी डेमॉन STDOUT को लिखता है वह आपके टर्मिनल पर भेजा जाएगा। अच्छा नहीं है।
लेकिन आप वास्तव में किसी भी पथ पर एसटीडीआईएन, एसटीडीओयूटी और एसटीडीईआरआर को फिर से रूट कर सकते हैं। यहां हम /dev/null पर फिर से रूट करते हैं।
# Optional: set the process name to something easy to type
$PROGRAM_NAME = "rubydaemon"
# Create a new child process and exit the parent. This "orphans"
# our process and creates a daemon.
exit if fork
# Create a new session, create a new child process in it and
# exit the current process.
Process.setsid
exit if fork
STDIN.reopen "/dev/null"
STDOUT.reopen "/dev/null", "a"
STDERR.reopen '/dev/null', 'a'
# Once per second, log the current time to a file
loop do
File.open("/tmp/rubydaemon.log", "a") { |f| f.puts(Time.now) }
sleep(1)
end
कार्यशील निर्देशिका को बदलना
अंत में, डेमॉन की कार्यशील निर्देशिका वह है जो हम उस निर्देशिका में होते थे जब हम इसे चलाते थे। यह शायद सबसे अच्छा विचार नहीं है, क्योंकि मैं बाद में निर्देशिका को हटाने का निर्णय ले सकता हूं। तो चलिए डायरेक्टरी को / में बदलते हैं।
# Optional: set the process name to something easy to type
$PROGRAM_NAME = "rubydaemon"
# Create a new child process and exit the parent. This "orphans"
# our process and creates a daemon.
exit if fork
# Create a new session, create a new child process in it and
# exit the current process.
Process.setsid
exit if fork
STDIN.reopen "/dev/null"
STDOUT.reopen "/dev/null", "a"
STDERR.reopen '/dev/null', 'a'
Dir.chdir("/")
# Once per second, log the current time to a file
loop do
File.open("/tmp/rubydaemon.log", "a") { |f| f.puts(Time.now) }
sleep(1)
end
क्या मैंने इसे पहले नहीं देखा?
चरणों का यह क्रम मूल रूप से वही है जो प्रत्येक रूबी डेमॉन को रूबी कोर में Process.daemon विधि को जोड़ने से पहले करना था। मैंने इसे ActiveSupport एक्सटेंशन से प्रोसेस मॉड्यूल तक लाइन के लिए काफी कॉपी किया है जिसे रेल 4.x में हटा दिया गया था। आप वह तरीका यहां देख सकते हैं।