ഇന്റര്‍ പ്രോസസ്സ് കമ്യൂണിക്കേഷന്‍ #2: പൈപ്പുകള്‍

സുബിന്‍ പി. റ്റി on 29 മാർച്ച്, 2019

ഒന്നിലധികം പ്രോസസ്സുകള്‍ക്ക് ഒരു കുഴലിലൂടെ എന്നവണ്ണം വിവരങ്ങള്‍ കൈമാറാന്‍ സാധിക്കുന്ന ഒരുപാധിയാണ് പൈപ്പുകള്‍. ഇവക്ക് രണ്ട് അറ്റങ്ങള്‍ ഉണ്ടാകും. ഒരറ്റത്ത് എഴുതപ്പെടുന്ന വിവരങ്ങള്‍ അടുത്ത അറ്റത്തുനിന്ന് വായിച്ചെടുക്കാം. പൈപ്പുകള്‍ കമാന്റ്‌‌ ലൈന്‍ ഇന്റര്‍ഫേസുകളില്‍ നല്കുന്ന സൗകര്യങ്ങള്‍ ചില്ലറയല്ല. ഒരു കമാന്റിന്റെ ഔട്ട്പുട്ട് മറ്റൊരു കമാന്റിന്റെ ഇന്‍പുട്ടായി നല്‍കാന്‍ സാധിക്കുന്നത് വഴി ഒന്നിലധികം ലളിതമായ കമാന്റുകള്‍ ഉപയോഗിച്ച് ഒരു സങ്കീര്‍ണ്ണമായ ജോലി ചെയ്യാന്‍ സാധിക്കുമല്ലോ.

ഒരു പൈപ്പിന്റെ എഴുതാനുള്ള അറ്റത്ത് എഴുതപ്പെടുന്ന കാര്യങ്ങള്‍ മറ്റേ അറ്റത്തുനിന്ന് വായിക്കപ്പെടുന്നത് വരെ കെര്‍ണല്‍ അതിന്റെ മെമ്മറിയില്‍ സൂക്ഷിച്ചു വയ്ക്കുന്നു. ഇങ്ങനെ സൂക്ഷിക്കപ്പെടാന്‍ സാധിക്കുന്ന വിവരങ്ങളുടെ അളവ് സംബന്ധിച്ച് ചില നിബന്ധനകളോ നിയന്ത്രണങ്ങളോ ഉണ്ടെങ്കിലും വിവിധ സിസ്റ്റങ്ങളില്‍ അവ വ്യത്യസ്തമായിരിക്കും. പോസിക്സ് മാനദണ്ഡമനുസരിച്ച് ഇതിന്റെ വലിപ്പം കുറഞ്ഞത് 512 ബൈറ്റുകള്‍ ആയിരിക്കണം എന്നതാണ്. മെമ്മറിയുടെ ലഭ്യത അനുസരിച്ച് കൂടിയ വലിപ്പം എത്രവേണമെങ്കിലും ആകാം. ലിനക്സില്‍ ഇത് 4096 ബൈറ്റുകള്‍ ആണ്. അതായത് ലിനക്സിലെ ഒരു പൈപ്പിന് സംഭരിക്കാന്‍ സാധിക്കുന്ന ഡാറ്റയുടെ പരമാവധി വലിപ്പം 4096 ബൈറ്റുകള്‍ ആണ്. ഇതില്‍ കൂടുതല്‍ ഡാറ്റ കൈമാറ്റം ചെയ്യേണ്ടത് ആവശ്യമാണെങ്കില്‍ എഴുതുന്ന പ്രോസസ്സിന് തുടര്‍ച്ചയായി എഴുതുന്നതില്‍ നിയന്ത്രണങ്ങള്‍ ഒന്നും ഇല്ല. എന്നാല്‍ പൈപ്പ് ഒരിക്കല്‍ നിറഞ്ഞ് കഴിഞ്ഞാല്‍ മറ്റൊരു പ്രോസസ്സ് അതില്‍ നിന്ന് ഡാറ്റ വായിക്കുന്നത് വരെ ഈ എഴുത്തിന് തുടരാന്‍ സാധിക്കുകയില്ല. വായിക്കേണ്ട അറ്റത്ത് നിന്നും ഒരു പ്രോസസ്സ് ഡാറ്റ വായിച്ചെടുത്ത് കഴിഞ്ഞാല്‍ ആ വായിച്ച അത്ര വിവരങ്ങള്‍ പൈപ്പില്‍ നിന്ന് നീക്കം ചെയ്യപ്പെടുന്നു. കൂടുതല്‍ വിവരങ്ങള്‍ എഴുതാന്‍ പ്രോസസ്സ് കാത്തുനില്‍ക്കുന്നുണ്ടെങ്കില്‍ അതിന് ഈ കാലിലായ സ്ഥലം നിറയുന്നത് വരെ വീണ്ടും എഴുതാവുന്നതാണ്.

