ماجرای درایورِ شبکه بهداد اسفهبد

بهداد اسفهبد
بهداد اسفهبد

دوستان خواستند که ماجرای درایورِ شبکه رو بنویسم...

حوالیِ سالِ ۱۳۸۰ بود. من سالِ اول / دوّم بودم. من و روزبهِ پورنادر تو مرکز محاسباتِ دانشگاهِ شریف رو پروژه‌ی فارسی‌وب کار می‌کردیم. پنج‌شنبه‌روزی به ما یه کامپیوترِ جدید دادن که از ماشین‌هایی که داشتیم خیلی بهتر بود. تنها چیزی که گفتند این بود که قبل از اینکه پلمب‌ش رو بشکونین، تِست کنین اگه خرابه پس بفرستیم. تست کردیم، درایوِ فلاپی‌ش خراب بود، ولی برامون مهم نبود.

مهم این بود که کارتِ شبکه‌ش درایور نیاز داشت و درایور روی فلاپی بود، و البته روی اینترنت هم بود، ولی رویِ خودِ ماشین نصب نبود و موجود نبود، در نتیجه به شبکه نمی‌تونستیم وصل شیم و کارتِ شبکه رو نمی‌تونستیم تست کنیم و یعنی نمی‌تونستیم پلمب رو بشکونیم و هارددیسک‌هامون رو بذاریم و خلاصه باید تا شنبه صبر می‌کردیم که یا ماشین رو پس بفرستیم، یا یه نفر که سی‌دی‌رایتِر داره پیدا کنیم این فایلِ DLL درایور رو روی سی‌دی بزنیم که بتونیم رو ماشین نصب کنیم. خلاصه فاز نمی‌داد. نیمه‌شب بود و ما کِرم‌مون گرفته بود که ماشین رو راه بندازیم تا صبح و جمعه بشینیم پاش و حالِش رو ببریم.

گفتیم چه کنیم چه نکنیم، وات توو دوو وات نات توو دوو، یادِ یه تکنولوژیِ ماقبلِ کارتِ شبکه افتادیم: کابلِ لینک. خودِ کابل رو که نداشتیم ولی گفتیم درست کنیم. کابلِ لینکِ سریال ۴ / ۵ تا سیم بیشتر نمی‌خواد. جزئیاتش تو راهنمای Quick Basic بود! یه تیکه سیمِ شبکه رو لُخت کردیم و کردیم تو سولاخای پورتِ سریالِ ماشینه و یه ماشین دیگه، ولی نتونستیم ارتباط برقرار کنیم. احتمالن به خاطرِ این بود که ویندوز ۲۰۰۰ یا اون حدود داشتیم، و می‌خواستیم با nc تحتِ DOS لینک کنیم که احتمالا ویندوزِ ۲۰۰۰ اینا دوست ندارن، لاقل نه بدونِ تشریفات. به‌هرحال، از کابلِ لینکِ ناداشته هم ناامید شدیم.

گفتیم تایپ کنیم. هر دو هم تاچ‌تایپیست بودیم و زیپ‌شده‌ی فایل ۴۰ کیلوبایت بود؛ چیزی نیست. تِست زدیم؛ اولا که ۴۰ک میشه ۸۰ک مبنای ۱۶ (hex)، و ۸۰ک میشه ۴۰ صفحه‌ی پر از اون صفحه‌های DOS. دوّم اینکه، با متوسط سرعتِ ۲۰۰ حرف در دقیقه، میشه ۴۰۰ دقیقه، یعنی ۷ ساعت! تازه بدونِ استراحت و به شرطِ اینکه اونی که قراره بخونه دهنش کف نکنه خفه شه! سِیُّم و مهم‌ترین اینکه، اون ۲۰۰ حرف‌دردقیقه سرعتِ تایپِ متنِ انگلیسی هست که شدیدن «زائد» (redundant) است، نه داده‌ی عملا تصادفی (خروجیِ زیپ)! خلاصه اینم خورد تو دیفال.

دیگه راهِ استانداردی نمونده بود. نیمه‌شب شده بود و وقتِ این بود که بیخیال شیم بریم بخوابیم. امّا علی‌آقا هم دیگه بسته بود و کلّا فاز نمی‌داد کوتاه بیایم. رفتم پشتِ ماشین رو وارسی کردن، ببینم سولاخ دیگه چی داره! چیزی که جای انگول کردن داشته باشه یکی بیشتر پیدا نکردم، ولی همون یکی آمّا جای انگول داشت: کارتِ صدا!

