সরাসরি পর্বে যান
পর্ব ৫৪ · ৬৪-এর মধ্যে · মডিউল ৮

মোবাইল, embedded আর real-time OS — Android, iOS আর RTOS

Mobile, Embedded & Real-Time Operating Systems — Android, iOS, Energy Management and RTOS Internals

Taught in Bangla, this lesson covers mobile, embedded and real-time operating systems: the constraints of small devices, the Android stack, Zygote and fork, app lifecycle and lmkd, iOS internals with XNU, app states and Jetsam, energy management with DVFS, governors, big.LITTLE, Doze and wake locks, bare-metal superloops, RTOS internals such as ticks, priorities and deterministic latency, embedded Linux, and long-lived IoT updates.

এই পর্বে যা শিখবেন

  • মোবাইল আর embedded যন্ত্রের সীমাবদ্ধতা কীভাবে OS-এর প্রতিটি সিদ্ধান্ত বদলে দেয়
  • Android-এর স্তরগুলো, Zygote থেকে অ্যাপের জন্ম (C-তে নিজে মেপে), অ্যাপের জীবনচক্র, importance আর lmkd
  • iOS-এর XNU, অ্যাপের অবস্থা, পেছনের কাজের সীমা আর Jetsam
  • DVFS, governor, big.LITTLE, Doze আর wake lock — P = C·V²·f দিয়ে শক্তির পুরো হিসাব
  • Bare-metal superloop, RTOS আর embedded Linux; FreeRTOS-এর task, queue, mutex; ISR থেকে task-এ কাজ পাঠানো; safety certification আর IoT-এর আপডেট

১ · ছোট যন্ত্রের বড় সীমাবদ্ধতা — যা OS-এর নকশা বদলে দেয়

এতদিন কোর্সে আমরা মূলত ডেস্কটপ আর সার্ভারের চোখে OS দেখেছি: বিদ্যুৎ দেয়ালের প্লাগ থেকে আসে, RAM কম পড়লে ডিস্কে swap আছে, গরম হলে পাখা ঘোরে। পর্ব ২-এ দেখেছেন মোবাইল OS আর embedded system কীভাবে এল। এবার প্রশ্ন: এদের ভেতরটা আলাদা কেন? উত্তর এক কথায় — সীমাবদ্ধতা. পকেটের ফোন বা দেয়ালের বিদ্যুৎ-মিটারের OS প্রতিটি সিদ্ধান্তে এই প্রশ্নগুলো করে:

🔋 ব্যাটারি আর শক্তি

প্রতিটি CPU-চক্র, প্রতিটি রেডিও-জাগরণ ব্যাটারি খায়। তাই OS-এর নতুন কাজ: কেউ যেন অকারণে যন্ত্র জাগিয়ে না রাখে। গড় গতির চেয়ে প্রতি কাজে কত জুল খরচ — এই মাপ বেশি গুরুত্বপূর্ণ।

🧠 কম মেমরি

কম দামের ফোনে কয়েক GB RAM, মাইক্রোকন্ট্রোলারে কয়েক দশ KB. ডিস্কে swap প্রায় নেই, তাই RAM ফুরোলে OS কাউকে বন্ধ করে জায়গা বানায় — কাকে, সেটিই নীতি।

💾 Flash storage

ফোনের storage NAND flash: পড়া দ্রুত, কিন্তু প্রতিটি block-এর মোছা-লেখার আয়ু সীমিত (পর্ব ৪০)। বারবার swap লিখলে storage-এর আয়ু কমে, ব্যাটারিও যায়।

🌡️ তাপ

ফোনে পাখা নেই। চিপ গরম হলে OS নিজেই গতি কমায় — thermal throttling. তাই “সর্বোচ্চ গতি” কয়েক সেকেন্ডের জন্য, স্থায়ী নয়।

📶 অনিয়মিত নেটওয়ার্ক

লিফটে সংযোগ নেই, গ্রামে 4G থেকে 2G. অ্যাপকে অফলাইনে চলতে হয়; OS অনেক অ্যাপের নেটওয়ার্ক-কাজ জড়ো করে একসাথে পাঠায়, যাতে রেডিও কম জাগে।

👆 স্পর্শের পর্দা

পর্দা সাথে সাথে সাড়া না দিলে মানুষ টের পান, তাই সামনের অ্যাপের UI thread পায় সবচেয়ে বেশি অগ্রাধিকার।

⏱️ Deadline

গাড়ির ব্রেক, পেসমেকার বা কারখানার মোটর-নিয়ন্ত্রকে দেরিতে আসা সঠিক উত্তরও ভুল (পর্ব ২০-এর hard real-time). এখানে গড় নয়, সবচেয়ে খারাপ সময় প্রমাণ করতে হয়।

📜 নিরাপত্তা-সনদ

বিমান, গাড়ি বা চিকিৎসা-যন্ত্রের সফটওয়্যারে মানদণ্ড মেনে প্রমাণ দিতে হয়; ছোট, বিশ্লেষণযোগ্য OS এখানে বড় সুবিধা।

ঢাকার একটি বড় হোটেলের রান্নাঘরে অনেক চুলা, বড় ফ্রিজ, অনেক কর্মী — কেউ একটু বসে থাকলেও চলে (ডেস্কটপ/সার্ভার)। ফুটপাতের চায়ের দোকানে একটি চুলা আর সীমিত গ্যাস; দোকানি জানেন কখন চুলা নিভিয়ে গ্যাস বাঁচাবেন আর ভিড় বাড়লে কোন অর্ডার বাদ দেবেন (মোবাইল)। আর রেলক্রসিংয়ের গেটম্যানের প্রশ্ন একটাই — ট্রেন আসার আগে গেট নামবে কি না; “গড়ে সময়মতো” নামলে চলে না (real-time).

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

২ · Android-এর স্তর — Linux kernel থেকে অ্যাপ framework

Android-এর নিচে Linux kernel, কিন্তু এটি Ubuntu-র মতো একটি “Linux distro” নয়। GNU-র glibc-এর বদলে Android নিজের ছোট C লাইব্রেরি Bionic ব্যবহার করে, X11 বা Wayland নেই, shell-এর টুলও আলাদা। ওপরের স্তরগুলো Google-এর নিজস্ব। Android-এর অফিসিয়াল ডকুমেন্টেশন সাধারণত স্তরগুলো এভাবে দেখায় (সরলীকৃত; সংস্করণভেদে নাম বদলায়):

Android-এর স্তর — ওপর থেকে নিচে

প্রতিটি স্তর শুধু নিচের স্তরের দেওয়া interface ব্যবহার করে

  1. অ্যাপAPK (Kotlin/Java)আপনার লেখা কোড
  2. FrameworkActivityManager…system_server-এ চলে
  3. ART + nativelibc, libart, SQLitebytecode চালানো
  4. HALcamera, audio HALনির্মাতার কোড
  5. Linux kernelBinder, lmkd-সহায়তাdriver, scheduler
ছবি ৫৪.১ — অ্যাপ সরাসরি ক্যামেরার driver ডাকে না; framework-এর API → Binder → HAL → driver পথে যায়।

Kernel-এর Android-বিশেষ অংশ

  • Binder: Android-এর প্রধান IPC. অ্যাপের “লোকেশন চাই” অনুরোধ Binder দিয়ে system_server-এ যায়, আর kernel সাথে জুড়ে দেয় পাঠানো প্রসেসের UID ও PID — অনুমতি যাচাইয়ের জন্য। খুঁটিনাটি পর্ব ১২-এ।
  • ashmem থেকে memfd: প্রসেসের মধ্যে মেমরি ভাগের জন্য Android-এর নিজস্ব driver, যা চাপের সময় kernel-কে কিছু অংশ ফেলে দিতে দিত। এর জায়গা ধীরে ধীরে নিচ্ছে মূল Linux-এর memfd_create() — একটি নামহীন, RAM-ভিত্তিক ফাইল, যার file descriptor অন্য প্রসেসে পাঠানো যায় (সংস্করণভেদে আলাদা)।
  • Wake lock-এর ইতিহাস: ফোন যত দ্রুত সম্ভব গভীর ঘুমে (suspend) যেতে চায়, কিন্তু গান বাজার সময় বা ডাউনলোডের মাঝে যাওয়া চলবে না। Android-এর প্রথম দিকের kernel-এ “wakelock” নামের একটি ব্যবস্থা ছিল: কেউ lock ধরে থাকলে suspend হবে না। মূল Linux প্রথমে এই নকশা নেয়নি; পরে একই প্রয়োজনে মূল kernel-এ “wakeup source” আর autosleep আসে (মোটামুটি ২০১২ সালের দিকে)। অ্যাপের স্তরে নামটি এখনো PowerManager.WakeLock.
  • Low memory killer → lmkd: শুরুতে এটি kernel-এর একটি driver ছিল, যা ফাঁকা RAM নির্দিষ্ট সীমার নিচে নামলে পেছনের অ্যাপ মারত। পরে কাজটি user space-এর একটি daemon, lmkd-তে সরানো হয়, আর নতুন সংস্করণে lmkd kernel-এর PSI (পর্ব ৩৮) দেখে সিদ্ধান্ত নেয় — “কাজগুলো মেমরির জন্য কতক্ষণ আটকে আছে”। অংশ ৪-এ ধাপে ধাপে দেখবেন।

HAL, native লাইব্রেরি, ART আর framework

HAL (hardware abstraction layer): প্রতিটি নির্মাতার ক্যামেরা বা অডিও চিপ আলাদা; HAL একটি নির্দিষ্ট interface দেয়, নির্মাতা নিজের চিপের জন্য তা বাস্তবায়ন করেন, আর framework শুধু interface চেনে।

Native লাইব্রেরি C/C++-এ লেখা — Bionic, OpenGL ES/Vulkan, SQLite, মিডিয়া codec. ART (Android Runtime): Android অ্যাপ সাধারণত Kotlin বা Java-তে লেখা, যা DEX নামের bytecode-এ পরিণত হয়। একে চালানোর যন্ত্র প্রথমে ছিল Dalvik — একটি interpreter, Android 2.2 থেকে তাতে JIT (চলার সময় ঘন ঘন চলা কোড machine code-এ রূপান্তর) যুক্ত হয়। Android 4.4-এ পরীক্ষামূলকভাবে আর 5.0 থেকে ডিফল্ট হিসেবে আসে ART, যা শুরুতে ইনস্টলের সময়েই পুরো অ্যাপ machine code-এ রূপান্তর করত — AOT (ahead-of-time). এতে অ্যাপ দ্রুত চলল, কিন্তু ইনস্টল আর সিস্টেম-আপডেট ধীর হলো, storage-ও বেশি লাগল। তাই Android 7.0 থেকে মিশ্র পথ: প্রথমে JIT, চলার সময় কোন কোড বেশি চলে তার profile রাখা, আর ফোন চার্জে ও অলস থাকলে সেই অংশ AOT-তে রূপান্তর। (সংস্করণগুলো সাধারণভাবে উদ্ধৃত।) ART-এর garbage collector-ও এমনভাবে বানানো, যাতে পর্দা আঁকার মাঝে লম্বা বিরতি না আসে।

Application framework: অ্যাপ-ডেভেলপার যে Java/Kotlin API দেখেন — Activity, Service, ContentProvider, NotificationManager. এর বেশিরভাগ “সেবা” আসলে চলে একটি বিশাল সিস্টেম-প্রসেস system_server-এ; অ্যাপ Binder দিয়ে তাকে ডাকে। অর্থাৎ Android-এর “OS”-এর বড় অংশ kernel-এর বাইরে, user space-এ।

অনুমতি আর মডুলার আপডেট

পর্ব ৪৯-এ দেখেছেন, প্রতিটি অ্যাপ নিজের Linux UID, SELinux আর seccomp-এর sandbox-এ চলে। তার ওপরে আছে অনুমতির মডেল। Android 6.0 (২০১৫) থেকে বিপজ্জনক অনুমতি — ক্যামেরা, মাইক্রোফোন, লোকেশন, কন্টাক্ট — ইনস্টলের সময় একবারে নয়, প্রথম ব্যবহারের মুহূর্তে চাওয়া হয়, আর ব্যবহারকারী পরে settings থেকে ফিরিয়ে নিতে পারেন: runtime permission. পরে “শুধু এবার” ধরনের বিকল্পও এসেছে।