പൈപ്പിലേക്ക് വിവരങ്ങള്‍ എഴുതുകയോ പൈപ്പില്‍ നിന്ന് വായിക്കുകയോ ചെയ്യുന്ന സമയത്ത് എഴുതാന്‍ സ്ഥലമില്ലാതെ വരിക/വായിക്കാന്‍ പൈപ്പില്‍ വിവരങ്ങള്‍ ഇല്ലാതെ വരിക എന്ന രണ്ട് സാഹചര്യങ്ങള്‍ ഉണ്ടാകാം. ഈ അവസരങ്ങളില്‍ അതിനായി ശ്രമിക്കുന്ന പ്രോസസ്സ് എങ്ങനെ പെരുമാറും എന്നതിന്റെ അടിസ്ഥാനത്തില്‍ ഇന്‍പുട്ട്/ഔട്ട്പുട്ട് പ്രക്രിയകള്‍ രണ്ട് തരത്തില്‍ ആകാം. (ഇവ ബാക്കി ഫയൽ ഇൻപുട്ട്/ഔട്ട്പുട്ടുകൾക്കും ബാധകമാണ്)

  1. ബ്ലോക്കിങ്ങ് ഐഒ: പൈപ്പിലേക്ക്/ഫയലിലേക്ക് എഴുതാനോ അവയിൽ നിന്ന് വായിക്കാനോ ശ്രമിക്കുമ്പോൾ വിവരം/സ്ഥലം ലഭ്യമല്ലെങ്കിൽ കെർണൽ ആ പ്രോസസ്സിനെ താൽക്കാലികമായി സസ്പെൻഡ് ചെയ്യുന്നു. പ്രോസസ്സ് വെയിറ്റ് അവസ്ഥയിലേക്ക് പോകും. വിവരങ്ങൾ ലഭ്യമാകുമ്പോൾ പഴയ നിലയിലേക്ക് തിരിച്ച് വരികയും പ്രവർത്തനം തുടരുകയും ചെയ്യും. ശരിക്കും ഈ അവസ്ഥാ മാറ്റങ്ങളൊക്കെ പ്രോസസ്സിന്റെ അറിവോടെ അല്ല നടക്കുന്നത്.

  2. നോൺ ബ്ലോക്കിങ്ങ് ഐഒ: ഈ രീതിയിലാണെങ്കിൽ വിവരങ്ങൾ/സ്ഥലം ലഭ്യമല്ലാതെ വരുമ്പോൾ ആ റൈറ്റ്/റീഡ് പ്രോസസ്സ് താൽക്കാലികമായി ആ പ്രവർത്തനം സാധ്യമല്ല എന്ന എറർ കോഡോടെ അവസാനിപ്പിക്കപ്പെടുന്നു. ഇവിടെ പ്രോസസ്സ് വിവര/സ്ഥല ലഭ്യതക്കായി കാത്തു നിൽക്കുന്നില്ല.

ആദ്യമേ‌ തന്നെ സൂചിപ്പിച്ചതുപോലെ പൈപ്പുകള്‍ രണ്ട് വിധത്തിലുണ്ട്. പേരുള്ളവയും ഇല്ലാത്തവയും. ആദ്യം പേരില്ലാത്ത പൈപ്പുകളെക്കുറിച്ച് വിശദീകരിക്കാം.

പേരില്ലാത്ത പൈപ്പുകള്‍ (Unnamed Pipes)

