പ്രോഗ്രാം, പ്രോസസ്സ് #5

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

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

  1. ഫോര്‍ക്ക് സിസ്റ്റം കോള്‍ ഉപയോഗിക്കപ്പെടുമ്പോള്‍ സിസ്റ്റം യൂസര്‍ മോഡില്‍ നിന്ന് കെര്‍ണല്‍ മോഡിലേക്ക് മാറുന്നു.
  2. കെര്‍ണലിന്റെ സിസ്റ്റം കോള്‍ ഇന്റര്‍ഫേസ് ഫോര്‍ക്ക് സിസ്റ്റം കോളിലെ നിര്‍ദ്ദേശങ്ങള്‍ പ്രവര്‍ത്തിപ്പിക്കാന്‍ ആരംഭിക്കുന്നു.
  3. സിസ്റ്റത്തില്‍ ഓരോ ഉപയോക്താവിനും ഒരു സമയത്ത് പ്രവര്‍ത്തിപ്പിക്കാനാകുന്ന പ്രോസസ്സുകളുടെ എണ്ണം തീരുമാനിക്കപ്പെട്ടിട്ടുണ്ടെങ്കില്‍ ആ പരിധി പാലിക്കപ്പെട്ടിട്ടുണ്ടോ എന്ന് പരിശോധിക്കപ്പെടുന്നു.
  4. ഒരു പ്രോസസ്സ് ആരംഭിക്കാനാവശ്യമായ വിഭവങ്ങള്‍ സിസ്റ്റത്തില്‍ ബാക്കിയുണ്ടോ എന്ന് പരിശോധിക്കപ്പെടുന്നു (മെമ്മറി ഇതില്‍ പ്രധാനമാണ്)
  5. പുതിയ ഒരു എന്‍ട്രി പ്രോസസ്സ് ടേബിളില്‍ സൃഷ്ടിക്കപ്പെടുന്നു. യു-ഏരിയക്കായി മെമ്മറി തയ്യാറാക്കുന്നു.
  6. ഫോര്‍ക്ക് ഉപയോഗിച്ച പ്രോസസ്സിന്റെ ടെക്സ്റ്റ്, ഡാറ്റ, സ്റ്റാക്ക്, ഹീപ്പ് എന്നിവയുടെ കോപ്പി തയ്യാറാക്കപ്പെടുന്നു. ഇവിടെ ശരിക്കും കോപ്പി ഉണ്ടാക്കുകയില്ല. ടെക്സ്റ്റ് സെഗ്മന്റ് ആദ്യത്തെ പ്രോസസ്സിന്റെ തന്നെ ഉപയോഗിക്കപ്പെടും. ശരിക്കും കോപ്പികള്‍ ഉണ്ട് എന്ന് തോന്നിപ്പിക്കുകയും അവിടെ പഴയ സെഗ്മെന്റുകളുടെ വിലാസം രേഖപ്പെടുത്തുകയും ആണ് ചെയ്യുന്നത്. സ്റ്റാക്ക്, ഡാറ്റ, ഹീപ്പ് സെഗ്മന്റുകള്‍ ടെക്സ്റ്റ് സെഗ്മന്റില്‍ നിന്നും വ്യത്യസ്തമായി മാറ്റങ്ങള്‍ വരുത്താന്‍ അനുവദിക്കപ്പെട്ടവയായതിനാല്‍ അവിടെ കോപ്പി ഓണ് റൈറ്റ് എന്ന സങ്കേതം ഉപയോഗിക്കും. അതായത് അവയില്‍ മാറ്റങ്ങള്‍ വരുത്തപ്പെടുന്നത് വരെ ആദ്യമുണ്ടായിരുന്നവ തന്നെ ഉപയോഗിക്കുകയും മാറ്റങ്ങള്‍ വരുത്തപ്പെട്ട ശേഷം മാത്രം പുതിയ പതിപ്പ് സൃഷ്ടിക്കുകയും ചെയ്യുന്നു.

