چرا NGINX انقدر سریعه؟!
بخش سوم: Non Blocking I/O
--------------------------------------
توی بخش قبلی فهمیدیم وقتی که یه سیستم کال برای باز کردن یک فایل انجام میشه چه اتفاقایی میوفته، و چه چیزهایی پتانسیل بلاک کردن پروسس برنامه رو دارند، توی این بخش میخوام در مورد مدل بعدی یعنی Nonblocking بنویسم.
نکته: توی مدل لینوکسی ترد با پروسس هیچ تفاوتی نداره، تنها امتیازی که ترد داره و توانایی دسترسی به مموری تردهای دیگه یا shared memory هست، پس هر وقت توی این سری پست ها گفتم ترد منظورم میتونه پروسس هم باشه، چون پروسس هم یه ترد محسوب میشه که ID اون برابر با PID هست.
نگهداری ترد ها برای سیستمعامل هزینه ی بالایی دارن، اولین هزینه ای که اینجا من بهشون اشاره میکنم انجام contex switching روی ترد ها توسط CPU هست، contex switching زمانی اتفاق میوفته که سیستمعامل cpu رو از یک ترد بگیره و به ترد دیگه ای بده. برای اینکار سیستم عامل باید اطلاعات فعلی که ترد داره (مثل کانتر برنامه، اطلاعات رجیستر، مموری الوکیت شده و..) رو ذخیره کنه و اطلاعات ترد بعدی روی روی سیستم لود کنه، برای همین اینکار هزینه ی محاسباتی بالایی داره و وقتی خیلی زیاد انجام بشه میتونه لود سیستم رو ببره بالا . (کتابخونه ی کوروتین کاتلین اینجا یه روش نسبتا خوبی رو پیاده سازی کرده که درموردش مینویسم)
اکثر وب سرورها برای هر رکویستی که دریافت میکنند یک ترد یا یک پروسس بهش اختصاص میدن، این کار از لحاظ پیاده سازی فنی آسونه و پیچیدگی کمی داره، ولی یه مشکل اساس وجود داره اینکه هر ترد بیشتر زمانش رو صرف منتظر موندن از I/O میکنه، یا منتظر آماده شدن سوکتی هست که روش باز شده تا دیتا بخونه، یا منتظره تا سوکتی که برای سروری origin باز کرده اماده ی نوشتن بشه.(این پست) ولی NGINX اینطوری نیست، با توجه به کانفیگ که من
ولی چطور؟! اگه بخوام توی یه خط جواب بدم میشه به کمک مدل Non Blocking I/O و معماری event driven ای که توسط epoll پیاده سازی کرده، توی پست های بعدی همین یه خط رو باز میکنم و تا عمق ماجرا میرم
توی پست بعدی دوباره در مورد Nonblocking I/O خواهم نوشت
@knowpow
بخش سوم: Non Blocking I/O
--------------------------------------
توی بخش قبلی فهمیدیم وقتی که یه سیستم کال برای باز کردن یک فایل انجام میشه چه اتفاقایی میوفته، و چه چیزهایی پتانسیل بلاک کردن پروسس برنامه رو دارند، توی این بخش میخوام در مورد مدل بعدی یعنی Nonblocking بنویسم.
نکته: توی مدل لینوکسی ترد با پروسس هیچ تفاوتی نداره، تنها امتیازی که ترد داره و توانایی دسترسی به مموری تردهای دیگه یا shared memory هست، پس هر وقت توی این سری پست ها گفتم ترد منظورم میتونه پروسس هم باشه، چون پروسس هم یه ترد محسوب میشه که ID اون برابر با PID هست.
نگهداری ترد ها برای سیستمعامل هزینه ی بالایی دارن، اولین هزینه ای که اینجا من بهشون اشاره میکنم انجام contex switching روی ترد ها توسط CPU هست، contex switching زمانی اتفاق میوفته که سیستمعامل cpu رو از یک ترد بگیره و به ترد دیگه ای بده. برای اینکار سیستم عامل باید اطلاعات فعلی که ترد داره (مثل کانتر برنامه، اطلاعات رجیستر، مموری الوکیت شده و..) رو ذخیره کنه و اطلاعات ترد بعدی روی روی سیستم لود کنه، برای همین اینکار هزینه ی محاسباتی بالایی داره و وقتی خیلی زیاد انجام بشه میتونه لود سیستم رو ببره بالا . (کتابخونه ی کوروتین کاتلین اینجا یه روش نسبتا خوبی رو پیاده سازی کرده که درموردش مینویسم)
اکثر وب سرورها برای هر رکویستی که دریافت میکنند یک ترد یا یک پروسس بهش اختصاص میدن، این کار از لحاظ پیاده سازی فنی آسونه و پیچیدگی کمی داره، ولی یه مشکل اساس وجود داره اینکه هر ترد بیشتر زمانش رو صرف منتظر موندن از I/O میکنه، یا منتظر آماده شدن سوکتی هست که روش باز شده تا دیتا بخونه، یا منتظره تا سوکتی که برای سروری origin باز کرده اماده ی نوشتن بشه.(این پست) ولی NGINX اینطوری نیست، با توجه به کانفیگ که من
worker_processes رو auto در نظر میگیرم، NGINX میاد و به تعداد هسته های موجود سیستم Worker Process میسازه، یعنی توی دنیای موازی اگر دوتا وب سرور با مشخصات سخت افزاری(۸ هسته) یکسان ولی روی یکیش NGINX و روی دیگری وب سرور x باشه و من ۱۰۰۰ تا رکویست همزمان رو سمت این دوتا سرور کنم وب سرور x به تعداد ۱۰۰۰ تا ترد میسازه ولی NGINX همه ی این ۱۰۰۰ رکویست رو روی همون ۸ تا پروسس(که هر کدوم فقط یک ترد دارند) تمامی این رکویست هارو مدیریت میکنه، اینکار از Contex switching زیاد جلوگیری میکنه چون دیگه نیازی نیست تردی رو بلاک کنیم و به ترد دیگه برای پردازش سوییچ کنیم، در حقیقت پروسس های ورکر NGINX رکویست رو میگیرن و اگه چیزی برای پردازش بود پردازش میکنند و اگه نبود ادامه میدن برای تسک بعدی.ولی چطور؟! اگه بخوام توی یه خط جواب بدم میشه به کمک مدل Non Blocking I/O و معماری event driven ای که توسط epoll پیاده سازی کرده، توی پست های بعدی همین یه خط رو باز میکنم و تا عمق ماجرا میرم
توی پست بعدی دوباره در مورد Nonblocking I/O خواهم نوشت
@knowpow
❤5👍2🎉2
An Inspired Engineer
چرا NGINX انقدر سریعه؟! بخش سوم: Non Blocking I/O -------------------------------------- توی بخش قبلی فهمیدیم وقتی که یه سیستم کال برای باز کردن یک فایل انجام میشه چه اتفاقایی میوفته، و چه چیزهایی پتانسیل بلاک کردن پروسس برنامه رو دارند، توی این بخش میخوام…
توی این تصویر یه نمای کلی از ساختار NGINX رو برای وقتی که تعداد ورکر ها بروی ۳ ست شده میبینیم، تعداد ورکر ها توی رفتار NGINX هیچ تاثیری نداره و فقط قدرت پردازش اون رو بالا میبره و horizontal scaling میکنه
چیزی که برام جالب بود این هست که وقتی کانفیگ جدید لود میشه، پروسس ورکرها دیگه درخواست باز کردن کانکشن جدید قبول نمیکنه، و NGINX دوباره n تعداد با کانفیگ جدید پروسس میسازه، بعد ورکرهایی که با کانفیگ قبلی کار میکردن اگه درخواست در حال پردازش یا keep-alive نداشته باشن خودشون رو terminate میکنن و دارفانی رو وداع میگن
پس میتونیم بگیم لود کردن کانفیگ جدید همون لحظه اعمال نمیشه و با توجه به بار سیستم بارگذاری میشه
@knowpow
چیزی که برام جالب بود این هست که وقتی کانفیگ جدید لود میشه، پروسس ورکرها دیگه درخواست باز کردن کانکشن جدید قبول نمیکنه، و NGINX دوباره n تعداد با کانفیگ جدید پروسس میسازه، بعد ورکرهایی که با کانفیگ قبلی کار میکردن اگه درخواست در حال پردازش یا keep-alive نداشته باشن خودشون رو terminate میکنن و دارفانی رو وداع میگن
پس میتونیم بگیم لود کردن کانفیگ جدید همون لحظه اعمال نمیشه و با توجه به بار سیستم بارگذاری میشه
@knowpow
👏4
چرا NGINX انقدر سریعه؟!
بخش چهارم: Non Blocking I/O
--------------------------------------
توی بخش قبلی فهمیدیم تردها چی هستن و رفتار NGINX در مقابل اونا چیه؟! در مورد worker processes نوشتم و چرایی وجود اونارو با هم بررسی کردیم، توی این پست در مورد NonBlocking I/O مینویسم.
خب توی این پست فهمیدیم که اصلا چرا block میشیم و گفتیم که راه حلمون استفاده از non blocking هست، اما چطور؟ عملا non blocking به هر کاری گفته میشه که برای ورودی خروجی بدون block کردن پروسس انجام میشه.
در حقیقت توی مدل non blocking وقتی ما یک درخواست I/O از کرنل میکینم، اگر امکان انجام اون درخواست به هر دلیل توی همون لحظه نباشه کرنل اون درخواست رو رد میکنه و پروسس ما بجای اینکه منتظر در دسترس بودن I/O بشه میتونه تسک های دیگه رو اجرا کنه، در عوض ما باید دوباره همون تسک رو از کرنل درخواست کنیم یا با مکانیزم های پولینگ زمانی که مناسب بود خبر دار بشیم، این روش کمک میکنه ما زمان سیپییو رو صرف منتظر موندن نکنیم و ازش کار مفید بگیریم.
فرض کنید اپ ما فقط یک پروسس داریم و یک سوکت باز کردیم و بعد از non block کردن اون تلاش میکنیم که به ادرس سرور وصل بشم، اگر ارتباط همون لحظه نتونه برقرار بشه کرنل همون لحظه بهمون ارور برمیگردونه و فیلد errno رو به EINPROGRESS ست میکنه، به این معنی هست که تنها پروسس من منتظر وصل شدن سوکت به سرور نمیشه، چون اینکار پتانسیل بلاک کردن ترد اپ رو داره.
حالا فرض کنید ما میخواییم زمانی که ارتباط با سرور برقرار شد شروع به ارسال داده به سرور کنیم، همونطور که گفتم سوکت رو non blocking کانکت میکنیم و توی هی لوپ که بهش busy loop هم میگن وضعیت سوکت رو بررسی میکنم، بعد از وصل شدن شروع به ارسال داده میکنم و از اونجا که ارسال داده هم به صورت non blocking انجام میگیره باید توی یه لوپ دیگه کنترل کنم که آیا کرنل تونسته داده رو ارسال کنه یا درخواست رو رد کرده تا مجدد تلاش کنیم؟!
شاید اینجا از خودتون بپرسین که خب درسته ما الان بلاک نمیشیم ولی داریم با لوپ هایی که برای کنترل در دسترس بودن I/O میکنیم کار بیهوده انجام میدیم، روشی هست که کرنل وقتی سوکت در دسترس بود به ما خبر بده؟ اینجا دقیقا همون نقطه ای هست که پروسس ما باید از مکانیزم های Polling یا Callbacks استفاده کنه تا حین انجام تسک های دیگه از وضعیت I/O هم با خبر بشه.
برگردیم به NGINX، در حقیقت پروسس های ورکر NGINX هر کدوم یه لوپ هستن که با استفاده از مکانیزم پولینگ به صورت نان بلاکینگ کانکشن های جدید رو قبول و داده هارو پردازش
میکنند.
پست بعدی تمرکز کامل روی نحوه ی انجام همین کار توسط NGINX خواهد بود
@knowpow
بخش چهارم: Non Blocking I/O
--------------------------------------
توی بخش قبلی فهمیدیم تردها چی هستن و رفتار NGINX در مقابل اونا چیه؟! در مورد worker processes نوشتم و چرایی وجود اونارو با هم بررسی کردیم، توی این پست در مورد NonBlocking I/O مینویسم.
خب توی این پست فهمیدیم که اصلا چرا block میشیم و گفتیم که راه حلمون استفاده از non blocking هست، اما چطور؟ عملا non blocking به هر کاری گفته میشه که برای ورودی خروجی بدون block کردن پروسس انجام میشه.
در حقیقت توی مدل non blocking وقتی ما یک درخواست I/O از کرنل میکینم، اگر امکان انجام اون درخواست به هر دلیل توی همون لحظه نباشه کرنل اون درخواست رو رد میکنه و پروسس ما بجای اینکه منتظر در دسترس بودن I/O بشه میتونه تسک های دیگه رو اجرا کنه، در عوض ما باید دوباره همون تسک رو از کرنل درخواست کنیم یا با مکانیزم های پولینگ زمانی که مناسب بود خبر دار بشیم، این روش کمک میکنه ما زمان سیپییو رو صرف منتظر موندن نکنیم و ازش کار مفید بگیریم.
فرض کنید اپ ما فقط یک پروسس داریم و یک سوکت باز کردیم و بعد از non block کردن اون تلاش میکنیم که به ادرس سرور وصل بشم، اگر ارتباط همون لحظه نتونه برقرار بشه کرنل همون لحظه بهمون ارور برمیگردونه و فیلد errno رو به EINPROGRESS ست میکنه، به این معنی هست که تنها پروسس من منتظر وصل شدن سوکت به سرور نمیشه، چون اینکار پتانسیل بلاک کردن ترد اپ رو داره.
حالا فرض کنید ما میخواییم زمانی که ارتباط با سرور برقرار شد شروع به ارسال داده به سرور کنیم، همونطور که گفتم سوکت رو non blocking کانکت میکنیم و توی هی لوپ که بهش busy loop هم میگن وضعیت سوکت رو بررسی میکنم، بعد از وصل شدن شروع به ارسال داده میکنم و از اونجا که ارسال داده هم به صورت non blocking انجام میگیره باید توی یه لوپ دیگه کنترل کنم که آیا کرنل تونسته داده رو ارسال کنه یا درخواست رو رد کرده تا مجدد تلاش کنیم؟!
شاید اینجا از خودتون بپرسین که خب درسته ما الان بلاک نمیشیم ولی داریم با لوپ هایی که برای کنترل در دسترس بودن I/O میکنیم کار بیهوده انجام میدیم، روشی هست که کرنل وقتی سوکت در دسترس بود به ما خبر بده؟ اینجا دقیقا همون نقطه ای هست که پروسس ما باید از مکانیزم های Polling یا Callbacks استفاده کنه تا حین انجام تسک های دیگه از وضعیت I/O هم با خبر بشه.
برگردیم به NGINX، در حقیقت پروسس های ورکر NGINX هر کدوم یه لوپ هستن که با استفاده از مکانیزم پولینگ به صورت نان بلاکینگ کانکشن های جدید رو قبول و داده هارو پردازش
میکنند.
پست بعدی تمرکز کامل روی نحوه ی انجام همین کار توسط NGINX خواهد بود
@knowpow
👍2
An Inspired Engineer
چرا NGINX انقدر وحشتناک سریعه؟! بخش اول: Traffic Routing -------------------------------------- مدتی بود دنبال پروژه ای بودم که با هدف عمیق تر شدن توی مفاهیم شبکه و کانکارنسی بتونم با سی++ پیاده سازی کنم، هم فال بود و هم تماشا. بعد از یه مدت تصمیم گرفتم…
این پستای انجینایکس رو ریختم توی مدیوم:
https://engineering.teknasyon.com/nginx-in-linux-systems-performance-and-internal-mechanisms-04c1ba1fb730
@knowpow
https://engineering.teknasyon.com/nginx-in-linux-systems-performance-and-internal-mechanisms-04c1ba1fb730
@knowpow
Medium
NGINX in Linux Systems, Performance and Internal Mechanisms
For some time, I had been searching for a project idea that would allow me to delve deeply into Linux Networking and C++ concurrency prog…
❤4
An Inspired Engineer
مدتی بود دنبال پروژه ای بودم که با هدف عمیق تر شدن توی مفاهیم شبکه و کانکارنسی بتونم با سی++ پیاده سازی کنم، هم فال بود و هم تماشا.
بعد از یه مدت تصمیم گرفتم سمت وبسرورها برم و رفتار اونارو زیر بار بررسی کنم
بعد از یه مدت تصمیم گرفتم سمت وبسرورها برم و رفتار اونارو زیر بار بررسی کنم
کد زدن این پروژه رو شروع کردم و اینجا هم کامیت به کامیت توضیح میدم، تا الان چیز خاصی نوشته نشده و فقط برای هر هستهی CPU یه پروسس ساخته میشه، کامیت بعدی باز کردن سوکت روی هر ورکر و شاردینگ اون ها روی یک پورت هست، ریپوی گیت هاب:
https://github.com/aabolfazl/Vortex
https://github.com/aabolfazl/Vortex
🔥2💯1
چرا NGINX انقدر سریعه؟!
بخش پنجم (آخر): Event Driven I/O
--------------------------------------
توی پست قبلی گفتم که مدل نان بلاکینگ چیه، توی بخش اخر این سری پست ها میخوام در مورد ساختار ورکرها صحبت کنم.
قبلا هم گفتم که وب سرور های سنتی مثل اپاچی از پترنی به اسم thread per connection استفاده میکنند، یعنی برای هر رکویستی که از سمت کلاینت ارسال میشه یک ترد میسازند و کارهای مربوط به اون رکویست رو توی اون ترد انجام میدن، همونطور که قبلا هم گفتم این ساختار مزیت هایی مثل سادگی توی پیاده سازی و راحتی دیباگ کردن رو داره، ولی از دید سیستم اصلا چیز جالبی نیست، سیستم عامل برای هر تردی که باز میکنه باید مموری جدیدی تخصیص بده، اطلاعات اون ترد رو نگهداره و دائم بین بقیه ی ترد ها context switching انجام بده، عمل context switching اصلا چیزی نیست که ما بخواییم ماشینمون به صورت مکرر باهاش درگیر بشه، چرا؟ جواب
توی این معماری میتونیم بگیم که تردها اکثر زمانشون صرف بلاک شدن یا همون منتظر موندن هستن، و ما میدونیم اصلا منتظر موندن I/O چیزی نیست که بخواییم پروسس ما انجام بده، در حقیقت ما داریم ریسورسی رو از سیستم عامل میگیریم به اسم ترد، و باهاش I/O خود کرنل رو مشاهده میکنیم! اگه پست های قبل رو خونده باشین میدونین که چرا میگم این اصلا خوب نیست، حالا بجاش سیستم عامل میاد میگه اقا تو برای هر I/O ای که داری ترد نساز و با بلاک کردن اون وقت CPU من رو نگیر، بجاش من برات یه مکانیزمی رو فراهم میکنم که هروقت I/O اماده بود تو متوجه بشی. خب خوبه دیگه؟ هم ترد برای کرنله، هم I/O پس همه چی رو میدونه، دیگه لازم نیست برای هر کانکشنی که داریم یه ترد بسازیم تا وقتش رو صرف منتظر موندن I/O کنه.
اسم این مکانیزم رو کرنل میزاره ePoll. مکانیزم های قدیمی هم توی کرنل وجود دارن مثل poll , select, signal که تفاوت هایی توی عملکردشون وجود داره، ولی انچه خوبان همه دارن رو ePoll همه یکجا داره.
کاری که NGINX میکنه چیه؟ خیلی جالبه اینجا NGINX میاد برای پروسس های ورکر یه سوکت باز میکنه و اماده میشه برای accept کردن کلاینت جدید، همون سوکت رو به ePoll میده و میگه اقا/خانم کرنل من دیگه همه چی رو میسپارم به شما، هر وقت اتفاق جدیدی توی این سوکت افتاد به من خبر بده، بعد متد
بعد از اینکه پروسس ورکر کلاینت جدید رو قبول کرد، خود سوکت کلاینت رو هم به لیست ePoll اضافه میکنه و دوباره همه چی رو به کرنل میسپاره، اگه چندین هزار تا کلاینت هم وصل بشن بازم ما ۱ ترد داریم که همشون رو مدیریت میکنه، هر کدوم از این چندین هزار کلاینت خواست چیزی بنویسه روی سوکت ما یا ما چیزی خواستیم بهش برگردونیم همش با event هایی که از طریق کرنل میاد انجام میشه.
اگه مرحله به مرحله بخوام بگم، پروسس ورکر کانکشن باز میکنه و شروع میکنه به گوش کردن، بعد همون سوکت رو به ePoll میده و منتظر کانکشن جدید میشه، وقتی کسی بخواد به سوکت ورکر وصل بشه کرنل به ورکر ایونت میفرسته و ورکر اون کلاینت رو accept میکنه و همون کانکشن کلاینت رو هم به لیست ePoll اضافه میکنه و دوباره منتظر ایونت بعدی میمونه، بعد از اینکه کانکشن برقرار شد کلاینت متوجه میشه و شروع میکنه به ارسال داده، کرنل اینجا میبینه از یه سوکت داده اومده و اون سوکت توی لیست انتظار ePoll پروسس NGINX قرارداره، پس یه ایونت به پروسس ورکر NGINX میفرسته و میگه که از کانکشن x داده ی جدید داری، بعد پروسس ورکر از حالت بلاک که توسط epoll_wait شده بود در میاد و از اون کانکشنی که کرنل بهش گفت شروع میکنه به خوندن داده، بعد از اینکه تمام داده رو خوند، اون رو به کانکشن سرور بالادستی(حواستون باشه که ما اینجا لودبالانسریم) میده و دوباره همون کانکشن سرور بالادستی رو هم به لیست ePoll اضافه میکنه و دوباره منتظر ایونت جدید میمونه، بعد از اینکه سرور بالادستی جواب رو اماده و به سوکت ورکر ارسال کرد کرنلی که روش NGINX داره ران میشه دوباره متوجه میشه و مثل قبل به پروسس ورکر خبر میده ولی اینسری با سوکتی که روی سرور مقصد باز کرده، حالا ورکر داده رو میخونه و به کلاینتی که درخواست داده بود برش میگردونه.
بخش پنجم (آخر): Event Driven I/O
--------------------------------------
توی پست قبلی گفتم که مدل نان بلاکینگ چیه، توی بخش اخر این سری پست ها میخوام در مورد ساختار ورکرها صحبت کنم.
قبلا هم گفتم که وب سرور های سنتی مثل اپاچی از پترنی به اسم thread per connection استفاده میکنند، یعنی برای هر رکویستی که از سمت کلاینت ارسال میشه یک ترد میسازند و کارهای مربوط به اون رکویست رو توی اون ترد انجام میدن، همونطور که قبلا هم گفتم این ساختار مزیت هایی مثل سادگی توی پیاده سازی و راحتی دیباگ کردن رو داره، ولی از دید سیستم اصلا چیز جالبی نیست، سیستم عامل برای هر تردی که باز میکنه باید مموری جدیدی تخصیص بده، اطلاعات اون ترد رو نگهداره و دائم بین بقیه ی ترد ها context switching انجام بده، عمل context switching اصلا چیزی نیست که ما بخواییم ماشینمون به صورت مکرر باهاش درگیر بشه، چرا؟ جواب
توی این معماری میتونیم بگیم که تردها اکثر زمانشون صرف بلاک شدن یا همون منتظر موندن هستن، و ما میدونیم اصلا منتظر موندن I/O چیزی نیست که بخواییم پروسس ما انجام بده، در حقیقت ما داریم ریسورسی رو از سیستم عامل میگیریم به اسم ترد، و باهاش I/O خود کرنل رو مشاهده میکنیم! اگه پست های قبل رو خونده باشین میدونین که چرا میگم این اصلا خوب نیست، حالا بجاش سیستم عامل میاد میگه اقا تو برای هر I/O ای که داری ترد نساز و با بلاک کردن اون وقت CPU من رو نگیر، بجاش من برات یه مکانیزمی رو فراهم میکنم که هروقت I/O اماده بود تو متوجه بشی. خب خوبه دیگه؟ هم ترد برای کرنله، هم I/O پس همه چی رو میدونه، دیگه لازم نیست برای هر کانکشنی که داریم یه ترد بسازیم تا وقتش رو صرف منتظر موندن I/O کنه.
اسم این مکانیزم رو کرنل میزاره ePoll. مکانیزم های قدیمی هم توی کرنل وجود دارن مثل poll , select, signal که تفاوت هایی توی عملکردشون وجود داره، ولی انچه خوبان همه دارن رو ePoll همه یکجا داره.
کاری که NGINX میکنه چیه؟ خیلی جالبه اینجا NGINX میاد برای پروسس های ورکر یه سوکت باز میکنه و اماده میشه برای accept کردن کلاینت جدید، همون سوکت رو به ePoll میده و میگه اقا/خانم کرنل من دیگه همه چی رو میسپارم به شما، هر وقت اتفاق جدیدی توی این سوکت افتاد به من خبر بده، بعد متد
epoll_wait رو صدا میزنه و منتظر کرنل میمونه، کرنل این سوکت رو به لیست خودش اضافه میکنه و هروقت تغییری روی این سوکت داشته باشه اون رو به پروسس ورکر میگه.بعد از اینکه پروسس ورکر کلاینت جدید رو قبول کرد، خود سوکت کلاینت رو هم به لیست ePoll اضافه میکنه و دوباره همه چی رو به کرنل میسپاره، اگه چندین هزار تا کلاینت هم وصل بشن بازم ما ۱ ترد داریم که همشون رو مدیریت میکنه، هر کدوم از این چندین هزار کلاینت خواست چیزی بنویسه روی سوکت ما یا ما چیزی خواستیم بهش برگردونیم همش با event هایی که از طریق کرنل میاد انجام میشه.
اگه مرحله به مرحله بخوام بگم، پروسس ورکر کانکشن باز میکنه و شروع میکنه به گوش کردن، بعد همون سوکت رو به ePoll میده و منتظر کانکشن جدید میشه، وقتی کسی بخواد به سوکت ورکر وصل بشه کرنل به ورکر ایونت میفرسته و ورکر اون کلاینت رو accept میکنه و همون کانکشن کلاینت رو هم به لیست ePoll اضافه میکنه و دوباره منتظر ایونت بعدی میمونه، بعد از اینکه کانکشن برقرار شد کلاینت متوجه میشه و شروع میکنه به ارسال داده، کرنل اینجا میبینه از یه سوکت داده اومده و اون سوکت توی لیست انتظار ePoll پروسس NGINX قرارداره، پس یه ایونت به پروسس ورکر NGINX میفرسته و میگه که از کانکشن x داده ی جدید داری، بعد پروسس ورکر از حالت بلاک که توسط epoll_wait شده بود در میاد و از اون کانکشنی که کرنل بهش گفت شروع میکنه به خوندن داده، بعد از اینکه تمام داده رو خوند، اون رو به کانکشن سرور بالادستی(حواستون باشه که ما اینجا لودبالانسریم) میده و دوباره همون کانکشن سرور بالادستی رو هم به لیست ePoll اضافه میکنه و دوباره منتظر ایونت جدید میمونه، بعد از اینکه سرور بالادستی جواب رو اماده و به سوکت ورکر ارسال کرد کرنلی که روش NGINX داره ران میشه دوباره متوجه میشه و مثل قبل به پروسس ورکر خبر میده ولی اینسری با سوکتی که روی سرور مقصد باز کرده، حالا ورکر داده رو میخونه و به کلاینتی که درخواست داده بود برش میگردونه.
👍2❤1
بخش دو:
حواستون باشه دیگه داریم با event ها بازی میکنیم، هر اتفاقی ممکنه بیوفته، یعنی دیگه پروسس من منتظر خوندن داده، اماده شدن و... سوکت نمیشه، در حقیقت پروسس من منتظر ایونتی از سمت کرنل میشه که برای اون سوکتها میوفته، ممکنه بعد از اینکه پروسس ما اطلاعات رو به سرور مقصد فرستاد یه کلاینت جدید بخواد وصل بشه! خب مشکلی نیست کرنل ایونتش رو میده و ما هندلش میکنیم، ولی توی پترن thread per connection پروسس ورکر برای هر کانکشن یه ترد باز میکرد و ولش میکرد به امون کرنل تا وقتی که کارش تموم بشه.
به این معماری میگن معماری Event driven که به خوبی چندین هزار کانکشن رو فقط و فقط روی یه پروسس یا همون ترد مدیریت میکنه.
نکته: توی مدل لینوکسی ترد با پروسس هیچ تفاوتی نداره، تنها امتیازی که ترد داره و توانایی دسترسی به مموری تردهای دیگه یا shared memory هست، پس هر وقت توی این سری پست ها گفتم ترد منظورم میتونه پروسس هم باشه، چون پروسس هم یه ترد محسوب میشه که ID اون برابر با PID هست.
خب این سری پست ها هم تموم شد، ولی اصلا هدف از نوشتن اینا چی بود؟ توی پست بعد میگم، مانا باشید.
@knowpow
حواستون باشه دیگه داریم با event ها بازی میکنیم، هر اتفاقی ممکنه بیوفته، یعنی دیگه پروسس من منتظر خوندن داده، اماده شدن و... سوکت نمیشه، در حقیقت پروسس من منتظر ایونتی از سمت کرنل میشه که برای اون سوکتها میوفته، ممکنه بعد از اینکه پروسس ما اطلاعات رو به سرور مقصد فرستاد یه کلاینت جدید بخواد وصل بشه! خب مشکلی نیست کرنل ایونتش رو میده و ما هندلش میکنیم، ولی توی پترن thread per connection پروسس ورکر برای هر کانکشن یه ترد باز میکرد و ولش میکرد به امون کرنل تا وقتی که کارش تموم بشه.
به این معماری میگن معماری Event driven که به خوبی چندین هزار کانکشن رو فقط و فقط روی یه پروسس یا همون ترد مدیریت میکنه.
نکته: توی مدل لینوکسی ترد با پروسس هیچ تفاوتی نداره، تنها امتیازی که ترد داره و توانایی دسترسی به مموری تردهای دیگه یا shared memory هست، پس هر وقت توی این سری پست ها گفتم ترد منظورم میتونه پروسس هم باشه، چون پروسس هم یه ترد محسوب میشه که ID اون برابر با PID هست.
خب این سری پست ها هم تموم شد، ولی اصلا هدف از نوشتن اینا چی بود؟ توی پست بعد میگم، مانا باشید.
@knowpow
👍3
An Inspired Engineer
چرا NGINX انقدر سریعه؟! بخش پنجم (آخر): Event Driven I/O -------------------------------------- توی پست قبلی گفتم که مدل نان بلاکینگ چیه، توی بخش اخر این سری پست ها میخوام در مورد ساختار ورکرها صحبت کنم. قبلا هم گفتم که وب سرور های سنتی مثل اپاچی از پترنی…
برای بخشی که گفتم: اکثر وب سرورها برای هر رکویستی که دریافت میکنند یک ترد یا یک پروسس بهش اختصاص میدن، این کار از لحاظ پیاده سازی فنی آسونه و پیچیدگی کمی داره، ولی یه مشکل اساس وجود داره اینکه هر ترد بیشتر زمانش رو صرف منتظر موندن از I/O میکنه، یا منتظر آماده شدن سوکتی هست که روش باز شده تا دیتا بخونه، یا منتظره تا سوکتی که برای سروری origin باز کرده اماده ی نوشتن بشه.
@knowpow
@knowpow
👍2
An Inspired Engineer
چرا NGINX انقدر سریعه؟! بخش پنجم (آخر): Event Driven I/O -------------------------------------- توی پست قبلی گفتم که مدل نان بلاکینگ چیه، توی بخش اخر این سری پست ها میخوام در مورد ساختار ورکرها صحبت کنم. قبلا هم گفتم که وب سرور های سنتی مثل اپاچی از پترنی…
برای بخشی که گفتم:
نگهداری ترد ها برای سیستمعامل هزینه ی بالایی دارن، اولین هزینه ای که اینجا من بهشون اشاره میکنم انجام contex switching روی ترد ها توسط CPU هست، contex switching زمانی اتفاق میوفته که سیستمعامل cpu رو از یک ترد بگیره و به ترد دیگه ای بده. برای اینکار سیستم عامل باید اطلاعات فعلی که ترد داره (مثل کانتر برنامه، اطلاعات رجیستر، مموری الوکیت شده و..) رو ذخیره کنه و اطلاعات ترد بعدی روی روی سیستم لود کنه، برای همین اینکار هزینه ی محاسباتی بالایی داره و وقتی خیلی زیاد انجام بشه میتونه لود سیستم رو ببره بالا.
@knowpow
نگهداری ترد ها برای سیستمعامل هزینه ی بالایی دارن، اولین هزینه ای که اینجا من بهشون اشاره میکنم انجام contex switching روی ترد ها توسط CPU هست، contex switching زمانی اتفاق میوفته که سیستمعامل cpu رو از یک ترد بگیره و به ترد دیگه ای بده. برای اینکار سیستم عامل باید اطلاعات فعلی که ترد داره (مثل کانتر برنامه، اطلاعات رجیستر، مموری الوکیت شده و..) رو ذخیره کنه و اطلاعات ترد بعدی روی روی سیستم لود کنه، برای همین اینکار هزینه ی محاسباتی بالایی داره و وقتی خیلی زیاد انجام بشه میتونه لود سیستم رو ببره بالا.
@knowpow
👍2
Long story short, NGINX is fast because:
It uses a non-blocking, event-driven, and asynchronous architecture.
@knowpow
It uses a non-blocking, event-driven, and asynchronous architecture.
@knowpow
👍2
An Inspired Engineer
ولی اصلا هدف از نوشتن اینا چی بود؟
طبق حرفی که اقامون فایمن میزنه:
همون اول که شروع کردم به نوشتن این پست ها گفتم که قرار بود یه پروژه برای عمیق شدن توی مفاهیم شبکه بزنم و سی++ بزنم، چند روزی میشه که پروژه رو شروع کردم و چند روز یه بار یه کامیت میزنم و اینجا توضیحش میدم.
دارم یه لود بالانسر مینویسم که قراره توی نسخه ی اول توی لایهی ۴ باشه و ترافیک رو به سرورهای بالا دستی روت کنه.
لینک ریپوی پروژه:
https://github.com/aabolfazl/Vortex
@knowpow
چیزی که نتونم بسازم، همون چیزیه که نمیتونم بفهمم
همون اول که شروع کردم به نوشتن این پست ها گفتم که قرار بود یه پروژه برای عمیق شدن توی مفاهیم شبکه بزنم و سی++ بزنم، چند روزی میشه که پروژه رو شروع کردم و چند روز یه بار یه کامیت میزنم و اینجا توضیحش میدم.
دارم یه لود بالانسر مینویسم که قراره توی نسخه ی اول توی لایهی ۴ باشه و ترافیک رو به سرورهای بالا دستی روت کنه.
لینک ریپوی پروژه:
https://github.com/aabolfazl/Vortex
@knowpow
👍10💯5🔥2❤1
جالبه بیتکوین هنوز برای فهمیدن اینکه کی باید از سوکت بخونه داره از (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