ഇത്തരം പൈപ്പുകളുടെ സവിശേഷത അവയെ പരസ്പരം ബന്ധപ്പെട്ടിട്ടില്ലാത്ത പ്രോസസ്സുകള്‍ക്ക് ഉപയോഗിക്കാന്‍ സാധിക്കില്ല എന്നതാണ്‌. ഈ പൈപ്പിനെ സൃഷ്ടിക്കുന്ന പ്രോസസ്സിനും അതിന്റെ ചൈല്‍ഡ് പ്രോസസ്സുകള്‍ക്കും മാത്രമേ ഈ പൈപ്പ് ഉപയോഗിക്കാന്‍ സാധിക്കുകയുള്ളു.ഒരു പ്രോസസ്സ് അതിൽ തുറന്ന് വച്ചിരിക്കുന്ന ഫയലുകളൊക്കെ തുറന്നതിനു ശേഷം സൃഷ്ടിക്കപ്പെടുന്ന ചൈൽഡ് പ്രോസസ്സുകൾക്ക് കൂടി ഉപയോഗിക്കാൻ സാധിക്കും എന്നതാണ് ഇതിന്റെ അടിസ്ഥാനം. എന്നാൽ മറ്റൊരു പ്രോസസ്സിന് തുറക്കാൻ പാകത്തിൽ ഈ പൈപ്പുമായി ബന്ധപ്പെട്ട ഫയൽ ഡിസ്കിലോ ഫയൽ സിസ്റ്റത്തിലോ കാണില്ല. അതിനാൽത്തന്നെ അതിനെ സൃഷ്ടിച്ച പ്രോസസ്സിന് മാത്രമേ അതിനെക്കുറിച്ചുള്ള വിവരങ്ങളും ലഭ്യമാവുകയുള്ളു. ഇവയുടെ ഉപയോഗത്തിന് താഴെക്കൊടുത്തിരിക്കുന്ന ഉദാഹരണം നോക്കൂ,

/* Creating an unnamed pipe */  
#include <unistd.h>  
#include <stdio.h>  

int main(void)  
{  
    int fd[2];  
    pid_t pid;  

    char hello[] = "Hello child";  
    char reply[] = "Got it parent..";  
    char buffer[100];  

    if (pipe(fd) == -1) {  
        perror("pipe");  
        return;  
    }  

    pid = fork();  
    if (pid < 0) {  
        perror("fork");  
        return;  
    }  

    if (pid != 0) {  
        printf("\nParent: writing hello to pipe\n");  
        printf("Wrote %d bytes\n",write(fd[1],hello,sizeof(hello)));  
        sleep(2);  
        printf("\nParent: Reading pipe\n");  
        printf("Read %d bytes\n",read(fd[0],buffer,sizeof(buffer)));  
        printf("Data: %s\n\n",buffer);  
        return;  
    } else {  
        sleep(1);  
        printf("\nChild: Reading pipe\n");  
        printf("Read %d bytes\n",read(fd[0],buffer,sizeof(buffer)));  
        printf("Data: %s\n",buffer);  
        printf("\nChild: writing reply to pipe\n");  
        printf("Wrote %d bytes\n",write(fd[1],reply,sizeof(reply)));  
        return;  
    }  

    return 0;  
}  

പേരില്ലാത്ത പൈപ്പുകള്‍ സൃഷ്ടിക്കാന്‍ pipe() സിസ്റ്റം കോളാണ് ഉപയോഗിക്കുന്നത്. ഇതിന്റെ ആര്‍ഗ്യുമെന്റായി നല്‍കുന്നത് രണ്ട് ഫയല്‍ ഡിസ്ക്രിപ്റ്ററുകള്‍ ഉള്ള ഒരു അറേ ആണ്. ഇതില്‍ ഒരെണ്ണം ആ പൈപ്പിന്റെ റീഡ് എന്‍ഡും മറ്റേത് റൈറ്റ് എന്‍ഡും ആണ്. fd[0] പൈപ്പില്‍ നിന്ന് വായിക്കാനും fd[1] പൈപ്പിലേക്ക് എഴുതാനും ഉപയോഗിക്കുന്നു. ഒരു ചൈല്‍ഡ് പ്രോസസ്സിന് അതിന്റെ പേരന്റ് പ്രോസസ്സില്‍ തുറക്കപ്പെട്ട എല്ലാ ഫയല്‍ ഡിസ്ക്രിപ്റ്ററുകളും ലഭ്യമാകും. അതിനാല്‍ പൈപ്പ് സൃഷ്ടിക്കപ്പെടുന്നത് fork നടക്കുന്നതിന്റെ മുന്‍പ് ആയിരിക്കണം. പ്രോഗ്രാമിലെ fork ഉപയോഗത്തെ പറ്റി സംശയമുണ്ടെങ്കില്‍ പ്രോസസ്സ് മാനേജ്മെന്റ് പോസ്റ്റിലെ ഫോര്‍ക്കിന്റെ ഉപയോഗം പരാമര്‍ശിച്ചിരിക്കുന്ന ലേഖനം വായിക്കൂ.