یه نگاه انداختم اونور اتاق رو میزِ بچه‌ها از این بلندگو جدیدا بود که ساب‌ووفر داشت. کابلِ ورودیِ صدا از کامپیوتر به این ساب‌ووفِرها از هردو طرف جدا میشد و کلّه‌گی‌ش متقارن بود. رفتم کندم کابل رو آوردم، یک سر زدم تو خروجیِ صدای ماشین بغلی، اون یکی سر تو ورودیِ صدای ماشینِ موردِ نظر. خلاصه ۵۰٪ِ قضیه حل شد، موند نرم‌افزار! یا به عبارتی موند درایور برا کارتِ صدا-تبدیل‌شده‌به-شبکه‌مون، که باهاش درایورِ کارتِ شبکه‌ی واقعی رو منتقل کنیم. «We need to go deeper»

خُب اوّل گفتم ظرفیتِ انتقال صدا رو برقرار و تست کنیم. یه هایده اون‌ور با Windows Media Player پخش کردیم، این‌ور با Windows Voice Recorder ضبط کردیم. درست اومد و پخش شد. تنظیمات ضبط رو هم گذاشتیم ۴۴۱۰۰هرتز مونو ۸بیت. فایل خروجی هم WAV بود که یه هِدِرِ ساده‌ست و داده‌ی خام. این از این.

به عنوانِ المپیادیِ متعهد CD توربوپاسکال و ابزارهای متفرقه همیشه همرام بود. کامپایلر رو راه انداختیم رو هر دو سیستم.

قسمت بعد تبدیلِ داده‌ی ورودی (فایلِ زیپ‌شده‌ی درایورِ شبکه) به فایلِ صوتی بود برای انتقال، و البته دریافت و بازیافت‌ش طرفِ دیگر!

یه گوگلِ مختصر کردم (اون موقع هنوز «گوگل کردن» واردِ لغت‌نامه نشده بود!) و ۳۰ خط کد نوشتم که به ازای هر بیت (bit) از فایل ورودی، ۱۰۰ تا نمونه‌ی (sample) خروجی تولید کنه: اگه بیتِ ورودی صفر بود، خروجی همه صفر، وگرنه خروجی نُتِ «لا» مرجع، یعنی موجِ سینوسی با فرکانس ۴۴۰ هرتز. این از این.

یه جمله تست زدیم، فرستادیم اونور. روزبه اونور نشسته بود پشتِ پاسکال و من دیکته می‌کردم براش. گفت من بیلمیرم چیکارش کنه این داده رو، گفتم تو تایپ کن: یه مشتق میگیریم اول: اختلاف هر نمونه با قبلیش رو حساب کن؛ بعد اگه قدرمطلقش از ۴ بزرگ‌تر بود بنویس یک، وگرنه بنویس صفر. بعد هر صد تا نمونه رو بگیر، «اکثریت» (majority) بگیر و بنویس تو خروجی. این میشه داده‌ی ما، اگه همه چی مثِ آدمیزاد بی‌نقص(!!) منتقل می‌شد این جواب بود. «اگه»...

مشکلِ اول این بود که ابتدای داده رو از کجا پیدا کنیم. در نتیجه یه چندین بیت ۰ و یه ۱ سرِ داده گذاشتم که علامتِ شروع باشه،و تو دیکُدِر (decoder) دنبالِ این الگو گشتم. مشکلِ دوم این بود که دو سه بیت درست دیکُد میشد بعد گاطی‌پاتی میشد. برای اینکه قابلیتِ اطمینان بالا بره، هر بیت رو کردم ۲۰۰ نمونه به جای ۱۰۰ تا، و برای تسهیلِ تقطیع (!!) هر طرفِ هر بیت ۱۰۰ تا نمونه هم صفر زدم. اون طرف هنوز همون مثِ قبل دیکُد می‌کردیم، ولی حالا برای هر بیت، چهار بیت می‌رسید که انتظار داشتیم اولی و آخری صفر باشن (جداکننده‌ها)، و دوتا وسطی داده‌مون، و انتظار داشتیم دوتاشون یه مقدار داشته باشن. توی کد ولی، اگه هرکدوم از وسطی‌ها روشن بود، خروجی رو روشن اعلام می‌کردیم.

