অপারেটিং সিস্টেম কী করে? কিভাবে করে? — প্রথম খন্ড

নিজে নিজে টুকটাক কম্পিউটার সায়েন্স শেখার পরিকল্পনার অংশ হিসেবে Operating Systems: Three Easy Pieces বইটি পড়া শুরু করলাম। এইমাত্র প্রথম অংশটি পড়ে শেষ করলাম (তিন পিসের এক পিস)। শেখাটা পাকাপোক্ত করার জন্য চুম্বকাংশগুলো লিখে ফেলার চেষ্টা করব।

আপারেটিং সিস্টেমের কাজ কী?

একটি অপারেটিং সিস্টেমের মূল কাজ হল একটি কম্পিউটারে চলা প্রোগ্রামগুলোকে ম্যানেজ করা। একেকটি প্রোগ্রামের বেশ কিছু রিসোর্স লাগে। যেমন প্রসেসর, মেমরি (RAM), স্টোরেজ, নেটওয়ার্ক ইত্যাদি। অপারেটিং সিস্টেমের একটি কাজ হল এই রিসোর্সগুলোকে সহজে ব্যবহারযোগ্য করে উপস্থাপন করা। যাতে রিসোর্সগুলো কিভাবে কাজ করে, available আছে কিনা ইত্যাদি নিয়ে সাধারণ প্রোগ্রমারের মাথা ঘামাতে না হয়। এই প্রক্রিয়াটিকে বলা হয় ভার্চুয়ালইজেশন।

আরেকটি গুরুত্বপূর্ণ কাজ হল এই রিসোর্সগুলোকে ভিন্ন ভিন্ন প্রোগ্রামের মধ্যে বন্টন করে দেয়া। যাতে এক প্রোগ্রাম অন্য প্রেগ্রামের এক্সিকিউশনে কোন বিঘ্ন ঘটাতে না পারে (ইচ্ছাকৃতভাবে কিংবা ভুলবশত)। একটি মর্ডান ডেস্কটপ অপারেটিং সিস্টেমের হয়ত আরো অনেক কিছুই করতে হয়। কিন্তু এগুলোই অপারেটিং সিস্টেমের মূল কাজ।

প্রসেস

প্রসেস হচ্ছে একটি চলমান প্রোগ্রাম। স্টোরেজে সংরক্ষিত কিছু ইন্সট্রাকশনকে আমরা বলি একটি প্রোগ্রাম। সেটি যখন মেশিনে চলতে শুরু করে, তখন হয়ে যায় একটি প্রসেস। একটি প্রসেসের সাথে বেশ কিছু জিনিস জড়িত থাকে। যেমনঃ সিপিইউ তে একটি রেজিস্টারে সংরক্ষিত সংখ্যা নির্দেশ করে, এই মুহূর্তে প্রসেসটির কত নাম্বার ইন্সট্রাকশন এক্সিকিউট হচ্ছে? এই রেজিস্টারটিকে বলে PC (Program Counter). এছাড়া আরো কিছু রেজিস্টার একটি প্রসেসের সাথে সংশ্লিষ্ট থাকে, যা প্রসেসটি হিসাব নিকাশের কাজে ব্যবহার করে থাকে।

তারপরে আছে প্রসেসের সাথে সম্পর্কিত মেমরি যা প্রসেসটি এক্সেস করতে পারে। এই মেমরির এক অংশে থাকে স্বয়ং প্রসেসটির কোড। তারপরে একটি স্ট্যাকে থাকে ডিক্লেয়ার করা ভ্যারিয়েবলগুলো। তারপরে থাকে হিপ যা ম্যানুয়ালি অ্যালোকেট করা মেমরি ধারণ করে।

অপারেশন মোড

কোন একটি প্রসেসকে সিস্টেমে সবকিছু করতে দেয়া যায় না। যেমন সব প্রসেসকে ইচ্ছামত মেমরি দখল করার, বা স্টোরেজ এক্সেস করার অধিকার দেয়া যায় না। দিলে প্রথমত সেটা অন্যান্য প্রোগ্রামের জন্য সমস্যা হতে পারে। অথবা প্রোগ্রামার যদি সেই রিসোর্স ভুলভাবে ব্যবহার করে তাহলে পুরো সিস্টেম ক্রাশ করতে পারে। কোনভাবে সীমিত আকারে প্রোগ্রামগুলোকে রিসোর্সগুলো ব্যবহার করার সুয‌োগ করে দিতে হবে।

