ورودی/خروجی ناهمگام

ورودی/خروجی ناهمگام (به انگلیسی: asynchronous I/O) یا ورودی/خروجی مسدودنشدنی (به انگلیسی: non-blocking I/O) یک نوع روش عمل ورودی/خروجی داده‌ها است که با جلوگیری از مسدودشدن یک پروسه، به آن اجازه می‌دهد تا در حین انجام عمل ورودی/خروجی هم بتواند مشغول باشد و کارهای مورد نظر خود را انجام دهد.

عملیات ورودی و خروجی در یک رایانه، در مقایسه با پردازش کردن داده‌ها، ممکن است تا حد بسیار زیادی آکرنل باشد. یک دستگاه ورودی/خروجی ممکن است از دستگاه‌های مکانیکی تشکیل شده باشد که نیاز به جابجایی فیزیکی داشته باشند، مثلاً در دیسک سخت هد خواندن نوشتن باید برای خواندن اطلاعات بین شیارها جابجا شود. این کار عمدتاً بسیار آکرنل‌تر از تغییر جریان الکتریکی است. برای مثال، در حین انجام یک عملیات در دیسک که مثلاً ممکن است ۱۰ میلی‌ثانیه طول بکشد، پردازنده‌ای که با پالس ساعت ۱ گیگاهرتز کار می‌کند، می‌تواند ده میلیون دستورالعمل را اجرا کند.

یکی از روش‌های انجام عمل ورودی/خروجی داده‌ها این است که پس از شروع شدن این عمل، تا اتمام آن منتظر ماند. این روش که به آن ورودی/خروی همگام یا مسدودشدنی می‌گویند، باعث می‌شود تا برنامه در حین انجام عمل به حالت مسدود درآید و قادر به انجام هیچ‌کاری نباشد، که باعث هدر رفتن و بیکار ماندن منابع سیستم می‌شود. اگر یک برنامه تعداد زیادی عمل ورودی/خروجی انجام دهد، ممکن است پردازنده تمام‌وقت بیکار بماند و منتظر باشد تا عمل ورودی/خروجی تمام شود.

یک روش دیگر این است که ابتدا عمل ورودی/خروجی آغاز شود، سپس به جای منتظر ماندن برای اتمام آن، همزمان به انجام کارهایی پرداخت که نیازی به تمام شدن عمل ورودی/خروجی ندارند. به این روش ورودی/خرجوی ناهمگام می‌گویند. هر کاری که نیازمند اتمام عمل ورودی/خروجی مورد نظر باشد (از جمله پردازش کردن اطلاعات ورودی، و همچنین عملیات‌های حیاتی دیگری که نیازمند این هستند که یک عمل نوشتن پایان یابد) هنوز هم باید صبر کنند تا عمل ورودی/خروجی به اتمام برسد، در نتیجه باید مسدود شوند، اما کارهای دیگری که نیاز به اتمام عمل ورودی/خروجی ندارند، می‌توانند همچنان پردازش شوند.

فریم‌ورک io-uring

ویرایش

فریم‌ورک io_uring Asynchronous I/O، اینترفیس جدید ورودی/خروجی لینوکس است که اولین بار در نسخه‌ی ۵.۱ کرنل لینوکس در مارس ۲۰۱۹ معرفی شد. این فریم‌ورک، اینترفیسی پر امکانات و با تاخیر زمانی کم، فراهم آورد تا نیاز اپلیکیشن‌هایی که به عملکرد ورودی/خروجی ناهمگام کرنل لینوکس نیاز داشتند را برطرف کند.

فریم‌ورک قبلی AIO لینوکس، محدودیت‌هایی داشت که هدف io_uring، رفع آن محدودیت‌ها بود:

  • این فریم‌ورک، ورودی و خروجی بافر را پشتیبانی نمی‌کرد و تنها ورودی و خروجی مستقیم، پشتیبانی می‌شد.
  • رفتارهای این فریم‌ورک زمانی که Block، تحت موقعیت‌های مختلف قرار داشت، کاملاً قابل پیش‌بینی نبود و ممکن بود رفتار متفاوتی نشان دهد.
  • واسط برنامه‌نویسی این فریم‌ورک نیاز به حداقل دو فراخوانی سیستمی به ازای هر ورودی/خروجی داشت. یکی برای ارسال درخواست و دیگری برای منتظر ماندن به جهت تکمیل درخواست.
  • هر ارسالی درخواستی به رونوشت ۶۴ + ۸ بایت از داده‌ها نیاز داشت و هر تکمیل درخواستی هم به کپی ۳۲ بایت داده.

