ஒரு இயங்குதளத்தை (operating system) ஆரம்பத்திலிருந்து உருவாக்குவது என்பது C மொழியில் kernel ஹேக்கர்கள் செய்யும் வேலையாகத் தோன்றலாம். ஆனால், ஒரு மதிய நேரத்தில் பைத்தானில் (Python) ஒரு எளிமையான உருவகப்படுத்துதலை (simulation) நீங்கள் உருவாக்க முடியும், மேலும் செயல்முறை மேலாண்மையின் (process management) தர்க்கம் உயர்நிலை மொழிகளிலும் (high-level languages) அதே அளவு சவாலானது என்பதை நீங்கள் விரைவில் கண்டறிவீர்கள். நான் இதை மிகவும் கடினமான முறையில் கற்றுக்கொண்டேன். ஒரு சிறிய OS உருவகப்படுத்துதலை எழுத நான் அமர்ந்தேன். அதன் நோக்கம் எளிமையானது: சில செயல்முறைகளை (processes) உருவாக்கி, அவற்றை முறைப்படுத்தி (schedule), அவற்றின் வேலை முடிந்ததும் முடிவடைந்ததாகக் குறிப்பது. குறியீடு (code) சுருக்கமாக இருந்தது. தர்க்கம் மிகவும் உறுதியாகத் தோன்றியது. பிறகு நான் அதை இயக்கினேன், ஆனால் எதுவுமே முடிவடையவில்லை.
ஏன் பைத்தானில் ஒரு சிறிய OS-ஐ உருவாக்க வேண்டும்?
ஒரு உண்மையான இயங்குதளம் மெமரி பேஜிங் (memory paging), கோப்பு முறைமைகள் (file systems), வன்பொருள் குறுக்கீடுகள் (hardware interrupts) மற்றும் சாதன இயக்கிகளை (device drivers) கையாள்கிறது. ஒரு உருவகப்படுத்துதல் இவை அனைத்தையும் நீக்கிவிட்டு, அதன் முக்கிய கருத்தான 'நிலை' (state) என்பதில் மட்டும் கவனம் செலுத்த அனுமதிக்கிறது. நீங்கள் ஒரு செயல்முறையை வரையறுக்கிறீர்கள். அதற்கு ஒரு PID, ஒரு பர்ஸ்ட் டைம் (burst time) மற்றும் ஒரு வாழ்க்கைச் சுழற்சி நிலை (lifecycle status) இருக்கும். தயார் (Ready). இயங்குகிறது (Running). முடிந்தது (Finished). ஒரு ஷெட்யூலர் லூப் (scheduler loop) அடுத்த வேட்பாளரைத் தேர்ந்தெடுத்து, அதன் நிலையை மேம்படுத்தி, ஒரு நேர இடைவெளியை உருவகப்படுத்தி, அதை முடிவுக்குக் கொண்டுவருகிறது.
பைத்தான் இத்தகைய சோதனைகளுக்கு ஒரு சிறந்த ஊடகமாகும், ஏனெனில் இது பாயிண்டர் கணிதம் (pointer arithmetic) மற்றும் மெமரி அலைன்மென்ட் (memory alignment) போன்றவற்றைத் தவிர்க்க அனுமதிக்கிறது. அகராதிகளின் (dictionaries) ஒரு பட்டியல் உங்கள் செயல்முறை அட்டவணையாக (process table) மாறுகிறது. ஒரு while லூப் உங்கள் கர்னல் ஷெட்யூலராக மாறுகிறது. நீங்கள் நிலையான லைப்ரரி கருவிகளைக் கொண்டே ரவுண்ட்-ராபின் ஷெட்யூலிங் (round-robin scheduling) அல்லது முன்னுரிமை வரிசைகளை (priority queues) செயல்படுத்த முடியும். இது அணுகுவதற்கு எளிதாகத் தோன்றுகிறது, அதனால்தான் அதன் தொடர்ச்சியாக வந்த பிழை (bug) மிகவும் எரிச்சலூட்டுவதாக இருந்தது.
அமைப்பு (The Setup)
எனது உருவகப்படுத்துதலில் process_table என்ற பட்டியல் பயன்படுத்தப்பட்டது. ஒவ்வொரு பதிவும் இந்த வடிவில் ஒரு அகராதியாக இருந்தது:
{
"pid": 1,
"burst_time": 3,
"status": "ready"
}
ஷெட்யூலர் ஒரு எளிய while லூப்பை இயக்கினது. அதன் நிலை "finished" ஆக இல்லாத முதல் செயல்முறையை அது அட்டவணையில் தேடியது. அதைத் கண்டதும், அந்தச் செயல்முறையை ஒரு உருவகப்படுத்தப்பட்ட சுழற்சிக்கு இயக்க, execute_tick(p) என்ற உதவியாளர் செயல்பாட்டை அழைத்தது. execute_tick-க்குள், நான் செயல்முறை நிலையை "running" என அமைத்து, பர்ஸ்ட் டைமை குறைத்து, மீதமுள்ள வேலை பூஜ்ஜியத்தை எட்டுகிறதா என்று சரிபார்த்தேன். அவ்வாறு இருந்தால், நிலையை "finished" என மாற்றினேன். ஒவ்வொரு செயல்முறையும் முடிவடைந்த நிலையை அடைந்தவுடன் வெளி லூப் (outer loop) நின்றுவிட வேண்டும் என்று கருதப்பட்டது.
தாளில் பார்த்தால், இந்த ஓட்டம் மிகத் தெளிவாக இருந்தது. ஒரு தயார் நிலையில் உள்ள செயல்முறையைக் கண்டறியவும். அதை இயக்கவும். அது முடிந்துவிட்டதா என்று சரிபார்க்கவும். முடிவடையும் வரை இதைத் திரும்பத் திரும்பச் செய்யவும். ஷெட்யூலர் தனது வேலையைச் செய்வதைக் கண்காணிக்க நான் print அறிக்கைகளையும் சேர்த்தேன். செயல்முறைகள் தேர்ந்தெடுக்கப்படுவதை என்னால் காண முடிந்தது. லூப் தொடர்ந்து இயங்கிக்கொண்டே இருந்தது. இருப்பினும், செயல்முறைகள் ஒரு முடிவில்லாத நிகழ்காலத்தில் சிக்கிக்கொண்டது போலத் தோன்றின; அவை எப்போதும் இயங்கிக்கொண்டே இருந்தன, ஆனால் ஒருபோதும் முடிவடையவில்லை.
அறிகுறி (The Symptom)
இது மிக மோசமான தோல்வி வகை: அமைதியான தோல்வி. டெர்மினலில் எந்த stack trace-உம் தோன்றவில்லை. எந்த IndexError அல்லது KeyError-உம் எனக்கு வழிகாட்டியாக அமையவில்லை. இன்டர்பிரிட்டர் (interpreter) எந்தப் பிரச்சனையும் இன்றி இயங்கிக் கொண்டிருந்தது. நிரல் செயல்பட மறுத்தது. செயல்முறைகள் தொடங்கின, ஆனால் அவை ஒருபோதும் முடியவில்லை. நான் பல மணிநேரம் அந்த ஓட்டத்தை மீண்டும் மீண்டும் ஆய்வு செய்தேன்.
லூப் நிபந்தனை தவறாக இருந்ததா? ஒருவேளை பர்ஸ்ட் டைம் கணக்கீட்டில் நான் ஏதேனும் பிழை செய்தேனா? செயல்முறை அட்டவணை மாற்றப்படுவதற்குப் பதிலாக நகலெடுக்கப்பட்டதா? எனது முடிவு நிபந்தனை (termination condition) தவறான சாவியை (key) சரிபார்த்ததா? நான் இன்னும் அதிகமான print அறிக்கைகளைச் சேர்த்தேன். ஒவ்வொரு பூலியன் (boolean) வெளிப்பாட்டையும் ஆய்வு செய்தேன். உண்மையில் முக்கியமாக இருந்த அந்த ஒரு வரியைத் தவிர மற்ற அனைத்தையும் நான் கேள்வி கேட்டேன்.
குற்றவாளி (The Culprit)
பிறகு நான் அதைக் கண்டேன். execute_tick-க்குள், நான் இவ்வாறு எழுதியிருந்தேன்:
p["status"] == "running"
இரண்டு சமக்குறிகள். இது ஒரு ஒப்பீடு (comparison), ஒதுக்கீடு (assignment) அல்ல. அதைச் சரிசெய்ய வேண்டியது ஒரே ஒரு எழுத்துத் தூரத்தில் இருந்தது:
p["status"] = "running"
பைத்தானில், p["status"] == "running" என்பது முற்றிலும் சரியான ஒரு வெளிப்பாடு (expression). இது True அல்லது False என மதிப்பிடப்படும், பின்னர் நான் அதை எதற்கும் ஒதுக்காததால் இன்டர்பிரிட்டர் அந்த முடிவை நீக்கிவிடும். அந்த வரி எதற்கும் பயனுள்ளதாக இருக்காது. அகராதிப் பதிவு மாற்றப்படாமல் அப்படியே இருந்தது, அது முன்னரே கொண்டிருந்த நிலையைத் தக்கவைத்துக் கொண்டது, இதனால் செயல்முறை அதன் வாழ்க்கைச் சுழற்சியில் முன்னேறவே இல்லை.
நான் அதை ஒரு சமக்குறியாக மாற்றினேன். ஸ்கிரிப்டை மீண்டும் இயக்கினேன். உருவகப்படுத்துதல் உயிர் பெற்றது. செயல்முறைகள் திட்டமிட்டபடி தயார், இயங்குகிறது மற்றும் முடிந்தது ஆகிய நிலைகளைக் கடந்து சென்றன. ஒரே ஒரு கூடுதல் கீஸ்டிரோக் (keystroke) எனக்கு பல மணிநேரங்களைச் செலவழிக்க வைத்தது.
ஏன் இந்த பிழைகள் மறைந்துவிடுகின்றன?
இது இவ்வளவு கஷ்டமாக இருப்பதற்குக் காரணம், ஒரு வெளிப்பாடு (expression statement) முற்றிலும் தவறான தொடரியல் (syntax) ஆக இல்லாவிட்டால், பைத்தான் அதை ஒரு பிழையாகக் குறிக்காது. அந்தப் பிழை ஒரு அர்த்தரீதியான தட்டச்சுப் பிழை (semantic typo). நிரல் நிலையை ஒப்பிட்டு, ஒரு பூலியன் மதிப்பை உருவாக்கி, அதைத் தூக்கி எறிந்தது. அந்த ஒப்பீடு False என்பதைத் திருப்பிக் கொடுத்ததால், செயல்முறை அதன் முந்தைய நிலையிலேயே தங்கியிருந்தது, மேலும் வெளி லூப்பிற்கு நிறுத்த எந்தக் காரணமும் இல்லை.
You compound this with confirmation bias. You know you typed an assignment because you intended an assignment. When you read the code for the fifth time, your brain autocorrects the symbol. This is why rubber ducking works. It forces you to articulate each line slowly enough that the gap between what is written and what you meant becomes visible.
Small bugs like this are harder to find than dramatic crashes. A segfault or a syntax error announces itself immediately. A silent no-op simply corrupts the state and lets the program limp forward. The failure is downstream, and your instinct is to debug the symptom rather than the cause.
A Better Defense
You cannot trust your eyes alone. After this episode, I changed a few habits that would have caught the mistake earlier.
First, if you are maintaining state in a dictionary, consider using a dataclass or an enum.Enum for process states. Define your statuses as constants or enum members:
from enum import Enum
class ProcessState(Enum):
READY = "ready"
RUNNING = "running"
FINISHED = "finished"
With explicit types, tools like mypy can flag suspicious comparisons during static analysis. An accidental comparison where an assignment should live becomes much easier to spot when the types do not align with expectations.
Second, write unit tests for state transitions before you write the scheduler logic. A simple test that creates a process with one tick of work, runs the scheduler, and asserts the final state is FINISHED would have failed immediately. That failure would have narrowed the search to the state update logic rather than letting me wander through the entire loop