പേരുള്ള പൈപ്പുകൾ (Named Pipes) ഈ പൈപ്പുകൾ ഫയൽ സിസ്റ്റത്തിൽ സാധാരണ ഫയലുകളെ പോലെ തന്നെ പ്രവർത്തിക്കുന്നു. അതിനാൽ ഈ ഫയലുകളുടെ പേരറിയുന്ന ഏത് പ്രോസസ്സിനും അതറിയാവുന്ന മറ്റ് പ്രോസസ്സുകളുമായുള്ള ആശയവിനിമയത്തിന് ഈ പൈപ്പ് ഉപയോഗിക്കാം. പേരുള്ള പൈപ്പുകള്‍ സൃഷ്ടിക്കാന്‍ ഉപയോഗിക്കുന്ന സിസ്റ്റം കോള്‍ mkfifo ആണ്. ഇതിന്റെ ഉപയോഗം താഴെപ്പറയുന്നത് പോലെ ആണ്,

int mkfifo(const char *pathname, mode_t mode);

ആദ്യത്തെ പരാമീറ്റര്‍ പൈപ്പിന്റെ പാത്ത് ആണ്. ഫയല്‍ സിസ്റ്റത്തിലെ ഏത് പാത്ത് വേണമെങ്കിലും നല്കാവുന്നതാണ്. ഒരു സാധാരണ ഫയല്‍ നിര്‍മ്മിക്കപ്പെടുമ്പോളുള്ള പരിമിതികള്‍ ഒക്കെ ഇവിടെയും ബാധകമായിരിക്കും. രണ്ടാമത്തെ പരാമീറ്റര്‍ ആ പൈപ്പിന്റെ അനുമതികള്‍ നിര്‍ണ്ണയിക്കുന്നു. (ഫയല്‍ അനുമതികളെക്കുറിച്ചുള്ള പോസ്റ്റ് കാണുക). വിജയകരമായി ഒരു പൈപ്പ് നിര്‍മ്മിക്കപ്പെട്ടാല്‍ ഈ സിസ്റ്റം കോള്‍ 0 റിട്ടേണ്‍ ചെയ്യുന്നു. വിജയകരമല്ലെങ്കില്‍ -1. ഉദാഹരണത്തിന്

mkfifo("myfifo", 0644);

എന്ന രീതിയില്‍ ഉപയോഗിച്ചാല്‍ അപ്പോളത്തെ വര്‍ക്കിങ്ങ് ഡയറക്ടറിയില്‍ myfifo എന്ന പേരില്‍ ഈ പൈപ്പ് സൃഷ്ടിക്കപ്പെടും. ഉപഭോക്താവിന് വായിക്കാനും എഴുതാനും ബാക്കിയുള്ളവര്‍ക്ക് എഴുതാന്‍ മാത്രവും ഉള്ള അനുമതികളായിരിക്കും ഇതിന് നല്‍കപ്പെടുക. സൃഷ്ടിക്കപ്പെട്ടതിന് ശേഷം open, read, write, close സിസ്റ്റം കോളൂകളുപയോഗിച്ച് മറ്റ് ഫയലുകളെ കൈകാര്യം ചെയ്യുന്നത് പോലെ തന്നെ ഇവയേയും കൈകാര്യം ചെയ്യാവുന്നതാണ്.

ഈ ലേഖനം ശ്രീ സുബിന്‍ പി. റ്റി അദ്ദേഹത്തിന്റെ ബ്ലോഗില്‍ ക്രിയേറ്റീവ് കോമണ്‍സ് സിസി-ബൈ-എസ്.എ 3.0 ലൈസന്‍സ് പ്രകാരം പ്രസിദ്ധീകരിച്ചതാണ്.