ইন্টারাপ্ট হ্যান্ডলিং — হার্ডওয়্যার পার্সপেক্টিভ
এই পাঠে যা শিখবেন
- ইন্টারাপ্ট রিকোয়েস্ট (IRQ) কী এবং একাধিক ডিভাইসের অনুরোধ কীভাবে অগ্রাধিকার পায়
- হার্ডওয়্যার-লেভেলে ইন্টারাপ্ট হ্যান্ডলিং-এর পাঁচটি ধাপ, নির্ভুলভাবে
- ইন্টারাপ্ট ভেক্টর টেবিল কীভাবে সঠিক হ্যান্ডলারে দ্রুত ডিসপ্যাচ করে
- vectored বনাম non-vectored ইন্টারাপ্ট — গতি বনাম হার্ডওয়্যার সরলতার ট্রেড-অফ
১ · ইন্টারাপ্ট রিকোয়েস্ট (IRQ) কী
একটি ডিভাইস (কীবোর্ড, ডিস্ক কন্ট্রোলার, নেটওয়ার্ক কার্ড) যখন CPU-এর মনোযোগ প্রয়োজন — যেমন "একটি কী চাপা হয়েছে" বা "ডিস্ক রিড শেষ হয়েছে" — তখন এটি একটি ইন্টারাপ্ট রিকোয়েস্ট (IRQ) পাঠায়। একাধিক ডিভাইস একই সাথে ইন্টারাপ্ট করতে পারে বলে, বাস্তব সিস্টেমে প্রায়ই একটি প্রায়োরিটি এনকোডার (L11) একাধিক ডিভাইস-লাইন একত্র করে সবচেয়ে গুরুত্বপূর্ণটি CPU-কে জানায় — L47-এর bus arbitration-এর মতোই একটি প্রায়োরিটি-নির্ভর সিদ্ধান্ত, শুধু "বাস-অ্যাক্সেস"-এর বদলে "CPU-অ্যাটেনশন"-এর জন্য।
২ · ইন্টারাপ্ট হ্যান্ডলিং-এর পাঁচটি ধাপ
একটি ইন্টারাপ্ট আসার পর হার্ডওয়্যার নিখুঁতভাবে এই পাঁচটি ধাপ অনুসরণ করে:
ধাপ ১-এ লক্ষণীয়: ইন্টারাপ্ট তাৎক্ষণিক নয় — CPU একটি নিরাপদ থামার জায়গা পর্যন্ত অপেক্ষা করে (M7/L36-এর precise-exception আলোচনার সাথে সরাসরি সম্পর্কিত)। ধাপ ২-এ PC ও প্রাসঙ্গিক রেজিস্টার (L26) সেভ করা হয় — ধারণাগতভাবে OS কোর্সের context-switch-এর মতোই, তবে এখানে সেভটি হার্ডওয়্যার নিজেই ট্রিগার করে, OS শিডিউলার নয়। ধাপ ৫-এ ঠিক সেই সেভ করা মান দিয়ে PC ও রেজিস্টার রিস্টোর হয়, যেন প্রোগ্রামটি কখনো থামেইনি।
পাইপলাইনে একটি ইন্টারাপ্ট/এক্সসেপশন এলে ঠিক কীভাবে সেটি ফ্লাশ ও হ্যান্ডল হয় তা M7/L36-এ বিস্তারিত কভার হয়েছে। এই পাঠ সেই আলোচনা পুনরাবৃত্তি করে না — বরং ডিভাইস-সাইড সিগন্যালিং এবং CPU-এর হার্ডওয়্যার-লেভেল response (vector table, হ্যান্ডলার ডিসপ্যাচ) নিয়ে মনোযোগ দেয়।
৩ · Vectored বনাম Non-vectored ইন্টারাপ্ট
ডিভাইস নিজেই (বা হার্ডওয়্যার) একটি vector table থেকে সরাসরি সঠিক হ্যান্ডলারের অ্যাড্রেস বের করে — দ্রুত, সরাসরি ডিসপ্যাচ। এটি L11-এর ডিকোডারেরই প্রয়োগ: ডিভাইস নাম্বার ডিকোড হয়ে একটি নির্দিষ্ট টেবিল-এন্ট্রি নির্দেশ করে।
সব ইন্টারাপ্ট একটিমাত্র ফিক্সড হ্যান্ডলারে জাম্প করে, যাকে নিজেই পোল করে বের করতে হয় কোন ডিভাইস আসলে ইন্টারাপ্ট করেছে — ধীর, কিন্তু হার্ডওয়্যার সরল।
নিচের কোড সেলে একটি interrupt_vector_table ও একটি handle_interrupt()
ফাংশন সিমুলেট করা হলো, যা vectored dispatch অনুকরণ করে — দুটি ভিন্ন ডিভাইস ইন্টারাপ্ট করলে
প্রতিটি সঠিক হ্যান্ডলারে যায় কিনা, এবং প্রতিবার স্টেট ঠিকভাবে রিস্টোর হয় কিনা তা যাচাই করা যাক।
# সিমুলেটেড ইন্টারাপ্ট হ্যান্ডলিং -- বাস্তব CPU ইন্টারাপ্ট নয়, শুধুই ধারণা বোঝানোর সিমুলেশন
def keyboard_handler(saved_pc, saved_registers):
print(" [Keyboard Handler] কী-প্রেস প্রসেস করা হলো")
return {"status": "keyboard done"}
def disk_handler(saved_pc, saved_registers):
print(" [Disk Handler] ডিস্ক রিড সম্পন্ন, বাফার আপডেট করা হলো")
return {"status": "disk done"}
# ইন্টারাপ্ট ভেক্টর টেবিল: device_id -> হ্যান্ডলার ফাংশন (L11-এর ডিকোডারের প্রয়োগ)
interrupt_vector_table = {
1: keyboard_handler,
2: disk_handler,
}
def handle_interrupt(device_id, saved_pc, saved_registers):
print(f"IRQ এলো: device_id={device_id} -- বর্তমান ইনস্ট্রাকশন শেষ, PC={hex(saved_pc)} ও registers সেভ হলো")
handler = interrupt_vector_table[device_id] # ধাপ ৩: ভেক্টর টেবিল লুকআপ
print(f" vector table থেকে সঠিক হ্যান্ডলারে জাম্প: {handler.__name__}")
result = handler(saved_pc, saved_registers) # ধাপ ৪: হ্যান্ডলার এক্সিকিউশন
print(f" হ্যান্ডলার শেষ -- PC={hex(saved_pc)} ও registers রিস্টোর করে আগের জায়গা থেকে আবার শুরু") # ধাপ ৫
return saved_pc, saved_registers, result
print("-- ডিভাইস ১: কীবোর্ড ইন্টারাপ্ট করলো --")
pc1, regs1, res1 = handle_interrupt(1, saved_pc=0x4010, saved_registers={"r0": 5, "r1": 12})
print(f"রিস্টোর হওয়া অবস্থা: PC={hex(pc1)} registers={regs1}")
print()
print("-- ডিভাইস ২: ডিস্ক কন্ট্রোলার ইন্টারাপ্ট করলো --")
pc2, regs2, res2 = handle_interrupt(2, saved_pc=0x5028, saved_registers={"r0": 99, "r1": 3})
print(f"রিস্টোর হওয়া অবস্থা: PC={hex(pc2)} registers={regs2}")
assert pc1 == 0x4010 and regs1 == {"r0": 5, "r1": 12}
assert pc2 == 0x5028 and regs2 == {"r0": 99, "r1": 3}
print()
print("দুটি ভিন্ন ডিভাইস দুটি ভিন্ন হ্যান্ডলারে সঠিকভাবে রাউট হলো, আর প্রতিবার state অবিকৃত রিস্টোর হলো।")
device_id=1 সবসময় keyboard_handler-এই যায়, আর
device_id=2 সবসময় disk_handler-এ — এটাই vectored dispatch-এর মূল
সুবিধা: কোনো পোলিং ছাড়াই, টেবিল লুকআপ দিয়ে সরাসরি সঠিক হ্যান্ডলারে পৌঁছানো যায়।
ইন্টারাপ্ট হার্ডওয়্যারকে "রিঅ্যাক্টিভ" করে তোলে — CPU-কে ক্রমাগত পোল না করিয়েও ডিভাইসগুলো নিজেদের প্রয়োজনমতো CPU-এর মনোযোগ চাইতে পারে, আর ভেক্টর টেবিল সেই মনোযোগকে দ্রুত সঠিক জায়গায় রুট করে দেয়। পরের পাঠে (L49) দেখা যাবে কীভাবে এই একই ইন্টারাপ্ট মেকানিজম DMA-ভিত্তিক বড় ডেটা-ট্রান্সফারের শেষে CPU-কে মাত্র একবার জানাতে ব্যবহৃত হয় — প্রতি বাইটে নয়।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ CPU কেন বর্তমান ইনস্ট্রাকশন সম্পূর্ণ শেষ না করে সাথে সাথেই ইন্টারাপ্টে লাফ দেয় না?
একটি ইনস্ট্রাকশন মাঝপথে থামালে CPU-এর ভেতরের স্টেট অসম্পূর্ণ/অসামঞ্জস্যপূর্ণ অবস্থায় থাকতে পারে — যেমন একটি রেজিস্টার আংশিক আপডেট হয়েছে কিন্তু পুরোপুরি নয়। যদি এই অবস্থায় স্টেট সেভ করে পরে রিস্টোর করা হয়, প্রোগ্রামটি ভুল ফলাফল দেবে যখন আবার শুরু হবে। তাই CPU একটি নিরাপদ, "পরিষ্কার" থামার বিন্দু পর্যন্ত অপেক্ষা করে — সাধারণত বর্তমান ইনস্ট্রাকশনটি সম্পূর্ণ শেষ হওয়া পর্যন্ত — যাতে রিস্টোর করা স্টেট সবসময় সামঞ্জস্যপূর্ণ (consistent) হয়।
প্র ০২ Vectored ইন্টারাপ্ট দ্রুত হলেও, কোন বাস্তব পরিস্থিতিতে একটি সিস্টেম ডিজাইনার তবুও non-vectored পদ্ধতি বেছে নিতে পারেন?
খরচ ও জটিলতার ট্রেড-অফ — vectored dispatch-এর জন্য অতিরিক্ত হার্ডওয়্যার দরকার (vector table স্টোরেজ, প্রতিটি ডিভাইসকে তার নিজস্ব ভেক্টর নাম্বার সরবরাহের সার্কিট)। খুবই সাধারণ, কম-খরচের এম্বেডেড সিস্টেমে, যেখানে ইন্টারাপ্টের সংখ্যা কম এবং গতি ততটা সংকটাপন্ন নয়, একটিমাত্র ফিক্সড হ্যান্ডলার এবং সফটওয়্যার পোলিং দিয়ে ডিভাইস শনাক্ত করাটাই সহজ ও সস্তা হতে পারে।
প্র ০৩
কোড সেলে saved_pc ও saved_registers হ্যান্ডলারে পাঠানো হয়েছে কিন্তু হ্যান্ডলার সেগুলো পরিবর্তন করেনি। বাস্তব হার্ডওয়্যারে এই "অপরিবর্তিত থাকা" নিশ্চিত করা কেন গুরুত্বপূর্ণ?
মূল প্রোগ্রামের দৃষ্টিকোণ থেকে, একটি ইন্টারাপ্ট সম্পূর্ণভাবে "অদৃশ্য" হওয়া উচিত — প্রোগ্রামটি যেন কখনো জানতেই না পারে যে এটি মাঝপথে থেমে ছিল। যদি হ্যান্ডলার ভুলবশত মূল প্রোগ্রামের PC বা রেজিস্টার পরিবর্তন করে ফেলে, তাহলে ইন্টারাপ্ট থেকে ফিরে আসার পর প্রোগ্রামটি ভুল জায়গা থেকে বা ভুল ডেটা নিয়ে চলা শুরু করবে — একটি গুরুতর, ধরা কঠিন বাগ। তাই বাস্তব হার্ডওয়্যার/হ্যান্ডলার কনভেনশন কঠোরভাবে নিশ্চিত করে যে সেভ করা স্টেট অবিকল রিস্টোর হয়।
অনুশীলন
-
চিন্তা করুন: উপরের কোড সেলে
interrupt_vector_table-এdevice_id=3নেই।handle_interrupt(3, ...)কল করলে কী ঘটবে বলে মনে করেন, এবং বাস্তব হার্ডওয়্যারে এই পরিস্থিতি (অজানা ইন্টারাপ্ট) কীভাবে হ্যান্ডল করা উচিত?Python-এ
interrupt_vector_table[3]একটিKeyErrorরেইজ করবে, কারণ dict-এ সেই কী নেই — প্রোগ্রামটি ক্র্যাশ করবে। বাস্তব হার্ডওয়্যারে অজানা/অব্যবহৃত ইন্টারাপ্ট নাম্বার সাধারণত একটি ডিফল্ট "spurious interrupt handler"-এ ম্যাপ করা থাকে, যা নিরাপদে ইন্টারাপ্টটি উপেক্ষা করে বা লগ করে ফিরে আসে — একটি undefined KeyError-এর মতো ক্র্যাশ কখনোই কাম্য নয়। -
পরীক্ষা করুন: কোড সেলে দুটো ডিভাইস একই
saved_pcমান (যেমন0x4010) দিয়ে পরপর ইন্টারাপ্ট করলে আউটপুট কী পরিবর্তন হবে, আর কী অপরিবর্তিত থাকবে?কোন হ্যান্ডলার কল হচ্ছে (Keyboard/Disk) আর কোন
device_idব্যবহৃত হচ্ছে তা পরিবর্তিত হবে, কিন্তু যেহেতুsaved_pcওhandle_interrupt()-এর রিস্টোর-লজিক একই থাকে, উভয় ক্ষেত্রেই রিস্টোর হওয়া PC হবে ঠিক সেই একই0x4010— যা প্রমাণ করে যে রিস্টোর প্রক্রিয়াটি হ্যান্ডলার কী করলো তার ওপর নির্ভরশীল নয়, বরং সেভ করা মূল মানের ওপর সম্পূর্ণ নির্ভরশীল।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পূর্ববর্তী পাঠ: বাস আর্কিটেকচার L47 system bus ও bus arbitration — এই একই বাস দিয়ে ইন্টারাপ্ট সিগন্যালও প্রবাহিত হয়।
- পরবর্তী পাঠ: DMA — ডাইরেক্ট মেমরি অ্যাক্সেস L49 বড় ডেটা ট্রান্সফার শেষে CPU-কে মাত্র একবার ইন্টারাপ্ট করা হয় কীভাবে — এই পাঠের ইন্টারাপ্ট মেকানিজমেরই একটি বাস্তব প্রয়োগ।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ লজিক গেট থেকে CPU ডেটাপাথ, ক্যাশ মেমরি ও প্যারালাল আর্কিটেকচার পর্যন্ত সম্পূর্ণ কোর্স।