ഇപ്പോള്‍ സിസ്റ്റത്തില്‍ ഒരേ പ്രോസസ്സിന്റെ രണ്ട് കോപ്പി ഉണ്ടായി. ഫലത്തില്‍ ഫോര്‍ക്ക് സിസ്റ്റം കോള്‍ ഉപയോഗിച്ച രണ്ട് പ്രോസസ്സുകള്‍. ഫോര്‍ക്ക് സിസ്റ്റം കോളിന് രണ്ട് വ്യത്യസ്ത റിട്ടേണ്‍ മൂല്യങ്ങള്‍ ഉണ്ട്. സാധാരണ ഫങ്ങ്ഷനുകള്‍ അവയെ വിളിച്ച ഒരു പ്രോസസ്സിലേക്ക് മാത്രമേ മൂല്യങ്ങള്‍ തിരികെ നല്‍കുകയുള്ളു. എന്നാല്‍ ഫോര്‍ക്ക് പേരന്റ് പ്രോസസ്സിലേക്കും ചൈല്‍ഡ് പ്രോസസ്സിലേക്കും ഓരോ മൂല്യങ്ങള്‍ മടക്കി നല്‍കും. വിജയകരമായ ഫോര്‍ക്ക് പേരന്റ് പ്രോസസ്സിലേക്ക് ചൈല്‍ഡ് പ്രോസസ്സിന്റെ പിഐഡിയും ചൈല്‍ഡ് പ്രോസസ്സിലേക്ക് പൂജ്യവും മടക്കി നല്‍കുന്നു. ഇത് ഉപയോഗിച്ചാണ് ഇപ്പോള്‍ പ്രവര്‍ത്തിക്കുന്നത് പേരന്റാണോ ചൈല്‍ഡ് ആണോ എന്ന് മനസ്സിലാക്കാന്‍ സാധിക്കുന്നത്. സി പ്രോഗ്രാമിങ്ങ് വശമുള്ളവര്‍ക്ക് ഈ ഉദാഹരണം പരീക്ഷിക്കാവുന്നതാണ്.

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  

int main(void)  {  
    int pid;  
   
    pid = fork();  
    if (pid == -1) {  
        printf("fork failed..\n");  
        exit(-1);  
    }  

    if (pid == 0) {  
        printf("I am the child process. My PID is %d\n", getpid());  
        exit(0);  
    } else {  
        printf("I am the parent process PID is %d. Child PID is %d\n",getpid(), pid);  
    }  

    return 0;  
}

ഇതിനെ fork.c എന്ന ഫയലില്‍ സേവ് ചെയ്യുക. അതിനു ശേഷം ടെര്‍മിനലില്‍ gcc fork.c -o fork എന്ന് ടൈപ്പ് ചെയ്ത് എന്റര്‍ അമര്‍ത്തുക. പ്രവര്‍ത്തിപ്പിക്കാന്‍ ./fork എന്ന് ടൈപ്പ് ചെയ്ത് എന്റര്‍ അമര്‍ത്തുക. താഴെക്കാണുന്നതിന് സമാനമായ ഔട്ട്പുട്ട് ലഭിക്കും,

I am the parent process PID is 3691. Child PID is 3692
I am the child process. My PID is 3692

if സ്റ്റേറ്റ്മെന്റ് ഉപയോഗിക്കുമ്പോള്‍ സാധാരണയായി നിര്‍ദ്ദേശത്തിലെ ഒരു ഭാഗം മാത്രം പ്രവര്‍ത്തിക്കുന്നിടത്ത് ഇവിടെ രണ്ട് ഭാഗങ്ങളും പ്രവര്‍ത്തിക്കുന്നതായി കാണാം. ശരിക്കും ഇത് രണ്ട് വ്യത്യസ്ത പ്രോസസ്സുകളില്‍ നിന്നാണ് വരുന്നത്. ആദ്യം ചൈല്‍ഡ്‌ പ്രവര്‍ത്തിക്കുമോ പേരന്റ് പ്രവര്‍ത്തിക്കുമോ എന്ന് പറയാന്‍ സാധിക്കുകയില്ല. സിസ്റ്റം കോള്‍ പൂര്‍ത്തിയായ ശേഷം പ്രോസസ്സ് വീണ്ടും യൂസര്‍ മോഡിലേക്ക് തന്നെ മടങ്ങി വരുന്നു.