تِست زدیم. چند کلمه می‌رفت بعد قاطی می‌شد. برای اینکه بفهمم چه خبره، قبل از اون تبدیلِ آخر، خروجی رو با حروفِ 0 و 1 نوشتم و نگاه کردم. دیدم این ترکیبی که باید چنین جوری باشه:

01100000000001100110
01100110011001100000
00000110000001100110
00000110011000000110
...

یعنی از هر چهارتا، اولی و آخری صفر باشه و دوتا وسط یه‌جور، بعد از چند خط شیفت پیدا میکنه، یعنی مثلا میشه:

01100000000001100110
01100110011001100000
00000110000001100110
00000110011000000110
00001100110011000000
11000000110000001100
...

این معنی‌ش اینه که فرکانس کارتِ صدای ماشینی که می‌خونه، با اونی که ضبط می‌کنه هردو دقیقن ۴۴۱۰۰ هرتز نیست. در واقع که خوب هیچ‌کدوم دقیقا اون نیست، ولی به‌هرحال، این‌که فرکانسِ دوتا فرق فیل‌کالینززججگگ‌ایناف که ما باید تو درایورِمون لحاظ کنیم.

اون جداکننده‌ها که اضافه کرده بودیم و الکی زمانِ صدا رو دوبرابر کرده بودن حالا به کار میان: از هر چهار حرف، اولی و آخریش باید صفر باشه. دیکُدِر رو دست زدم که هروقت غیر از این دید خودش رو تنظیم کنه. یعنی اگه حرف اول خدای‌ناکرده یک بود، بفهمه که تُند رفته و حرفِ دوّم رو خونده عملا، پس یه حرف کم‌تر بخونه، و برعکس برای حرفِ چهارم.

با این کار چند جمله تست‌مون بی‌نقص رفت، پس «رفتیم که داشته‌باشیم» درایورِ شبکه رو! یه حسابِ سرانگشتی:

۴۰ * ۱۰۲۴ * ۸ * ۱۰۰ * ۴ ÷ ۴۴۱۰۰ ÷ ۶۰ ≈ ۵۰

پنجاه دقه چُرت زدیم تا ماشینِ اوّل براش خوند و ماشینِ دوّم با جون و دل نُت ورداشت و به یاد سپُرد. فایل رو گرفتیم، آن‌زیپ کردیم، وا نشد، خطا داد. حالا چی؟!

یه وارسیِ چشمی کردیم فایل رو تو nc، دیدیم کلیت‌ش درست منتقل شده و اول و آخرش هم می‌خونه، پس نشستیم غلط‌هاشو درآریم! نشستیم دونفری پای دو ماشین، من دیکته کردم، دوتایی تایپ کردیم: ۲۰ خط کد که یه فایل و دو تا عددِ شروع و پایان بگیره و جمعِ بایت‌های فایل از شروع تا پایان رو بنویسه.

بعد شروع کردیم به جستجوی دودوئیِ بایت‌هایی که فایلِ من با روزبه فرق داشت... اینطور که من یه شروع و پایان می‌گفتم، جمعش رو با هم مقایسه می‌کردیم، اگه فرق داشت بازه رو دو نیم میکردیم و از اول (recurse).

در کل سه بیت غلط یافتیم؛ با دست درست کردیم و فایل آن‌زیپ شد. درایورِ شبکه نصب و کارتِ شبکه تِست شد! کابلِ صدا رو به بلندگویی که بهش تعلق داشت برگردوندیم، ماشینِ جدیدمون رو خاموش کردیم، چاییِ آخر رو زدیم، و چون دیگه ۷ِ صبح شده بود، از مرکز زدیم بیرون که بریم بخوابیم.

این بود ماجرای درایوِرِ شبکه. چند نکته‌ی آموزشی از این داستان به نظرِ من (پس از پونزده سال) درباره‌ی حل کردنِ مسئله:

۱. من نه مامانم تاحالا درایورِ مودِم نوشته بود نه بابام نه درسش رو خونده بودم نه چیزی، روزبه هم احتمالا همین‌طور. ولی با برخوردِ سیستماتیک، مسئله رو خورد کردیم به تیکه‌هایی که هرکدوم رو می‌دونستیم چه‌جوری حل کنیم.

۲. هنگام دیباگ کردنِ یه مسئله، شدیدا کمک می‌کنه اگه بتونی ویژوآلیزِیشِنِ (visualization) درست درآری. در اینجور مواقع، قدرتِ سیستمِ بینائیِ انسان تو کسری از ثانیه ناشی به درکی میشه که بدونِ این روش می‌تونه به‌دست‌آوردنش ساعت‌ها طول بکشه.

۳. شاید خیلی پایه‌ای تر، مهمّه که یاد بگیرین هر بار که گفتین «کار نمیکنه»، چجوری پیش برین. در واقع همیشه این کار به این می‌انجامه که قسمتی که کار نمیکنه رو به تکه‌های متعدد تقسیم کنین و یکی‌یکی اون‌ها رو تست کنین تا دایره‌ی ظنّ رو کاهش بدین تا وقتی که مشکل بدیهی بشه.

۴. هر راه‌حلّی یه مصالحه (tradeoff) هست. در این مورد، ما بینِ پیچیدگی و درنتیجه زمانِ پیاده‌سازیِ الگوریتم‌های coder و decoder از یک‌طرف، و طولِ فایلِ صوتیِ حاصل تعادل برقرار کردیم. طوری که وقتی راه‌حل به جایی رسید که فایلِ صوتی پنجاه دقیقه شد، به جای اینکه سه ساعت وقت بذاریم از پنجاه دقیقه بکنیم‌ش ۵ دقیقه، قبول کردیم که سریع‌تره که همین ۵۰دقه‌ای رو بفرستیم بره. لازم نبود کوتاه‌ترین باشه، لازم بود فقط به‌اندازه‌ی کافی کوتاه باشه!

یه گریزی هم این وسط بزنم: دوستی داشتیم که تو شرکتش ترجیحن دانشگاه آزادی استخدام می‌کرد و شریفی استخدام نمیکرد، پرسیدیم چرا، گفت چون شریفیه یه مسئله میدی بهش میگی دوهفته‌ای حل کن، بعدِ یه هفته حل میکنه میاره، بعدِ یه هفته دیگه میاره دو برابر سریع‌ترش کرده، بعدِ یه هفته دیگه میاره دو برابر کوچیک‌ترش کرده، بعدِ یه هفته دیگه میاره دو برابر قوی‌ترش کرده، ... دانشگاه آزادیه یه مسئله میدی بهش میگی دوهفته‌ای حل کن، بعدِ دو هفته میاره تحویل میده مسئله‌ی بعد رو میگیره میره. :))

۵. ابزارِ درست رو داشتن فرقِ بینِ موفقیت و شکسته. خیلی‌ها رو میبینم که یه خروار کد رو می‌خوان دیباگ کنن، بعد اصلا ابزار و محیطِ اجرا و خلاصه مقدماتِ اینکه بتونن مسئله رو بشکونن به اجزای کوچیک‌تر ندارن. فقط می‌تونن کُلِّش رو اجرا کنن و بگن «کار نمی‌کنه».

نکته: اگه به طورِ روزمرّه با کامپیوتر سروکار دارین، ابزار نوشتن باید یکی از عادات‌تون باشه. حالا ابزار می‌تونه ماکروی اِکسل باشه یا اسکریپتِ شِل یا پایتون یا کدِ سی. می‌تونه یه‌بارمصرف باشه، یا بیست سال استفاده شه. می‌تونه فقط مالِ شما بمونه، یا هزاران کاربر پیدا کنه.

۶. در نهایت، شاید تایپ می‌کردیم هم همونقدر طول می‌کشید. و اصلا مهم نبود تا شنبه نفهمیم کارتِ شبکه سالمه یا نه! ولی لذّتی که تو اون شب تا صبح بردیم، تا امروز برای من تو حلِّ کمتر مسئله‌ای تکرار شده، و پونزده سال بعد دارم با جزئیات براتون می‌نویسم.

۷. هروقت خواستی بگی «نمیشه»، به این فکر کن که چرا نمیشه؟ می‌تونی ثابت کنی که نمیشه؟ یکی از چیزهای دیگه که تو این پونزده سال یاد گرفتم اینه که وقتی بقیه میگن «نمیشه»، به حرف‌شون توجه نکنم. در اکثر موارد نتیجه این شده که نشون دادم که میشه.

۸. من بیشتر با این‌جور بازی‌ها و کِرم‌ها چیز یاد گرفتم تا سرِ کلاسِ درس!

حدودِ ده سال بعد از این ماجرا، روزبه یه تی‌شرت با طرحِ زیر برام فرستاد، دستش درد نکنه، خوب روزایی بود


منبع: https://www.facebook.com/behdad/posts/10156172852130604