ساختار AIC io-uring

ویرایش

هر نمونه از io-uring، دو حلقه دارد که یکی صف حلقه‌ای ارسال‌هاست و دیگری صف حلقه‌ای تکمیل‌شده‌هاست. این دو حلقه بین کرنل و برنامه به اشتراک‌گذاشته شده‌اند. صف‌ها هر کدام یک پاسخ‌دهنده و یک درخواست‌کننده دارند و اندازه‌شان توانی از دو است. هر برنامه، یک یا چند ورودی به صف ارسال می‌دهد و انتهای این صف را بروزرسانی می‌کند. از طرف دیگر نیز کرنل‌ی لینوکس، درخواست‌ها را از سر صف برداشته و رسیدگی می‌کند. صف تکمیل هم توسط کرنل با پاسخ‌های درخواست‌ها بروزرسانی می‌شود و برنامه از سر صف، پاسخ‌ها را دریافت می‌کند.

API فراخوانی سیستمی

ویرایش

این فریم‌ورک، سه فراخوانی سیستمی دارد:

int io_uring_register(int fd, unsigned int opcode, void *arg, unsigned int nr_args);

int io_uring_setup(unsigned entries, struct io_uring_params *p);

int io_uring_enter(unsigned fd, unsigned to_submit, unsigned min_complete, unsigned flags, sigset_t *sig);

۱) فراخوانی اول وظیفه دارد که بافر فایل یا کاربر را برای استفاده در یک نمونه io-uring که با فایل دیسکریپتور (توصیف‌کننده فایل) ارجاع داده می‌شود، ثبت کند. ثبت‌کردن بافر فایل یا کاربر به کرنل اجازه می‌دهد که به مدت طولانی به ساختمان‌داده‌های داخلی کرنل که به فایل مرتبط است، اشاره داشته باشد یا باعث ایجاد نگاشت (Map) طولانی مدت حافظه برنامه که با بافرها مرتبط است می‌شود.


۲) فراخوانی دوم، زمینه را برای آماده‌سازی و برپایی ورودی/خروجی ناهمگام آماده می‌کند. این فراخوانی سیستمی، صف ارسال و صف تکمیل را با المان‌های ورودی اول برپا می‌نماید. خروجی این فراخوانی سیستمی، یک توصیف‌کننده فایل است که در مراحل بعد قرار است روی آن به عنوان یک نمونه io-uring، عملیات انجام شود. وجود این دو صف در میان کرنل و برنامه باعث می‌شود که دیگر نیازی به کپی‌کردن داده‌ها در زمان مقداردهی اولیه و تکمیل ورودی/خروجی نباشد. از ورودی دوم هم برای پیکربندی نمونه io-uring و بازگردانی اطلاعات استفاده می‌شود.


۳) از فراخوانی سوم برای مقداردهی اولیه و تکمیل ورودی/خروجی با استفاده از صفوف مشترک ارسال و تکمیلی که با تابع io_uring_setup برپا شده استفاده می‌کنیم. تنها یک فراخوانی می‌تواند ارسال ورودی/خروجی‌های جدید و تکمیل آن‌ها را با هم انجام دهد اگر که با فراخوانی سوم، الان یا قبلا فراخوانی شده باشد.

Liburing API

ویرایش

لیبرینگ، رابط برنامه‌نویسی‌ای سطح بالا برای موارد استفاده‌ی ساده است و همچنین باعث می‌شود تا برنامه با پیاده‌سازی کامل فراخوانی سیستمی درگیر نشود. این رابط همچنین از بازنویسی و تکرار کد برای عملیات‌هایی مثل برپایی نمونه‌های io_uring جلوگیری می‌کند.

برای مثال بعد از برگردانده شدن توصیف‌کننده فایل حلقه از تابع io_uring_setup، برنامه همیشه باید تابع mmap را فراخوانی کند تا صفوف ارسال و تکمیل را به هم نگاشت کند.

یک فراخوانی لیبرینگ را مشاهده می‌کنید:

int io_uring_queue_init(unsigned entries, struct io_uring *ring, unsigned flags);

منابع

ویرایش

Wikipedia contributors. Asynchronous I/O. Wikipedia, The Free Encyclopedia. November 27, 2014, 10:29 UTC. Available at: http://en.wikipedia.org/w/index.php?title=Asynchronous_I/O&oldid=635634463. Accessed February 17, 2015.