এই সমস্যাটি সমাধান করতে সিপিইউতে একটি বিশেষ মোড ব্যবহার করা হয়। user mode। এই মোডে একটি প্রসেস কোন নিষিদ্ধ কাজ (যেমন I/O অপারেশন) করতে পারে না। যদি করে তাহলে সিপিইউ একটি এক্সেপশন রেইজ করবে। আর OS সেই আসামী প্রসেসটিকে মেরে ফেলবে!

প্রসেসগুলো system call এর মাধ্যমে নিষিদ্ধ কাজগুলো করতে পারবে। system call করার জন্য প্রসেসকে একটি বিশেষ trap instruction একক্সিকিউট করতে হবে। এই ইন্সট্রাকশন এক্সিকিউট করলে প্রসেসর লাফ দিয়ে kernel এর ভেতরে চলে যাবে আর মুড বদলে হয়ে যাবে kernel mode। এসময় প্রসেসটির PC এবং CPU তে থাকা অন্যান্য তথ্য প্রতি প্রসেসের জন্য বরাদ্দ একটি kernel stack -এ পুশ করা হবে।

Kernel মোডে CPU কোন বাধা ছাড়া সব ধরণের ইন্সট্রাকশন এক্সিকিউ করতে পারে। সিস্টেম তখন দেখতে পারবে সেই নিষিদ্ধ কাজটি অনুমোদিত কিনা বা করলে কোন সমস্যা হবে কিনা। সব ঠিক থাকলে সিস্টেম কাজটি করে আবার একটি বিশেষ return-from-trap ইন্সট্রাকশন এক্সিকিউট করবে। তখন ম‌োড আবার user mode এ ফিরে যাবে এবং kernel stack পপ হবে। প্রসেসটি স্বাভাবিকভাবে চলতে থাকবে।

Rough illustration of operation mode change

এখন kernel এর কোথায় কী কোড আছে সেটা সেটা ইউজার প্রসেসকে জানিয়ে দিলে বিভিন্ন রকমের নিরাপত্তাজনিত সমস্যা তৈরী হবে। তাই boot এর সময় একটি ট্র্যাপ টেবিল সেট আপ করে CPU কে জানিয়ে রাখা হয়, যেখানে সিস্টেম অপারেশনগুলোর অ্যাড্রেস একটি নাম্বার দিয়ে সংরক্ষণ করা থাকে। ইউজার প্রোগ্রাম যখন কোন একটি সিস্টেম অপারেশন করতে চাইবে, তখন এই নাম্বারটি ধরে কল করবে। CPU তখন বুঝতে পারবে এক্সিকিউশন কোথায় লাফ দিবে। C প্রোগ্রামিং ল্যাঙ্গুয়েজে আমরা এমন অনেক সিস্টেম কল দেখতে পাই, যেমন- open , read , write , fork , connect ইত্যাদি।

টাইম শেয়ারিং

সিপিইউ ভার্চুয়ালইজেশনের একটি অন্যতম ধাপ হল একটি সিপিইউ তে এক সাথে একাধিক প্রোগ্রাম চালানোর একটি ব্যবস্থা করা। (যদিও আধুনকি কম্পিউটারগুলোতে একাধিক প্রসেসর থাকে। কিন্তু একসাথে এর চেয়ে অনেক বেশি প্রোগ্রাম চালাতে হয়)। সমাধান খুবই সিম্পল! একটি প্রোগ্রাম কিছুক্ষণ চালিয়ে তারপর সেটা বন্ধ রেখে অন্য একটি প্রোগ্রাম চালানো শুরু করতে হবে, তারপর অন্য একটি। এই কৌশল টাইম শেয়ারিং হিসেবে পরিচিত।

ইন্টারাপ্ট

একটি বিষয় খেয়াল করতে হবে যে OS যদি অন্যান্য প্রসেস নিয়ন্ত্রণ করতে চায় তাহলে OS এর নিজেরই কিন্তু CPU এর দরকার পড়বে, মানে OS এর কোড CPU তে একক্সিকিউট হতে হবে। এখন মনে করুন একটি প্রসেস সিপিইউতে চলছে। সিপিইউ ঐ প্রোগ্রামের ইন্সট্রাকশনগুলো একটি একটি করে এক্সিকিউট করে যাচ্ছে। এখন যদি এভাবে চলতেই থাকে তাহলে OS নিজে CPU তে ফিরে আসবে কী করে? আর সেই প্রসেস বা অন্য প্রসেসগুলোকে নিয়ন্ত্রণ ই বা করবে কিভাবে? আর প্রসেসটিতে যদি কোন কারণে infinity loop সৃষ্টি হয় তাহলে? পাওয়ার কর্ড খুলা ছাড়া উপায় কী?