ഒരു പുതിയ പ്രോസസ്സ് ഇവിടെ സൃഷ്ടിക്കപ്പെട്ടെങ്കിലും അത് ആദ്യത്തെ പ്രോസസ്സിന്റെ തന്നെ പകര്‍പ്പാണെന്ന് മനസ്സിലാക്കിയിരിക്കുമല്ലോ. എന്നാല്‍ നമുക്കാവശ്യം ഒരു പുതിയ പ്രോഗ്രാമിനെ പ്രവര്‍ത്തിപ്പിക്കലാണ്. ഇതിനായി ഉപയോഗിക്കുന്ന സിസ്റ്റം കോള്‍ ആണ് എക്സക്ക് (exec). ഈ സിസ്റ്റം കോള്‍ ചെയ്യുന്നത് അതിനെ ഉപയോഗിച്ച പ്രോസസ്സിന്റെ സെഗ്മന്റുകള്‍ മാറ്റി അവിടെ ഒരു പുതിയ പ്രോഗ്രാമില്‍ നിന്നുള്ള സെഗ്‌‌മന്റുകള്‍ ചേര്‍ക്കുകയാണ്. ഇതുവഴി ഒറിജിനല്‍ പ്രോസസ്സ് ഇല്ലാതാകുകയും പകരം പുതിയ പ്രോഗ്രാം പ്രോസസ്സായി അവിടെ വരികയും ചെയ്യുന്നു. ഇതിന്റെ പ്രവര്‍ത്തനം താഴെപ്പറയുന്നത് പോലെയാണ്.

  1. എക്സെക്ക് സിസ്റ്റം കോള്‍ ഉപയോഗിക്കുമ്പോള്‍ പ്രതിപാദിക്കപ്പെട്ട എക്സിക്യൂട്ടബിള്‍ ഫയല്‍ ഡിസ്കില്‍ കണ്ടെത്തുക. (ഫയല്‍ അനുബന്ധമായ ക്രിയകളുടെ വിശദാംശങ്ങള്‍ക്കായി ഫയല്‍ സിസ്റ്റങ്ങളെക്കുറിച്ചുള്ള പോസ്റ്റുകള്‍ കാണുക)
  2. ആ ഫയല്‍ സാധുവായ ഒരു പ്രോഗ്രാം ആണോ എന്ന് പരിശോധിക്കുക (elf ഫയലുകളെപ്പറ്റി നേരത്തെ പ്രദിപാദിച്ചിരുന്നു)
  3. സാധുവായ ഒരു പ്രോഗ്രാം ആണെങ്കില്‍ അതില്‍ നിന്ന് വിവിധ സെഗ്മന്റുകളെക്കുറിച്ചുള്ള വിവരങ്ങള്‍ വായിക്കുകയും അവയെ മെമ്മറിയിലേക്ക് കൊണ്ടുവരികയും ചെയ്യുക.
  4. നിലവിലെ സെഗ്മന്റുകളെ നശിപ്പിക്കുകയും അവക്ക് പകരം പുതിയ സെഗ്മെന്റുകള്‍ അവിടെ ചേര്‍ക്കുകയും ചെയ്യുക.
  5. പുതിയ പ്രോഗ്രാമിന്റെ തുടക്കം മുതല്‍ പ്രവര്‍ത്തനം ആരംഭിക്കുക.

