लिनक्स के कई कार्यक्रमों और मैन पेजों में, मैंने कोड का उपयोग करते हुए देखा है fork()
। हमें उपयोग करने की आवश्यकता क्यों है fork()
और इसका उद्देश्य क्या है?
जवाबों:
fork()
यह है कि आप यूनिक्स में नई प्रक्रियाएँ कैसे बनाते हैं। जब आप कॉल करते हैं fork
, तो आप अपनी स्वयं की प्रक्रिया की एक प्रति बना रहे होते हैं जिसका अपना पता स्थान होता है । यह कई कार्यों को एक-दूसरे से स्वतंत्र रूप से चलाने की अनुमति देता है, जैसे कि उनमें से प्रत्येक में मशीन की पूरी स्मृति स्वयं के पास थी।
यहां कुछ उदाहरण दिए गए हैं fork
:
fork
कमांड लाइन से आपके द्वारा आमंत्रित किए गए प्रोग्राम को चलाने के लिए उपयोग करता है।fork
कई सर्वर प्रक्रियाओं को बनाने के लिए उपयोग करते हैं, जिनमें से प्रत्येक अपने स्वयं के पते स्थान में अनुरोधों को संभालता है। यदि किसी की मृत्यु हो जाती है या स्मृति लीक हो जाती है, तो अन्य अप्रभावित रहते हैं, इसलिए यह दोष सहिष्णुता के लिए एक तंत्र के रूप में कार्य करता है।fork
एक अलग प्रक्रिया के भीतर प्रत्येक पृष्ठ को संभालने के लिए उपयोग करता है। यह आपके पूरे ब्राउज़र को नीचे लाने से एक पृष्ठ पर क्लाइंट-साइड कोड को रोक देगा।fork
कुछ समानांतर कार्यक्रमों में प्रक्रियाओं को स्पॉन करने के लिए उपयोग किया जाता है (जैसे कि MPI का उपयोग करके लिखा गया है )। ध्यान दें कि यह थ्रेड्स का उपयोग करने से अलग है , जिनके पास अपना पता स्थान नहीं है और एक प्रक्रिया के भीतर मौजूद है।fork
अप्रत्यक्ष रूप से बाल प्रक्रियाओं को शुरू करने के लिए उपयोग करती हैं। उदाहरण के लिए, हर बार जब आप subprocess.Popen
पायथन की तरह एक कमांड का उपयोग करते हैं , तो आप fork
एक बच्चे की प्रक्रिया करते हैं और उसका आउटपुट पढ़ते हैं। यह कार्यक्रमों को एक साथ काम करने में सक्षम बनाता है।fork
शेल में विशिष्ट उपयोग कुछ इस तरह दिखाई दे सकता है:
int child_process_id = fork();
if (child_process_id) {
// Fork returns a valid pid in the parent process. Parent executes this.
// wait for the child process to complete
waitpid(child_process_id, ...); // omitted extra args for brevity
// child process finished!
} else {
// Fork returns 0 in the child process. Child executes this.
// new argv array for the child process
const char *argv[] = {"arg1", "arg2", "arg3", NULL};
// now start executing some other program
exec("/path/to/a/program", argv);
}
शेल एक बच्चे की प्रक्रिया का उपयोग exec
करता है और इसे पूरा करने के लिए इंतजार करता है, फिर अपने स्वयं के निष्पादन के साथ जारी रहता है। ध्यान दें कि आपको इस तरह कांटा इस्तेमाल नहीं करना है। आप हमेशा बहुत सारी बाल प्रक्रियाएँ बंद कर सकते हैं, जैसा कि एक समानांतर कार्यक्रम कर सकता है, और प्रत्येक एक कार्यक्रम को समवर्ती रूप से चला सकता है। असल में, किसी भी समय आप यूनिक्स प्रणाली में नई प्रक्रियाएँ बना रहे हैं, आप उपयोग कर रहे हैं fork()
। विंडोज समकक्ष के लिए, पर एक नज़र डालें CreateProcess
।
यदि आप अधिक उदाहरण और अधिक स्पष्टीकरण चाहते हैं, तो विकिपीडिया का एक अच्छा सारांश है। और यहां कुछ स्लाइड्स हैं कि आधुनिक ऑपरेटिंग सिस्टम में प्रक्रियाएं, थ्रेड्स और कंसीलर कैसे काम करते हैं।
fork()
UNIX में एक नई प्रक्रिया बनाने का तरीका है, लेकिन पांडित्यपूर्ण होने के लिए, कम से कम एक अन्य है posix_spawn()
:।
कांटा () कैसे यूनिक्स नई प्रक्रियाओं का निर्माण करता है। उस बिंदु पर जिसे आपने कांटा कहा है (), आपकी प्रक्रिया को क्लोन किया गया है, और दो अलग-अलग प्रक्रियाएं वहां से निष्पादन जारी रखती हैं। उनमें से एक, बच्चे के पास कांटा () रिटर्न 0. होगा, दूसरे, माता-पिता, बच्चे के पीआईडी (प्रक्रिया आईडी) में कांटा होगा।
उदाहरण के लिए, यदि आप एक शेल में निम्नलिखित टाइप करते हैं, तो शेल प्रोग्राम फोर्क () को कॉल करेगा, और फिर आपके द्वारा पास किए गए कमांड को निष्पादित करेगा (टेलनेट, इस मामले में), जबकि माता-पिता शीघ्र संकेत प्रदर्शित करेंगे, साथ ही साथ पृष्ठभूमि प्रक्रिया के पीआईडी को इंगित करने वाले संदेश के रूप में।
$ telnetd &
जिस कारण से आप नई प्रक्रियाएँ बनाते हैं, उसी तरह आपका ऑपरेटिंग सिस्टम एक ही समय में कई काम कर सकता है। यही कारण है कि आप एक कार्यक्रम चला सकते हैं और, जबकि यह चल रहा है, दूसरी विंडो पर स्विच करें और कुछ और करें।
कांटा () का उपयोग बाल प्रक्रिया बनाने के लिए किया जाता है। जब एक कांटा () फ़ंक्शन कहा जाता है, तो एक नई प्रक्रिया को जन्म दिया जाएगा और कांटा () फ़ंक्शन कॉल बच्चे और माता-पिता के लिए एक अलग मान लौटाएगा।
यदि रिटर्न मान 0 है, तो आप जानते हैं कि आप चाइल्ड प्रोसेस हैं और यदि रिटर्न वैल्यू एक संख्या है (जो चाइल्ड प्रोसेस आईडी है), तो आप जानते हैं कि आप पेरेंट हैं। (और यदि यह एक ऋणात्मक संख्या है, तो कांटा विफल हो गया था और कोई बाल प्रक्रिया नहीं बनाई गई थी)
कांटा () मूल रूप से उस प्रक्रिया के लिए एक बच्चे की प्रक्रिया बनाने के लिए उपयोग किया जाता है जिसमें आप इस फ़ंक्शन को बुला रहे हैं। जब भी आप एक कांटा () कहते हैं, यह चाइल्ड आईडी के लिए एक शून्य देता है।
pid=fork()
if pid==0
//this is the child process
else if pid!=0
//this is the parent process
इसके द्वारा आप माता-पिता और बच्चे के लिए अलग-अलग कार्य कर सकते हैं और मल्टीथ्रेडिंग सुविधा का उपयोग कर सकते हैं।
कांटा () माता-पिता के समान एक नई बाल प्रक्रिया बनाएगा। इसलिए आपके द्वारा कोड में चलने वाली सभी चीजें दोनों प्रक्रियाओं द्वारा चलाई जाएंगी - यदि आपके पास सर्वर है, तो बहुत उपयोगी है, और आप उनके अनुरोधों को संभालना चाहते हैं।
यदि आप एप्लिकेशन लिख रहे हैं तो आपको दिन-प्रतिदिन की प्रोग्रामिंग में कांटे का उपयोग करने की आवश्यकता नहीं है।
यहां तक कि अगर आप चाहते हैं कि आपका कार्यक्रम कुछ कार्य करने के लिए एक और कार्यक्रम शुरू करे, तो अन्य सरल इंटरफ़ेस हैं जो पर्दे के पीछे कांटा का उपयोग करते हैं, जैसे सी और पर्ल में "सिस्टम"।
उदाहरण के लिए, यदि आप चाहते थे कि आपका एप्लिकेशन किसी अन्य प्रोग्राम को लॉन्च करने के लिए जैसे कि बीसी आपके लिए कुछ गणना करे, तो आप इसे चलाने के लिए 'सिस्टम' का उपयोग कर सकते हैं। सिस्टम एक नई प्रक्रिया बनाने के लिए एक 'कांटा' करता है, फिर उस प्रक्रिया को bc में बदलने के लिए एक 'निष्पादित' करता है। एक बार bc पूरा होने पर, सिस्टम आपके प्रोग्राम पर नियंत्रण लौटाता है।
आप अन्य कार्यक्रमों को भी अतुल्यकालिक रूप से चला सकते हैं, लेकिन मुझे याद नहीं है कि कैसे।
यदि आप सर्वर, शेल, वायरस या ऑपरेटिंग सिस्टम लिख रहे हैं, तो आप कांटे का उपयोग करना चाहते हैं।
system()
। मैं इस बारे में पढ़ रहा था fork()
क्योंकि मैं चाहता हूं कि मेरा सी कोड एक पायथन स्क्रिप्ट चलाए।
सिस्टम कॉल फोर्क () का उपयोग प्रक्रियाओं को बनाने के लिए किया जाता है। यह कोई तर्क नहीं लेता है और एक प्रक्रिया आईडी लौटाता है। कांटा () का उद्देश्य एक नई प्रक्रिया बनाना है, जो कॉलर की बाल प्रक्रिया बन जाती है। एक नई चाइल्ड प्रक्रिया बनने के बाद, दोनों प्रक्रियाएँ फोर्क () सिस्टम कॉल के बाद अगले निर्देश को निष्पादित करेंगी। इसलिए, हमें माता-पिता को बच्चे से अलग करना होगा। यह कांटा के लौटे मूल्य का परीक्षण करके किया जा सकता है ():
यदि कांटा () एक नकारात्मक मान लौटाता है, तो एक बच्चे की प्रक्रिया का निर्माण असफल रहा। कांटा () नव निर्मित बच्चे की प्रक्रिया में एक शून्य देता है। कांटा () एक सकारात्मक मान देता है, माता-पिता को बच्चे की प्रक्रिया की प्रक्रिया आईडी। लौटी हुई प्रक्रिया आईडी sys / type.h में परिभाषित pid_t प्रकार की है। आम तौर पर, प्रक्रिया आईडी एक पूर्णांक है। इसके अलावा, एक प्रक्रिया इस प्रक्रिया को सौंपी गई प्रक्रिया आईडी को पुनः प्राप्त करने के लिए फ़ंक्शन गेटपीड () का उपयोग कर सकती है। इसलिए, सिस्टम फोर्क () को कॉल करने के बाद, एक साधारण परीक्षण बता सकता है कि कौन सी प्रक्रिया बच्चे की है। कृपया ध्यान दें कि यूनिक्स माता-पिता के पते के स्थान की एक सटीक प्रतिलिपि बनाएगा और बच्चे को देगा। इसलिए, माता-पिता और बच्चे की प्रक्रियाओं में अलग-अलग पता स्थान होते हैं।
उपरोक्त बिंदुओं को स्पष्ट करने के लिए इसे एक उदाहरण से समझते हैं। यह उदाहरण माता-पिता और बच्चे की प्रक्रियाओं में अंतर नहीं करता है।
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#define MAX_COUNT 200
#define BUF_SIZE 100
void main(void)
{
pid_t pid;
int i;
char buf[BUF_SIZE];
fork();
pid = getpid();
for (i = 1; i <= MAX_COUNT; i++) {
sprintf(buf, "This line is from pid %d, value = %d\n", pid, i);
write(1, buf, strlen(buf));
}
}
मान लीजिए उपरोक्त कार्यक्रम फोर्क () को कॉल के बिंदु तक निष्पादित करता है।
यदि फोर्क () को कॉल सफलतापूर्वक निष्पादित किया जाता है, तो यूनिक्स पता स्थान की दो समान प्रतियां बना देगा, एक माता-पिता के लिए और दूसरा बच्चे के लिए। दोनों प्रक्रियाएँ फोर्क () कॉल के बाद अगले स्टेटमेंट पर अपना अमल शुरू करेंगी। इस मामले में, दोनों प्रक्रियाएं असाइनमेंट पर अपना निष्पादन शुरू कर देंगी
pid = .....;
सिस्टम कॉल फोर्क () के ठीक बाद दोनों प्रक्रियाएं अपना निष्पादन शुरू कर देती हैं। चूंकि दोनों प्रक्रियाओं में समान लेकिन अलग-अलग पते के स्थान होते हैं, कांटे () कॉल से पहले आरम्भ किए गए वे चर दोनों पता स्थानों में समान मान होते हैं। चूंकि हर प्रक्रिया का अपना पता स्थान होता है, इसलिए कोई भी संशोधन दूसरों से स्वतंत्र होगा। दूसरे शब्दों में, यदि माता-पिता अपने चर के मूल्य को बदलते हैं, तो संशोधन केवल माता-पिता की पता स्थान में परिवर्तन को प्रभावित करेगा। कांटे () कॉल द्वारा बनाए गए अन्य पता स्थान प्रभावित नहीं होंगे, भले ही उनके समान चर नाम हों।
प्रिंटफ के बजाय लेखन का उपयोग करने का क्या कारण है? ऐसा इसलिए है क्योंकि Printf () "बफ़र्ड" है, जिसका अर्थ है printf () एक प्रक्रिया के आउटपुट को एक साथ समूहित करेगा। मूल प्रक्रिया के लिए आउटपुट को बफ़र करते समय, बच्चा कुछ जानकारी प्रिंट करने के लिए प्रिंटफ़ का भी उपयोग कर सकता है, जिसे बफर भी किया जाएगा। परिणामस्वरूप, चूंकि आउटपुट तुरंत स्क्रीन पर नहीं भेजा जाएगा, इसलिए आपको अपेक्षित परिणाम का सही क्रम नहीं मिल सकता है। इससे भी बदतर, दो प्रक्रियाओं से आउटपुट को अजीब तरीकों से मिश्रित किया जा सकता है। इस समस्या को दूर करने के लिए, आप "असंबद्ध" लिखने का उपयोग करने पर विचार कर सकते हैं।
यदि आप इस कार्यक्रम को चलाते हैं, तो आप स्क्रीन पर निम्नलिखित देख सकते हैं:
................
This line is from pid 3456, value 13
This line is from pid 3456, value 14
................
This line is from pid 3456, value 20
This line is from pid 4617, value 100
This line is from pid 4617, value 101
................
This line is from pid 3456, value 21
This line is from pid 3456, value 22
................
प्रोसेस आईडी 3456 माता-पिता या बच्चे को सौंपा गया हो सकता है। इस तथ्य के कारण कि इन प्रक्रियाओं को समवर्ती रूप से चलाया जाता है, उनकी आउटपुट लाइनें एक अप्रत्याशित तरीके से रुक जाती हैं। इसके अलावा, इन लाइनों का क्रम सीपीयू अनुसूचक द्वारा निर्धारित किया जाता है। इसलिए, यदि आप इस कार्यक्रम को फिर से चलाते हैं, तो आपको एक पूरी तरह से अलग परिणाम मिल सकता है।
मल्टीप्रोसेसिंग कंप्यूटिंग के लिए केंद्रीय है। उदाहरण के लिए, आपका IE या फ़ायरफ़ॉक्स आपके लिए एक फ़ाइल डाउनलोड करने के लिए एक प्रक्रिया बना सकता है जबकि आप अभी भी इंटरनेट ब्राउज़ कर रहे हैं। या, जब आप किसी वर्ड प्रोसेसर में कोई डॉक्यूमेंट प्रिंट कर रहे होते हैं, तब भी आप अलग-अलग पेज देख सकते हैं और फिर भी इसके साथ कुछ एडिटिंग कर सकते हैं।
फोर्क () का उपयोग नई प्रक्रियाओं को बनाने के लिए किया जाता है जैसा कि हर निकाय ने लिखा है।
यहाँ मेरा कोड है जो बाइनरी ट्री के रूप में प्रक्रियाएं बनाता है ....... यह उन स्तरों की संख्या को स्कैन करने के लिए कहेगा जो आप बाइनरी ट्री में प्रक्रियाएँ बनाना चाहते हैं
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
int main()
{
int t1,t2,p,i,n,ab;
p=getpid();
printf("enter the number of levels\n");fflush(stdout);
scanf("%d",&n);
printf("root %d\n",p);fflush(stdout);
for(i=1;i<n;i++)
{
t1=fork();
if(t1!=0)
t2=fork();
if(t1!=0 && t2!=0)
break;
printf("child pid %d parent pid %d\n",getpid(),getppid());fflush(stdout);
}
waitpid(t1,&ab,0);
waitpid(t2,&ab,0);
return 0;
}
आउटपुट
enter the number of levels
3
root 20665
child pid 20670 parent pid 20665
child pid 20669 parent pid 20665
child pid 20672 parent pid 20670
child pid 20671 parent pid 20670
child pid 20674 parent pid 20669
child pid 20673 parent pid 20669
पहले एक को समझने की जरूरत है कि कांटा () सिस्टम कॉल क्या है। मुझे समझाने दो
कांटा () सिस्टम कॉल माता-पिता की प्रक्रिया का सटीक डुप्लिकेट बनाता है, यह माता-पिता के ढेर, ढेर, इनिशियलाइज्ड डेटा, अनइंस्टाल्यूटेड डेटा का डुप्लिकेट बनाता है और कोड को केवल पैरेंट प्रोसेस के साथ रीड-ओनली मोड में साझा करता है।
फोर्क सिस्टम कॉल कॉपी-ऑन-राइट आधार पर मेमोरी को कॉपी करता है, इसका मतलब है कि बच्चा वर्चुअल मेमोरी पेज में बनाता है जब कॉपी करने की आवश्यकता होती है।
अब कांटे का उद्देश्य ():
fork()
एक बच्चे की प्रक्रिया को स्पॉन करने के लिए उपयोग किया जाता है। आमतौर पर इसका उपयोग थ्रेडिंग के समान स्थितियों में किया जाता है, लेकिन अंतर होते हैं। थ्रेड्स के विपरीत, fork()
पूरे अलग प्रक्रियाओं का निर्माण करता है, जिसका अर्थ है कि बच्चे और माता-पिता, जबकि वे उस बिंदु पर एक दूसरे की सीधी प्रतियां हैं, जिन्हें fork()
वे पूरी तरह से अलग करते हैं, न तो दूसरे की मेमोरी स्पेस तक पहुंच सकते हैं (सामान्य परेशानियों के बिना) आप दूसरे प्रोग्राम की मेमोरी एक्सेस करने के लिए जाते हैं)।
fork()
अभी भी कुछ सर्वर अनुप्रयोगों द्वारा उपयोग किया जाता है, ज्यादातर जो कि एक * NIX मशीन पर रूट के रूप में चलते हैं जो उपयोगकर्ता के अनुरोधों को संसाधित करने से पहले अनुमतियाँ छोड़ देते हैं। अभी भी कुछ अन्य यूसेकस हैं, लेकिन ज्यादातर लोग अब मल्टीथ्रेडिंग में चले गए हैं।
एक नई प्रक्रिया शुरू करने के लिए केवल एक निष्पादन () फ़ंक्शन होने के कारण कांटा () बनाम के तर्क को समझाया गया है यूनिक्स स्टैक एक्सचेंज पर एक समान प्रश्न के उत्तर ।
अनिवार्य रूप से, चूंकि कांटा वर्तमान प्रक्रिया को कॉपी करता है, प्रक्रिया के लिए विभिन्न संभावित विकल्पों में से सभी डिफ़ॉल्ट रूप से स्थापित होते हैं, इसलिए प्रोग्रामर के पास उनकी आपूर्ति नहीं होती है।
विंडोज ऑपरेटिंग सिस्टम में, इसके विपरीत, प्रोग्रामर को CreateProcess फ़ंक्शन का उपयोग करना होता है जो MUCH अधिक जटिल होता है और नई प्रक्रिया के मापदंडों को परिभाषित करने के लिए एक विविध संरचना को आबाद करने की आवश्यकता होती है।
इसलिए, योग करने के लिए, नई प्रक्रियाओं को बनाने के लिए (बनाम निष्पादित) करने का कारण सरलता है।
एक बच्चे की प्रक्रिया बनाने के लिए कांटा () सिस्टम कॉल उपयोग। यह मूल प्रक्रिया का सटीक डुप्लिकेट है। कांटा प्रतियां ढेर अनुभाग, ढेर अनुभाग, डेटा अनुभाग, पर्यावरण चर, माता-पिता से कमांड लाइन तर्क।
कांटा () समारोह मौजूदा प्रक्रिया है जिसमें से यह कहा जाता है को दोहराने के लिए एक नई प्रक्रिया बनाने के लिए प्रयोग किया जाता है। मौजूदा प्रक्रिया जिसमें से इस फ़ंक्शन को पेरेंट प्रक्रिया कहा जाता है और नव निर्मित प्रक्रिया चाइल्ड प्रोसेस बन जाती है। जैसा कि पहले ही कहा जा चुका है कि बच्चा माता-पिता की नकल है लेकिन इसके कुछ अपवाद भी हैं।
बच्चे के पास ऑपरेटिंग सिस्टम में चलने वाली किसी अन्य प्रक्रिया की तरह एक अद्वितीय पीआईडी है।
बच्चे के पास एक पेरेंट प्रोसेस आईडी है जो उस
प्रक्रिया के पीआईडी के समान है जो इसे बनाया था।
संसाधन उपयोग और सीपीयू समय काउंटर बाल प्रक्रिया में शून्य पर रीसेट हो जाते हैं।
बच्चे में लंबित संकेतों का सेट खाली है।
बच्चे को अपने माता-पिता से कोई समय विरासत में नहीं मिलता है
उदाहरण :
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int var_glb; /* A global variable*/
int main(void)
{
pid_t childPID;
int var_lcl = 0;
childPID = fork();
if(childPID >= 0) // fork was successful
{
if(childPID == 0) // child process
{
var_lcl++;
var_glb++;
printf("\n Child Process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
else //Parent process
{
var_lcl = 10;
var_glb = 20;
printf("\n Parent process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
}
else // fork failed
{
printf("\n Fork failed, quitting!!!!!!\n");
return 1;
}
return 0;
}
अब, जब उपरोक्त कोड संकलित किया गया है और चला गया है:
$ ./fork
Parent process :: var_lcl = [10], var_glb[20]
Child Process :: var_lcl = [1], var_glb[1]