परिचय
इस LinuxHint लेख में, आप सीखेंगे कि fork() और exec() फ़ंक्शन का उपयोग करके किसी प्रक्रिया को कैसे बनाएं, चलाएं या किसी अन्य प्रक्रिया से प्रतिस्थापित करें। हम इन दो फ़ंक्शन का विवरण देखेंगे और उनके सिंटैक्स और कॉलिंग मेथड की व्याख्या करेंगे। हम प्रत्येक दो फ़ंक्शन का एक संक्षिप्त व्यावहारिक उदाहरण भी देखेंगे। फिर हम समझाएंगे कि किसी प्रक्रिया को बनाने और उसे किसी अन्य से प्रतिस्थापित करने के लिए fork() और execv() का संयोजन में उपयोग कैसे करें।
C भाषा में fork() फ़ंक्शन
fork() फ़ंक्शन कॉलिंग प्रक्रिया की एक प्रति बनाता है। हालांकि चाइल्ड प्रक्रिया पैरेंट प्रक्रिया की एक प्रति होती है, वे कुछ प्रॉपर्टी साझा नहीं करते हैं जैसे आवंटित मेमोरी क्षेत्र, PID, आदि। आइए fork() फ़ंक्शन के सिंटैक्स को देखें:
pid_t fork(void);
fork() फ़ंक्शन पैरेंट प्रक्रिया में परिणाम के रूप में चाइल्ड प्रक्रिया का PID लौटाता है, जबकि वही कॉल चाइल्ड प्रक्रिया पर कोई प्रभाव नहीं डालता है और परिणाम के रूप में 0 लौटाता है। यह तंत्र हमें fork() के रिटर्न वैल्यू को कंडीशन बनाकर एक "if" कंडीशन के माध्यम से दो प्रक्रियाओं में अलग-अलग कोड निष्पादित करने की अनुमति देता है। आइए इस अवधारणा को देखें:
if (fork() == 0) {
// चाइल्ड प्रक्रिया के लिए कोड
} else {
// पैरेंट प्रक्रिया के लिए कोड
}
इस तरह, पैरेंट प्रक्रिया "else" स्टेटमेंट के घुंघराले ब्रेसिज़ के बीच का कोड निष्पादित करती है, जबकि fork() द्वारा बनाई गई चाइल्ड प्रक्रिया "if" स्टेटमेंट में मौजूद कोड निष्पादित करती है।
आइए इसे एक सरल उदाहरण से देखें। निम्न कोड में दो अनंत लूप हैं। पैरेंट प्रक्रिया में, प्रोग्राम "else" स्टेटमेंट से संबंधित लूप में आता है जो "पैरेंट प्रक्रिया द्वारा लिखें" संदेश प्रदर्शित करता है, जबकि fork() द्वारा बनाई गई चाइल्ड प्रक्रिया "if" स्टेटमेंट में आती है जो "चाइल्ड प्रक्रिया द्वारा लिखें" संदेश प्रदर्शित करती है।
#include <stdio.h>
#include <unistd.h>
void main() {
if (fork() == 0) {
while (1) {
printf("चाइल्ड प्रक्रिया द्वारा लिखें \n");
sleep(5);
}
} else {
while (1) {
printf("पैरेंट प्रक्रिया द्वारा लिखें \n");
sleep(2);
}
}
}
निम्न छवि इस कोड का संकलन और निष्पादन दिखाती है। जैसा कि कमांड कंसोल में देखा गया है, प्रत्येक प्रक्रिया अलग-अलग कोड निष्पादित करती है:

C भाषा में execXXX() फ़ंक्शन
execXXX() फ़ंक्शन का परिवार एक चल रही प्रक्रिया को एक नई प्रक्रिया से प्रतिस्थापित करता है। नई प्रक्रिया की छवि को उस प्रक्रिया को आवंटित मेमोरी क्षेत्र में कॉपी किया जाता है जिसे प्रतिस्थापित किया जा रहा है, जिससे अन्य चीजों के साथ इसका PID और आवंटित संसाधन संरक्षित रहते हैं।
इस समूह में फ़ंक्शन जो "unistd.h" हेडर में परिभाषित हैं, उनके इनपुट के आधार पर अलग-अलग कॉल मेथड का उपयोग करते हैं और "वेरिएडिक्स" प्रकार के होते हैं, इसलिए वे पुरानी प्रक्रिया से नई प्रक्रिया में अनिर्दिष्ट आर्गुमेंट की सूची या पॉइंटर पास कर सकते हैं। आइए प्रत्येक फ़ंक्शन के सिंटैक्स को देखें:
int execl(const char *path, const char *arg, ...(char *) NULL);
int execlp(const char *file, const char *arg, ... (char *) NULL);
int execle(const char *path, const char *arg, ... , (char *) NULL, char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
execl(), execle(), और execv() फ़ंक्शन अपने पहले इनपुट आर्गुमेंट के रूप में एक पॉइंटर का उपयोग करते हैं जो उस स्ट्रिंग को इंगित करता है जिसमें नई प्रक्रिया की निष्पादन योग्य फ़ाइल का पूर्ण पथ होता है, जबकि execlp(), execvp(), और execvpe() वर्तमान डायरेक्टरी में फ़ाइल के नाम का उपयोग करते हैं। दूसरा इनपुट वह आर्गुमेंट है जो आप नई प्रक्रिया को पास करते हैं। ये या तो const char *arg स्ट्रिंग्स होने चाहिए या char *const argv[] स्ट्रिंग्स के पॉइंटर्स की सूची।
अब, आइए एक उदाहरण देखें जो किसी प्रक्रिया को प्रतिस्थापित करने और एक प्रोग्राम से दूसरे प्रोग्राम में इनपुट आर्गुमेंट पास करने के लिए execv() फ़ंक्शन का उपयोग करता है।
ऐसा करने के लिए, हम दो बहुत सरल कोड बनाते हैं। एक पैरेंट प्रक्रिया है जो चाइल्ड प्रक्रिया को निष्पादित करने के लिए execv() फ़ंक्शन को कॉल करती है। जब execv() फ़ंक्शन चाइल्ड प्रक्रिया शुरू करता है, तो यह उसे स्ट्रिंग के रूप में दो इनपुट आर्गुमेंट पास करता है जिसे चाइल्ड प्रक्रिया पुनर्प्राप्त करेगी और शेल पर प्रदर्शित करेगी।
चाइल्ड प्रक्रिया
चाइल्ड प्रक्रिया एक सरल कोड है जो "मैं चाइल्ड प्रक्रिया हूँ" संदेश प्रिंट करता है, पैरेंट प्रक्रिया द्वारा भेजे गए इनपुट आर्गुमेंट को पुनर्प्राप्त करता है, और उन्हें शेल पर प्रदर्शित करता है। चाइल्ड प्रक्रिया का कोड यहां दिया गया है:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
printf("मैं चाइल्ड प्रक्रिया हूँ\n\n");
printf("आर्गुमेंट 1: %s\n", argv[1]);
printf("आर्गुमेंट 2: %s\n", argv[2]);
}
हम इस कोड को संकलित करते हैं और इसके आउटपुट को "Documents" में "child" नाम से ".bin" एक्सटेंशन के साथ सहेजते हैं जैसा कि निम्नलिखित में दिखाया गया है:
~$ gcc Documents/child.c -o Documents/child.bin
इस तरह, हम चाइल्ड निष्पादन योग्य फ़ाइल को "Documents" में सहेजते हैं। इस निष्पादन योग्य का पथ पैरेंट प्रक्रिया में execv() को कॉल करते समय इनपुट आर्गुमेंट पथ होता है।
पैरेंट प्रक्रिया
पैरेंट प्रक्रिया वह है जिससे हम execv() फ़ंक्शन को कॉल करके इसे चाइल्ड प्रक्रिया से प्रतिस्थापित करते हैं। इस कोड में, हम स्ट्रिंग्स के पॉइंटर्स की एक सरणी परिभाषित करते हैं जो execv() फ़ंक्शन द्वारा खोली जाने वाली प्रक्रिया के इनपुट आर्गुमेंट को दर्शाती है।
निम्न चित्रण में, आप देख सकते हैं कि स्ट्रिंग्स के पॉइंटर्स की सरणी को सही तरीके से कैसे बनाया जाए। इस मामले में, इसमें चार पॉइंटर्स होते हैं और इसे "arg_Ptr[]" कहा जाता है।
एक बार पॉइंटर सरणी परिभाषित हो जाने पर, प्रत्येक पॉइंटर को एक स्ट्रिंग असाइन की जानी चाहिए जिसमें वह इनपुट आर्गुमेंट हो जिसे हम चाइल्ड प्रक्रिया को भेजते हैं। execxx() फ़ंक्शन का उपयोग करने के लिए एक नियम के रूप में, पहला आर्गुमेंट एक स्ट्रिंग होना चाहिए जिसमें निष्पादन योग्य फ़ाइल का नाम और एक्सटेंशन हो, और अंतिम पॉइंटर NULL होना चाहिए।
इस प्रकार, हम प्रत्येक पॉइंटर को स्ट्रिंग प्रारूप में संबंधित आर्गुमेंट असाइन करते हैं:
arg_Ptr[0] = "child.bin";
arg_Ptr[1] = " Hello from ";
arg_Ptr[2] = " process 2 ";
arg_Ptr[3] = NULL;
अगला कदम execv() फ़ंक्शन को कॉल करना है, जिसमें निष्पादन योग्य फ़ाइल का पूर्ण पथ रखने वाली स्ट्रिंग को पहले आर्गुमेंट के रूप में और स्ट्रिंग्स की सरणी arg_Ptr[] को दूसरे आर्गुमेंट के रूप में पास किया जाता है। आप पैरेंट प्रक्रिया का पूरा कोड निम्न में देख सकते हैं:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main() {
printf("मैं पैरेंट प्रक्रिया हूँ");
char *arg_Ptr[4];
arg_Ptr[0] = "child.bin";
arg_Ptr[1] = " Hello from ";
arg_Ptr[2] = " process 2 ";
arg_Ptr[3] = NULL;
execv("/home/linuxhint/Documents/child.bin", arg_Ptr);
}
हम इस कोड को संकलित करते हैं जो ".c" फ़ाइल का पथ और आउटपुट का नाम निर्दिष्ट करता है:
~$ gcc Documents/parent.c -o pattern
फिर, हम आउटपुट चलाते हैं:
पैरेंट प्रक्रिया "मैं पैरेंट प्रक्रिया हूँ" संदेश प्रदर्शित करती है, प्रत्येक इनपुट आर्गुमेंट के लिए एक स्ट्रिंग असाइन करके स्ट्रिंग सरणी बनाती है जिसे अगली प्रक्रिया को पास किया जाता है, और execv() फ़ंक्शन को कॉल करती है।
यदि execv() फ़ंक्शन सफलतापूर्वक निष्पादित होता है, तो "child.bin" निष्पादन योग्य पैरेंट प्रक्रिया को प्रतिस्थापित करता है और इसकी ID और आवंटित मेमोरी पर कब्जा कर लेता है। इसलिए, इस क्रिया को पूर्ववत नहीं किया जा सकता।
चाइल्ड प्रक्रिया "मैं चाइल्ड प्रक्रिया हूँ" संदेश प्रदर्शित करती है और पैरेंट प्रक्रिया द्वारा पास किए गए प्रत्येक इनपुट आर्गुमेंट को कमांड कंसोल पर प्रदर्शित करने के लिए पुनर्प्राप्त करती है।