ഇവിടെ ശ്രദ്ധിക്കേണ്ട ഒരു പ്രധാന കാര്യം വിജയകരമായ ഒരു എക്സെക്ക് സിസ്റ്റം കോള്‍ ഫോര്‍ക്ക് സിസ്റ്റം കോള്‍ ചെയ്തതുപോലെ മൂല്യങ്ങളൊന്നും മടക്കി നല്‍കുന്നില്ല. ഒരു ഫങ്ങ്ഷന്‍ റിട്ടേണ്‍ ചെയ്യുന്നത് അതിനെ വിളിച്ച പ്രോസസ്സിലേക്കാണ്. ഇവിടെ എക്സെക്കിനെ വിളിച്ച പ്രോസസ്സ് ബാക്കിയില്ല. അവിടെ പുതിയ പ്രോഗ്രാം പ്രവര്‍ത്തനം തുടങ്ങിയിരിക്കുന്നു. അതിനാല്‍ തന്നെ വിജയകരമായ എക്സെക്കില്‍ നിന്ന് റിട്ടേണ്‍ ഇല്ല. (ഇത് മിക്കവാറും ഇന്റര്‍വ്യൂകളില്‍ ഒക്കെ ചോദിക്കാറുള്ള ഒരു ചോദ്യമാണ്) മുകളിലെ പ്രോഗ്രാമിനെ അല്‍പം പരിഷ്കരിച്ച് ls എന്ന പ്രോഗ്രാമിനെ എങ്ങനെ പ്രവര്‍ത്തിപ്പിക്കം എന്ന് നോക്കാം. ഷെല്ലുകള്‍ എങ്ങനെ പ്രവര്‍ത്തിക്കുന്നു എന്ന് മനസ്സിലാക്കാന്‍ ഇത് ഉപകരിക്കും. സി പ്രോഗ്രാമിങ്ങില്‍ എക്സെക്ക് നേരിട്ട് ഉപയോഗിക്കാന്‍ വിഷമമാണ്. അതിനാല്‍ സി ലൈബ്രറി നല്‍കുന്ന execl, execlp, execle, execv, execvp, execvpe എന്ന വകഭേദങ്ങളില്‍ ഒന്ന് ഉപയോഗിക്കാം. ഇവയൊക്കെ ഉള്ളില്‍ എക്സെക്ക് തന്നെയാണ് ഉപയോഗിക്കുന്നത്.

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  

int main(void)  
{  
    int pid, ret;  

    pid = fork();  
    if (pid == -1) {  
        printf("fork failed..\n");  
        exit(-1);  
    }  

    if (pid == 0) {    
        printf("I am the child process. Now executing ls\n");  
        ret = execl("/bin/ls", "ls", "/", NULL);  
        if (ret == -1) {   
            printf("exec failed\n");  
        }   
    } else {  
        printf("I am the parent process PID is %d. Child PID is %d\n",getpid(), pid);  
    }  

    return 0;  
}  