পুরোনো সমস্যা: নতুন Android ফোনে পৌঁছাতে নির্মাতা আর চিপ-কোম্পানির কোড নতুন করে মেলাতে হয়, তাই বহু ফোন আপডেট পেত দেরিতে বা পেতই না। এর জবাবে Android 8.0 (২০১৭)-এ Project Treble: নির্মাতার hardware-নির্ভর অংশ (vendor partition, HAL) আর Android framework-এর মাঝে একটি স্থির interface, যাতে framework আলাদাভাবে হালনাগাদ করা যায়। Kernel-এর ক্ষেত্রেও একই চিন্তায় পরে GKI (Generic Kernel Image): Google-এর রক্ষণাবেক্ষণ করা একটি সাধারণ kernel, আর নির্মাতার driver আলাদা module হিসেবে (Android 12-এর দিকের নতুন ফোনে সাধারণভাবে বাধ্যতামূলক বলে জানা যায়)। লক্ষ্য একটাই — নিরাপত্তা-প্যাচ দ্রুত ফোনে পৌঁছানো।

৩ · Zygote — একবার তৈরি, বারবার fork

একটি Android অ্যাপ চালু করতে runtime শুরু, হাজার হাজার framework class লোড আর resource প্রস্তুত করা লাগে। প্রতিবার এসব করলে চালু ধীর হতো, আর প্রতিটি অ্যাপ একই জিনিসের আলাদা কপি রাখত। Android-এর সমাধান পর্ব ১০-এর fork() আর পর্ব ৩৫-এর copy-on-write-এর সুন্দর প্রয়োগ: বুটের সময় একটি প্রসেস — Zygote (জীববিজ্ঞানে নিষিক্ত কোষ, যা থেকে পুরো জীব জন্মায়) — একবার runtime চালু করে, সাধারণ class আর resource preload করে socket-এ অপেক্ষা করে। নতুন অ্যাপ লাগলে Zygote শুধু fork করে; সন্তান প্রস্তুত runtime পায়, আর সব page বাবার সাথে ভাগ থাকে — যতক্ষণ কেউ সেগুলোতে লেখে না।

একটি রেস্তোরাঁর রান্নাঘরে ভোরে বড় পাতিলে মসলা কষিয়ে, ভাত আর ডাল রেঁধে রাখা হয়। অর্ডার এলে প্রতিবার শুরু থেকে রাঁধতে হয় না — সেই প্রস্তুত ভিত্তি থেকে প্লেট সাজানো হয়। যে খদ্দের শুধু খান (পড়েন), সবাই একই পাতিল থেকে নেন। কেউ বিশেষ ঝাল চাইলে (লিখলে) তাঁর জন্যই শুধু আলাদা বাটিতে তুলে বদলানো হয় — সেটিই copy-on-write.

  1. LauncherstartActivity()নিজেও একটি অ্যাপ
  2. system_serverActivityManagerপ্রসেস আছে?
  3. Zygotefork()প্রস্তুত runtime
  4. নতুন প্রসেসsetuid, SELinuxঅ্যাপের পরিচয়
  5. অ্যাপonCreate()প্রথম পর্দা
এই ধাপে
কে কাজ করছে
প্রসেসের পরিচয়
মেমরি
  1. আঙুলের চাপ। হোম-স্ক্রিন বা Launcher নিজেও একটি সাধারণ অ্যাপ। আইকন চাপলে সে একটি Intent বানিয়ে startActivity() ডাকে — যা Binder দিয়ে system_server-এ যায়।
  2. প্রসেস আছে কি? system_server-এর ActivityManager দেখে, এই অ্যাপের কোনো প্রসেস আগে থেকে চলছে কি না। নেই, তাই সে Zygote-এর socket-এ অনুরোধ পাঠায়: কোন UID, group আর SELinux তথ্য।
  3. Zygote-এর fork. Zygote ঠিক একটি fork() করে। নতুন প্রসেসের নিজস্ব PID হলো, কিন্তু মেমরির কোনো page কপি হয়নি — page table-এ সব page শুধু-পড়া হিসেবে বাবার সাথে ভাগে (পর্ব ৩৫)।
  4. নিজের পরিচয় নেওয়া। সন্তান এখনো root, নাম “zygote”. সে নিজেকে “বিশেষায়িত” করে: অ্যাপের UID (যেমন 10123, যা ps-এ u0_a123) আর group নেয়, SELinux context untrusted_app-এ যায়, seccomp filter বসায় (পর্ব ৪৯)। এরপর আর root-এ ফেরা যায় না; Zygote আবার অপেক্ষায়।
  5. অ্যাপের কোড শুরু। সন্তান ActivityThread.main() চালায়, Binder দিয়ে system_server-কে জানায় “আমি এসেছি”। উত্তরে সে অ্যাপের APK-র নিজস্ব class লোড করে আর Application.onCreate() ডাকে। এখন যে page-এ লেখা হয়, শুধু সেগুলোর নতুন কপি হয়।
  6. প্রথম পর্দা। Activity-র onCreate → onStart → onResume, তারপর প্রথম frame আঁকা — একে বলে cold start. Framework-এর বেশিরভাগ page এখনো সব অ্যাপের সাথে ভাগে, তাই ২০টি অ্যাপ চললেও সেগুলো RAM-এ মূলত একবারই থাকে।