लिनक्स में नई प्रक्रियाएं बनाने के लिए fork() और execve() फ़ंक्शन का संयोजन
जैसा कि हमने अब तक देखा है, fork() फ़ंक्शन एक प्रक्रिया को डुप्लिकेट करता है, जबकि execve() एक प्रक्रिया को प्रतिस्थापित करता है। इस उदाहरण में, हम देखेंगे कि कैसे हम इन दो फ़ंक्शन का उपयोग संयोजन में एक डुप्लिकेट से एक नई प्रक्रिया खोलने के लिए कर सकते हैं जिसे फिर प्रतिस्थापित किया जाता है। ऐसा करने के लिए, हम पहले देखे गए दो फ़ंक्शन के कोड को जोड़ते हैं ताकि fork() पैरेंट प्रक्रिया को डुप्लिकेट करे और execve() इसे पिछले "child.bin" उदाहरण में उपयोग किए गए उसी निष्पादन योग्य से प्रतिस्थापित करे।
अब, हम ".c" एक्सटेंशन वाली एक खाली फ़ाइल लेते हैं और fork() फ़ंक्शन उदाहरण में देखे गए प्रोग्राम कोड को डालते हैं।
इस प्रोग्राम में, हम केवल चाइल्ड प्रक्रिया के कोड को संशोधित करते हैं ताकि execv() फ़ंक्शन इसे "child.bin" निष्पादन योग्य से प्रतिस्थापित करे, जबकि मुख्य कार्य fork() फ़ंक्शन उदाहरण के समान है।
ऐसा करने के लिए, हम execv() फ़ंक्शन उदाहरण के main() फ़ंक्शन की सामग्री को कॉपी करते हैं और इसे "if" स्टेटमेंट की सामग्री से प्रतिस्थापित करते हैं। अब, आइए देखें कि पूरा प्रोग्राम कैसा दिखता है:
#include <stdio.h>
#include <unistd.h>
void main() {
if (fork() == 0) {
printf("मैं चाइल्ड प्रक्रिया हूँ\n");
char *arg_Ptr[5];
arg_Ptr[0] = "child.bin";
arg_Ptr[1] = " Hello from ";
arg_Ptr[2] = " process 2 ";
arg_Ptr[3] = NULL;
execv("/home/linuxhint/Documents/child.bin", arg_Ptr);
} else {
while (1) {
printf("पैरेंट प्रक्रिया द्वारा लिखें \n");
sleep(3);
}
}
}
इस तरह, सिस्टम पर एक नई प्रक्रिया बनाकर इसे डुप्लिकेट किया जाता है जिसे execv() फ़ंक्शन तब "child.bin" निष्पादन योग्य से प्रक्रिया के साथ प्रतिस्थापित करता है। अगला, हम इस कोड के संकलन के साथ एक छवि देखते हैं।

जैसा कि हम छवि में देख सकते हैं, fork() फ़ंक्शन इसे डुप्लिकेट करके एक नई प्रक्रिया बनाता है, जिसे execv() फ़ंक्शन तब "child.bin" निष्पादन योग्य से प्रतिस्थापित करता है।
निष्कर्ष
इस LinuxHint लेख में, हमने आपको दिखाया कि लिनक्स में नई प्रक्रियाओं को खोलने के लिए fork() और execv() फ़ंक्शन का उपयोग कैसे करें। हमने आपको इनमें से प्रत्येक फ़ंक्शन का संक्षिप्त विवरण, उनका सिंटैक्स, और इनपुट और आउटपुट आर्गुमेंट दिखाया। आपको बेहतर ढंग से समझने में मदद करने के लिए कि यह कैसे काम करता है, हमने इनमें से प्रत्येक फ़ंक्शन का एक उदाहरण भी शामिल किया जहां हमने उनकी कॉल मेथड और प्रत्येक द्वारा किए जाने वाले कार्य को सीखा। इन दो फ़ंक्शन को व्यक्तिगत रूप से देखने के बाद, हमने बताया कि लिनक्स में C भाषा द्वारा प्रदान की जाने वाली नई प्रक्रियाओं को बनाने की विधि को लागू करने के लिए उन्हें संयोजन में कैसे उपयोग किया जाए।