এই সমস্যা সমাধান করার সময় সিপিইউতে একটি মেকানিজম থাকে। প্রোগ্রাম চালানোর সময় কিছুক্ষণ পর পর সিপিইউ একটি interrupt রেইজ করে। এতে করে প্রসসেসটি সাময়িকভাবে থেমে যায় ও এক্সিকিউশন কার্নেলে চলে যায়। ইন্টারাপ্টে এক্সিকিউশন কার্নেলের কোথায় যাবে সেটা আগে থেকেই ট্রাপ টেবিলে বলে দেয়া থাকে। এভাবে OS নিয়ন্ত্রণ ফিরে পায় ও প্রয়োজনে অন্য প্রসেসে সুইচ করতে পারে।

কন্টেক্সট সুইচ

টাইম শেয়ারিং ইম্প্লিমেন্ট করার জন্য অবশ্যই এক প্রসেস থেকে সুইচ করে অন্য প্রসেস চালানোর সক্ষমতা থাকবে হবে। এজন্য OS সাধারণত একটি লো লেভেল ইন্সট্রাকশন এক্সিকিউট করে, যা প্রসেসটির PC এবং অন্য রেজিস্টারগুলো কপি করে মেমরিতে কোথাও রেখে দেয়। আর যে প্রসেসে সুইচ করা হচ্ছে সেই প্রসেসের PC আর অন্য রেজিস্টরগুলো সিপিইউতে লোড করে নেয়। এই প্রক্রিয়াটিকে বলা হয় কন্টেক্সট সুইচ।

শিডিউলিং

তাহলে এখন প্রসেসগুলোকে নিয়ন্ত্রণে রেখে সীমিত প্রিভিলেজ দিয়ে চালানোর ব্যবস্থা হল। কন্টেক্সট সুইচেরও ব্যবস্থা হল। এখন সিদ্ধান্ত নিতে হবে কোন প্রসেস বা প্রসেসগুলোকে আগে চলতে দেয়া হবে হবে আর কোনগুলোকে পরে। এই সিদ্ধান্ত নেয়ার জন্য সব অপারেটিং সিস্টেমে একটি শিডিউলার অংশ থাকে। একটি শিডিউলারের উদ্দেশ্যগুলো হল:

  • কাজ থাকতেও সিপিইউ যাতে কখনোই বসে না থাকে
  • সিস্টেমে থাকা সবগুলো প্রসেস যত তাড়াতাড়ি সম্ভব শেষ করা
  • আলাদাভাবে প্রত্যেকটি প্রসেস নূন্যতম সময়ে শেষ করা। মানে সবগুলো প্রসেস তাড়াতাড়ি শেষ করতে গিয়ে কোন একটি প্রসেস আটকে থেকে অনেক বেশি সময় লেগে গেল, এমন যাতে না হয়
  • প্রথমবার রেসপন্স করতে বা আউটপুট জেনারেট করতে একটি প্রসেসের যে সময় লাগবে সেটা সর্বনিম্ন করা
  • একেকটি প্রসেসের এক্সিকিউট হওয়ার জন্য যে সময় অপেক্ষা করবে সেটা সর্বনিম্ন করা

একটি সহজ সমাধান হতে পারে, যে প্রসেসটি আগে আসবে সেটি আগে চালিয়ে সম্পূর্ণ শেষ করে তারপরে বাকিগুলোতে যাওয়া। এতে সমস্যা হল, কোন একটি প্রোগ্রাম যদি সময় বিবেচনায় অনেক বড় হয় এবং আগে আসে তাহলে সেটা অন্য সব ছোট ছোট প্রোগ্রামকে বসিয়ে রাখবে। এর মধ্যে ইউজার নতুন কোন প্রোগ্রাম চালাতে চাইলে সেটাও বসে থাকবে।

