جالبه بیتکوین هنوز برای فهمیدن اینکه کی باید از سوکت بخونه داره از (2)select و poll() استفاده میکنه:
https://github.com/bitcoin/bitcoin/blob/master/src/util/sock.cpp#L223
@knowpow
https://github.com/bitcoin/bitcoin/blob/master/src/util/sock.cpp#L223
@knowpow
GitHub
bitcoin/src/util/sock.cpp at master · bitcoin/bitcoin
Bitcoin Core integration/staging tree. Contribute to bitcoin/bitcoin development by creating an account on GitHub.
🤔1🍾1
ارتباط بین کرنل و کارت شبکه NIC
امروز یه چیز جالبی در مورد کرنل خوندم و این بود که توی کرنل های قبل ۲.۶ هر بار که پکت جدیدی توسط کارت شبکه دریافت میشه، کارت شبکه (NIC) پکت داده رو روی مموری میزاره و وجود پکت رو با اینتراپت به کرنل خبر میده، کرنل با این اینتراپت متوجه وجود پکت جدید میشه و شروع میکنه به خوندن داده، خیلی هم عالی، مشکل کجاست؟!
مشکل وقتی بوجود میاد که ترافیک دریافتی میره بالا و تعداد این اینتراپت ها زیاد میشه و توی کار CPU تداخل ایجاد میکنه. (اگه نیاز هست بگم که هر اینتراپتی که اتفاق میوفته CPU رو مجبور میکنه هر تسکی که داره رو ول کنه و به اینتراپت رسیدگی کنه)
حالا جالبترم میشه، یه حالت دیگه هم وجود داره که کرنل دوره ای کارت شبکه رو چک میکنه و میبینه اگه دیتای جدیدی برای پردازش بود اون رو پردازش میکنه، به این حالت هم میگیم Polling مشکل مورد اول اینه که وقتی ترافیک ورودی از یه حدی زیادتر میره CPU زیر بار اینتراپت میمونه و به اصطلاح Interrupt storm اتفاق میوفته، مشکل مورد دوم هم اینه که latency نسبتا بالایی داره، یعنی اگر کرنل بیاد و هر ۱۰۰ میلی ثانیه قرار باشه که NIC رو چک کنه و یک پکت توی ۷۰ ام میلی ثانیه امادهی پردازش باشه باید ۳۰ میلی ثانیه منتظر تشریف اوردن کرنل بشه.
حالا چطور مشکل رو حل کردن؟ اومدن گفتن اقا ما بیاییم یه Api جدید بنویسیم و اسمش رو بزاریم NAPI و بهش بگم با توجه به ترافیک ورودی بیا و بین حالت اول که interrupt driven هست و مورد دوم که polling هست سوییچ کن، یعنی NIC بیا و ببین وقتی ترافیک بالا نیست روی interrupt باش و وقتی ترافیک حد تعیین شده رو گذشت به polling سوییچ کن جالب شد نه؟ اره ولی ما هنوز مشکل interrupt storm و latency رو حل نکردیم، فقط اومدیم داینامیک کردیم سیستم رو تا بین این حالات سوییچ کنه، پس میاییم با تلفیق این دو مدل چندتا حالت دیگه هم تعریف میکنیم و میگیم که وقتی کارت شبکه اینتراپتی تولید کرد، به یک هسته توی CPU نره و اون رو بین هسته های دیگه هم توزیع بکنه، به این روش میگن Receive Packet Steering (RPS).
روش دیگه استفاده از یک اینتراپت برای پردازش چندین پکت داده اس، یعنی وقتی یه پکت داده وارد NIC میشه و NIC اون رو اماده میکنه، همون لحظه اینتراپت نمیده و صبر میکنه چون احتمال اومدن پکت های دیگه هست، و بعد از مدتی دستور اینتراپت رو میده، اینجا مشکل storm حل شد ولی بازم مشکل latency رو داریم.
روش اخر و فوق العاده جالبی که من رو واقعا شیفته ی خودش کرد این بود که کارت شبکه مستقیم دیتا رو توی کش cpu میزاره و تمام. Cpu میاد و بدون هیچ اینتراپتی داده رو میخونه. به این روش Direct Cache Access (DCA) میگن. اینجا دیگه نوشتن روی مموری توسط NIC حذف شده و مستقیم توی کش cpu نوشته میشه.
روش های متعدد دیگه ای هم هست که من باهاشون حال نکردم اما ایدهی اصلی تمامی این روش ها بازی کردن با دوتا مفهوم آشنای دنیای مهندسی نرم افزار به اسم های Inversion of Control (IoC) که میگه تو از من چیزی نپرس، خبری شد خودم بهت میگم و Polling که میگه من نمیتونم بهت خبر بدم، خودت مکرر باید ازم بپرسی هست، به عبارتی یا کرنل باید از کارت شبکه بپرسه که چیز جدیدی برای پردازش داری؟! یا کارت شبکه به کرنل خبر بده که دادهی جدیدی برای پردازش دارم.
حین اینکه کرنل و کتابهای مرتبط رو میخونم این چند روز سعی میکنم بیشتر در موردش پست بزارم
@knowpow
امروز یه چیز جالبی در مورد کرنل خوندم و این بود که توی کرنل های قبل ۲.۶ هر بار که پکت جدیدی توسط کارت شبکه دریافت میشه، کارت شبکه (NIC) پکت داده رو روی مموری میزاره و وجود پکت رو با اینتراپت به کرنل خبر میده، کرنل با این اینتراپت متوجه وجود پکت جدید میشه و شروع میکنه به خوندن داده، خیلی هم عالی، مشکل کجاست؟!
مشکل وقتی بوجود میاد که ترافیک دریافتی میره بالا و تعداد این اینتراپت ها زیاد میشه و توی کار CPU تداخل ایجاد میکنه. (اگه نیاز هست بگم که هر اینتراپتی که اتفاق میوفته CPU رو مجبور میکنه هر تسکی که داره رو ول کنه و به اینتراپت رسیدگی کنه)
حالا جالبترم میشه، یه حالت دیگه هم وجود داره که کرنل دوره ای کارت شبکه رو چک میکنه و میبینه اگه دیتای جدیدی برای پردازش بود اون رو پردازش میکنه، به این حالت هم میگیم Polling مشکل مورد اول اینه که وقتی ترافیک ورودی از یه حدی زیادتر میره CPU زیر بار اینتراپت میمونه و به اصطلاح Interrupt storm اتفاق میوفته، مشکل مورد دوم هم اینه که latency نسبتا بالایی داره، یعنی اگر کرنل بیاد و هر ۱۰۰ میلی ثانیه قرار باشه که NIC رو چک کنه و یک پکت توی ۷۰ ام میلی ثانیه امادهی پردازش باشه باید ۳۰ میلی ثانیه منتظر تشریف اوردن کرنل بشه.
حالا چطور مشکل رو حل کردن؟ اومدن گفتن اقا ما بیاییم یه Api جدید بنویسیم و اسمش رو بزاریم NAPI و بهش بگم با توجه به ترافیک ورودی بیا و بین حالت اول که interrupt driven هست و مورد دوم که polling هست سوییچ کن، یعنی NIC بیا و ببین وقتی ترافیک بالا نیست روی interrupt باش و وقتی ترافیک حد تعیین شده رو گذشت به polling سوییچ کن جالب شد نه؟ اره ولی ما هنوز مشکل interrupt storm و latency رو حل نکردیم، فقط اومدیم داینامیک کردیم سیستم رو تا بین این حالات سوییچ کنه، پس میاییم با تلفیق این دو مدل چندتا حالت دیگه هم تعریف میکنیم و میگیم که وقتی کارت شبکه اینتراپتی تولید کرد، به یک هسته توی CPU نره و اون رو بین هسته های دیگه هم توزیع بکنه، به این روش میگن Receive Packet Steering (RPS).
روش دیگه استفاده از یک اینتراپت برای پردازش چندین پکت داده اس، یعنی وقتی یه پکت داده وارد NIC میشه و NIC اون رو اماده میکنه، همون لحظه اینتراپت نمیده و صبر میکنه چون احتمال اومدن پکت های دیگه هست، و بعد از مدتی دستور اینتراپت رو میده، اینجا مشکل storm حل شد ولی بازم مشکل latency رو داریم.
روش اخر و فوق العاده جالبی که من رو واقعا شیفته ی خودش کرد این بود که کارت شبکه مستقیم دیتا رو توی کش cpu میزاره و تمام. Cpu میاد و بدون هیچ اینتراپتی داده رو میخونه. به این روش Direct Cache Access (DCA) میگن. اینجا دیگه نوشتن روی مموری توسط NIC حذف شده و مستقیم توی کش cpu نوشته میشه.
روش های متعدد دیگه ای هم هست که من باهاشون حال نکردم اما ایدهی اصلی تمامی این روش ها بازی کردن با دوتا مفهوم آشنای دنیای مهندسی نرم افزار به اسم های Inversion of Control (IoC) که میگه تو از من چیزی نپرس، خبری شد خودم بهت میگم و Polling که میگه من نمیتونم بهت خبر بدم، خودت مکرر باید ازم بپرسی هست، به عبارتی یا کرنل باید از کارت شبکه بپرسه که چیز جدیدی برای پردازش داری؟! یا کارت شبکه به کرنل خبر بده که دادهی جدیدی برای پردازش دارم.
حین اینکه کرنل و کتابهای مرتبط رو میخونم این چند روز سعی میکنم بیشتر در موردش پست بزارم
@knowpow
👏14👍6❤3🔥2🤔1🍾1
گذشته رو میدونیم ولی نمیتونیم تغییرش بدیم، آینده رو نمیدونیم ولی میتونیم کنترلش کنیم.
همتون میدونین جبر بولی چیه ولی نمیدونین که کلود شانون اولین کسی بود که از جبر بولی توی طراحی کامپیوتر استفاده کرد.
@knowpow
همتون میدونین جبر بولی چیه ولی نمیدونین که کلود شانون اولین کسی بود که از جبر بولی توی طراحی کامپیوتر استفاده کرد.
@knowpow
👍9🔥2👎1
ویدیویی که لینکش رو گذاشتم یه ماشین IBM 1401 رو نشون میده که داره FORTRAN II رو اجرا میکنه:
https://www.youtube.com/watch?v=uFQ3sajIdaM
@knowpow
https://www.youtube.com/watch?v=uFQ3sajIdaM
@knowpow
YouTube
The IBM 1401 compiles and runs FORTRAN II
We attempt to compile and run a simple FORTRAN program on our vintage 1959 IBM mainframe computer at the Computer History Museum. FORTRAN is a big stretch for this business oriented machine, with 16k memory and a CPU not meant at all for scientific applications.…
👍4💯1
دسترسی به یک چیپست RAM از چند هستهی CPU
همه چی با این سوال شروع میشه که اوکی ما یک چیپ RAM داریم و یک CPU، مگه CPU چند هسته ای نیست؟ چطور میتونن همزمان به یک چیپ RAM دسترسی داشته باشند؟
از پایین ترین لایه بیاییم بالا، ما یک باس داده داریم و دو تا واحد مختلف که از طریق این باس داده با هم صحبت میکنند، موجودیت اول که CPU باشه خودش چند هسته ای هست و ممکنه یک هسته بدون اطلاع هستهی دیگه بخواد با موجودیت دوم که RAM باشه صحبت کنه، اوکی صحبت کنه ولی اگه این باس داده از قبل توسط یک هستهی دیگه اشغال شده بود و RAM در حال سرویس دادن به یک هستهی دیگه از CPU بود باید چیکار کنه؟ باید صبر کنه.
دقیقا مثل اینه که به یک نفر زنگ میزنید ولی خطش اشغاله و شما باید صبر کنید تا تماسش تموم بشه.
حالا کی این فرایند اشغال بودن باس داده و منتظر نگهداشتن رو هندل میکنه؟ Memory Arbiter
این Memory Arbiter هست که به شکل یک مدار سختافزاری بین باس و هر چیپ RAM قرار میگیره تا دسترسی CPU به رم رو مدیریت کنه، ینی اگه چیپ خالی باشه دسترسی بده و اگه چیپ در حال کال شدن از هستهی دیگه ای باشه اون رو به تاخیر بندازه.
تو سیستمهای چندهستهای، چرا Arbiter ها مهم هستن؟ چون عملیات خوندن یا نوشتن روی یک چیپ RAM باید به صورت سریالی انجام بشه نه موازی.
جالبتر اینجاست که سیستمهای تک هسته ای هم از آربیتر استفاده میکنند. چون اگه یادتون باشه توی مثال کارت شبکه که در مورد DMA نوشتم گفتم که:
خب اینجا هم یکیشون اگه از قبل در حال استفاده باشند بین تک هستهی cpu و کارت شبکه برای دسترسی به رم مشکل خواهیم داشت به همین دلیل سیستم های تکهسته ای هم از Memory Arbiter استفاده میکنند.
@knowpow
همه چی با این سوال شروع میشه که اوکی ما یک چیپ RAM داریم و یک CPU، مگه CPU چند هسته ای نیست؟ چطور میتونن همزمان به یک چیپ RAM دسترسی داشته باشند؟
از پایین ترین لایه بیاییم بالا، ما یک باس داده داریم و دو تا واحد مختلف که از طریق این باس داده با هم صحبت میکنند، موجودیت اول که CPU باشه خودش چند هسته ای هست و ممکنه یک هسته بدون اطلاع هستهی دیگه بخواد با موجودیت دوم که RAM باشه صحبت کنه، اوکی صحبت کنه ولی اگه این باس داده از قبل توسط یک هستهی دیگه اشغال شده بود و RAM در حال سرویس دادن به یک هستهی دیگه از CPU بود باید چیکار کنه؟ باید صبر کنه.
دقیقا مثل اینه که به یک نفر زنگ میزنید ولی خطش اشغاله و شما باید صبر کنید تا تماسش تموم بشه.
حالا کی این فرایند اشغال بودن باس داده و منتظر نگهداشتن رو هندل میکنه؟ Memory Arbiter
این Memory Arbiter هست که به شکل یک مدار سختافزاری بین باس و هر چیپ RAM قرار میگیره تا دسترسی CPU به رم رو مدیریت کنه، ینی اگه چیپ خالی باشه دسترسی بده و اگه چیپ در حال کال شدن از هستهی دیگه ای باشه اون رو به تاخیر بندازه.
تو سیستمهای چندهستهای، چرا Arbiter ها مهم هستن؟ چون عملیات خوندن یا نوشتن روی یک چیپ RAM باید به صورت سریالی انجام بشه نه موازی.
جالبتر اینجاست که سیستمهای تک هسته ای هم از آربیتر استفاده میکنند. چون اگه یادتون باشه توی مثال کارت شبکه که در مورد DMA نوشتم گفتم که:
کارت شبکه (NIC) پکت داده رو روی مموری میزاره و وجود پکت رو با اینتراپت به کرنل خبر میده، کرنل با این اینتراپت متوجه وجود پکت جدید میشه و شروع میکنه به خوندن داده
خب اینجا هم یکیشون اگه از قبل در حال استفاده باشند بین تک هستهی cpu و کارت شبکه برای دسترسی به رم مشکل خواهیم داشت به همین دلیل سیستم های تکهسته ای هم از Memory Arbiter استفاده میکنند.
@knowpow
👍10🔥4❤1
من یه تست ساده به اسم "99 درصد" انجام دادم تا بگم وقتی میگیم پرفورمنس منظورمون فور زدن توی یه لیست چند هزار تایی نیست! و پرفورمنس فانتزی نیست...
هرچند مقایسه کردن زبونی مثل سی و پایتون اشتباه محضه ولی سرم درد میکرد برا مقایسه، منظورمون از پرفورمنس اینه:
نتیجه perf کد پرینت در پایتون:
نتیجه perf کد پرینت در سی:
@knowpow
هرچند مقایسه کردن زبونی مثل سی و پایتون اشتباه محضه ولی سرم درد میکرد برا مقایسه، منظورمون از پرفورمنس اینه:
نتیجه perf کد پرینت در پایتون:
11.29 msec task-clock
36,937,232 cycles
40,528,451 instructions
3,239,576 cache-references
689,931 cache-misses
520,379 branch-misses
نتیجه perf کد پرینت در سی:
0.62 msec task-clock
2,059,550 cycles
1,152,194 instructions
90,595 cache-references
31,567 cache-misses
42,675 branch-misses
@knowpow
👍21🍾3👎1
کرنل چطور میدونه توی این لحظه کدوم پروسس روی یک هستهی cpu در حال اجراست؟
کرنل تسک(پروسس)هایی که در حال اجرا هستن رو توی یک Linked List به صورت double به اسم task list نگهداری میکنه، ساختمون داده ای که این linked list نگهداری میکنه task_struct هست که نسبتا بزرگه و تمامی اطلاعاتی که کرنل نیاز داره تا بروی پروسس ها context switch انجام بده یا چه فایل هایی توسط این پروسس بازه، وضعیت پروسس چیه، فضای آدرس حافظه کجاست و ... رو توی خودش داره.
نکته:
هر پروسس توی فضای کرنل یک استک داره که کرنل اطلاعات مربوط به این پروسس، ادرس برگشت متد ها، متغیرهای محلی و... رو توش نگهمیداره. و توی معماری x86 کرنل پوینتر ساختمون داده task_struct متعلق به هر پروسس رو توی انتهای استک همون پروسس میزاره، یعنی اگه از انتهای استک یک پروسس به عقب برگردیم به اطلاعات اون پروسس دسترسی داریم(البته که توی فضای کرنل).
خب حالا خود کرنل چطور به تسکی که الان در حال اجراست میرسه تا بتونه اصلا به task_struct دسترسی داشته باشه؟
همونطور که بالاتر گفتم لیست تسک ها توی یک linked list نگهداری میشن و برای اینکه بتونیم تسکی که در حال اجراست رو پیدا کنیم باید کل لیست رو بگردیم، شاید روی ماشین های خونگی به چشم نیاد ولی روی سرورهایی که چندین هزار پروسس میتونن داشته باشن O(n) هزینهی بالایی محسوب میشه. راه حل؟ کرنل میاد برای اینکه تسک درحال اجرا رو پیدا کنه از ماکروی current استفاده میکنه که اون هم متد current_thread_info() رو کال میکنه. حالا با توجه به معماری یا ادرس این ساختمون داده روی یکی از رجیستر های cpu میزاره. یعنی دیگه نیازی نیست کل لیست رو بگردیم.
پیاده سازی این متد به معماری سخت افزار وابسته اس، برای مثال توی ماشین های power pc IBM رو اجرا میکنند کرنل این اطلاعات رو توی رجیستر r2 نگهداری میکنه و از طریق اسمبلی بهش دسترسی داره:
حالا شاید بگین که اوکی ما دیگه چه نیازی به لیست داریم وقتی اطلاعات توی رجیستر وجود داره؟
در حقیقت لیست برای اینه که scheduler بدونه تسک بعدی متعلق به کیه و آیا باید اجرا بشه یا نه؟! اینجا برای انتخاب کردن تسک بعدی کرنل پارامترای اون پروسس رو چک میکنه که مهمترینشون state و priority هست، بعد از اینکه تسک بعدی انتخاب شد، اطلاعات تسک فعلی روی استک نوشته میشه و اطلاعات تسک بعدی روی رجیستر بارگیری میشه.
جمع بندی بخوام بکنم میشه:
- زمانی که پروسس جدید با fork یا clone ساخته میشه کرنل کپی task_struct از parent پروسس جدید هست رو توی task list که یک linked list گلوبال توی فضای کرنل هست اضافه میکنه.
- ماژول scheduler میاد و بر اساس اولویت و پارامتر های دیگه بین پروسس ها برای هر هستهی cpu عملیات context-switching رو انجام میده ولی قبلش مقدار جای فعلی استک، رجیسترهای cpu و وضعیت پروسس رو توی task_struct ذخیره کنه و بعد از انتخاب عملیات context swithing رو انجام بده.
- کرنل باید پوینتر ماکروی current رو به روز کنه تا به task_struct تسک جدید اشاره کنه و این ماکرو از طریق رجیسترهای cpu با سرعت بالا به task_struct دسترسی پیدا میکنه.
- وضعیت تسک جدید توی رجیسترهای cpu و از استک بارگیری میشه.
پس جواب ما برای سوال: کرنل چطور میدونه توی این لحظه کدوم پروسس روی یک هستهی cpu در حال اجراست؟ میشه اینکه کرنل اطلاعات رو توی یک لینک لیست نگهداری میکنه و پوینتر تسک فعلی رو هم برای اینکه O(n) هزینه نکنه از رجیستر میخونه.
منبع ۱
منبع ۲
@knowpow
کرنل تسک(پروسس)هایی که در حال اجرا هستن رو توی یک Linked List به صورت double به اسم task list نگهداری میکنه، ساختمون داده ای که این linked list نگهداری میکنه task_struct هست که نسبتا بزرگه و تمامی اطلاعاتی که کرنل نیاز داره تا بروی پروسس ها context switch انجام بده یا چه فایل هایی توسط این پروسس بازه، وضعیت پروسس چیه، فضای آدرس حافظه کجاست و ... رو توی خودش داره.
نکته:
هر پروسس توی فضای کرنل یک استک داره که کرنل اطلاعات مربوط به این پروسس، ادرس برگشت متد ها، متغیرهای محلی و... رو توش نگهمیداره. و توی معماری x86 کرنل پوینتر ساختمون داده task_struct متعلق به هر پروسس رو توی انتهای استک همون پروسس میزاره، یعنی اگه از انتهای استک یک پروسس به عقب برگردیم به اطلاعات اون پروسس دسترسی داریم(البته که توی فضای کرنل).
خب حالا خود کرنل چطور به تسکی که الان در حال اجراست میرسه تا بتونه اصلا به task_struct دسترسی داشته باشه؟
همونطور که بالاتر گفتم لیست تسک ها توی یک linked list نگهداری میشن و برای اینکه بتونیم تسکی که در حال اجراست رو پیدا کنیم باید کل لیست رو بگردیم، شاید روی ماشین های خونگی به چشم نیاد ولی روی سرورهایی که چندین هزار پروسس میتونن داشته باشن O(n) هزینهی بالایی محسوب میشه. راه حل؟ کرنل میاد برای اینکه تسک درحال اجرا رو پیدا کنه از ماکروی current استفاده میکنه که اون هم متد current_thread_info() رو کال میکنه. حالا با توجه به معماری یا ادرس این ساختمون داده روی یکی از رجیستر های cpu میزاره. یعنی دیگه نیازی نیست کل لیست رو بگردیم.
پیاده سازی این متد به معماری سخت افزار وابسته اس، برای مثال توی ماشین های power pc IBM رو اجرا میکنند کرنل این اطلاعات رو توی رجیستر r2 نگهداری میکنه و از طریق اسمبلی بهش دسترسی داره:
register struct task_struct *current asm ("r2");
حالا شاید بگین که اوکی ما دیگه چه نیازی به لیست داریم وقتی اطلاعات توی رجیستر وجود داره؟
در حقیقت لیست برای اینه که scheduler بدونه تسک بعدی متعلق به کیه و آیا باید اجرا بشه یا نه؟! اینجا برای انتخاب کردن تسک بعدی کرنل پارامترای اون پروسس رو چک میکنه که مهمترینشون state و priority هست، بعد از اینکه تسک بعدی انتخاب شد، اطلاعات تسک فعلی روی استک نوشته میشه و اطلاعات تسک بعدی روی رجیستر بارگیری میشه.
جمع بندی بخوام بکنم میشه:
- زمانی که پروسس جدید با fork یا clone ساخته میشه کرنل کپی task_struct از parent پروسس جدید هست رو توی task list که یک linked list گلوبال توی فضای کرنل هست اضافه میکنه.
- ماژول scheduler میاد و بر اساس اولویت و پارامتر های دیگه بین پروسس ها برای هر هستهی cpu عملیات context-switching رو انجام میده ولی قبلش مقدار جای فعلی استک، رجیسترهای cpu و وضعیت پروسس رو توی task_struct ذخیره کنه و بعد از انتخاب عملیات context swithing رو انجام بده.
- کرنل باید پوینتر ماکروی current رو به روز کنه تا به task_struct تسک جدید اشاره کنه و این ماکرو از طریق رجیسترهای cpu با سرعت بالا به task_struct دسترسی پیدا میکنه.
- وضعیت تسک جدید توی رجیسترهای cpu و از استک بارگیری میشه.
پس جواب ما برای سوال: کرنل چطور میدونه توی این لحظه کدوم پروسس روی یک هستهی cpu در حال اجراست؟ میشه اینکه کرنل اطلاعات رو توی یک لینک لیست نگهداری میکنه و پوینتر تسک فعلی رو هم برای اینکه O(n) هزینه نکنه از رجیستر میخونه.
منبع ۱
منبع ۲
@knowpow
👏12👍7❤1
یه مدت نبودم، اینو داشته باشین که پستای آینده در مورد پروتکل HTTP و load-balancer توی لایه ی ۴ شبکه خواهد بود، البته که کرنل لینوکس رو هم میخونیم و چطور کار کردنش رو تشریح میکنیم.
👍27❤2🔥2🍾2💯1
همونطور که میدونین HTTP میاد روی TCP سوار میشه و توسط اون داده رو میفرسته، عملا پروتکل http چیزی جز یه مشت قوانین روی چند خط string نیست. اکثرا وظایفش توسط Tcp پیاده سازی شده و ما فکر میکنیم که Http داره نقشش رو خوب بازی میکنه.
حالا دوتا از این نقش هایی که توسط Tcp بازی میشن چی هستن؟
- اطمینان از تحویل داده: توی Http ما گارانتی اینو از پروتکل میگیریم که داده ها صحیح و سالم تحویل داده بشن
- ترتیب صحیح داده ها: توی شبکه ممکنه بستهها به صورت ناهمزمان و با ترتیبی غیر از ترتیب ارسال شده دریافت بشن، اما TCP این تضمین رو میده که دادهها توی مقصد به ترتیب ارسال مرتب و بازسازی بشن. خود HTTP هم از این ویژگی استفاده میکنه و نیازی به مدیریت ترتیب دادهها نداره.
به طور کلی، خیلی از ویژگی هایی که ما از HTTP میدونیم در حقیقت توسط TCP پیادهسازی میشن و HTTP بیشتر به عنوان یه لایه برای تعریف اون قوانینی که بالا گفتم برای تبادل این داده ها و نوع محتوای ارسال شده عمل میکنه.
همه ی اینارو گفتم که بگم تا حالا به این فکر کردین که بیاییم Http رو روی UDP پیاده سازی کنیم چی میشه؟ میشه پروتکل Http/3 که بهش میگن QUIC حالا چالشاش چیه؟ پستای بعدی میگم
@knowpow
حالا دوتا از این نقش هایی که توسط Tcp بازی میشن چی هستن؟
- اطمینان از تحویل داده: توی Http ما گارانتی اینو از پروتکل میگیریم که داده ها صحیح و سالم تحویل داده بشن
- ترتیب صحیح داده ها: توی شبکه ممکنه بستهها به صورت ناهمزمان و با ترتیبی غیر از ترتیب ارسال شده دریافت بشن، اما TCP این تضمین رو میده که دادهها توی مقصد به ترتیب ارسال مرتب و بازسازی بشن. خود HTTP هم از این ویژگی استفاده میکنه و نیازی به مدیریت ترتیب دادهها نداره.
به طور کلی، خیلی از ویژگی هایی که ما از HTTP میدونیم در حقیقت توسط TCP پیادهسازی میشن و HTTP بیشتر به عنوان یه لایه برای تعریف اون قوانینی که بالا گفتم برای تبادل این داده ها و نوع محتوای ارسال شده عمل میکنه.
همه ی اینارو گفتم که بگم تا حالا به این فکر کردین که بیاییم Http رو روی UDP پیاده سازی کنیم چی میشه؟ میشه پروتکل Http/3 که بهش میگن QUIC حالا چالشاش چیه؟ پستای بعدی میگم
@knowpow
❤26🔥8👍7👎1👏1🍾1
When I was a young man, Dirac was my hero. He made a breakthrough, a new method of doing physics. He had the courage to simply guess at the form of an equation, the equation we now call the Dirac equation, and to try to interpret it afterwards.
- Richard Feynman
@knowpow
- Richard Feynman
@knowpow
👍3🔥3❤1💯1
کلیت ماجرای load balancer خیلی ساده اس و حتی با 100 خط میشه جمش کرد. یه سرور بین ارتباط کلاینت و سرور میاد و رکویست هایی که از سمت کلاینت میاد رو با توجه به استراتژی که داره ارسال میکنه. همین.
چی پیچیده اش میکنه؟
1- پیاده سازی caching:
لود بالانسر باید بتونه ریسپانس رو کش کنه، فرض کنید کاربر وارد سایت فروشگاهی ایکس میشه و رکویست صفحه ی اصلی رو میفرسته، لود بالانسر اون رو میگیره و به سرور اصلی میفرسته و بعد از اینکه ریسپانس رو گرفت اون رو کش میکنه تا کاربر بعدی که درخواست صفحه ی اصلی رو فرستاد بدون اینکه به سرور اصلی رکویست بفرسته از کش خودش به کاربر جواب رو برگردونه. حالا خود این کش کردن هم یه عالمه داستان داره که از کجا بخونه؟ ردیس؟ فایل؟ ... پس اینم یه پیچیدگی جدی اضافه میکنه که باید پیاده سازی بشه.
2- انتخاب مقصد بر اساس پروتکل باید باشه: لود بالانسری که من دارم مینویسم توی لایه ی 4 ام کار میکنه و با کانکشن های TCP سر و کار داره، تو این لایه نمیتونیم رکویست هایی که از سمت کاربر میاد رو بر اساس هدر یا ادرس url به سرور بالادستی بفرستیم چون هنوز داده رو باز نکردیم ببینیم قراره به کجا ارسال بشه 👇
چی پیچیده اش میکنه؟
1- پیاده سازی caching:
لود بالانسر باید بتونه ریسپانس رو کش کنه، فرض کنید کاربر وارد سایت فروشگاهی ایکس میشه و رکویست صفحه ی اصلی رو میفرسته، لود بالانسر اون رو میگیره و به سرور اصلی میفرسته و بعد از اینکه ریسپانس رو گرفت اون رو کش میکنه تا کاربر بعدی که درخواست صفحه ی اصلی رو فرستاد بدون اینکه به سرور اصلی رکویست بفرسته از کش خودش به کاربر جواب رو برگردونه. حالا خود این کش کردن هم یه عالمه داستان داره که از کجا بخونه؟ ردیس؟ فایل؟ ... پس اینم یه پیچیدگی جدی اضافه میکنه که باید پیاده سازی بشه.
2- انتخاب مقصد بر اساس پروتکل باید باشه: لود بالانسری که من دارم مینویسم توی لایه ی 4 ام کار میکنه و با کانکشن های TCP سر و کار داره، تو این لایه نمیتونیم رکویست هایی که از سمت کاربر میاد رو بر اساس هدر یا ادرس url به سرور بالادستی بفرستیم چون هنوز داده رو باز نکردیم ببینیم قراره به کجا ارسال بشه 👇
👍16🍾1
پس اگر میخواییم که مسیریابی بر اساس داده ای که توی رکویست هست انجام بشه باید بیاییم پرتوکل مدنظرمون رو پیاده کنیم تا مسیریابی انجام بشه. به طور مثال: ما میخواییم توی کانفیگ لودبالانسرمون بنویسیم که اگر کاربر میخواست به x.com/users رکویست بفرسته اون رو به سمت سرور A هدایت کن، وقتی میخواست به x.com/feed رکویست فرستاد اون رو به سرور B بفرست. از اونجایی که این رکویست ها برای پروتکل HTTP هست و روی TCP نشسته(اینجا گفتم) ، ما باید اول از TCP داده رو بخونیم بعد با قوانین HTTP ببینیم رکویست قراره به کجا بره. حالا باید HTTP 2 و HTTP 3 رو هم پیاده سازی کنیم، حتی باید UDP رو هم پیاده سازی کنیم. پس عملا داریم به کد پیچیدگی اضافه میکنیم.
3- باید منابع رو درست مصرف کنه:
فکر کنید ما برای هر رکویست یه ترد باز کنیم (اینجا گفتم چرا بده)، اونوقت تعداد زیادی ترد داریم که منتظر IO موندن و هیچ کاری نمیکنن، یعنی از سیستم ریسورس گرفتن ولی میخورن و میخوابن. یا وقتی که کلاینت داده رو میفرسته ما یه بار اون رو توی یوزراسپیس از کرنل کپی کنیم و دوباره بعد از انتخاب سرور بالادستی اون رو به کرنل بفرستیم. پس باید بیاییم معماری درستی بچینیم که بیشترین استفاده رو از منابع سیستم ببریم.
4- باید روی همه ی سرورا اجرا بشه:
تعداد زیادی از شرکتای تجاری روی سرورای ماکروسافت کار میکنند(واقعا wtf) و ویندوز سیستم محبوبشونه، پس کد ما باید هم روی ویندوز کار کنه هم روی یونیکس و لینوکس. این هم پیچیدگی به کد اضافه میکنه.
یادتون نره ایده ی اصلی اینه که در نهایت بار روی یه سرور نباشه و لودبالانسر وظیفه اش برداشتن بار از روی سرور های بالادستی یا توزیع بار با توجه به درخواست یا هرچیز دیگه ای هست.
@knowpow
3- باید منابع رو درست مصرف کنه:
فکر کنید ما برای هر رکویست یه ترد باز کنیم (اینجا گفتم چرا بده)، اونوقت تعداد زیادی ترد داریم که منتظر IO موندن و هیچ کاری نمیکنن، یعنی از سیستم ریسورس گرفتن ولی میخورن و میخوابن. یا وقتی که کلاینت داده رو میفرسته ما یه بار اون رو توی یوزراسپیس از کرنل کپی کنیم و دوباره بعد از انتخاب سرور بالادستی اون رو به کرنل بفرستیم. پس باید بیاییم معماری درستی بچینیم که بیشترین استفاده رو از منابع سیستم ببریم.
4- باید روی همه ی سرورا اجرا بشه:
تعداد زیادی از شرکتای تجاری روی سرورای ماکروسافت کار میکنند(واقعا wtf) و ویندوز سیستم محبوبشونه، پس کد ما باید هم روی ویندوز کار کنه هم روی یونیکس و لینوکس. این هم پیچیدگی به کد اضافه میکنه.
یادتون نره ایده ی اصلی اینه که در نهایت بار روی یه سرور نباشه و لودبالانسر وظیفه اش برداشتن بار از روی سرور های بالادستی یا توزیع بار با توجه به درخواست یا هرچیز دیگه ای هست.
@knowpow
👍17👎1
پیاده سازی لود بالانسر Vortex با IO Uring
توی این کامیت دو تا کار جالب کردم:
۱- اومدم تو io uring از روشی استفاده کردم که تعداد سیستم کال هارو به حداقل برسونه. یعنی اگر توی روش قبلی ۱۰۰ تا سوکت به صورت همزمان به سرور وصا میشدن، باید ۱۰۰ تا سیستم کال انجام میدادیم، ولی الان توی یک سیستم کال همشون رو پردازش میکنیم:
وقتی که 1 رو به
۲- اومدم یکم دیزاین پترن و اینترفیس ریختم تو پروژه، طوری که الان هسته ی نوتیفیکیشن بر اساس io uring کار میکنه، ولی اگه خواستم فردا روی epoll یا هرچیز دیگه ای ببرم راحتتر انجام بشه
لینک فایلای تغیر داده شده:
https://github.com/aabolfazl/Vortex/pull/3/files
نظری یا سوالی اگه بود بهم بگین.
@knowpow
توی این کامیت دو تا کار جالب کردم:
۱- اومدم تو io uring از روشی استفاده کردم که تعداد سیستم کال هارو به حداقل برسونه. یعنی اگر توی روش قبلی ۱۰۰ تا سوکت به صورت همزمان به سرور وصا میشدن، باید ۱۰۰ تا سیستم کال انجام میدادیم، ولی الان توی یک سیستم کال همشون رو پردازش میکنیم:
while (true) {
io_uring_submit_and_wait(&ring_, 1);
unsigned cqe_count = 0;
unsigned head;
io_uring_for_each_cqe(&ring_, head, cqe) {
++cqe_count;
auto *request = static_cast<io_request *>(io_uring_cqe_get_data(cqe));
if (request) {
switch (request->type()) {
case io_request::request_type::accept:
request->socket().on_accept(request, cqe->res);
prepare_accept(request->socket());
break;
default:
core::logger::error("Unknown request type");
break;
}
delete request;
}
}
io_uring_cq_advance(&ring_, cqe_count);
}
وقتی که 1 رو به
io_uring_submit_and_wait میدیم، این متد منتظر فقط یک ایونت میشه تا ترد من رو ازاد کنه و لوپ اصلی شروع به کار کنه. خب مگه نگفتیم یک ایونت؟ پس چطور ممکنه ۱۰۰ تا سوکت رو همزمان داشته باشیم؟! این یک که گفتم به این معنی نیست که اگه فقط یک ایونت اتفاق بیفته تنها همون ایونت توی صف باشه، در واقع io_uring این قابلیت رو داره که چندین ایونت (مثلاً چندین درخواست از سوکتهای مختلف توی یک لحظه) رو توی یه مرحله جمعآوری کنه. یعنی اگه چندتا سوکت همزمان به به سرور من درخواست بفرستن، io_uring تمامی اوتارو توی یه سیستم کال مدیریت میکنه و همشون رو به صف completion queue اضافه میکنه. در نتیجه وقتی io_uring_submit_and_wait حداقل منتظر یه ایونته احتمالاً چندین ایونت به صف اضافه شده و توی لوپ با ماکرو io_uring_for_each_cqe همهی این ایونت ها رو بدون نیاز به سیستم کال جدید پردازش میکنیم.۲- اومدم یکم دیزاین پترن و اینترفیس ریختم تو پروژه، طوری که الان هسته ی نوتیفیکیشن بر اساس io uring کار میکنه، ولی اگه خواستم فردا روی epoll یا هرچیز دیگه ای ببرم راحتتر انجام بشه
لینک فایلای تغیر داده شده:
https://github.com/aabolfazl/Vortex/pull/3/files
نظری یا سوالی اگه بود بهم بگین.
@knowpow
GitHub
Type erasure structure by aabolfazl · Pull Request #3 · aabolfazl/Vortex
Summary by CodeRabbit
New Features
Introduced asynchronous socket handling with new async_socket interface.
Added socket_event_handler for managing various socket events.
Implemented io_uring_so...
New Features
Introduced asynchronous socket handling with new async_socket interface.
Added socket_event_handler for managing various socket events.
Implemented io_uring_so...
1👍13❤1💯1
An Inspired Engineer
چرا NGINX انقدر سریعه؟! بخش پنجم (آخر): Event Driven I/O -------------------------------------- توی پست قبلی گفتم که مدل نان بلاکینگ چیه، توی بخش اخر این سری پست ها میخوام در مورد ساختار ورکرها صحبت کنم. قبلا هم گفتم که وب سرور های سنتی مثل اپاچی از پترنی…
ما توی مدل های چند پروسس/ترد میاییم روی یه پورت با چندین سوکت گوش میدیم، ولی کرنله که میاد تصمیم میگیره که کانکشن جدید رو به کدوم ترد بده. یعنی اگه من ۳ تا ترد داشته باشم و توی سه تاشونم به یک پورت گوش بدم، کرنل تصمیم میگیره که کانکشن جدید رو کدوم ترد هندل کنه و بر اساس اون میاد سوکت مورد نظرش رو کال میکنه. عملا داره یه لودبالانسینگ انجام میده.
@knowpow
@knowpow
👍13🔥3
Forwarded from Morteza Bashsiz (Morteza Bashsiz)
درود دوستان
عمیقا باور دارم که اگه از چیزی خوشم اومد حتما باید ازش قدردانی کنم
من حقیقتا از خوندن پستهای این کانال لذت میبرم
اینم به عنوان قدردانی از مطالب زیبایی که مینویسه
عمیقا باور دارم که اگه از چیزی خوشم اومد حتما باید ازش قدردانی کنم
من حقیقتا از خوندن پستهای این کانال لذت میبرم
اینم به عنوان قدردانی از مطالب زیبایی که مینویسه
❤13🍾1
Morteza Bashsiz
درود دوستان عمیقا باور دارم که اگه از چیزی خوشم اومد حتما باید ازش قدردانی کنم من حقیقتا از خوندن پستهای این کانال لذت میبرم اینم به عنوان قدردانی از مطالب زیبایی که مینویسه
مرتضی عزیز لطف داره به من و منم ایشون رو با VPN و کانالی که توی یوتیوب دارن شناختم، ممنونم ازتون و خوش اومد میگم به دوستان جدیدی که عضو کانال شدن 🖖
👍11❤8🔥3
روش پیاده سازی ACK پروتکل TCP توی کرنل
خب وقتی اسم TCP میاد وسط اولین چیزی که به ذهنمون خطور میکنه اینه که TCP برعکس UDP گارانتی میکنه که پکت ارسالی شما نهایت به مقصدش برسه. یکی از راه هایی که میاد میفهمه چیزی ارسال شده یا نه اینه که به ازای دریافت هر پکت داده به گیرنده بگه که این پکتی که فرستادی رو گرفتمش(ACK).
خب تا اینجای ماجرا ما یه فرستنده داریم که داده میفرسته و گیرنده به ازای هر دریافت پکت به فرستنده میگه که پکت ارسال شده رو گرفتم، اینو بزاریم کنار بریم توی کد سمت سرور:
من یه سوکت میسازم، بایند میکنمش به پورت ۸۰۸۰، شروع میکنم به گوش دادن و وقتی کانکشن جدید میاد اکسپتش میکنم و ارتباط بین کانکشن کلاینت و من برقرار میشه:
حالا کلاینت شروع میکنه برای ما دیتا میفرسته ولی ما هیچ دیتایی رو با سیستم کال read() نمیخونیم، سوالی که پیش میاد اینه که ایا کلاینت بدون اینکه من read انجام بدم ACK رو دریافت میکنه؟ جواب بله اس، بله دریافت میکنه!
هنگامی که یک بسته میرسه، استک TCP کرنل بلافاصله اون رو توی بافر دریافت مینویسه و یه ACK برای فرستنده میفرسته. این ACK تایید می کنه که بسته به کرنل رسیده و برای خوندن از سمت برنامه ی من که از طریق Go نوشتم آماده است. با این حال دیگه TCP منتظر نمیمونه تا برنامه قبل از تایید بیاد و داده ها رو پردازش کنه، این جدایی بین لایه های انتقال و برنامه دقیقا چیزیه که TCP رو بهینه و سریع و پاسخگو نگه میداره و تضمین میکنه که فرستنده میتونه بدون منتظر بودن پردازش داده توسط برنامه(کد ما که بالا نوشته شده) به ارسال داده ادامه بده.
- خب پس داده ها کجان؟
+ اینجا این بافر رو یادتونه؟ کرنل دقیقا وقتی دیتا رو میگیره اون رو داخل این بافر میریزه و به فرستنده که کلاینت باشه ACK رو میفرسته. و دیگه منتظر برنامه نمیمونه تا تایید بده
@knowpow
خب وقتی اسم TCP میاد وسط اولین چیزی که به ذهنمون خطور میکنه اینه که TCP برعکس UDP گارانتی میکنه که پکت ارسالی شما نهایت به مقصدش برسه. یکی از راه هایی که میاد میفهمه چیزی ارسال شده یا نه اینه که به ازای دریافت هر پکت داده به گیرنده بگه که این پکتی که فرستادی رو گرفتمش(ACK).
خب تا اینجای ماجرا ما یه فرستنده داریم که داده میفرسته و گیرنده به ازای هر دریافت پکت به فرستنده میگه که پکت ارسال شده رو گرفتم، اینو بزاریم کنار بریم توی کد سمت سرور:
من یه سوکت میسازم، بایند میکنمش به پورت ۸۰۸۰، شروع میکنم به گوش دادن و وقتی کانکشن جدید میاد اکسپتش میکنم و ارتباط بین کانکشن کلاینت و من برقرار میشه:
listener := net.Listen("tcp", "localhost:8080")
fmt.Printf("Server listening on %s\n", address)
conn := listener.Accept()
fmt.Printf("Accepted connection from %s\n", conn.RemoteAddr().String())
// اینجا کلاینت داره برای ما داده میفرسته ولی ما نمیخونیم. نخوندن ما به معنی ارسال نکردن ACK توسط کرنل نیست.
حالا کلاینت شروع میکنه برای ما دیتا میفرسته ولی ما هیچ دیتایی رو با سیستم کال read() نمیخونیم، سوالی که پیش میاد اینه که ایا کلاینت بدون اینکه من read انجام بدم ACK رو دریافت میکنه؟ جواب بله اس، بله دریافت میکنه!
هنگامی که یک بسته میرسه، استک TCP کرنل بلافاصله اون رو توی بافر دریافت مینویسه و یه ACK برای فرستنده میفرسته. این ACK تایید می کنه که بسته به کرنل رسیده و برای خوندن از سمت برنامه ی من که از طریق Go نوشتم آماده است. با این حال دیگه TCP منتظر نمیمونه تا برنامه قبل از تایید بیاد و داده ها رو پردازش کنه، این جدایی بین لایه های انتقال و برنامه دقیقا چیزیه که TCP رو بهینه و سریع و پاسخگو نگه میداره و تضمین میکنه که فرستنده میتونه بدون منتظر بودن پردازش داده توسط برنامه(کد ما که بالا نوشته شده) به ارسال داده ادامه بده.
- خب پس داده ها کجان؟
+ اینجا این بافر رو یادتونه؟ کرنل دقیقا وقتی دیتا رو میگیره اون رو داخل این بافر میریزه و به فرستنده که کلاینت باشه ACK رو میفرسته. و دیگه منتظر برنامه نمیمونه تا تایید بده
@knowpow
Telegram
An Inspired Engineer
اگر بخوام مثال دیگه ای از نوشتن به صورت بلاکینگ بگم این تصویر به خوبی میتونه گویای ماجرا باشه، اینجا من نوشتن بروی I/O سوکت TCP رو مثال میزنم و سیستم کال write() رو توضیح میدم.
خب توی تصویر میبینیم که برای هر اندپوینت سوکتی که از کرنل درخواست باز کردن میکنیم…
خب توی تصویر میبینیم که برای هر اندپوینت سوکتی که از کرنل درخواست باز کردن میکنیم…
👍16🔥4🤔2
بنظرتون پستایی که نیاز به کد نشون دادن داره رو چطور بزارم؟
مثلا پست بالایی میخواستم با وایرشارک و کد کرنل نشون بدم اتفاقایی که میوفته رو
مثلا پست بالایی میخواستم با وایرشارک و کد کرنل نشون بدم اتفاقایی که میوفته رو
Final Results
45%
📝 پست وبلاگ باشه اینجا خلاصه اش رو بزار
54%
📹 ویدیو تو یوتیوب باشه ببینیم چی به چیه
29%
🤨 همین خوبه هرکی بخواد میره خودش میخونه