ആദ്യത്തെ പ്രോഗ്രാമിനെ മേലെ കാണുന്നതുപോലെ മാറ്റുക. ബാക്കി നിര്‍ദ്ദേശങ്ങള്‍ പഴയത് തന്നെ. ഇവിടെ താരതമ്യേന എളുപ്പമുള്ള execl എന്ന രൂപമാണ് ഉപയോഗിച്ചിരിക്കുന്നത്. അതിലെ ആദ്യത്തെ പരാമീറ്റര്‍ എക്സിക്യൂട്ട് ചെയ്യണ്ട പ്രോഗ്രാമിന്റെ പാത്ത് ആണ്. ബാക്കിയുള്ളവ ആ പ്രോഗ്രാമിന് ഉള്ള ആര്‍ഗ്യുമെന്റുകള്‍. യൂണിക്സ് സിസ്റ്റങ്ങളില്‍ ഒരു പ്രോഗ്രാമിന്റെ ആദ്യത്തെ ആര്‍ഗ്യുമെന്റായി ആ പ്രോഗ്രാമിന്റെ തന്നെ പേര് നല്‍കുന്നതാണ് കീഴ്‌‌വഴക്കം. വിവിധ പേരുകളില്‍ ഉപയോഗിക്കുമ്പോള്‍ വിവിധ തരത്തില്‍ പ്രവര്‍ത്തിക്കുന്ന പ്രോഗ്രാമുകള്‍ തയ്യാറാക്കാന്‍ ഇതുവഴി സാധിക്കും. അതാണ് രണ്ടാമത്തെ ആര്‍ഗ്യുമെന്റായി ls എന്ന് തന്നെ കൊടുത്തിരിക്കുന്നത്. മൂന്നാമതെ ആര്‍ഗ്യുമെന്റ്‌‌ "/" ആണ്. / എന്ന പാത്തിലെ ഫയലുകളുടെ പട്ടിക നല്‍കാന്‍ ls പ്രോഗ്രാമിനോട് ആവശ്യപ്പെടുന്നതിനാണ് ഇത്. അവസാനമായി NULL എന്നത് സെന്റിനല്‍ ആര്‍ഗ്യുമെന്റ് അഥവാ സെന്റിനല്‍ എന്നറിയപ്പെടുന്നു. വ്യത്യസ്ത എണ്ണത്തിലുള്ള പരാമീറ്ററുകള്‍ സ്വീകരിക്കുന്ന ഫങ്ങ്ഷനുകളോട് ഇതാണ് അവസാനത്തെ പരാമീറ്റര്‍ എന്ന് സൂചിപ്പിക്കാന്‍ ഇവ ഉപയോഗിക്കുന്നു. (NULL എന്നത് സെന്റിനല്‍ അല്ല. അതിന് മറ്റ് ഉപയോഗങ്ങള്‍ ഉണ്ട്. ഇനി പരാമീറ്ററുകള്‍ ഇല്ല എന്നത് സൂചിപ്പിക്കാനായി ഉപയോഗിക്കുന്നവയെ ആണ് സെന്റിനല്‍ എന്ന് വിളിക്കുന്നത്. എക്സെക്ക് പ്രതീക്ഷിക്കുന്ന സെന്റിനല്‍ NULL ആണ്).ls കമാന്റ് -l എന്ന ആര്‍ഗ്യുമെന്റ് സ്വീകരിക്കും. ls -l എന്നുപയോഗിക്കുമ്പോള്‍ ലഭിക്കുന്ന രീതിയിലുള്ള ഔട്ട്പുട്ട് ലഭിക്കാന്‍ ret = execl("/bin/ls", "ls", "-l", "/", NULL); എന്നാക്കി ആ വരിയെ മാറ്റിയാല്‍ മതി.

ഒരു പ്രോസസ്സിലെ തന്നെ സ്വതന്ത്രമായ ഒരു ഭാഗം മറ്റൊരു പ്രോസസ്സ് പോലെ ഷെഡ്യൂള്‍ ചെയ്യാന്‍ സാധിക്കുന്ന തരത്തില്‍ പ്രവര്‍ത്തിപ്പിക്കുക എന്നതാണ് ത്രെഡുകള്‍ വഴി ചെയ്യുന്നത്. ഒരു പ്രോസസ്സില്‍ ചിലപ്പോള്‍ ത്രെഡുകള്‍ ഉണ്ടാകാം. ഈ ത്രെഡുകള്‍ക്ക് വ്യത്യസ്ത സ്റ്റാക്ക് സെഗ്‌‌മന്റുകള്‍ ഉണ്ടായിരിക്കുമെങ്കിലും ടെക്സ്റ്റ്, ഡാറ്റ തുടങ്ങിയ സെഗ്മന്റുകള്‍ ഒക്കെ ഒന്നുതന്നെ ആയിരിക്കും. ലിനക്സില്‍ ഒരു ത്രെഡ് സൃഷ്ടിക്കാന്‍ ഉപയോഗിക്കുന്ന സിസ്റ്റം കോള്‍ ക്ലോണ്‍ (clone) ആണ്. ഇത് ഒരു പ്രോഗ്രാമില്‍ നേരിട്ട് ഉപയോഗിക്കുന്നതിന് പകരം പി-ത്രെഡ്‌ പോലെ ത്രെഡുകള്‍ കൈകാര്യം ചെയ്യാനുള്ള ലൈബ്രറികള്‍ എന്തെങ്കിലും ഉപയോഗിക്കുന്ന രീതിയാണ് ശുപാര്‍ശ ചെയ്യപ്പെട്ടിരിക്കുന്നത്.

പ്രോസസ്സ് ടേബിള്‍, യു-ഏരിയ എന്നിവയെക്കുറിച്ച് വിശദമായി അടുത്ത പോസ്റ്റില്‍.

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