আরেকটি সমাধান করা যায় এভাবে। প্রত্যেকটি প্রসেস একটি নির্দিষ্ট সময়ের জন্য চালানো, এর মধ্যে এটি শেষ হয়ে গেলে তো গেলই, না হলে এর পরের প্রসেসে চলে যাওয়া। এভাবে সবগুলো প্রসেস ঘুুরে এসে আবার প্রথমটিতে আসা। এই কৌশল Round Robin (RR) নামে পরিচিত। এই নির্দিষ্ট সময়ের স্লাইসটিকে অনেকসময় বলা হয়ে থাকে scheduling quantum. এখানে স্লাইসটি যত ছোট করা হবে, রেসপন্স টাইম তত ভাল হবে। কিন্তু অনেক বেশি ছোট হলে আবার সমস্যা। কেননা কন্টেক্সট সুইচ করতে বেশ কিছু সময় লাগে। খুব বেশি বার করলে এতেই সিস্টেমের অনেক সময় চলে যাবে।

আরেকটি ব্যাপার হল I/O। I/O অপারেশন সাধারণত সিপিইউর বাইরে অনেক সময় নেয়। তাই কোন একটি প্রসেস I/O শুরু করলে CPU বসিয়ে না রেখে অন্য প্রসেসে সুইচ করা একটি ভাল বুদ্ধি। তখন আবার আরেকটি সিদ্ধান্ত নিতে হবে যে ঐ প্রসেসের I/O শেষ হলে সাথে সাথে আবার ঐ প্রসেসে সুইচ করা হবে নাকি অন্যগুলোর রাউন্ড শেষ হলে আবার ঐটাতে আসবে। যদি সাথে সাথে সুইচ না করা হয় তাহলে দেখা যাবে যে প্রসেসগুলোতে I/O বেশি সেগুলো সিপিইউ টাইম খুব কম পাবে। অথচ সেগুলো সাধারণত ইন্টারেক্টিভ ধরণের প্রোগ্রাম যেখানে ইউজার ইনপুট দিয়ে সাথে সাথে আউটপুট পাবার আশা করে। আবার যদি সাথে সাথে সুইচ করা হয় তাহলে একটি প্রসেস চাইলে একটি নির্দিষ্ট সময় পর পর (scheduling quantum শেষ হওয়ার ঠিক আগে) বিনা কারণে একটি ছোট I/O কল করে প্রায় সাথে সাথেই সিপিইউতে ফিরে আসতে পারবে। এভাবে পুরো সময়ই ঐ প্রসেস একা সিপিইউ দখল করে রাখতে পারবে। এমন সুযোগ দিয়ে রাখা সিস্টেমের জন্য ভাল হতে পারে না।

মাল্টিলেভেল ফিডব্যাক কিউ (MLFQ)

Multilevel Feedback Que একটি জনপ্রিয় শিডিউলার। এতে একাধিক ভিন্ন প্রায়োরিটিসম্পন্ন একাধিক কিউতে প্রসেসগুলো রেখে অনেকটা RR এর মত এক্সিকিউট করা হয়। এতে কিছু বিশেষ কৌশল ব্যবহার করা হয় যাতে প্রসেগুলো বৈশিষ্ট্য/আচরণের উপর ভিত্তি করে সিপিইউ বরাদ্দ পায়। এর কৌশলগুলো কিছুটা এমন:

  • প্রথম কিউ-টির প্রায়োরিটি সবচেয়ে বেশি। তারপরেরটির কম। এভবে বিভিন্ন প্রায়োরিটির বেশ কয়েকটি কিউ থাকতে পারে
  • সিস্টেমে নতুন কোন প্রসেসে আসলে সেটিকে প্রথম কিউতে ঢুকানো হবে
  • সবচেয়ে বেশি প্রয়োটির যে কিউ-টিতে কোন প্রসেস আছে সেটির প্রসেসগুলোকে RR কৌশলে এক্সিকিউট করা হবে। এক্ষেত্রে কোন প্রসেস I/O ইস্যু করলে সেটিকে বন্ধ রাখা হবে। I/O শেষ হলে আবার সাথে সাথে সুউচ করা হবে
  • একটি প্রসেস কোন কিউতে টানা একটি scheduling quantum পার করে ফেললে সেটিকে এর নিচের কিউতে নামিয়ে দেয়া হবে। এতে করে নন-ইন্টাঅ্যাক্টিভ লম্বা প্রসেসগুলো সিপিইউ দখল করে রাখতে পারবে না
  • কোন প্রসেস I/O ইস্যু করে কতবার থামল সেটি উপেক্ষা করে কোন একটি কিউতে এর scheduling quantum শেষ হয়ে গেলে সেটিকেও নিচের কিউতে নামিয়ে দেয়া হবে। এতে করে কোন প্রসেস চালাকি করে সিপিইউ দখল করে রাখতে পারবে না
  • এখন একটি সমস্য হল যদি ছোটখাট প্রসেস সিস্টেমে আসতেই থাকে তাহলে বড় প্রসেসগুলো একেবারেই সিপিইউ পাবে না। আবার কোন প্রসেস প্রথম দিকে কোন ইন্টারঅ্যাকশন ছাড়া শুধু সিপিইউ ব্যবহার করে যদি পরে ইন্টার‍অ্যাক্টিভ হতে চায় তাহলে সেটাও সমস্যা হবে। এজন্য যা করতে হবে তা হল একটি নির্দিষ্ট সময় পর প্রায়োরিটি বুস্ট করে সবগুলো প্রসেসকেই একেবারে সবার প্রথম কিউ-তে তুলে দিতে হবে।