এবার Zygote-এর মূল যুক্তি নিজে মেপে দেখুন। নিচের C প্রোগ্রামে বাবা (“zygote”) একবার একটি ৮ MiB-র টেবিল তৈরি করে ভরে রাখে — যেন preload করা class. তারপর একে একে চারটি সন্তান (“app”) fork করে। প্রথম তিনটি টেবিলের প্রতিটি page থেকে শুধু পড়ে; চতুর্থটি প্রতিটি page-এ লেখে। প্রতিটি সন্তান নিজের কাজের আগে-পরে getrusage() দিয়ে নিজের minor page fault গোনে। সন্তানগুলো একটির পর একটি চলে (বাবা প্রতিবার wait করে), তাই ছাপার ক্রম নির্দিষ্ট।

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/wait.h>
#define PAGES 2048                     /* 2048 pages x 4 KiB = 8 MiB "preloaded" table */
static long minflt(void) { struct rusage ru; getrusage(RUSAGE_SELF, &ru); return ru.ru_minflt; }
int main(void) {
    long pg = sysconf(_SC_PAGESIZE);
    char *table = mmap(NULL, PAGES * pg, PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (table == MAP_FAILED) { perror("mmap"); return 1; }
    madvise(table, PAGES * pg, MADV_NOHUGEPAGE);  /* keep plain 4 KiB pages */
    memset(table, 7, PAGES * pg);                 /* zygote: initialise once */
    printf("zygote: table of %d pages ready (page size %ld)\n", PAGES, pg);
    const char *name[] = { "app-A", "app-B", "app-C", "app-W" };
    for (int i = 0; i < 4; i++) {
        fflush(stdout);                           /* nothing buffered before fork */
        if (fork() == 0) {
            long before = minflt(), sum = 0;
            for (long p = 0; p < PAGES; p++) {
                if (i < 3) sum += table[p * pg];    /* read only: stay shared */
                else table[p * pg] = 1;            /* write: kernel copies page */
            }
            long f = minflt() - before;
            printf("%s: %-5s sum=%-5ld minor faults = %4ld -> %s\n", name[i],
                   i < 3 ? "read" : "write", sum, f, f < 64 ? "pages shared" : "pages copied");
            fflush(stdout); _exit(0);             /* _exit() skips stdio flushing */
        }
        wait(NULL);
    }
    return 0;
}

একটি আসল রানের আউটপুট:

zygote: table of 2048 pages ready (page size 4096)
app-A: read  sum=14336 minor faults =    0 -> pages shared
app-B: read  sum=14336 minor faults =    0 -> pages shared
app-C: read  sum=14336 minor faults =    0 -> pages shared
app-W: write sum=0     minor faults = 2048 -> pages copied

তিন পাঠকের fault ০: fork-এর সময় kernel page table কপি করেছে, তাই বাবার ভরা ২০৪৮টি page সন্তানের কাছে আগে থেকেই উপস্থিত — একই physical frame, শুধু-পড়া অবস্থায় (sum = ২০৪৮ × ৭ = ১৪৩৩৬ প্রমাণ করে তারা সত্যিই পড়েছে)। লেখকের fault ঠিক ২০৪৮ — প্রতি page-এ একটি: প্রথম লেখায় CPU protection fault দেয়, kernel একটি নতুন frame-এ page কপি করে লিখতে দেয় (পর্ব ৩৫)। app-W কিছু পড়েনি বলে তার sum ০. Zygote-এর সব অ্যাপ এভাবেই framework-এর page ভাগ করে, আর যে অ্যাপ যা বদলায় শুধু তারই কপি হয়। সংখ্যাগুলো এই রানের; অন্য kernel-এ সামান্য আলাদা হতে পারে, কিন্তু “পাঠক ≈ ০, লেখক ≈ page-সংখ্যা” সম্পর্কটি থাকবে। আরেকটি শিক্ষা: _exit() stdio-র buffer খালি করে না, তাই সন্তান বেরোনোর আগে fflush(stdout) — না দিলে pipe-এ চলা এই রানারে সন্তানদের লাইনগুলো হারিয়ে যায়।

নিজের Android ফোনে (Developer options-এ USB debugging চালু করে, কম্পিউটার থেকে) এই বংশধারা দেখা যায়। নিচে একটি উদাহরণ — সংখ্যা আর নাম যন্ত্রভেদে আলাদা হবে, আর ৬৪-bit ফোনে Zygote-এর নাম সাধারণত zygote64:

$ adb shell ps -A -o USER,PID,PPID,NAME | grep -E "zygote|com.android.settings"
root           612     1 zygote64
root           613     1 zygote
u0_a98        4721   612 com.android.settings

Settings অ্যাপের PPID = ৬১২, অর্থাৎ বাবা zygote64, আর UID অ্যাপের নিজস্ব u0_a98 — পর্ব ১০-এর fork আপনার পকেটেই।

৪ · অ্যাপের জীবনচক্র, importance আর lmkd — RAM ফুরোলে কে যাবে

ডেস্কটপে প্রোগ্রাম বন্ধ করেন আপনি; মোবাইলে প্রায়ই করে OS. তাই অ্যাপের একটি নির্দিষ্ট জীবনচক্র আছে। একটি Activity (অ্যাপের একটি পর্দা) এই callback-গুলো পায়: onCreate (তৈরি), onStart (দেখা যাচ্ছে), onResume (সামনে, স্পর্শ নিচ্ছে), onPause (আংশিক ঢাকা পড়ছে), onStop (আর দেখা যাচ্ছে না), onDestroy (শেষ)। মূল নিয়ম: onStop-এর পরে প্রসেস যেকোনো মুহূর্তে বিনা সতর্কতায় মারা যেতে পারে, তাই দরকারি অবস্থা (অর্ধেক লেখা মেসেজ, ফর্মের তথ্য) আগেই সংরক্ষণ করতে হয়; ফিরলে অ্যাপ নতুন প্রসেসে জন্মে সেখান থেকে সাজিয়ে নেয়।

কাকে আগে মারা হবে, তা ঠিক হয় প্রসেসের importance দিয়ে। Android-এর ডকুমেন্টেশন মোটামুটি চারটি শ্রেণি বলে: foreground (যে অ্যাপ এখন সামনে, বা ব্যবহারকারী যার সাথে কাজ করছেন), visible (দেখা যাচ্ছে কিন্তু সামনে নয়, যেমন একটি dialog-এর পেছনের অ্যাপ), service (পেছনে কাজ করছে — গান বাজানোর মতো ব্যবহারকারীর টের পাওয়া কাজ হলে আরও উঁচুতে), আর cached (পেছনে বসে আছে, এখন কিছুই করছে না — শুধু পরে দ্রুত ফেরার জন্য রাখা)। system_server প্রতিটি অ্যাপ-প্রসেসের জন্য পর্ব ৩৮-এর oom_score_adj-এর মতো একটি সংখ্যা বসায় — যত বড়, তত আগে বলি। Android-এর source-এ সাধারণভাবে দেখা যায়: foreground ০, visible ১০০, perceptible (যেমন গান) ২০০, home ৬০০, previous (একটু আগে ব্যবহৃত) ৭০০, cached ৯০০–৯৯৯; আর গুরুত্বপূর্ণ সিস্টেম-প্রসেস ঋণাত্মক (মান সংস্করণভেদে বদলাতে পারে)।

ফোনের অবস্থা (উদাহরণের সংখ্যা)
সামনের অ্যাপ
ফাঁকা RAM
memory PSI (some)
lmkd-এর কাজ
যারা বেঁচে আছে
  1. স্বাভাবিক অবস্থা। সামনে মেসেঞ্জার (adj ০)। পেছনে গান বাজছে (২০০), home (৬০০), আর দুটি cached অ্যাপ — নিউজ (৯২০), গেম (৯৫০)। ফাঁকা RAM ৯০০ MB, PSI শূন্য — কেউ মেমরির জন্য আটকে নেই। lmkd ঘুমিয়ে।
  2. ক্যামেরা খোলা। ক্যামেরা সামনে এল (০); মেসেঞ্জার হলো “previous” (৭০০)। ক্যামেরা অনেক মেমরি নিল, ফাঁকা RAM ২০০ MB. Kernel আগে নিজের সহজ পথ নেয়: kswapd page cache ফেরত নেয়, zram কম ব্যবহৃত page চেপে রাখে। PSI ৮% — lmkd-এর সীমার নিচে, তাই এখনো কাউকে মারার দরকার নেই।
  3. প্রথম বলি। 4K ভিডিও শুরু, ফাঁকা RAM ৫০ MB-তে নামল; কাজগুলো এখন সময়ের ২৫% মেমরির জন্য আটকে — সীমা পার। lmkd জাগে আর সবচেয়ে বড় adj খোঁজে: cached গেম (৯৫০)। SIGKILL — কোনো সতর্কতা নেই। ৪২০ MB ফেরত, ফাঁকা RAM ৪৭০ MB.
  4. দ্বিতীয় বলি। ভিডিও এডিটে আরও ৪০০ MB গেল, ফাঁকা RAM ৭০ MB, চাপ আবার সীমার ওপরে। পরের বড় adj: নিউজ (৯২০) — বন্ধ, ২৩০ MB ফেরত, ফাঁকা RAM ৩০০ MB.
  5. Cached শেষ, previous-এর পালা। আরও ২৮০ MB খরচ, ফাঁকা RAM ২০ MB, এবার “full” stall-ও বাড়ছে। Cached কেউ নেই, তাই পরের সারির previous মেসেঞ্জার (৭০০) বন্ধ। লক্ষ করুন, গান (২০০) আর সামনের ক্যামেরা (০) অক্ষত — ব্যবহারকারী যা টের পান, তা শেষে।
  6. ফিরে আসা। ব্যবহারকারী রিসেন্ট-তালিকা থেকে গেমে ফিরলেন। তালিকায় গেমের নাম ছিল, কিন্তু প্রসেস নেই — তাই Zygote থেকে নতুন প্রসেস (cold start), আর গেম সংরক্ষিত অবস্থা থেকে আগের লেভেল সাজায়। যে অ্যাপ অবস্থা সংরক্ষণ করেনি, সেটি শুরু থেকে খোলে — ব্যবহারকারী ভাবেন “অ্যাপটি নিজে থেকে রিস্টার্ট হয়”।

পর্ব ৩৮-এর OOM killer আসে একেবারে শেষ মুহূর্তে, প্রায়ই thrashing-এ (পর্ব ৩৭) যন্ত্র জমে যাওয়ার পরে। lmkd আগেভাগে, চাপের লক্ষণ দেখেই, সবচেয়ে কম ক্ষতির প্রসেস সরায় — মোবাইলে একটি cached অ্যাপ হারানো তুচ্ছ, জমে থাকা পর্দা বড় ক্ষতি।

৫ · iOS-এর ভেতর — XNU, স্তর, অ্যাপের অবস্থা আর Jetsam

iOS, macOS, iPadOS, watchOS — Apple-এর সব OS-এর kernel একই: XNU (“X is Not Unix”). পর্ব ৬-এর hybrid kernel-এর এটি সবচেয়ে পরিচিত উদাহরণ। এর ভিত্তি Mach — ১৯৮০-এর দশকে Carnegie Mellon University-তে তৈরি একটি microkernel, যা task, thread, virtual memory আর message-ভিত্তিক IPC (Mach port) দেয়। তার পাশে, একই kernel address space-এ, বসানো BSD Unix-এর অংশ: প্রসেস, POSIX system call, ফাইল সিস্টেম, নেটওয়ার্ক stack, অনুমতি। আর driver লেখার জন্য C++-ভিত্তিক I/O Kit. অর্থাৎ নকশায় microkernel-এর ধারণা, কিন্তু গতির জন্য সব এক জায়গায়।

Apple-এর পুরোনো ডকুমেন্টেশন iOS-কে চারটি স্তরে বর্ণনা করত (এখন এই ভাগ কম ব্যবহৃত হয়, কিন্তু পরীক্ষায় ও বইয়ে প্রায়ই আসে):

স্তরকী দেয়উদাহরণ
Cocoa TouchUI, স্পর্শ, notification, অ্যাপের জীবনচক্রUIKit (এখন SwiftUI-ও)
Mediaগ্রাফিক্স, অডিও, ভিডিওCore Animation, AVFoundation, Metal
Core Servicesমৌলিক সেবা — ডেটা, নেটওয়ার্ক, থ্রেডFoundation, Core Data, Grand Central Dispatch
Core OSkernel আর নিচের স্তরXNU, নিরাপত্তা, driver, ফাইল সিস্টেম (APFS)

অ্যাপের অবস্থা আর পেছনের কাজের সীমা

Apple-এর ডকুমেন্টেশনে একটি iOS অ্যাপের পাঁচটি অবস্থা: Not running (চলছে না), Inactive (সামনে, কিন্তু ঘটনা নিচ্ছে না — যেমন ফোন-কল আসার মুহূর্তে), Active (সামনে, স্বাভাবিক কাজ), Background (পেছনে, কিন্তু কোড চলছে) আর Suspended (মেমরিতে আছে, কিন্তু একটি নির্দেশও চলছে না)। হোম-এ গেলে অ্যাপ সাধারণত অল্প সময় Background-এ থেকে Suspended হয় — সব thread থামানো, তাই CPU বা ব্যাটারি খরচ নেই।

পেছনে কাজ চালাতে অ্যাপকে নির্দিষ্ট পথ নিতে হয়: চলতি কাজ শেষ করতে অল্প সময় চেয়ে নেওয়া, গান বাজানো, নেভিগেশন বা VoIP কলের মতো ঘোষিত background mode, OS-এর কাছে ডাউনলোড ছেড়ে দেওয়া (background URL session — OS নিজে ডাউনলোড করে, অ্যাপ ঘুমিয়ে থাকে), আর iOS 13 থেকে BackgroundTasks framework — অ্যাপ বলে “আমার একটি রিফ্রেশ দরকার”, কিন্তু কখন চলবে তা ঠিক করে OS, ব্যাটারি, নেটওয়ার্ক আর ব্যবহারকারীর অভ্যাস দেখে। Android-এর তুলনায় iOS এখানে বেশি কড়া।

Jetsam — swap ছাড়া মেমরি ব্যবস্থাপনা

পর্ব ৩০-এ উল্লেখ করা হয়েছে, iOS ক্লাসিক অর্থে অ্যাপের মেমরি flash-এ swap করে না (কিছু নতুন iPad-এ iPadOS 16 থেকে সীমিত swap এসেছে বলে জানা যায়)। RAM কমলে iOS ধাপে ধাপে এগোয়: প্রথমে কম ব্যবহৃত page চেপে ছোট করে রাখে (memory compression — macOS-এও OS X 10.9 থেকে আছে), অ্যাপকে memory warning পাঠায়, আর তাতেও না কুলালে Jetsam অগ্রাধিকারের ক্রমে প্রসেস বন্ধ করে — আগে Suspended আর পেছনের অ্যাপ, শেষে সামনের অ্যাপ। এছাড়া প্রতিটি অ্যাপের একটি সর্বোচ্চ মেমরির সীমা থাকে; সামনের অ্যাপও সেটি পার হলে Jetsam তাকে বন্ধ করে। ধারণা lmkd-এর মতোই: swap-এর বদলে কম দামি প্রসেস বন্ধ. Jetsam-এর ভেতরের সব খুঁটিনাটি Apple প্রকাশ করে না, তাই এই বিবরণ সরলীকৃত।

বিষয়AndroidiOS
KernelLinux (monolithic + module)XNU (Mach + BSD, hybrid)
অ্যাপের কোড চালায়ART (DEX bytecode, JIT + AOT); NDK-তে nativeসরাসরি native ARM কোড (Swift/Objective-C)
অ্যাপ প্রসেস তৈরিZygote-এর forklaunchd নতুন প্রসেস চালায় (fork-ধাঁচের preload নেই)
পেছনের অ্যাপcached, কিছু কাজের অনুমতি; Doze ও standby-র সীমাসাধারণত Suspended; নির্দিষ্ট background mode
RAM ফুরোলেzram, তারপর lmkd (PSI + adj)memory compression, warning, তারপর Jetsam
IPCBinderMach port, XPC

৬ · ব্যাটারির হিসাব — DVFS, governor, big.LITTLE, Doze আর wake lock

CMOS চিপের ট্রানজিস্টর প্রতিবার ০ থেকে ১-এ বদলালে একটু বিদ্যুৎ খায়। এই dynamic power-এর বহুল ব্যবহৃত মডেল:

Pdyn ≈ α · C · V² · f

এখানে C = চিপের সুইচ হওয়া মোট ধারকত্ব (capacitance), V = সরবরাহ ভোল্টেজ, f = clock frequency, আর α = প্রতি চক্রে কত অংশ ট্রানজিস্টর আসলে বদলায় (নিচে α-কে C-এর ভেতরে ধরে নেওয়া হলো)। এর পাশে আছে leakage power — চিপ চালু থাকলেই কিছু বিদ্যুৎ চুইয়ে যায়, কাজ না করলেও। মূল কৌশলটি হলো DVFS (dynamic voltage and frequency scaling): কাজ কম থাকলে frequency কমানো, আর তার সাথে ভোল্টেজও — কারণ কম frequency-তে ট্রানজিস্টর কম ভোল্টেজেও নির্ভরযোগ্যভাবে চলে। প্রতিটি চিপের কয়েকটি নির্দিষ্ট (frequency, voltage) জোড়া থাকে, যাদের বলে operating point.

কাজ-প্রতি শক্তির সূত্র — একটি ছোট প্রমাণ

একটি কাজে N টি CPU-চক্র লাগে। f frequency-তে সময় t = N / f. তাহলে dynamic শক্তি:

Edyn = Pdyn · t = (C · V² · f) · (N / f) = C · V² · N

f বাদ পড়ে গেল! অর্থাৎ একটি নির্দিষ্ট কাজের dynamic শক্তি frequency-র ওপর নির্ভর করে না, নির্ভর করে শুধু V²-এর ওপর। শুধু frequency কমালে (ভোল্টেজ একই রেখে) power কমে, কিন্তু কাজটি দীর্ঘ হয় — শক্তি একই থাকে, আর সেই দীর্ঘ সময়ে leakage আরও বেশি খায়। লাভ আসে শুধু তখনই, যখন কম frequency ভোল্টেজও কমাতে দেয়।

হাতে-কলমে হিসাব

প্রশ্ন: একটি core ২.০ GHz, ১.০ V-এ চলে। frequency অর্ধেক (১.০ GHz) করলে ভোল্টেজ ০.৮ V-এ নামানো যায়। dynamic power আর একটি কাজের dynamic শক্তির অনুপাত কত? ভোল্টেজ না কমালে কী হতো?

সমাধান: Power-এর অনুপাত = (V₂/V₁)² × (f₂/f₁) = (০.৮/১.০)² × (১/২) = ০.৬৪ × ০.৫ = ০.৩২ — power কমল ৬৮%. সময় হলো দ্বিগুণ। তাই শক্তির অনুপাত = ০.৩২ × ২ = ০.৬৪ (= (০.৮)², সূত্রের সাথে মিলে গেল) — dynamic শক্তি কমল ৩৬%. ভোল্টেজ ১.০ V রাখলে power-এর অনুপাত ০.৫, কিন্তু শক্তির অনুপাত ০.৫ × ২ = ১.০ — কোনো সাশ্রয় নেই, উল্টো leakage-এর জন্য বেশি খরচ।

Race-to-idle, নাকি ধীরে-স্থিরে?

উপরের সূত্র বলে ধীরে চালানো ভালো। কিন্তু চলার সময় মেমরি, bus আর অন্য অংশও জেগে থাকে, যাদের খরচ সময়ের সাথে বাড়ে। তখন উল্টো কৌশল জিততে পারে: race-to-idle — সর্বোচ্চ গতিতে কাজ শেষ করে পুরো চিপকে গভীর ঘুমে পাঠানো। কোনটি জেতে, তা সংখ্যার ওপর নির্ভর করে। নিচের ক্যালকুলেটরে একটি খেলনা মডেল (অনুমানগুলো কোডের মন্তব্যে লেখা) দিয়ে দুটি পরিস্থিতি তুলনা করুন।

// DVFS energy for ONE task of 2e9 CPU cycles that must finish within a 2 s window.
// Assumptions (toy numbers): P_dyn = C*V^2*f with C = 1 nF; leakage = 0.25 W * V
// while running; "rest of chip" (memory, buses) costs plat watts while the CPU runs;
// after the task the CPU sleeps at 0.02 W until the window ends.
const C = 1e-9, CYCLES = 2e9, WINDOW = 2.0, SLEEP = 0.02;
const points = [[2.0, 1.00], [1.5, 0.90], [1.0, 0.80], [1.0, 1.00]];   // [GHz, volts]
function energy(ghz, v, plat) {
  const f = ghz * 1e9, t = CYCLES / f;                  // run time in seconds
  const pDyn = C * v * v * f, pRun = pDyn + 0.25 * v + plat;
  return { t, pDyn, e: pRun * t + SLEEP * (WINDOW - t) };
}
const fx = (x, d) => x.toFixed(d);
const base = energy(2.0, 1.0, 0);
for (const plat of [0.1, 0.8]) {
  console.log("rest-of-chip power while running = " + plat + " W");
  for (const [g, v] of points) {
    const r = energy(g, v, plat);
    console.log("  " + fx(g, 1) + " GHz @ " + fx(v, 2) + " V: run " + fx(r.t, 2) + " s, P_dyn " +
      fx(r.pDyn, 3) + " W (x" + fx(r.pDyn / base.pDyn, 2) + "), total energy " + fx(r.e, 3) + " J");
  }
}

আউটপুট (নির্ধারিত, প্রতিবার একই):

rest-of-chip power while running = 0.1 W
  2.0 GHz @ 1.00 V: run 1.00 s, P_dyn 2.000 W (x1.00), total energy 2.370 J
  1.5 GHz @ 0.90 V: run 1.33 s, P_dyn 1.215 W (x0.61), total energy 2.067 J
  1.0 GHz @ 0.80 V: run 2.00 s, P_dyn 0.640 W (x0.32), total energy 1.880 J
  1.0 GHz @ 1.00 V: run 2.00 s, P_dyn 1.000 W (x0.50), total energy 2.700 J
rest-of-chip power while running = 0.8 W
  2.0 GHz @ 1.00 V: run 1.00 s, P_dyn 2.000 W (x1.00), total energy 3.070 J
  1.5 GHz @ 0.90 V: run 1.33 s, P_dyn 1.215 W (x0.61), total energy 3.000 J
  1.0 GHz @ 0.80 V: run 2.00 s, P_dyn 0.640 W (x0.32), total energy 3.280 J
  1.0 GHz @ 1.00 V: run 2.00 s, P_dyn 1.000 W (x0.50), total energy 4.100 J

তিনটি শিক্ষা। এক, x0.32 — উপরের হাতের হিসাবের অনুপাতই। দুই, বাকি চিপের খরচ কম (০.১ W) হলে ধীরে-স্থিরে (১.০ GHz @ ০.৮ V, ১.৮৮ J) সবচেয়ে ভালো; কিন্তু সেটি ০.৮ W হলে দ্রুত শেষ করা (৩.০৭ J) ধীরেরটিকে (৩.২৮ J) হারায়, আর সেরা হয় মাঝের ১.৫ GHz (৩.০০ J). তিন, ভোল্টেজ না কমিয়ে শুধু frequency কমানো (শেষ লাইন) সব ক্ষেত্রে সবচেয়ে খারাপ। সংখ্যাগুলো খেলনা; বাস্তব মান আসে মাপা পরীক্ষা থেকে।

কে frequency বাছে — governor আর schedutil

Linux-এ DVFS-এর ব্যবস্থা cpufreq, আর কখন কোন frequency, তা ঠিক করে একটি নীতি — governor: performance (সবসময় সর্বোচ্চ), powersave (সর্বনিম্ন), ondemand (নিয়মিত বিরতিতে CPU-র ব্যস্ততা মেপে), আর আধুনিক Android ফোনে সাধারণত schedutil (Linux 4.7, ২০১৬ থেকে), যা সরাসরি scheduler-এর হিসাব করা প্রতিটি task-এর ব্যবহার (utilization) দেখে সিদ্ধান্ত নেয়। নিজের Linux ল্যাপটপে দেখতে পারেন (উদাহরণ; মান যন্ত্র আর driver-ভেদে আলাদা, আর Intel-এর intel_pstate-এ governor-এর তালিকা ছোট):

$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
conservative ondemand userspace powersave performance schedutil
$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
schedutil
$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq     # kHz
1804800

big.LITTLE: ২০১১ সালে ARM এই নকশা ঘোষণা করে — একই চিপে কয়েকটি বড়, দ্রুত কিন্তু বিদ্যুৎ-খেকো core আর কয়েকটি ছোট, ধীর কিন্তু সাশ্রয়ী core. পর্ব ২০-এ heterogeneous multiprocessing হিসেবে দেখেছেন। পেছনের সিঙ্ক ছোট core-এ, গেম বড় core-এ। Linux-এর Energy Aware Scheduling প্রতিটি core-এর শক্তি-মডেল দেখে task বসায়। ঠিক উপরের হিসাবের কারণে: ছোট core-এর C আর V দুটোই কম।

Doze, App Standby আর wake lock — অ্যাপকে ঘুম পাড়ানো

সবচেয়ে বড় শক্তির অপচয় প্রায়ই CPU-র গতি নয়, অকারণে জেগে থাকা। কয়েক বাইট পাঠাতেও মোবাইল রেডিও কয়েক সেকেন্ড উঁচু শক্তিতে থাকে; ২০টি অ্যাপ আলাদা সময়ে প্রতি কয়েক মিনিটে সার্ভারে খবর নিলে ফোন সারারাত প্রায় জেগেই থাকে। Android 6.0 এর জবাবে আনে Doze: ফোন চার্জে নেই, পর্দা বন্ধ আর নড়াচড়া নেই — কিছুক্ষণ এভাবে থাকলে OS অ্যাপের নেটওয়ার্ক, wake lock আর alarm স্থগিত রাখে, আর মাঝে মাঝে একটি ছোট “maintenance window”-তে সবার জমা কাজ একসাথে চালায়। পাশাপাশি App Standby: যে অ্যাপ ব্যবহারকারী অনেকদিন খোলেননি, তার পেছনের কাজ সীমিত; পরের সংস্করণে ব্যবহারের অভ্যাস দেখে অ্যাপকে কয়েকটি “bucket”-এ ভাগ করা হয়। জরুরি বার্তা আসে push notification-এর একটি সাধারণ চ্যানেলে। (নিয়ম সংস্করণভেদে বদলেছে; ডেভেলপাররা adb shell dumpsys deviceidle force-idle দিয়ে Doze জোর করে চালু করে অ্যাপ পরীক্ষা করেন।)

খারাপ অ্যাপ কীভাবে ব্যাটারি শেষ করে: একটি অ্যাপ partial wake lock নিল (“পর্দা বন্ধ থাকুক, কিন্তু CPU ঘুমাবে না”) আর কোনো বাগে ছাড়ল না — ফোন সারারাত suspend-এ যেতে পারল না; সকালে ব্যাটারি অর্ধেক। প্রতিকার: wake lock-এ সবসময় সময়সীমা দিন আর finally-তে ছাড়ুন; নিয়মিত পেছনের কাজ OS-এর scheduler-এ (Android-এ WorkManager, iOS-এ BackgroundTasks) দিন; আর Battery settings ও adb shell dumpsys batterystats দিয়ে কোন অ্যাপ জাগিয়ে রাখছে তা যাচাই করুন।

৭ · Embedded জগৎ — bare-metal superloop থেকে RTOS আর embedded Linux

সব যন্ত্রে OS লাগে না। Embedded সফটওয়্যারের একটি বর্ণালী আছে, আর কোনটি বাছবেন তা নির্ভর করে কাজের জটিলতা, deadline আর হার্ডওয়্যারের ওপর:

ধরনসাধারণ হার্ডওয়্যারমেমরি (মোটামুটি)উদাহরণ
Bare-metal superloopছোট microcontrollerকয়েক KB থেকে কয়েক দশ KBরিমোট কন্ট্রোল, সাধারণ খেলনা, ডিজিটাল থার্মোমিটার
RTOSmicrocontroller (যেমন ARM Cortex-M)দশ থেকে কয়েকশো KBড্রোনের flight controller, চিকিৎসা-যন্ত্র, শিল্প-নিয়ন্ত্রক, স্মার্টওয়াচ
Embedded LinuxMMU-সহ application processorদশ MB থেকে GBWi-Fi রাউটার, স্মার্ট টিভি, গাড়ির infotainment, শিল্প-গেটওয়ে

Superloop: কোনো OS নেই — main()-এর ভেতর একটি অসীম লুপ, যা পালাক্রমে প্রতিটি কাজের ফাংশন ডাকে। Interrupt এলে ছোট একটি ISR শুধু একটি পতাকা (flag) তোলে; লুপ ঘুরে সেই পতাকা দেখলে কাজটি করে। সহজ, ছোট, বোঝা যায় — কিন্তু সমস্যা: কোনো ফাংশন মাঝপথে থামানো যায় না। ধীর ফাংশন (পর্দা আঁকা, flash-এ লগ লেখা) চলার সময় জরুরি পতাকা পড়ে থাকে।

volatile bool urgent_flag = false;          /* set by the ISR, read by the loop */

void BUTTON_IRQHandler(void) { urgent_flag = true; clear_irq(); }   /* tiny ISR */

int main(void) {
    hardware_init();
    for (;;) {                               /* the superloop */
        if (sensor_due()) read_sensor();     /* 2 ms */
        if (urgent_flag) { urgent_flag = false; stop_motor(); }   /* 1 ms */
        update_display();                    /* 4 ms, cannot be interrupted by tasks */
        write_log();                         /* 3 ms */
    }
}

সবচেয়ে খারাপ ক্ষেত্রে কী হয়? পতাকা উঠল ঠিক যাচাইয়ের পরপরই। তখন অপেক্ষা: display ৪ + log ৩ + (পরের ঘোরার) sensor ২ + নিজের কাজ ১ = ১০ ms. সাধারণভাবে superloop-এ একটি ঘটনার worst-case response ≈ পুরো লুপের দৈর্ঘ্য (নিজের কাজসহ) — আর লুপে নতুন ফাংশন যোগ করলে প্রত্যেকের response বদলে যায়। এখন একই কাজ একটি preemptive RTOS-এ: priority urgent (৩) > sensor (২) > display/log (১)। ISR একটি semaphore দেয়, আর scheduler সাথে সাথে urgent task-কে চালায় — display মাঝপথে থামিয়ে। নিচের simulator দুটোকে একই ঘটনা-ক্রমে ১০০ ms চালায় (context switch আর ISR-এর খরচ শূন্য ধরা)।

// Superloop vs preemptive RTOS. 1 tick = 1 ms, 100 ms simulated, no overheads.
// sensor: every 10 ms, needs 2 ms | urgent IRQ work: 1 ms | display 4 ms, log 3 ms
const H = 100, irqAt = [3, 31, 48, 88];                        // sporadic interrupts
const sensorAt = Array.from({ length: 10 }, (_, k) => 10 * k);  // 0, 10, ..., 90
function superloop() {            // one loop: check sensor, check flag, display, log
  let t = 0, s = 0, i = 0; const rs = [], ri = [];
  while (t < H) {
    if (s < sensorAt.length && sensorAt[s] <= t) { t += 2; rs.push(t - sensorAt[s++]); }
    if (i < irqAt.length && irqAt[i] <= t) { t += 1; ri.push(t - irqAt[i++]); }  // ISR only set a flag
    t += 4 + 3;                                                 // display + log, never interrupted
  }
  return [rs, ri];
}
function rtos() {                 // priorities: urgent 3 > sensor 2 > display/log 1
  const rs = [], ri = [], sq = [], iq = [];                    // pending jobs [release, left]
  for (let t = 0; t < H; t++) {
    if (sensorAt.includes(t)) sq.push([t, 2]);
    if (irqAt.includes(t)) iq.push([t, 1]);                    // ISR gives a semaphore
    const q = iq.length ? iq : sq.length ? sq : null;          // highest ready task runs
    if (q && --q[0][1] === 0) (q === iq ? ri : rs).push(t + 1 - q.shift()[0]);
  }                                                             // otherwise background runs
  return [rs, ri];
}
for (const [name, [rs, ri]] of [["superloop", superloop()], ["RTOS     ", rtos()]]) {
  console.log(name + " sensor: " + rs.join(" ") + "  -> worst " + Math.max(...rs) + " ms");
  console.log(name + " urgent: " + ri.join(" ") + "  -> worst " + Math.max(...ri) + " ms");
}

আউটপুট (নির্ধারিত, প্রতিবার একই):

superloop sensor: 2 9 8 7 7 6 6 5 4 3  -> worst 9 ms
superloop urgent: 7 7 9 6  -> worst 9 ms
RTOS      sensor: 2 2 2 3 2 2 2 2 2 2  -> worst 3 ms
RTOS      urgent: 1 1 1 1  -> worst 1 ms

Superloop-এ জরুরি কাজের response ৬ থেকে ৯ ms — কখন interrupt এল তার ওপর নির্ভর করে, আর এই ঘটনা-ক্রমে সবচেয়ে খারাপ ৯ ms (তাত্ত্বিক সীমা ১০ ms, উপরে হিসাব করা)। Sensor-এর response-ও ২ থেকে ৯ ms-এ দোলে, কারণ লুপের দৈর্ঘ্য (৭–১০ ms, কোন কাজ পড়ল তার ওপর নির্ভর করে) আর sensor-এর period (১০ ms) মেলে না — এই অনিয়মিত দেরিকে বলে jitter. RTOS-এ urgent সবসময় ১ ms — শুধু নিজের কাজের সময়। Sensor সাধারণত ২ ms, শুধু একবার ৩ ms: t = ৩০-এ sensor চলছিল, t = ৩১-এ urgent এসে তাকে ১ ms সরিয়ে রাখল। পর্ব ২০-এর response-time analysis দিয়ে এটি প্রমাণও করা যায়: urgent কমপক্ষে ১৫ ms পরপর এলে sensor-এর R = ২ + ⌈R/১৫⌉ × ১ ⇒ R₀ = ২ → R₁ = ৩ → R₂ = ২ + ⌈৩/১৫⌉ × ১ = ৩, স্থির — sensor-এর worst-case ৩ ms, simulator-এর সাথে মিলে গেল। এটিই RTOS-এর আসল দান: নতুন নিম্ন-অগ্রাধিকারের কাজ যোগ করলে উঁচু কাজের সময় বদলায় না।

পরিচিত কয়েকটি RTOS (সংক্ষেপে; মালিকানা ও লাইসেন্স সময়ের সাথে বদলেছে): FreeRTOS — Richard Barry-র শুরু করা ছোট, জনপ্রিয় kernel, ২০১৭ থেকে Amazon-এর তত্ত্বাবধানে, MIT লাইসেন্সে; Zephyr — Linux Foundation-এর open-source প্রকল্প (২০১৬ থেকে); VxWorks — Wind River-এর বাণিজ্যিক RTOS, মহাকাশযান আর বিমানে বহুল ব্যবহৃত (পর্ব ২৯-এর Mars Pathfinder-এর গল্প মনে করুন); QNX — একটি POSIX-ধাঁচের microkernel RTOS, ২০১০ থেকে BlackBerry-র, গাড়ির সফটওয়্যারে বিশেষভাবে পরিচিত; RTEMS — open-source, মূলত মহাকাশ ও বৈজ্ঞানিক যন্ত্রে।

Embedded Linux: MMU আর কয়েক দশ MB RAM থাকলে পুরো Linux চালানো যায় — নেটওয়ার্ক, ফাইল সিস্টেম, হাজারো driver তৈরি। তবে যন্ত্রের মাপে ছেঁটে বানানো image: Yocto Project (শিল্প-মানের build কাঠামো) বা Buildroot (সহজ, ছোট image). অনেক Wi-Fi রাউটারের ভেতরে Linux; OpenWrt সমর্থিত রাউটারে নির্মাতার পুরোনো firmware-এর বদলে হালনাগাদ open-source Linux চালাতে দেয়। সাধারণ Linux hard real-time নয়, তবে PREEMPT_RT প্যাচ (Linux 6.12 থেকে মূল kernel-এ যুক্ত বলে জানা যায়) দিয়ে latency অনেক কমানো যায়; অনেক যন্ত্রে দুটি মিলিয়ে নকশা হয় — Linux UI আর নেটওয়ার্ক সামলায়, পাশের একটি ছোট microcontroller-এ RTOS কঠিন deadline রক্ষা করে।

৮ · RTOS-এর ভেতর — tick, priority, নির্ধারিত latency আর ISR থেকে task-এ কাজ

একটি RTOS kernel সাধারণত কয়েক থেকে দশ KB-র — Linux-এর তুলনায় হাজার গুণ ছোট (memory footprint). এর মূল উপাদান:

  • Fixed-priority preemptive scheduler: প্রতিটি task-এর অগ্রাধিকার ডেভেলপার আগেই ঠিক করেন (প্রায়ই পর্ব ২০-এর Rate Monotonic নিয়মে)। নিয়ম একটাই: ready task-দের মধ্যে সর্বোচ্চ অগ্রাধিকারেরটি চলে (সমানে ঐচ্ছিকভাবে round robin চলে)। CFS-এর মতো “ন্যায্যতা” নেই — নিচু task না-ও চলতে পারে, আর সেটাই ইচ্ছাকৃত।
  • নির্ধারিত latency: scheduler-এর প্রতিটি কাজ (পরের task বাছা, ready তালিকায় যোগ) সীমিত, আগে থেকে জানা সময়ে শেষ হয় — যেমন প্রতিটি অগ্রাধিকারের আলাদা তালিকা আর একটি bitmap, যাতে সর্বোচ্চ ready task খুঁজতে কয়েকটি CPU নির্দেশই যথেষ্ট। এর নাম deterministic latency: গড়ে দ্রুত নয়, সবসময় একটি সীমার মধ্যে। তাই RTOS-এ dynamic memory (malloc) প্রায়ই এড়ানো হয় বা শুধু শুরুতে করা হয়।
  • Tick আর tickless: একটি hardware timer নিয়মিত (যেমন প্রতি ১ ms) interrupt দেয় — system tick; kernel এতে সময় গোনে, delay শেষ হওয়া task জাগায়। কাজ না থাকলেও প্রতি ms-এ জাগা ব্যাটারির শত্রু, তাই tickless মোড: অলস হলে kernel হিসাব করে পরের task কখন জাগবে, timer সেই সময়ের জন্য বসিয়ে CPU-কে গভীর ঘুমে পাঠায় (FreeRTOS-এ configUSE_TICKLESS_IDLE; Linux-এও ২০০৭ সাল থেকে অলস CPU-তে tick বন্ধ রাখার ব্যবস্থা আছে)।
  • Task-এর মধ্যে যোগাযোগ: queue (কপি করে বার্তা পাঠানো), semaphore (ঘটনার সংকেত), আর mutex — যাতে থাকে priority inheritance, পর্ব ২৯-এর priority inversion-এর প্রতিষেধক।

নিচে FreeRTOS-এর আসল API দিয়ে লেখা একটি ছোট নকশা (চালানো যাবে না — একটি microcontroller বোর্ড আর FreeRTOS লাগে; ফাংশনের নাম আর argument FreeRTOS-এর ডকুমেন্টেশন অনুযায়ী, read_sensor_i2c-এর মতো হার্ডওয়্যার-ফাংশন কাল্পনিক)। FreeRTOS-এ সংখ্যা বড় মানে অগ্রাধিকার বেশি, আর ০ হলো idle task-এর।

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

static QueueHandle_t     xReadings;   /* sensor -> logger, 32 items        */
static SemaphoreHandle_t xBusMutex;   /* shared I2C bus, has priority inheritance */

static void vSensorTask(void *pv) {
    TickType_t xLast = xTaskGetTickCount();
    for (;;) {
        xSemaphoreTake(xBusMutex, portMAX_DELAY);
        int16_t v = read_sensor_i2c();
        xSemaphoreGive(xBusMutex);
        xQueueSend(xReadings, &v, 0);                  /* do not block if full */
        vTaskDelayUntil(&xLast, pdMS_TO_TICKS(10));    /* exact 10 ms period, no drift */
    }
}
static void vLoggerTask(void *pv) {
    int16_t v;
    for (;;)
        if (xQueueReceive(xReadings, &v, portMAX_DELAY) == pdPASS)
            write_to_flash(v);                          /* slow, low priority */
}
int main(void) {
    hardware_init();
    xReadings = xQueueCreate(32, sizeof(int16_t));
    xBusMutex = xSemaphoreCreateMutex();
    xTaskCreate(vSensorTask, "sensor", 256, NULL, 3, NULL);   /* stack in words, prio 3 */
    xTaskCreate(vLoggerTask, "logger", 512, NULL, 1, NULL);
    vTaskStartScheduler();                                    /* never returns normally */
    for (;;) {}
}

দুটি খুঁটিনাটি লক্ষ করুন। vTaskDelay(10) মানে এখন থেকে ১০ tick ঘুম — কাজের সময় যোগ হয়ে period ধীরে ধীরে সরে যায়; vTaskDelayUntil মানে শেষবার জাগার ঠিক ১০ ms পরে জাগা — নিয়মিত period. আর logger-এর portMAX_DELAY মানে ডেটা না আসা পর্যন্ত task blocked — CPU একটুও খায় না, idle task চলে, আর tickless মোডে চিপ ঘুমায়।

Interrupt latency আর ISR থেকে কাজ পাঠানো

পর্ব ৩৯-এ দেখেছেন, Linux interrupt-এর কাজ ভাগ করে: জরুরি অংশ (top half) সাথে সাথে, ভারী অংশ (bottom half) পরে। RTOS-এ একই নীতি আরও কড়া — deferred interrupt handling: ISR-এ শুধু হার্ডওয়্যার থেকে ডেটা নেওয়া আর একটি task-কে সংকেত দেওয়া; বাকি সব কাজ সেই task-এ, যার একটি স্পষ্ট অগ্রাধিকার আছে। কারণ ISR চলার সময় সমান বা নিচু interrupt আটকে থাকে, আর লম্বা ISR সবার interrupt latency বাড়ায় — hardware সংকেত দেওয়া থেকে handler শুরু হওয়া পর্যন্ত সময়, যা RTOS-এর বিজ্ঞাপনের মূল সংখ্যাগুলোর একটি। ISR-এর ভেতর থেকে শুধু ...FromISR নামের বিশেষ API ডাকা যায়, কারণ ISR কখনো block হতে পারে না।

1void vLogTask(void *p) {          /* prio 1 */2  for (;;) write_log_chunk();3}4void ADC_IRQHandler(void) {        /* ISR */5  BaseType_t woken = pdFALSE;6  xSemaphoreGiveFromISR(xSem, &woken);7  portYIELD_FROM_ISR(woken);8}9void vUrgentTask(void *p) {       /* prio 3 */10  for (;;) { xSemaphoreTake(xSem, portMAX_DELAY);11    stop_motor(); }12}
Scheduler-এর অবস্থা
এখন CPU-তে
Ready
Blocked
xSem
woken
  1. শান্ত অবস্থা। নিচু অগ্রাধিকারের Log task (১) লগ লিখছে। Urgent task (৩) আগে xSemaphoreTake ডেকে blocked — semaphore ০, তাই সে কোনো CPU খায় না।
  2. Interrupt. ADC (সেন্সর-রূপান্তরক) একটি interrupt দিল। হার্ডওয়্যার Log-এর register-গুলো stack-এ রেখে ISR-এ লাফ দিল। Log মাঝপথে থেমে আছে, কিন্তু এখনো Ready. এই দেরিই interrupt latency.
  3. Semaphore দেওয়া। ISR কোনো ভারী কাজ করে না — শুধু xSemaphoreGiveFromISR. xSem হলো ১, Urgent Blocked থেকে Ready-তে এল। যেহেতু ৩ > চলতে থাকা task-এর ১, তাই woken = pdTRUE.
  4. Switch চাওয়া। portYIELD_FROM_ISR(pdTRUE) scheduler-কে জানায়: ISR থেকে ফেরার সময় Log-এ নয়, সর্বোচ্চ ready task-এ ফেরা চাই। (ARM Cortex-M-এ এটি সাধারণত PendSV নামের একটি নিচু-অগ্রাধিকারের exception দিয়ে করা হয়।)
  5. Urgent চলে। ISR থেকে সরাসরি Urgent-এ ফেরা। তার xSemaphoreTake সফল (xSem আবার ০), stop_motor() চলল। Response = interrupt latency + ISR + একটি context switch + নিজের কাজ — Log কতটা লম্বা, তার ওপর নির্ভর করে না।
  6. আবার অপেক্ষা। Urgent লুপ ঘুরে আবার xSemaphoreTake-এ blocked. Scheduler Log-কে ঠিক যেখানে থেমেছিল সেখান থেকে চালায় — Log টেরও পায় না। এই নকশায় ISR ছোট, আর কাজটি পেল একটি স্পষ্ট অগ্রাধিকার, যা বিশ্লেষণ করা যায়।

৯ · নিরাপত্তা-সনদ, IoT আর আপডেট — যন্ত্র যখন বছরের পর বছর চলে

Functional safety-র মানদণ্ড: যেখানে সফটওয়্যারের ভুলে মানুষ আহত হতে পারেন, সেখানে নিয়ন্ত্রক সংস্থা প্রমাণ চায় যে নকশা, কোড, পরীক্ষা আর পরিবর্তন একটি নির্দিষ্ট প্রক্রিয়ায় হয়েছে, আর প্রতিটি প্রয়োজন থেকে কোড ও পরীক্ষা পর্যন্ত চিহ্ন অনুসরণ করা যায়। পরিচিত তিনটি (সংক্ষেপে):

  • DO-178C: বিমানের সফটওয়্যারের জন্য বহুল স্বীকৃত নির্দেশিকা (২০১১-এর দিকে প্রকাশিত, আগের DO-178B-র উত্তরসূরি)। ব্যর্থতার পরিণাম অনুযায়ী স্তর (Level A সবচেয়ে কঠোর), আর উঁচু স্তরে কোডের প্রতিটি শাখা পরীক্ষায় ছোঁয়ার প্রমাণ চায়।
  • ISO 26262: সড়ক-যানের বৈদ্যুতিক ও ইলেকট্রনিক ব্যবস্থার functional safety. ঝুঁকি মেপে ASIL A থেকে D (D সবচেয়ে কঠোর, যেমন ব্রেক বা স্টিয়ারিং)।
  • IEC 62304: চিকিৎসা-যন্ত্রের সফটওয়্যারের জীবনচক্রের মানদণ্ড; সম্ভাব্য ক্ষতি অনুযায়ী class A, B, C.

এখানে OS-এর আকার বড় প্রশ্ন: Linux-এর কয়েক কোটি লাইন এমন প্রমাণের আওতায় আনা প্রায় অসম্ভব, একটি ছোট RTOS kernel আনা যায়। তাই কয়েকটি বাণিজ্যিক RTOS (যেমন VxWorks, QNX, আর FreeRTOS-এর একটি নিরাপত্তা-সংস্করণ) এসব মানদণ্ডের জন্য প্রস্তুত “certification package” দেয় বলে বাজারজাত হয় — তবুও যন্ত্র-নির্মাতাকে নিজের পুরো সিস্টেমের প্রমাণ দিতে হয়।

IoT আর নিরাপত্তা-আপডেট: IoT (Internet of Things) মানে ইন্টারনেটে যুক্ত ছোট যন্ত্র — IP ক্যামেরা, স্মার্ট প্লাগ, রাউটার, শিল্প-সেন্সর। এরা প্রায়ই বছরের পর বছর একই firmware-এ চলে। ২০১৬ সালে Mirai নামের একটি ম্যালওয়্যার মূলত কারখানার ডিফল্ট পাসওয়ার্ড বদলানো হয়নি এমন লক্ষ লক্ষ ক্যামেরা আর রাউটার দখল করে বিশাল DDoS আক্রমণ চালায়। সুরক্ষা: প্রতিটি যন্ত্রের আলাদা পাসওয়ার্ড, অপ্রয়োজনীয় সেবা (Telnet ইত্যাদি) বন্ধ, স্বাক্ষর-যাচাই করা OTA আপডেট (পর্ব ৭ আর ৪৯-এর secure/verified boot-এর মতো, যাতে নকল firmware বসানো না যায়), আপডেট ব্যর্থ হলে আগের সংস্করণে ফেরার জন্য দুটি partition (A/B), আর নির্মাতার পক্ষ থেকে কত বছর প্যাচ দেওয়া হবে তার ঘোষণা। ব্যবহারকারী হিসেবে করণীয়: রাউটার ও ক্যামেরার পাসওয়ার্ড বদলানো, firmware হালনাগাদ রাখা, আর আপডেট থেমে যাওয়া যন্ত্রকে ইন্টারনেটে সরাসরি খোলা না রাখা।

আরও গভীরে — ঘুমানোও একটি scheduling-সিদ্ধান্ত। ARM Cortex-M-এ FreeRTOS context switch করে PendSV exception-এ, যাকে সবচেয়ে নিচু interrupt-অগ্রাধিকার দেওয়া হয়, যাতে কোনো ISR-এর মাঝে switch না ঘটে; আর যে ISR FreeRTOS-এর API ডাকে, তার অগ্রাধিকার configMAX_SYSCALL_INTERRUPT_PRIORITY-এর সীমার মধ্যে রাখতে হয় — Cortex-M-এ ছোট সংখ্যা মানে বেশি জরুরি, task-অগ্রাধিকারের উল্টো, যা নতুনদের খুব সাধারণ ভুল। Linux-এর NO_HZ_FULL একটিমাত্র task চলা CPU-তেও tick প্রায় বন্ধ রেখে jitter কমায়। Android-এ Zygote-এর একটি বিকল্প (USAP pool) আগেভাগে কয়েকটি “অবিশেষায়িত” প্রসেস fork করে রাখে, যাতে অ্যাপ চালুর সময় fork-এর খরচও বাঁচে — চালু থাকা সংস্করণ ও নির্মাতাভেদে আলাদা।

পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:

  • Android-এ Zygote কী আর কেন? — বুটের সময় runtime ও সাধারণ class preload করা একটি প্রসেস; প্রতিটি অ্যাপ তার fork, তাই চালু দ্রুত আর copy-on-write-এ মেমরি ভাগ হয়।
  • RTOS আর সাধারণ OS-এর মূল পার্থক্য? — RTOS সবচেয়ে খারাপ সময়ের সীমা (determinism) দেয়, fixed-priority preemptive scheduling, ছোট footprint; সাধারণ OS গড় throughput আর ন্যায্যতা খোঁজে।
  • DVFS-এ frequency অর্ধেক আর ভোল্টেজ ১.০ থেকে ০.৮ V করলে power কত? — ০.৮² × ০.৫ = ০.৩২ গুণ; কাজ-প্রতি dynamic শক্তি ০.৬৪ গুণ।
  • মোবাইলে swap কম কেন, RAM ফুরোলে কী হয়? — flash-এর আয়ু, শক্তি আর গতি; Android zram + lmkd, iOS memory compression + Jetsam — importance-এর ক্রমে পেছনের অ্যাপ বন্ধ।
  • ISR ছোট রাখা হয় কেন? — লম্বা ISR অন্য interrupt আটকে latency বাড়ায়; ভারী কাজ semaphore বা notification দিয়ে একটি task-এ পাঠানো হয় (deferred handling).

১০ · AI-এর যুগে মোবাইল আর embedded কাজ — যাচাই না করলে বিপদ

Android API, FreeRTOS-এর configuration বা ব্যাটারির হিসাব — AI দ্রুত উত্তর দেয়, কিন্তু এখানে ভুলের দাম বেশি: মোবাইলে লক্ষ ফোনের ব্যাটারি, embedded-এ মাঠে বছরের পর বছর চলা একটি যন্ত্র, যার আপডেট কঠিন। সাধারণ ভুলগুলো:

AI-এর সাধারণ উত্তরসমস্যাভালো পথ
পুরোনো Android API বা অনুমতি (যেমন ইনস্টলের সময় সব অনুমতি)নতুন সংস্করণে কাজ করে না বা Play-তে প্রত্যাখ্যাতtarget SDK অনুযায়ী অফিসিয়াল ডকুমেন্টেশন দেখা
পেছনের কাজের জন্য অসীম wake lock বা নিজের timerDoze ভাঙে বা OS বন্ধ করে দেয়; ব্যাটারি নষ্টWorkManager / BackgroundTasks, সময়সীমাসহ wake lock
Cortex-M-এ interrupt-অগ্রাধিকারের সংখ্যা উল্টো বোঝাISR থেকে FreeRTOS API ডেকে hard fault বা মাঝে মাঝে ক্র্যাশFreeRTOS-এর port-এর নির্দেশনা আর configASSERT চালু রাখা
ISR-এর ভেতরে printf, malloc বা blocking কলঅনির্ধারিত latency, deadlockISR-এ শুধু ...FromISR আর সংকেত; কাজ task-এ
“কম frequency মানেই কম শক্তি”ভোল্টেজ আর বাকি চিপের খরচ উপেক্ষাE = C·V²·N আর মাপা power দিয়ে হিসাব
Embedded Linux-এ সব কিছু root হিসেবে, chmod 777, Telnet চালুIoT-তে সরাসরি আক্রমণের পথআলাদা user, ন্যূনতম অনুমতি, SSH key, স্বাক্ষরিত আপডেট

AI-কে embedded বা মোবাইলের প্রশ্ন করার সময় যন্ত্র, সংস্করণ আর সীমা স্পষ্ট লিখুন, আর হিসাব ও worst-case যুক্তি চেয়ে নিন — শুধু কোড নয়:

আমি STM32 (ARM Cortex-M4) বোর্ডে FreeRTOS 10.x চালাচ্ছি। - একটি ADC interrupt প্রতি 1 ms-এ আসে; একটি motor-stop কাজ interrupt-এর 200 µs-এর মধ্যে শেষ হতেই হবে। - আরও আছে: 10 ms period-এর sensor task, আর flash-এ লগ লেখার একটি ধীর task. ১. কোন task-এর কোন অগ্রাধিকার হবে আর কেন, তা Rate Monotonic যুক্তি দিয়ে বলো। ২. ISR-এ কী থাকবে আর কী task-এ যাবে — xSemaphoreGiveFromISR / task notification দিয়ে কোড দাও। ৩. Cortex-M-এর interrupt priority আর configMAX_SYSCALL_INTERRUPT_PRIORITY কীভাবে বসাব, সংখ্যার দিক (ছোট = জরুরি) সহ। ৪. worst-case response-এর হিসাবটি ধাপে ধাপে দেখাও, আর কোন অনুমান তুমি নিয়েছ তা আলাদা করে লেখো। ৫. ISR-এ printf, malloc বা blocking কল রাখবে না।

AI-এর মোবাইল/embedded পরামর্শ প্রয়োগের আগে — চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১Android Linux kernel নিল, কিন্তু wakelock-এর মতো নিজের পরিবর্তন মূল Linux-এ ঢোকাতে বছরের পর বছর লাগল কেন?

Android-এর দল ২০০০-এর দশকের মাঝামাঝি Linux kernel বাছে প্রস্তুত driver, প্রসেস-ব্যবস্থাপনা, নেটওয়ার্ক stack আর নিরাপত্তা-মডেলের জন্য। কিন্তু ফোনের প্রয়োজন ছিল ডেস্কটপের চেয়ে আলাদা, তাই তারা নিজের kernel-এ কয়েকটি বড় পরিবর্তন করে: Binder, ashmem, low memory killer, আর wakelock. প্রথম Android ফোন (২০০৮) এসব নিয়েই বাজারে যায় — কিন্তু মূল Linux-এ এগুলো ছিল না।

সমস্যা হলো, মূল Linux-এ কোড ঢোকানোর নিয়ম কঠিন: নকশা সবার জন্য সাধারণ হতে হবে, পুরোনো ব্যবস্থার সাথে মিলতে হবে, আর কেউ বছরের পর বছর রক্ষণাবেক্ষণের দায়িত্ব নেবেন। Wakelock নিয়ে আপত্তি ছিল যে এটি power management-এর একটি পুরো নতুন দর্শন — যেকোনো অ্যাপ বা driver suspend আটকাতে পারে — যা মূল kernel-এর বিদ্যমান ব্যবস্থার সাথে মেলে না। কিছু সময়ের জন্য Android-এর driver-গুলো kernel-এর staging এলাকা থেকে সরিয়েও দেওয়া হয়েছিল, কারণ রক্ষণাবেক্ষণ হচ্ছিল না। পরে উভয় পক্ষ কাছাকাছি আসে: বেশ কিছু Android driver আবার যুক্ত হয়, আর wakelock-এর প্রয়োজন মেটাতে মূল kernel-এ নিজস্ব নকশায় “wakeup source” আর autosleep আসে। Binder-ও শেষে মূল kernel-এর অংশ হয়। পুরো প্রক্রিয়া প্রায় কয়েক বছর ধরে চলেছে।

এই বিচ্ছিন্নতার দাম দিয়েছেন ব্যবহারকারীরা। নির্মাতা আর চিপ-কোম্পানিরা Google-এর kernel-এর ওপর নিজেদের পরিবর্তন জুড়তেন, ফলে ফোনের kernel মূল Linux থেকে অনেক দূরে, আর নিরাপত্তা-প্যাচ পৌঁছাতে মাসের পর মাস। Project Treble আর GKI মূলত এই ইতিহাসেরই প্রতিকার: সাধারণ অংশ এক জায়গায় রাখা, নির্মাতার অংশ একটি স্থির interface-এর পেছনে আলাদা করা।

মূল উপলব্ধি: একটি বড় open-source প্রকল্পের ওপর নিজের পণ্য বানালে নিজের পরিবর্তন মূল প্রকল্পে ফেরত দেওয়া (upstream) শুধু ভদ্রতা নয়, দীর্ঘমেয়াদি রক্ষণাবেক্ষণের অর্থনীতি। দেরি যত বাড়ে, ফাঁক তত বড় হয়, আর আপডেট তত ধীর।

প্র ০২Dalvik-এর JIT থেকে ART-এর AOT, তারপর আবার মিশ্র পথ — Android কেন বারবার মত বদলাল?

প্রথম Android ফোনগুলোতে RAM ছিল খুব কম, প্রসেসরও ধীর। Dalvik ছিল একটি register-ভিত্তিক bytecode interpreter, যা কম মেমরিতে চলার জন্য নকশা করা; Android 2.2-তে এতে JIT যুক্ত হয়, যা ঘন ঘন চলা কোডের ছোট অংশ চলার সময় machine code-এ রূপান্তর করত। JIT-এর সুবিধা: শুধু যা লাগে তা রূপান্তর, storage কম। অসুবিধা: প্রতিবার অ্যাপ চালু হলে আবার “গরম” হতে সময় লাগে, আর রূপান্তরের কাজটাই চলার সময় CPU ও ব্যাটারি খায়।

ART AOT দিয়ে উল্টো পথে গেল: ইনস্টলের সময়ই পুরো অ্যাপ machine code. চালু দ্রুত, চলা মসৃণ, garbage collector-ও উন্নত। কিন্তু নতুন সমস্যা দেখা দিল — বড় অ্যাপ ইনস্টলে অনেক সময়, রূপান্তরিত কোড storage-এ অনেক জায়গা নেয়, আর বড় সিস্টেম-আপডেটের পর ফোন দীর্ঘক্ষণ “অ্যাপ অপ্টিমাইজ করা হচ্ছে” দেখাত, কারণ সব অ্যাপ আবার রূপান্তর করতে হতো। কম দামের ফোনের ছোট storage-এ এটি বিশেষ কষ্টের ছিল।

Android 7.0-এর মিশ্র নকশা দুই জগতের ভালো দিক নেয়: শুরুতে interpreter আর JIT দিয়ে দ্রুত ইনস্টল; চলার সময় কোন method আসলে বেশি চলে তার profile; আর ফোন অলস ও চার্জে থাকলে শুধু সেই অংশ AOT. পরে Play Store-এর মাধ্যমে অন্য ব্যবহারকারীদের সংগৃহীত profile-ও ব্যবহারের ব্যবস্থা আসে বলে জানা যায়, যাতে প্রথম চালু থেকেই গুরুত্বপূর্ণ অংশ প্রস্তুত থাকে। পর্ব ৬-এর ভাষায়, mechanism একই রইল, policy বদলাল হার্ডওয়্যারের সাথে।

মূল উপলব্ধি: “আগে রূপান্তর” আর “চলার সময় রূপান্তর” — কোনোটিই সব অবস্থায় সেরা নয়। মোবাইলে বিচার করতে হয় তিনটি সীমা একসাথে: চালুর সময়, storage, আর ব্যাটারি। সেরা নকশা প্রায়ই মাপা তথ্যের (profile) ওপর ভিত্তি করে কাজকে সঠিক সময়ে সরিয়ে নেয় — এখানে চার্জে থাকা রাতের অলস সময়ে।

প্র ০৩ডেস্কটপে swap স্বাভাবিক, ফোনে কেন “অ্যাপ মেরে ফেলা”-ই প্রধান কৌশল? এটি কি ব্যবহারকারীর জন্য খারাপ নয়?

পর্ব ৩৫–৩৭-এ দেখেছেন, swap RAM-এর চেয়ে বড় প্রোগ্রাম চালাতে দেয়, কিন্তু দাম আছে — ডিস্কে লেখা-পড়া ধীর, আর চাপ বেশি হলে thrashing. ডেস্কটপে মানুষ কিছুটা ধীরতা মেনে নেন, কারণ বন্ধ হয়ে যাওয়া একটি লম্বা নথি বা চলতি হিসাব হারানো আরও খারাপ। ফোনে হিসাব আলাদা। প্রথমত, flash-এর প্রতিটি block-এর মোছা-লেখার আয়ু সীমিত, আর ফোনের storage বদলানো যায় না — নিয়মিত swap লিখে কয়েক বছরে storage দুর্বল করা গ্রহণযোগ্য নয়। দ্বিতীয়ত, swap-এর I/O ব্যাটারি খায়। তৃতীয়ত, স্পর্শের পর্দায় একটি বিরতিও চোখে পড়ে।

তাই মোবাইল OS-গুলো একটি সামাজিক চুক্তি করেছে: OS যেকোনো সময় পেছনের অ্যাপ বন্ধ করতে পারে, আর অ্যাপের দায়িত্ব নিজের অবস্থা সংরক্ষণ করা। Android-এর জীবনচক্রের callback আর saved state, iOS-এর state restoration — এগুলো সেই চুক্তির অংশ। ঠিকঠাক বানানো অ্যাপে ব্যবহারকারী টেরও পান না যে প্রসেস বদলে গেছে। তার আগে দুই OS-ই নরম পথ নেয়: RAM-এর ভেতরে compress করে রাখা (Android-এ zram, iOS/macOS-এ memory compressor), যা flash-এ না লিখে কিছুটা swap-এর সুবিধা দেয় — CPU-র খরচে।

তবুও দাম আছে, বিশেষ করে কম RAM-এর ফোনে, যেখানে অনেক ব্যবহারকারী আছেন। অ্যাপ বারবার cold start হলে ডেটা আর ব্যাটারি খরচ বাড়ে, আর ভুলভাবে লেখা অ্যাপ অর্ধেক লেখা ফর্ম হারায়। Google কম মেমরির ফোনের জন্য Android-এর একটি হালকা রূপ (Android Go) এনেছে বলে জানা যায়, আর ডেভেলপারদের অ্যাপের মেমরি কম রাখতে উৎসাহিত করে। আবার শক্তিশালী কিছু iPad-এ, যেখানে storage দ্রুত আর বড়, Apple পরে সীমিত swap ফিরিয়ে এনেছে।

মূল উপলব্ধি: মেমরি-ব্যবস্থাপনার “সঠিক” কৌশল হার্ডওয়্যারের দাম আর ব্যবহারকারীর প্রত্যাশা থেকে আসে। Swap আর kill — দুটোই একই প্রশ্নের উত্তর: RAM কম পড়লে কোন ক্ষতি সবচেয়ে কম? ফোনে উত্তর হলো একটি অলস প্রসেস, যদি অ্যাপ তার অবস্থা নিজে বাঁচায়।

প্র ০৪“Race-to-idle” একসময় প্রায় নিয়ম ছিল; এখন ফোনের চিপ কেন মাঝামাঝি frequency আর ছোট core বেছে নেয়?

২০০০-এর দশকের শেষ দিকে ল্যাপটপ আর সার্ভারের জগতে race-to-idle জনপ্রিয় পরামর্শ ছিল: দ্রুত কাজ শেষ করে গভীর ঘুমে যাওয়া। যুক্তিটি এই পর্বের ক্যালকুলেটরের দ্বিতীয় পরিস্থিতি — যখন চলার সময় মেমরি, bus আর অন্য অংশের খরচ বড়, আর গভীর ঘুম প্রায় বিনা খরচে, তখন সময় কমানোই শক্তি কমানো। সেই সময়ের প্রসেসরে ভোল্টেজ কমানোর পরিসরও ছিল তুলনামূলক সংকীর্ণ।

কিন্তু কয়েকটি জিনিস বদলাল। ফোনের চিপে CPU-র বাইরের অংশগুলো নিজেরাই আলাদা ঘুমের অবস্থা পেল, ফলে “বাকি চিপের খরচ” কমে এল — ক্যালকুলেটরের প্রথম পরিস্থিতির কাছাকাছি, যেখানে ধীরে-স্থিরে জেতে। সর্বোচ্চ frequency-র কাছে পৌঁছাতে ভোল্টেজ অনেক বাড়াতে হয়, আর শক্তি V²-এ বাড়ে — তাই সর্বোচ্চ গতির শেষ কয়েক শতাংশ সবচেয়ে দামি। তার ওপর তাপ: পাখাহীন ফোন সর্বোচ্চ গতিতে কয়েক সেকেন্ড চলেই গরম হয়, তখন thermal throttling বাধ্য করে গতি কমাতে। আর big.LITTLE দিল আরেকটি মাত্রা — একই কাজ ছোট core-এ, যার C আর V দুটোই কম, প্রায়ই অনেক কম শক্তিতে হয়, যদি deadline ঢিলা হয়।

তাই আধুনিক ব্যবস্থা একটি নিয়ম নয়, একটি মডেল: Linux-এর Energy Aware Scheduling প্রতিটি core-এর প্রতিটি operating point-এর power জানে, schedutil scheduler-এর ব্যবহার-হিসাব দেখে frequency বাছে, আর ব্যবহারকারী-দৃশ্যমান কাজে (স্ক্রল, স্পর্শ) সাময়িকভাবে গতি বাড়ানো হয় যাতে পর্দা মসৃণ থাকে। নির্মাতারা নিজের চিপ মেপে এই মডেল বানান।

মূল উপলব্ধি: শক্তি-ব্যবস্থাপনায় একটি স্লোগান টেকে না; টেকে মাপা মডেল। P = C·V²·f, leakage আর বাকি চিপের খরচ — তিনটি একসাথে না ভাবলে “দ্রুত” আর “ধীর” দুটোই ভুল হতে পারে, আর সঠিক উত্তর প্রায়ই মাঝে।

প্র ০৫একটি ১৫ ডলারের IP ক্যামেরার নিরাপত্তা-আপডেটের দায়িত্ব কার — নির্মাতা, বিক্রেতা, না ব্যবহারকারীর?

২০১৬ সালের Mirai-এর ঘটনা এই প্রশ্নকে সামনে আনে। ম্যালওয়্যারটি জটিল কোনো দুর্বলতা খোঁজেনি; মূলত ইন্টারনেটে খোলা Telnet-এ কারখানার ডিফল্ট username-password-এর একটি ছোট তালিকা চেষ্টা করত। লক্ষ লক্ষ ক্যামেরা আর রাউটার দখল হয়, আর তাদের মিলিত ট্রাফিক দিয়ে বড় বড় ওয়েবসাইটের সেবা ব্যাহত করা হয়। যন্ত্রের মালিকেরা প্রায় কেউই টের পাননি — তাঁদের ক্যামেরা ঠিকই ছবি দেখাচ্ছিল।

দায় ভাগ করা কঠিন। ব্যবহারকারী একটি সস্তা যন্ত্র কিনেছেন, প্রযুক্তি জানেন না, আর বাক্সে কোথাও লেখা নেই যে পাসওয়ার্ড বদলাতে হবে। নির্মাতা প্রায়ই একটি তৈরি SDK-র ওপর firmware বানান, যন্ত্র বিক্রির পর আয় নেই, তাই কয়েক বছর আপডেট দেওয়ার অর্থনৈতিক প্রণোদনাও কম। চিপ-কোম্পানির SDK হয়তো পুরোনো kernel-এ আটকে। ক্ষতি হয় তৃতীয় পক্ষের — যাদের ওয়েবসাইট আক্রান্ত। অর্থনীতিবিদেরা একে externality বলেন: খরচ যে করে, ক্ষতি সে পায় না।

এর জবাবে কয়েকটি দেশ আইন করেছে বা করছে: সর্বজনীন ডিফল্ট পাসওয়ার্ড নিষিদ্ধ, দুর্বলতা জানানোর একটি পথ রাখা বাধ্যতামূলক, আর কত বছর নিরাপত্তা-আপডেট দেওয়া হবে তা বিক্রির সময় জানানো। প্রকৌশলের দিকে ভালো অভ্যাসগুলো এখন সুপরিচিত: প্রতিটি যন্ত্রে আলাদা পাসওয়ার্ড, স্বাক্ষরিত OTA আপডেট আর A/B partition, ন্যূনতম খোলা সেবা, আর OS-এর সঠিক বাছাই — যে RTOS বা embedded Linux-এর রক্ষণাবেক্ষণ আর নিরাপত্তা-প্যাচের ইতিহাস ভালো।

মূল উপলব্ধি: একটি যন্ত্রের OS-এর নিরাপত্তা বিক্রির দিনে শেষ হয় না — যন্ত্রটি যতদিন নেটওয়ার্কে থাকে, ততদিন চলে। তাই আপডেটের পথ (স্বাক্ষর, rollback, সমর্থনের মেয়াদ) নকশার শুরু থেকেই OS-এর অংশ হওয়া উচিত, পরে জোড়া দেওয়ার জিনিস নয়।

অনুশীলন

খাতায় হিসাব করুন, তারপর উত্তর মেলান।

  1. হিসাব — DVFS: একটি কাজে ৩ × ১০⁹ চক্র লাগে, C = ১ nF. Operating point A: ২.৪ GHz @ ১.১ V; B: ১.২ GHz @ ০.৮৫ V. (ক) প্রতিটির dynamic power, সময় আর dynamic শক্তি বের করুন। (খ) চলার সময় বাকি চিপের খরচ স্থির ০.৮ W যোগ করলে কোনটি কম শক্তি নেয়?

    (ক) A: P = ১০⁻⁹ × ১.২১ × ২.৪ × ১০⁹ = ২.৯০৪ W, t = ৩ × ১০⁹ / ২.৪ × ১০⁹ = ১.২৫ s, E = ২.৯০৪ × ১.২৫ = ৩.৬৩ J (অথবা সরাসরি C·V²·N = ১০⁻⁹ × ১.২১ × ৩ × ১০⁹ = ৩.৬৩ J). B: P = ১০⁻⁹ × ০.৭২২৫ × ১.২ × ১০⁹ = ০.৮৬৭ W, t = ২.৫ s, E = ২.১৬৭৫ J. শক্তির অনুপাত = ০.৭২২৫ / ১.২১ ≈ ০.৫৯৭ — B প্রায় ৪০% কম dynamic শক্তি।

    (খ) A: ৩.৬৩ + ০.৮ × ১.২৫ = ৩.৬৩ + ১.০ = ৪.৬৩ J. B: ২.১৬৭৫ + ০.৮ × ২.৫ = ২.১৬৭৫ + ২.০ = ৪.১৬৭৫ J. এখনো B জেতে, তবে ব্যবধান কমেছে (৪০% থেকে প্রায় ১০%). বাকি চিপের খরচ আরও বাড়লে একসময় A জিতবে — race-to-idle-এর যুক্তি।

  2. হাতে trace — superloop বনাম RTOS: একটি superloop-এ ক্রমে চলে: check_flag (জরুরি কাজ ১ ms, পতাকা থাকলে), read_gps ৩ ms, draw_screen ৫ ms, send_data ২ ms. (ক) জরুরি ঘটনার worst-case response কত? (খ) একই কাজ RTOS-এ, যেখানে জরুরি task সর্বোচ্চ অগ্রাধিকারে, আর দ্বিতীয় অগ্রাধিকারে একটি task (period ১০ ms, কাজ ৩ ms); জরুরি ঘটনা কমপক্ষে ২০ ms পরপর আসে। দুই task-এর worst-case response?

    (ক) সবচেয়ে খারাপ: পতাকা উঠল ঠিক check_flag-এর পরপর। অপেক্ষা ৩ + ৫ + ২ = ১০ ms, তারপর পরের ঘোরায় নিজের কাজ ১ ms ⇒ ১১ ms.

    (খ) জরুরি task সবার ওপরে, তাই R = নিজের কাজ = ১ ms (overhead বাদে)। দ্বিতীয় task: R = ৩ + ⌈R/২০⌉ × ১. R₀ = ৩ → R₁ = ৩ + ⌈৩/২০⌉ × ১ = ৪ → R₂ = ৩ + ⌈৪/২০⌉ × ১ = ৪, স্থির ⇒ ৪ ms ≤ period ১০ ms, তাই deadline রক্ষা হয়। Superloop-এ draw_screen ৮ ms হলে (ক) হতো ১৪ ms; RTOS-এর উত্তর বদলাত না।

  3. Output অনুমান করুন: zygote_cow.c প্রোগ্রামে app-W-এর লুপ বদলে শুধু প্রথম ৫১২টি page-এ লেখা হলো (if (p < 512) table[p * pg] = 1;). আর একটি নতুন সন্তান app-R যোগ হলো, যে app-W-এর পরে চলে আর আবার সব ২০৪৮ page পড়ে। app-W আর app-R-এর minor fault মোটামুটি কত হবে, আর কেন?

    app-W: প্রায় ৫১২ (প্রতি লেখা page-এ একটি copy-on-write fault, সাথে দু-একটি অতিরিক্ত) — ছাপবে “pages copied”.

    app-R: আগের পাঠকদের মতোই প্রায় ০, “pages shared”. কারণ app-W-এর লেখা তার নিজের কপিতে হয়েছিল, আর app-W শেষও হয়ে গেছে; বাবার (zygote) page একটুও বদলায়নি। প্রতিটি fork-এর সন্তান বাবার সেই অপরিবর্তিত page-ই ভাগ করে পায়। copy-on-write একই সাথে সাশ্রয় আর বিচ্ছিন্নতা দেয়।

  4. কোড লিখুন — FreeRTOS: একটি বোতাম চাপলে interrupt আসে। লিখুন: (ক) এমন একটি ISR যা শুধু একটি binary semaphore দেয় আর প্রয়োজনে context switch চায়; (খ) একটি task (অগ্রাধিকার ২) যা semaphore-এর অপেক্ষা করে আর পেলে toggle_led() ডাকে; (গ) আরেকটি task (অগ্রাধিকার ১) যা ঠিক প্রতি ৫০০ ms-এ blink_status() ডাকে, period-এ সরে যাওয়া ছাড়া।
    static SemaphoreHandle_t xBtn;          /* xBtn = xSemaphoreCreateBinary(); in main */
    
    void BUTTON_IRQHandler(void) {
        BaseType_t woken = pdFALSE;
        clear_button_irq();
        xSemaphoreGiveFromISR(xBtn, &woken);
        portYIELD_FROM_ISR(woken);
    }
    static void vButtonTask(void *pv) {      /* xTaskCreate(..., 2, ...) */
        for (;;)
            if (xSemaphoreTake(xBtn, portMAX_DELAY) == pdTRUE)
                toggle_led();
    }
    static void vBlinkTask(void *pv) {       /* xTaskCreate(..., 1, ...) */
        TickType_t xLast = xTaskGetTickCount();
        for (;;) {
            blink_status();
            vTaskDelayUntil(&xLast, pdMS_TO_TICKS(500));
        }
    }

    যাচাইয়ের বিন্দু: ISR-এ শুধু ...FromISR API; button task portMAX_DELAY-এ blocked থাকে, তাই অপেক্ষার সময় CPU খায় না; blink-এ vTaskDelay নয়, vTaskDelayUntil, যাতে blink_status()-এর নিজের সময় period-এ যোগ না হয়।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমAndroid-এ একটি বন্ধ থাকা অ্যাপের আইকন চাপার পর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 👆 Launcher Binder দিয়ে system_server-কে অ্যাপ চালুর অনুরোধ পাঠায়
  2. 🧭 system_server দেখে প্রসেস নেই, Zygote-এর socket-এ অনুরোধ পাঠায়
  3. 🧬 Zygote fork করে — preload করা page copy-on-write-এ ভাগ হয়
  4. 🐣 সন্তান অ্যাপের UID, SELinux context আর seccomp নিয়ে নিজেকে বিশেষায়িত করে
  5. 📱 ActivityThread শুরু, Application.onCreate, তারপর Activity-র onCreate → onResume

Android-এ নতুন অ্যাপ প্রসেস সাধারণত কীভাবে তৈরি হয়?

  • init প্রতিবার একটি নতুন runtime-সহ প্রোগ্রাম exec করে
  • আগে থেকে প্রস্তুত Zygote প্রসেস fork করে, তারপর সন্তান অ্যাপের পরিচয় নেয়
  • system_server-এর ভেতরে প্রতিটি অ্যাপের জন্য একটি নতুন thread খোলা হয়
  • Launcher নিজে fork করে নতুন অ্যাপ চালায়

একটি CPU ২.০ GHz, ১.০ V থেকে ১.০ GHz, ০.৮ V-এ নামানো হলো। P = C·V²·f মডেলে একটি নির্দিষ্ট কাজের dynamic শক্তি আগের তুলনায় কত গুণ?

  • ০.৩২ গুণ
  • ০.৫ গুণ
  • ০.৬৪ গুণ
  • ১.০ গুণ — কোনো পরিবর্তন নেই

Android-এ মেমরির চাপ বাড়লে lmkd প্রথমে কোন প্রসেস বন্ধ করার সম্ভাবনা সবচেয়ে বেশি? (oom_score_adj: সামনের অ্যাপ 0, গান বাজানো সেবা 200, home 600, cached অ্যাপ 950)

  • সামনের অ্যাপ (adj 0)
  • গান বাজানো সেবা (adj 200)
  • home (adj 600)
  • cached অ্যাপ (adj 950)

iOS-এ RAM কম পড়লে ক্লাসিক ধারায় কী ঘটে?

  • কম ব্যবহৃত page compress হয়, অ্যাপ সতর্কবার্তা পায়, তারপর Jetsam অগ্রাধিকার দেখে প্রসেস বন্ধ করে
  • সব পেছনের অ্যাপের মেমরি flash-এর swap file-এ লেখা হয়
  • নতুন বরাদ্দ ব্যর্থ হয় আর পুরো সিস্টেম রিস্টার্ট হয়
  • ব্যবহারকারীকে একটি তালিকা দেখিয়ে অ্যাপ বন্ধ করতে বলা হয়

একটি superloop-এ ক্রমে চলে: পতাকা যাচাই (জরুরি কাজ 1 ms), sensor 2 ms, display 4 ms, log 3 ms; কোনো ফাংশন মাঝপথে থামে না। জরুরি ঘটনার worst-case response কত?

  • 1 ms
  • 10 ms
  • 4 ms
  • 20 ms

FreeRTOS-এ shared I2C bus রক্ষায় binary semaphore-এর বদলে mutex (xSemaphoreCreateMutex) ব্যবহারের প্রধান কারণ কী?

  • mutex অনেক দ্রুত
  • mutex ISR থেকে দেওয়া যায়, semaphore যায় না
  • mutex-এ priority inheritance আছে, যা priority inversion সীমিত রাখে
  • mutex deadlock অসম্ভব করে

একটি RTOS-ভিত্তিক যন্ত্রে interrupt handler (ISR) লেখার সঠিক নীতি কোনটি?

  • ছোট রাখা — হার্ডওয়্যারের জরুরি কাজ আর একটি task-কে সংকেত; বাকি কাজ সেই task-এ
  • পুরো কাজ ISR-এই করা, যাতে task switch-এর খরচ না লাগে
  • ISR-এ xSemaphoreTake(x, portMAX_DELAY) দিয়ে ডেটার অপেক্ষা করা
  • ডিবাগের সুবিধার জন্য ISR-এ printf আর malloc ব্যবহার করা

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. সীমাবদ্ধতা — ব্যাটারি, কম RAM, flash, তাপ, নেটওয়ার্ক, স্পর্শ, deadline, সনদ — মোবাইল ও embedded OS-এর প্রতিটি নকশা ঠিক করে।
  2. Android: Linux kernel (Binder, ashmem/memfd, wakeup source, lmkd + PSI) → HAL → native লাইব্রেরি ও ART (JIT + profile-ভিত্তিক AOT) → framework (system_server). অ্যাপ জন্মায় Zygote-এর fork-এ, copy-on-write-এ page ভাগ; runtime permission, Treble ও GKI.
  3. মেমরি ফুরোলে কাউকে মারা হয়: Android-এ importance/adj-এর ক্রমে lmkd, iOS-এ (XNU = Mach + BSD) memory compression আর Jetsam; তাই অ্যাপকে নিজের অবস্থা সংরক্ষণ করতে হয়। iOS-এ অ্যাপ সাধারণত পেছনে Suspended.
  4. শক্তি: P = C·V²·f, কাজ-প্রতি E = C·V²·N — ভোল্টেজ কমানোই আসল লাভ; বাকি চিপের খরচ বেশি হলে race-to-idle. schedutil, big.LITTLE + EAS, Doze, App Standby; wake lock সবসময় সময়সীমাসহ।
  5. Embedded: superloop-এ worst-case ≈ পুরো লুপ; RTOS = fixed-priority preemptive, deterministic latency, KB-র footprint, tick/tickless, queue, mutex-এ priority inheritance, ছোট ISR + task-এ কাজ। নিরাপত্তা-সনদ (DO-178C, ISO 26262, IEC 62304) আর IoT-তে স্বাক্ষরিত আপডেট।

পরের পর্বে: মডিউল ৮ শেষ — VM, container, ক্লাউড, distributed আর মোবাইল OS দেখা হলো। এবার মডিউল ৯-এ বাস্তব kernel-এর ভেতরে ঢোকা: পর্ব ৫৫-এ Linux kernel-এর গঠন, module আর eBPF.

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান