സിഗ്നലുകള് ഇന്റര് പ്രോസസ്സ് കമ്മ്യൂണിക്കേഷനില് വളരെ പ്രധാനപ്പെട്ടവയാണെങ്കിലും അവയുടെ സാധ്യതകള് പരിമിതമാണ്. എന്നാല് സിസ്റ്റത്തില് നടക്കുന്ന വിവിധ സംഭവങ്ങളെക്കുറിച്ചുള്ള അറിയിപ്പുകള് വിവിധ പ്രോസസ്സുകള്ക്ക് നല്കാനും പ്രോസസ്സുകളുടെ പ്രവര്ത്തനത്തെ നിയന്ത്രിക്കാനും സിഗ്നലുകള് ഉപയോഗിക്കാന് സാധിക്കും. ഓരോ സിഗ്നലുകള്ക്കും ഓരോ അര്ഥമാണുള്ളത്. ഈ സിഗ്നലുകളെ തിരിച്ചറിയാന് പോസിക്സ് മാനദണ്ഡത്തില് ഓരോ സംഖ്യകള് അവക്ക് നല്കപ്പെട്ടിരിക്കുന്നു. kill സിസ്റ്റം കോള്, അല്ലെങ്കില് kill കമാന്റ് തുടങ്ങിയവ വഴി വിവിധ പ്രോസസ്സുകളിലേക്ക് സിഗ്നലുകള് അയക്കാന് സാധിക്കും. പോസിക്സ് നിര്വ്വചിച്ചിരിക്കുന്നതില് കൂടുതല് സിഗ്നലുകള് പല സിസ്റ്റങ്ങളിലും ലഭ്യമാണ്. ലഭ്യമായ സിഗ്നലുകളുടെ പട്ടിക കാണുന്നതിന് kill -l എന്ന കമാന്റ് ഉപയോഗിക്കാവുന്നതാണ്.
സിസ്റ്റത്തിലെ ഓരോ പ്രോസസ്സിനും കെര്ണലിന്റെ പ്രോസസ്സ് ടേബിളില് ഒരു എന്ട്രി ഉണ്ടായിരിക്കും എന്ന് പറഞ്ഞിരുന്നല്ലോ. ഓരോ പ്രോസസ്സിലേക്കും അയക്കപ്പെടുന്ന സിഗ്നലുകള് ഇതില് രേഖപ്പെടുത്തിയിരിക്കുന്നു. ഒരു പ്രോസസ്സിന്റെ പ്രോസസ്സ് ടേബിള് എന്ട്രിയില് ഒരു സിഗ്നലിനെ സൂചിപ്പിക്കാന് ഒരു ബിറ്റ് എന്ന കണക്കിലാണ് മെമ്മറി അനുവദിച്ചിരിക്കുക. അതിനാല്ത്തന്നെ ഒരേ സിഗ്നല് എത്ര തവണ ഒരു പ്രോസസ്സിലേക്ക് അയക്കപ്പെട്ടു എന്ന് അറീയാന് സാധ്യമല്ല. എന്നാല് ആ പ്രോസസ്സിലേക്ക് ഏതൊക്കെ സിഗ്നല് അയക്കപ്പെട്ടിട്ടുണ്ട് എന്നറിയാന് സാധിക്കും.
ഒരു പ്രോസസ്സ് ഒരു സിഗ്നല് സ്വീകരിച്ച് കഴിഞ്ഞാല് ആ സിഗ്നലിനോട് മൂന്നുതരത്തിലുള്ള പ്രതികരണങ്ങളാണ് സാധ്യമാകുക. ആ സിഗ്നല് കണ്ടില്ലെന്ന് നടിക്കുക (Ignore), ആ സിഗ്നലിനെ കൈകാര്യം ചെയ്യുക (Handle), പ്രോസസ്സ് അവസാനിപ്പിക്കുക (Terminate) എന്നിവയാണ് അവ. എല്ലാ സിഗ്നലുകളെയും കണ്ടില്ലെന്ന് നടിക്കാന് സാധിക്കുകയില്ല. എല്ലാ സിഗ്നലുകളെയും കൈകാര്യം ചെയ്യാനും സാധിക്കില്ല. ഒരു സിഗ്നലിനോട് നാമെഴുതുന്ന ഒരു പ്രോഗ്രാം എങ്ങനെ പ്രതികരിക്കണമെന്ന് നാം പരാമര്ശിക്കുന്നില്ലെങ്കില് കെര്ണല് മുന്കൂട്ടി നിശ്ചയിച്ചിരിക്കുന്ന രീതിയിലുള്ള ഒരു നടപടി എടുക്കുന്നു. ഓരോ സിഗ്നലുകള്ക്കും ഈ നടപടി വ്യത്യസ്തമായിരിക്കും. ഒരു സിഗ്നലിലെ നമ്മുടെ പ്രോഗ്രാമില് കൈകാര്യം ചെയ്യുന്നതിനായി ഒരു സിഗ്നല് ഹാന്ഡ്ലര് ഫങ്ഷന് എഴുതുകയും അതിനെ ആ സിഗ്നലുമായി ബന്ധിപ്പിക്കുകയും ചെയ്യണം. നമ്മുടെ പ്രോഗ്രാം പിന്നീട് ആ സിഗ്നല് സ്വീകരിക്കുകയാണെങ്കില് ബന്ധപ്പെട്ട ഫങ്ഷന് പ്രവര്ത്തിക്കുന്നതായിരിക്കും. എന്നാല് ഇക്കാര്യങ്ങളൊന്നും തന്നെ നാമെഴുതുന്ന പ്രോഗ്രാമിന്റെ നിയന്ത്രണത്തില് വരുന്ന കാര്യങ്ങളല്ല. ഇതൊക്കെ ചെയ്യുന്നത് കെര്ണല് നേരിട്ടാണ്. നമ്മുടെ പ്രോസസ്സിന്റെ യു-ഏരിയയില് ആണ് അത് ഓരോ സിഗ്നലിനോടും എങ്ങനെ പ്രതികരിക്കുന്നു എന്നും സിഗ്നലുകള് കൈകാര്യം ചെയ്യാനുള്ള ഫങ്ങ്ഷനുകള് ഉണ്ടെങ്കില് അവയുടെ വിലാസം രേഖപ്പെടുത്തിയിരിക്കുന്നതും.
ഒരു പ്രോസസ്സിന് അത് ഏതവസ്ഥയില് ആയിരിക്കുമ്പോളും സിഗ്നലുകള് സ്വീകരിക്കാന് സാധിക്കും. എന്നാല് ആ സിഗ്നല് കൈകാര്യം ചെയ്യപ്പെടുന്നത് ആ പ്രോസസ്സിന്റെ അവസ്ഥയില് മാറ്റങ്ങള് വരുമ്പോളാണ്.
- ഒരു പ്രോസസ്സിന്റെ പ്രവര്ത്തനം കെര്ണല് സ്പേസില് നിന്ന് യൂസര് സ്പേസിലേക്ക് മാറുക
- സ്ലീപ്പ് അവസ്ഥയില് നിന്ന് പ്രോസസ്സ് പ്രവര്ത്തിക്കുന്ന അവസ്ഥയിലേക്ക് വരിക
- പ്രോസസ്സ് ഷെഡ്യൂള് ചെയ്യപ്പെടുക
ഇതില്നിന്ന് മനസ്സിലാക്കാനുള്ള കാര്യം സിഗ്നലുകള് സ്വീകരിക്കുകയോ അവയെ കൈകാര്യം ചെയ്യുകയോ ചെയ്യുന്നത് ഒരു പ്രോസസ്സിന്റെ അധികാര പരിധിയില് ഉള്ള കാര്യമല്ല. അവ നടക്കുന്നത് ആ പ്രോസസ്സ് അറിയുകയും ഇല്ല. ഒരു പ്രോസസ്സ് ഫോര്ക്ക് ഉപയോഗിച്ച് അതിന്റെ ചൈല്ഡ് പ്രോസസ്സിനെ സൃഷ്ടിക്കുമ്പോള് അത് രെജിസ്റ്റര് ചെയ്തിരുന്ന ഹാന്ഡ്ലര് ഫങ്ങ്ഷനുകള് ഒക്കെ ചൈല്ഡ് പ്രോസസ്സിലും അതേ നിലയില് തുടരും. എന്നാല് ചൈല്ഡ് പ്രോസസ്സ് സൃഷ്ടിക്കപ്പെടുന്നതിനു മുന്പ് പേരന്റ് പ്രോസസ്സ് സ്വീകരിച്ചതും കൈകാര്യം ചെയ്യപ്പെട്ടിട്ടില്ലാത്തതുമായ സിഗ്നലുകള് ഒന്നും ചൈല്ഡ് പ്രോസസ്സിന് ബാധകമാകില്ല.
ഒരു പ്രോസസ്സിന് വേറൊരു പ്രോസസ്സിലേക്ക് സിഗ്നല് അയക്കാന് kill() സിസ്റ്റം കോള് ഉപയോഗിക്കാമെന്ന് പറഞ്ഞു. അതേ പ്രോസസ്സിലേക്ക് തന്നെ സിഗ്നല് അയക്കാന് (സ്വയം) ഒരു പ്രോസസ്സിന് raise() എന്ന ഫങ്ങ്ഷന് ഉപയോഗിക്കാവുന്നതാണ്. ഇതല്ലാതെ പ്രത്യേക സാഹചര്യങ്ങളില് പ്രോസസ്സുകള് സിഗ്നലുകള് സ്വീകരിക്കാറുണ്ട്. ഉദാഹരണത്തിന് കമാന്റ് ലൈനില് പ്രവര്ത്തിച്ചുകൊണ്ടിരിക്കുന്ന ഒരു പ്രോസസ്സിലേക്ക് ഉപയോക്താവ് കണ്ട്രോള് + സി എന്ന കീബോര്ഡ് കോമ്പിനേഷന് ഉപയോഗിക്കുമ്പോള് SIGINT എന്ന സിഗ്നല് അയക്കപ്പെടുന്നു. പ്രോസസ്സ് അതിന്റെ അഡ്രസ്സ് സ്പേസിനു പുറത്തുള്ള ഒരു അഡ്രസ്സിലെ വിവരങ്ങള് വായിക്കാനോ എഴുതാനോ ശ്രമിക്കുമ്പോള് SIGSEGV എന്ന സിഗ്നല്, പ്രോസസ്സില് ഒരു സംഖ്യയെ പൂജ്യം കൊണ്ട് ഹരിക്കാന് ശ്രമിച്ചാല് SIGFPE, ഒരു പ്രോസസ്സിന്റെ ചൈല്ഡ് പ്രോസസ്സ് പ്രവര്ത്തനം അവസാനിപ്പിച്ചാല് SIGCHILD എന്നിങ്ങനെ. ഇത് കൂടാതെ പോസിക്സ് ഓപ്പറേറ്റിങ്ങ് സിസ്റ്റത്തില് ആപ്ലിക്കേഷന് പ്രോഗ്രാമുകള്ക്ക് ലഭ്യമായ ടൈമര്/കൗണ്ടര് സര്വ്വീസുകള് ഒക്കെ സിഗ്നലുകള് ഉപയോഗപ്പെടുത്തിയാണ് പ്രവര്ത്തിക്കുന്നത്.
പോസിക്സില് നിര്വ്വചിക്കപ്പെട്ടിരിക്കുന്ന സിഗ്നലുകളുടെ വിശദാംശങ്ങള് ഈ വിക്കിപീഡിയ ലേഖനത്തില് കാണാം. പ്രോസസ്സിലെ സിഗ്നല് ഹാന്ഡ്ലിങ്ങിന്റെ വിശദാംശത്തിനും ഓരോ സിഗ്നലും എങ്ങനെ പെരുമാറുന്നു എന്നുമുള്ള വിശദാംശങ്ങള്ക്കായി man 7 signal എന്ന് ടെര്മിനലില് ടൈപ്പ് ചെയ്ത് നോക്കുക.
കമാന്റ് ലൈനില് നിന്നയക്കപ്പെടുന്ന SIGINT എങ്ങനെ കൈകാര്യം ചെയ്യാം എന്നൊരു ഉദാഹരണം നോക്കാം
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
volatile sig_atomic_t terminated = 0;
void sigint_handler(int signum)
{
printf("\nSignal %d received..\n", signum);
terminated = 1;
}
int main(void)
{
unsigned long counter = 0;
signal(SIGINT, sigint_handler);
printf("Entering an infinit loop..\n");
while (!terminated) {
counter++;
sleep(1);
printf("\r%ld seconds without signal..", counter);
fflush(stdout);
}
printf("\nLoop terminated\n");
return 0;
}
ഇവിടെ SIGINT എന്ന സിഗ്നല് ഹാന്ഡില് ചെയ്യാന് sigint_handler എന്ന ഫങ്ങ്ഷനെ രെജിസ്റ്റര് ചെയ്യുകയാണ് signal ഫങ്ങ്ഷന് ഉപയോഗിച്ച് ചെയ്യുന്നത്. terminated എന്ന വേരിയബിളിന്റെ മൂല്യം പൂജ്യമല്ലാതാകുന്നത് വരെ മെയിന് ഫങ്ങ്ഷനിനെ while ലൂപ്പ് പ്രവര്ത്തിച്ച് കൊണ്ടിരിക്കുന്നു. ഇവിടെ terminated എന്ന വേരിയബിളിന്റെ ഡാറ്റ ടൈപ്പ് ശ്രദ്ധിക്കുക. volatile sig_atomic_t എന്ന പ്രത്യേക ഡാറ്റാ ടൈപ്പ് ആണ് ഉപയോഗിച്ചിരിക്കുന്നത്. ഇതിലെ volatile എന്ന് കീവേര്ഡ് ആ ചരത്തിന് ചില പ്രത്യേകതകള് നല്കുന്നതാണ്. ആ ചരത്തിന്റെ മൂല്യം പ്രോഗ്രാമില് എവിടെ വേണമെങ്കിലും മാറ്റം വരാവുന്നതാണെന്നും അതിനാല് തന്നെ ഒരു രീതിയിലുള്ള ഓപ്റ്റിമൈസേഷനും ആ വേരിയബിളിന്റെ മേല് നടത്തരുതെന്നും ആ വേരിയബിള് ആവശ്യമാകുമ്പോളൊക്കെ അതിനെ പ്രത്യേകം വായിക്കണം എന്നും സി കമ്പൈലറിനോട് പറയുന്നതാണത്. സിഗ്നലുകള് റേസ് കണ്ടീഷന്സ് എന്ന പ്രശ്നത്തില് നിന്ന് മുക്തമല്ലെന്ന് മാത്രമല്ല ആ പ്രശ്നത്തിന്റെ കാഠിന്യം നന്നായി അനുഭവിക്കുന്നവയും ആണ്. സ്വീകരിക്കപ്പെട്ട ഒരു സിഗ്നല് കൈകാര്യം ചെയ്യപ്പെടുന്ന അവസരത്തില് മറ്റൊരു സിഗ്നല് സ്വീകരിക്കപ്പെട്ടാല് സംഭവിക്കാവുന്ന പല പ്രശ്നങ്ങളും ഉണ്ട്. ഉദാഹരണത്തിന് ഒരു സിഗ്നല് എത്ര തവണ പ്രോഗ്രാം കൈകാര്യം ചെയ്തു എന്നറിയാന് ഒരു കൗണ്ടര് ഉപയോഗിക്കുന്നു എന്ന് കരുതുക. ഓരോ കൈകാര്യം ചെയ്യലിലും അതിന്റെ മൂല്യം ഒന്ന് വീതം വര്ദ്ധിക്കുകയും ഒരു പ്രത്യേക മൂല്യം എത്തുമ്പോള് ഒരു കാര്യം ചെയ്യേണ്ടതും ആണെന്ന് കരുതൂ. അതായത് ആ കൗണ്ടറിന്റെ മൂല്യം നാലിന്റെ ഗുണിതങ്ങളാകുമ്പോള് ഒരു സന്ദേശം ഉപയോക്താവിനെ കാണിക്കണം. കൗണ്ടറിലെ മൂല്യം 3 ആയിരിക്കുമ്പോള് പ്രോഗ്രാം സിഗ്നല് സ്വീകരിക്കുകയും അതിന്റെ മൂല്യം 4 ആക്കുകയും ചെയ്തു എന്ന് കരുതുക. അതിന്റെ മൂല്യം നാലിന്റെ ഗുണിതമാണോ എന്ന് പരിശോധിക്കാനുള്ള നിര്ദ്ദേശത്തിന്റെ തൊട്ട് മുന്പേ കെര്ണല് ഷെഡ്യൂളിങ്ങ് നടത്തുകയാണെങ്കില് ആ പ്രോസസ്സ് താല്ക്കാലികമായി പ്രവര്ത്തനം നിര്ത്തും. വീണ്ടും പ്രവര്ത്തനം തുടങ്ങുന്നതിനു മുന്പേ ഒരിക്കല് കൂടി ആ സിഗ്നല് സ്വീകരിക്കപ്പെട്ടാല് വീണ്ടും ആ നിര്ദ്ദേശത്തിലെത്തുമ്പോള് ആ കൗണ്ടറിന്റെ മൂല്യം 5 ആയിട്ടുണ്ടാകും. ഷെഡ്യൂളിങ്ങ് നടന്നില്ലെങ്കില് പോലും മൂല്യം വര്ദ്ധിപ്പിക്കുന്ന നിര്ദ്ദേശം പ്രവര്ത്തിക്കുമ്പോള് അടുത്ത സിഗ്നല് വന്നാലും ഈ പ്രശ്നം ഉണ്ടാകാം. sig_atomic_t ആ ചരത്തിന്റെ ഉപയോഗത്തെ ഒരു ആറ്റോമിക് ഓപ്പറേഷന് ആയി നടത്തണം എന്ന് ജിസിസി കമ്പൈലറിനോട് ആവശ്യപ്പെടും. ഒരു പരിധി വരെ പ്രശ്നങ്ങള് ഇതുവഴി പരിഹരിക്കാം.
ഇനി SIGINT എന്ന സിഗ്നലിനെ കണ്ടില്ലെന്ന് നടിക്കുന്നതെങ്ങനെ എന്ന് നോക്കാം
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
volatile sig_atomic_t terminated = 0;
int main(void)
{
unsigned long counter = 0;
signal(SIGINT, SIG_IGN);
printf("Entering an infinit loop..\n");
while (!terminated) {
counter++;
sleep(1);
printf("\r%ld seconds without signal..", counter);
fflush(stdout);
}
printf("\nLoop terminated\n");
return 0;
}
ഇവിടെ SIG_IGN എന്നതാണ് signal ഫങ്ങ്ഷന്റെ രണ്ടാമത്തെ ആര്ഗ്യുമെന്റ്. പരാമര്ശിക്കപ്പെട്ട സിഗ്നലിനെ ഇഗ്നോര് ചെയ്യുക എന്നതാണ് ഇതിന്റെ അര്ഥം. ഈ പ്രോഗ്രാം പ്രവര്ത്തിക്കുമ്പോള് CTRL+C അമര്ത്തിയാല് ഒന്നും സംഭവിക്കില്ലെന്ന് കാണാം. എന്നാല് ആദ്യത്തെ പ്രോഗ്രാം അതോടെ ലൂപ്പില് നിന്ന് പുറത്ത് വരുമായിരുന്നു. ഇനി ഈ പ്രോഗ്രാമിന്റെ പ്രവര്ത്തനം അവസാനിപ്പിക്കാന് ഒരു പുതിയ ടെര്മിനല് തുറന്ന് ps -e കമാന്റ് ഉപയോഗിച്ച് അതിന്റെ പിഐഡി കണ്ടുപിടിക്കുക. അതിന് ശേഷം kill -9 <pid> ഉപയോഗിച്ച് SIGKILL എന്ന സിഗ്നല് അതിലേക്ക് അയക്കാം. അപ്പോള് ആ പ്രോസസ്സ് killed എന്ന് ടെര്മിനലില് കാണിക്കും. SIGKILL സിഗ്നലിന്റെ സംഖ്യ 9 ആണ്. kill -l വഴി വിവിധ സിഗ്നലുകളുടെ സംഖ്യകള് കണ്ട് പിടിച്ച് 9 നു പകരം ഉപയോഗിച്ച് എന്ത് സംഭവിക്കുന്നു എന്ന് നോക്കൂ..
പോസിക്സ് സിഗ്നലുകളെ വികസിപ്പിച്ചാണ് ജിറ്റികെ പോലെയുള്ള യുഐ ലൈബ്രറികള് ഈവന്റ് മെക്കാനിസം പ്രായോഗികമാക്കിയതെന്ന് പറയപ്പെടുന്നു..