কোন কোন ইম্প্লিমেন্টেশনে একেক কিউর জন্য একেক আকারের scheduling quantum ব্যবহার করা হয়।

Rough illustration of MLFQ

MLFQ শিডিউলিং এর অনেকগুলো সমস্যাই বেশ ভালভাবে সমাধান করে। ইউনিক্সের বিএসডি ডেরিভেটিভগুলোতে এবং খুব সম্ভবত ইউন্ডোজেও এই শিডিউলারটিরই কিছুটা পরিবর্তিত সংস্করণ ব্যবহার করা হয়।

তবে এর একটি অন্যতম সমস্যা হল এতে অনেকগুলো প্যারামিটার বিদ্যমান। যেমন কয়টি কিউ ব্যবহার করা হবে, scheduling quantum কত হবে, প্রয়োরিটি বুস্ট কতক্ষণ পর পর করতে হবে ইত্যাদি। এগুলো সঠিকভাবে নির্ধারণ করা বেশ কঠিন।

কম্প্লিটলি ফেয়ার শিডিউলার (CFS)

লিনাক্স কার্নেল বর্তমানে ব্যবহৃত শিডিউলারটি হল Completely Fair Scheduler. নাম থেকে যেমন বুঝা যায়, এটি সব প্রসেসকে সমানভাবে দেখে। প্রত্যেকে প্রসেসের সাথে সংশ্লিষ্ট একটি virtual runtime বা vruntime থাকে। একটি প্রসেস যতক্ষণ চলে এর vruntime সেই অনুসারে বাড়ানো হয়। কোন প্রসেস চালানো হবে সিদ্ধান্ত নেয়ার সময় vruntime ওয়ালা প্রসেসটিকে নেয়া হয়। ব্যাপারটি ইফিশিয়েন্ট করতে প্রসেসগুলোকে একটি রেড ব্ল্যাক ট্রিতে রাখা হয়। CFS এ প্রত্যেকটি প্রসেসের আবার একটি nice মান থাকে। এই মানটি ব্যবহার করে প্রত্যেকটি প্রসেসের জন্য আলাদাভাবে time slice ও vruntime হিসেব করা হয়। এতে করে মানটি একটি প্রসেসের প্রয়োরিটি নির্ধারণ করে। ছোট মান বেশি প্রয়োরিটি নির্দেশ করে। (একটি প্রসেসের নাইস মান বেশি মানে হল প্রসেসটি ভদ্র, বেশি পাত্তা না দিলেও হবে)। লিনাক্সের ‍top টুলের NI কলামে এই মানটি দেখা যায়। এটি চাইলে ইউজার বা সিস্টেম অ্যাডমিন পরিবর্তন করতে পারে।

আরো কিছু শিডিউলার হল BFS, O(1) শিডিউলার (নামটি প্রসেস খোঁজার টাইম কম্প্লেক্সিটির উপর ভিত্তি করে), O(n) শিডিউলার ইত্যাদি।

বইটি সম্পূর্ণ ফ্রিতে এখানে পড়া যাবে: http://ostep.org/

আমার হোমওয়ার্কগুলো এখানে আছে: https://github.com/sjsakib/cs, ছোট করে একটা স্টার দিয়ে আসলে ভাল লাগবে :D

--

--