Module ০ · C কেন, আর program জিনিসটা আসলে কী
আজকের দিনে C চলছে কোথায়: তুমি রোজ ব্যবহার করো এমন ছয়টা সিস্টেম
এই lesson-এ যা শিখবে
- আজ তুমি ব্যবহার করেছ এমন ছয়টা system-র নাম বলতে পারবে, যেগুলো C দিয়ে লেখা।
- প্রতিটার ভেতরে C ঠিক কী কাজ করছে, বলতে পারবে।
- Kernel আর embedded-র কাজ কেন C-তেই থেকে গেছে, ব্যাখ্যা করতে পারবে।
শেষ কয়েক ঘণ্টার কথা ভাবো। কিছু একটা তোমাকে জাগিয়েছে। তুমি একটা ফোন খুলেছ, একটা browser খুলেছ, আর এই page তোমার বাড়ির একটা router পেরিয়ে এসেছে।
এই বাক্যটা পড়ার আগেই C-র ছয়টা আলাদা টুকরো চলেছে। তুমি একটাও দেখোনি, আর ব্যাপারটা ঠিক এখানেই। C হলো ভিত্তি, আর ভিত্তি নষ্ট না হওয়া পর্যন্ত চোখেই পড়ে না।
এই lesson একটা ঘুরে দেখা। প্রতিটা system-র জন্য থাকবে একটা প্রকল্পের নাম, আর এক বাক্যে C সেখানে কী করে। তারপর ওই প্রকল্পের public interface-র একটা সত্যিকারের লাইন। তারপর এক বাক্যে সৎ উত্তর, কেন এটা অন্য কিছুতে লেখা হয়নি।
06:00, alarm: FreeRTOS
একটা fitness band, smart meter বা microwave-র ভেতরের chip-টা Android চালায় না। ওটা চালায় ছোট একটা scheduler, প্রায়ই FreeRTOS। FreeRTOS 2003 সাল থেকে C-তে লেখা হচ্ছে, আর এখন কোটি কোটি যন্ত্রের ভেতরে আছে।
C সেখানে কী করে: সে ঠিক করে পরের কাজটা কোনটা চলবে। আর সেটা করে জানা, নির্দিষ্ট পরিমাণ memory দিয়ে। খারাপ সময়ে থামিয়ে দেওয়ার মতো কোনো garbage collector নেই।
void vTaskDelay( const TickType_t xTicksToDelay );
এটা FreeRTOS-র API থেকে নেওয়া, freertos.org-এ নথিভুক্ত। এটা চলতি কাজটাকে ঘড়ির নির্দিষ্ট কয়েকটা tick-র জন্য ঘুম পাড়িয়ে দেয়। অন্য কিছু কেন না? 64 KB memory-র যন্ত্রে runtime রাখার জায়গা নেই। আর ভুল সময়ে 30 milliseconds থেমে যাওয়া মানে একটা ত্রুটি, ধীর হয়ে যাওয়া না।
06:02, ফোন: Linux kernel
Android একটা Linux kernel চালায়। ইন্টারনেটের বেশিরভাগ server-ও তাই। এটা প্রায় তিন কোটি লাইন, বেশিরভাগটাই C, শুরু 1991 সালে, আর এখনো রোজ নতুন পরিবর্তন নিচ্ছে।
C সেখানে কী করে: তোমার app আর hardware-র মাঝের সবকিছু। Touchscreen পড়া, process-র পালা ঠিক করা, storage chip-এ byte পাঠানো।
ssize_t read(int fd, void *buf, size_t count);
এই ঘোষণাটাই kernel-এ ঢোকার দরজা, আর যেকোনো Linux মেশিনে man 2 read দিয়ে এর manual পড়তে পারো। তোমার program যত file খুলবে, সব এটার ভেতর দিয়েই যায়। অন্য কিছু কেন না? Kernel-কে সরাসরি hardware-র সঙ্গে কথা বলতে হয়, আর C হলো সেই ভাষা যে দুইয়ের মাঝখানে দাঁড়ায় না।
06:03, router: BusyBox আর dnsmasq
তোমার ঘরের কোণের বাক্সটা আসলে ছোট একটা Linux computer। বেশিরভাগ home router চালায় BusyBox, একটাই C program যার ভেতরে প্রায় 400টা Unix command-র ছোট করা version আছে। বেশিরভাগ router dnsmasq-ও চালায়, যে C program নাম খোঁজার উত্তর দেয়।
C সেখানে কী করে: সে "progsity.io"-কে একটা address-এ বদলায়, স্থানীয় address বিলি করে, আর packet সরায়, এমন একটা যন্ত্রের ভেতরে যার storage কয়েক megabyte।
int socket(int domain, int type, int protocol);
এটা standard socket call, man 2 socket-এ নথিভুক্ত, আর পৃথিবীর প্রায় প্রতিটা network program এখান থেকেই শুরু হয়। অন্য কিছু কেন না? কারণ পুরো firmware image-টাকে flash memory-তে আঁটতে হবে। একটা runtime-র খরচই পুরো program-র চেয়ে বেশি হতো।
06:03, lesson-র data: SQLite আর Redis
SQLite একটাই C library, public domain, আর পৃথিবীতে সবচেয়ে বেশি জায়গায় বসানো database engine। এটা তোমার browser-এ, ফোনে আর বেশিরভাগ উড়োজাহাজের বিনোদন system-এ আছে। Redis 2009 সাল থেকে C-তে লেখা, আর web backend-র জন্য অল্প সময়ের data memory-তে রাখে, এই backend-টাও তার মধ্যে।
C সেখানে কী করে: সে একটা file format byte ধরে ধরে সামলায়। কোনো server process ছাড়াই microsecond-এ একটা query-র উত্তর দেয়।
int sqlite3_open(const char *filename, sqlite3 **ppDb);
এটা SQLite-র খোলার function, sqlite.org-এ নথিভুক্ত। অন্য কিছু কেন না? Database engine-র সারা জীবন কাটে memory আর disk-র মাঝে byte সরাতে। আবার তাকে অন্য বিশটা ভাষায় লেখা program-র ভেতরে ঢুকতে হয়। একটা C library-কে ওই সবগুলো থেকেই call করা যায়।
06:04, tooling: CPython
Python-র standard interpreter হলো CPython নামের একটা C program। কেউ যখন বলে "Python ধীর", সে আসলে এই C program-টার গতির কথা বলছে, যে তার নির্দেশগুলো একটা একটা করে পড়ছে।
C সেখানে কী করে: সে প্রতিটা Python object ধরে রাখে, bytecode চালানোর loop-টা চালায়, আর standard library-র দ্রুত অংশগুলো বানায়।
PyObject *PyLong_FromLong(long v);
এটা CPython-র public C API থেকে, docs.python.org-এ নথিভুক্ত। সাদামাটা একটা C সংখ্যা এভাবেই Python-র integer হয়ে যায়। অন্য কিছু কেন না? একটা language runtime-কে নিজের memory-র বিন্যাস নিখুঁতভাবে নিয়ন্ত্রণ করতে হয়, আর operating system থেকে তাকে call করা যেতে হয়।
06:04, ছবিটা: zlib আর libpng
এই page-র প্রতিটা ছবি এসেছে চাপা দেওয়া অবস্থায়। zlib 1995 সালে C-তে লেখা, আর PNG file, HTTP compression আর .zip format-র পেছনের compression library এটাই। libpng বসে থাকে তার উপরে, আর PNG format-টা নিজে বোঝে।
C সেখানে কী করে: সে প্রতিটা ছবির জন্য কয়েক লাখ byte-র উপর দিয়ে হাঁটে। Code নিজে না চাইলে কেউ কোনো array-র সীমা যাচাই করে না। সেজন্যই এটা দ্রুত, আর সেজন্যই এর যত্ন করে review দরকার।
int deflate(z_streamp strm, int flush);
এটা zlib-র compression ধাপ, zlib.net-র zlib manual-এ নথিভুক্ত। অন্য কিছু কেন না? একটা ছবি খোলা মানে কাঁচা byte-র উপর দিয়ে একটা আঁটসাঁট loop। প্রতিটা array access যাচাই করে এমন ভাষা হলে তোমার ছবিগুলো চোখে পড়ার মতো ধীরে আসত।
Kernel আর embedded-র কাজ কেন C-তেই থাকে
চারটা কারণ প্রতিবারই আসে, আর একটাও পুরোনো দিনের মায়া না।
- নিচে কোনো runtime লাগে না। C program-র নিচে কিছু চালু থাকার দরকার নেই। Kernel কোনো runtime-র উপর ভরসা করতে পারে না, কারণ runtime তো kernel-র উপরেই চলত।
- সময়ের হিসাব আগেই বলা যায়। তুমি না লিখলে কোনো pause আসে না। একটা pacemaker বা গাড়ির brake controller-এ "সাধারণত দ্রুত" কোনো শর্ত না।
- Memory-টা জানা। একটা structure ঠিক কত byte নেবে, সেটা তুমি বলে দিতে পারো, যেটা Example 1 দেখাচ্ছে। 64 KB memory-তে এটাই ঠিক করে দেয় জিনিসটা বাজারে যাবে কি না।
- এটা এমনিতেই সব জায়গায় আছে। ভালো একটা ভাষা এল বলে তিন কোটি লাইন চলতে থাকা kernel-র code আবার লেখা হয় না। Linux-এ Rust যোগ হচ্ছে, নতুন driver-এ, C-র পাশে।
পরের তিনটা program প্রথম তিনটা কারণ code-এ দেখিয়ে দেয়, আর তুমি সেগুলো চালাতে পারবে।
Embedded-র কাজ করা একজন engineer কিছু লেখার আগে এটাই করে: byte গোনে।
#include <stdint.h>
#include <stdio.h>
/* One reading from a temperature sensor, laid out by hand. */
struct Reading {
uint32_t seconds; /* 4 bytes: time since the device started */
int16_t celsius; /* 2 bytes: temperature times ten */
int16_t battery; /* 2 bytes: percent times ten */
};
int main(void)
{
struct Reading r = {3600, 267, 812};
printf("one reading is %zu bytes\n", sizeof(struct Reading));
printf("a thousand readings fit in %zu bytes\n", 1000 * sizeof(struct Reading));
printf("at %u seconds: %d.%d C, battery %d.%d%%\n",
r.seconds, r.celsius / 10, r.celsius % 10,
r.battery / 10, r.battery % 10);
return 0;
}
one reading is 8 bytes
a thousand readings fit in 8000 bytes
at 3600 seconds: 26.7 C, battery 81.2%
আট byte, আর আটটাই তুমি বেছেছ। সেজন্যই 32 KB memory-র একটা sensor আগে থেকেই ঠিক করতে পারে যে চার হাজার reading রাখবে। এরকম structure তুমি লিখবে Module 12-এ।
Compiler-এ চালাওএই program যত byte ব্যবহার করবে, সব শুরু হওয়ার আগেই ঠিক করা। চলার সময় সে কিছু চায় না, তাই চলার সময় কিছু ব্যর্থও হতে পারে না।
#include <stdio.h>
/* Five alarms, fixed in place. No memory is ever requested while running. */
#define ALARM_COUNT 5
int main(void)
{
int alarm_minute[ALARM_COUNT] = {330, 360, 420, 480, 1320};
const char *label[ALARM_COUNT] = {"early", "wake up", "bus", "class", "sleep"};
int now = 420; /* 07:00, counted in minutes since midnight */
for (int i = 0; i < ALARM_COUNT; i++) {
if (alarm_minute[i] == now) {
printf("ring: %s\n", label[i]);
}
}
printf("checked %d alarms, used %zu bytes of table\n",
ALARM_COUNT, sizeof(alarm_minute));
return 0;
}
ring: bus
checked 5 alarms, used 20 bytes of table
কুড়ি byte, পাঁচটা তুলনা, একটা উত্তর, প্রতিবার। এক milliseconds-র মধ্যে সাড়া দিতে হয় এমন যন্ত্র ঠিক এরকম loop দিয়েই তৈরি।
Compiler-এ চালাওlibpng কিছু খোলার আগে আটটা byte যাচাই করে। ওই আটটা byte PNG specification-এ বাঁধা, আর পৃথিবীর প্রতিটা PNG file ওগুলো দিয়েই শুরু হয়।
#include <stdint.h>
#include <stdio.h>
/* The first eight bytes of every PNG file, from the PNG specification. */
static const uint8_t PNG_SIGNATURE[8] = {
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
};
int looks_like_png(const uint8_t *bytes, int count)
{
if (count < 8) {
return 0;
}
for (int i = 0; i < 8; i++) {
if (bytes[i] != PNG_SIGNATURE[i]) {
return 0;
}
}
return 1;
}
int main(void)
{
uint8_t real_png[10] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00};
uint8_t a_jpeg[10] = {0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46};
printf("file 1 is a PNG: %d\n", looks_like_png(real_png, 10));
printf("file 2 is a PNG: %d\n", looks_like_png(a_jpeg, 10));
return 0;
}
file 1 is a PNG: 1
file 2 is a PNG: 0
2, 3 আর 4 নম্বর byte হলো P, N আর G অক্ষর। Module 1-র ASCII table-এ ওগুলো খুঁজে দেখো, পাবে 0x50, 0x4E আর 0x47। "File format পড়া" বলতে পুরোটা এটাই বোঝায়।
Compiler-এ চালাওএটা কোথায় কাজে লাগছে, আর এরপর কী পড়বে
- SQLite. শুরু করো sqlite.org-র C interface-র তালিকা দিয়ে, তারপর "How SQLite Works" page-টা। পড়ার জন্য এটাই সবচেয়ে বন্ধুভাবাপন্ন বড় C codebase, একটা কারণ হলো এর test suite code-র চেয়ে অনেক বড়।
- Redis. এর source এক সপ্তাহান্তে পড়ে ফেলার মতো ছোট। শুরু করো
sds.cদিয়ে, এর string type, যেটা আছে কারণ C-র নিজের string ব্যবহার করা ঝামেলার। Module 10-র পর তুমি বুঝবে কেন। - CPython. Python-র documentation-র "Extending and Embedding" অধ্যায়টা পড়ো। ওটা তোমাকে C-তে লেখা একটা Python module দেখায়, যেটা দুই ভাষার মাঝের সবচেয়ে পরিষ্কার সেতু।
- Linux kernel. এখনই না। Module 12-র পর ফিরে এসো। শুরু করার জায়গা kernel-র নিজের documentation-র directory, source না।
যে ভুলগুলো সবাই করে
১. ধরে নেওয়া যে একটা চওড়া type সব জায়গায় একই মাপের।
struct Reading {
long seconds; /* 8 bytes on Linux, 4 bytes on Windows */
int celsius;
};
এখানে কোনো error message নেই। তোমার sensor যে file লেখে, সেটা অন্য মেশিনে পড়াই যায় না। সমাধান Example 1-এ: <stdint.h> ব্যবহার করো, আর চার byte বোঝাতে uint32_t লেখো।
২. "C-তে লেখা" আর "C-র উপর চলে" গুলিয়ে ফেলা।
তোমার Python script C-তে লেখা না। ওটা চলে CPython-র উপর, আর সেটা C-তে লেখা। কেউ যখন বলে এটা দ্রুত করে দাও, তখন এই পার্থক্যটাই আসল, কারণ দুইটার উত্তর সম্পূর্ণ আলাদা।
৩. দ্রুত বলে C বেছে নেওয়া, যে কাজে গতি সমস্যাই না।
একটা web page, একটা form, একটা report: এগুলোর সময় কাটে network বা মানুষের জন্য অপেক্ষা করে। C এগুলো লেখা কঠিন করত, আর ব্যবহারে এক ফোঁটাও দ্রুত করত না। C বেছো তখনই, যখন memory-র বিন্যাস, সময় বা আকার হলো বাধা।
৪. ছোট C প্রকল্প পড়তে না পারার আগেই বড়টা খুলে বসা।
দ্বিতীয় সপ্তাহে Linux kernel খোলা নিজেকে বোকা মনে করার উপায়, শেখার উপায় না। উপরের প্রতিটা প্রকল্প পড়া যায়, ক্রম মেনে, আর শুরুটা এই track-র Module 10-র আশেপাশে।
তোমার দিনের ছয়টা মুহূর্তের নিজের একটা তালিকা খাতায় লেখো, আর প্রতিটা মুহূর্তের পেছনের C প্রকল্পের নাম দাও।
নিয়ম। ছয়টা সারি: সময়, তুমি কী করেছ, প্রকল্প, আর এক বাক্যে C ওখানে কী করে। অন্তত দুইটা সারি ছবি 1-র থেকে আলাদা হতে হবে।
নিজে যাচাই করো। তুমি যে প্রকল্পের নাম লিখবে, তার একটা home page খুঁজে পেতে হবে। না পেলে এমন একটা দিয়ে বদলে দাও যেটা পাওয়া যায়।
Example 1 নাও, আর sensor-র বিন্যাসটা বদলাও যাতে প্রতিটা record-এ একটার বদলে চারটা reading থাকে।
নিয়ম। <stdint.h>-র type-গুলো রাখো। নতুন মাপটা ছাপাও। চালানোর আগে লিখে রাখো তুমি কত আশা করছ, আর কেন।
নিজে যাচাই করো। তোমার বলে দেওয়া মাপ আর ছাপা মাপ মিলে গেলে বুঝবে বিন্যাসটা তুমি বুঝেছ। আলাদা হলে compiler ফাঁকা জায়গা বসিয়েছে, আর কেন সেটা Module 12 বলে দেয়।
ছয়টা প্রকল্পের একটা বেছে নিয়ে তার উপরে এক পাতার একটা briefing লেখো। এমন একজন সহকর্মীর জন্য লেখো, যে এর নামই শোনেনি।
নিয়ম। পাঁচটা জিনিস থাকতে হবে। এটা কী করে, কে আর কবে লিখেছে, কোথায় চলে। তারপর এটা ইচ্ছা করে যা করে না এমন একটা জিনিস, আর সত্যিকারের পার্থক্য আছে এমন একটা বিকল্প। প্রতিটা দাবি আসবে ওই প্রকল্পের নিজের site বা manual থেকে।
নিজে যাচাই করো। ভালো briefing-এ অন্তত একটা তথ্য থাকে যেটা লিখতে গিয়ে তোমাকে চমকে দিয়েছে। কিছুই না চমকালে বুঝবে তুমি home page ছেড়ে এগোওনি।
যে প্রশ্নগুলো সবার মনে আসে
C যদি সব জায়গায়, তাহলে চাকরির বিজ্ঞাপনে Python আর JavaScript চায় কেন?
কারণ বেশিরভাগ চাকরি application বানায়, আর application বসে থাকে এই স্তরটার উপরে। C-র চাকরি আছে, system, embedded, database আর game-এ, আর সেগুলো করতে পারা লোক কম।
এই প্রকল্পগুলোতে কি Rust C-কে সরিয়ে দিচ্ছে?
নতুন code-এ, কখনো কখনো। Linux 2022 সাল থেকে Rust-র driver নিচ্ছে, আর Android নতুন system component-এ এটা ব্যবহার করে। চলতে থাকা C পাশেই চলতে থাকে, কারণ কাজ করা code আবার লেখা ব্যয়বহুল আর ঝুঁকির।
এগুলোর কোনো একটা codebase কি এখনই পড়তে পারি?
আরামে না। Module 11-র পর Redis-এ ফিরে এসো, আর Module 13-র পর SQLite-এ। সত্যিকারের code পড়া নিজেই একটা দক্ষতা, আর Module 16 পুরোটাই সেটাকে ঘিরে।
C-তে এত memory bug হলে এই প্রকল্পগুলো নিরাপদ কীভাবে?
ওগুলোতে প্রচুর test আর প্রচুর review হয়, ঝুঁকিটা এভাবেই সামলানো হয়। SQLite-র test suite SQLite-র চেয়ে অনেক বড়। সৎ সারমর্ম হলো, C নিরাপত্তার কাজটা compiler থেকে সরিয়ে তার চারপাশের প্রক্রিয়ার ঘাড়ে দেয়।
চাকরি পেতে কি ছয়টাই জানতে হবে?
না। একটা ভাষা এতটা জানতে হবে যাতে তুমি নিজের না লেখা code পড়তে পারো। এই lesson একটা মানচিত্র, পড়ার তালিকা না, আর এখন শুধু অনুশীলন 3 করাই যথেষ্ট।
মূল কথা
- FreeRTOS, Linux kernel, BusyBox, SQLite, CPython আর zlib সবই C, আর সবই আজ চলেছে।
- যেখানে memory, সময় বা আকার দুর্লভ, C সেখানেই বসে।
- Kernel আর embedded যন্ত্র C-তেই থাকে, কারণ ওদের নিচে কোনো runtime নেই।
- C-তে তুমি বলে দিতে পারো তোমার data ঠিক কত byte নেবে, আর সেটাই ঠিক করে কী বাজারে যাবে।
- "C-তে লেখা" আর "C-র উপর চলে" আলাদা দাবি, আর পার্থক্যটা কাজে লাগে।
- Rust যোগ হচ্ছে নতুন code-এ C-র পাশে, পুরোনোটার বদলে না।
পরের lesson-এ দেখবে, programmer-রা বড় একটা program সাজানোর জন্য যে দুইটা রাস্তা ব্যবহার করে। আর এই track কেন পুরোনোটা আগে শেখায়, সেটাও দেখবে।
lesson ৫ শেষ
শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।
পরেরটা: Structured আর object oriented: program সাজানোর দুই রাস্তা