لینوکس را بیشتر بشناسیم (پارت۲)SCI

تو پارت اول از لینوکس رو بهتر بشناسیم با کلیات آشنا شدیم اینکه کرنل چیه انواعش و این چیزا . تو این پارت با System call interface که به عنوان واسط بین فضای کاربر و فضای کرنل هست آشنا میشیم .

System Call Interface(SCI)

خب با یه دید خیلی ساده میشه گفت که این لایه امکان فراخوانی توابع که تو فضای کرنل هست رو فراهم میکنه . اگه از بالا نگا کنیم ، سیستم کال ها یه سرویس هایی هستن که کرنل به برنامه های کاربر ارائه میده و شبیه api های کتابخونه هستن یعنی برای فراخوانی توایع، یک اسم دارن و پارامتر می گیرن و مقدار برگشتی دارن . در واقع لایه SCI یک واسط و اینترفیسی هست برای فراخوانی این توابع و سیستم کال ها .

sci

اگه یکم زوم کنیم سیستم کال ها اصلا فراخوانی توابع نیستن ! سیستم کالها در واقع یه سری دستورالعمل هستن به زبان اسمبلی که کارای زیر رو انجام میده :‌

  • شناسایی سیستم کال و پارامتر ها
  • فعال کردن سوئیچ کرنل مد
  • گرفتن نتیجه سیستم کال

(نوشتن این تیکه خیلی برام سخت بود . هر وقت یه توضیح بهتر به ذهنم رسید عوضش می کنم . )

kernel entry point

تو لینوکس سیستم کال ها با اعداد مشخص میشن و برای پارامتر ها از کلمه های به اندازه ۳۲ یا ۶۴ بیتی استفاده می شه که بستگی به معماری پردازنده داره . سیستم کال نهایت می تونه ۶ تا پارامتر ورودی بگیره . شماره سیستم کال و پارامتر ها تو رجیستر ها ذخیره میشن . برای مثال ، روی معماری ۳۲ بیت x86 ، شماره سیستم کال تو رجیستر EAX و پارامتراش تو رجیسترای EBX, ECX , EDX , ESI ,EBP ذخیره میشن .

gllibc

کتابخونه های سیستمی مثل glibc یه سری تابع میده بهمون که پیاده سازی های سیستم کال ها هستن تا برنامه ها راحت تر ازشون استفاده کنن . مثلا توابع malloc , read , write . واضحه که عملیات اختصاص حافظه کاری نیست که تو فضای کاربر بتونه انجام بشه کاری که ما میکنیم اینکه این تابع که از کتابخونه سیستمی glibc هست را فراخوانی میکنیم ، و این تابع از طریق SCI سیستم کال مربوطه رو اجرا می کنه و عملیات مورد نظر ما در کرنل اتفاق می افته و یه حافظه n بایتی در اختیار ما میزاره.

وقتی این تغییر مد از کاربر به کرنل اتفاق می افته ، و جریان اجرا به نقطه ورودی کرنل می رسه ، ورودی سیستم کال مربوطه رجیستر ها رو توی یه استک ذخیره می کنه و بعد system call dispatcher اجرا میشه .

system call dispatcher شماره سیستم کال رو اعتبار سنجی میکنه و سیستم کال مربوطه رو به اجرا در میاره .

به طور خلاصه اتفاقایی با فراخوانی سیستم کال اتفاق می افته :

  • برنامه شماره سیستم کال و پارامتر هارو تنظیم میکنه
  • مد اجرا از مد کاربر به کد کرنل تغییر میکنه
  • نقطه ورودی کرنل رجیستر ها رو تو استک ذخیره می کنه
  • system call dispatcher اجرا میشه و سیستم کال کارشو شروع میکنه
  • رجیستر های فضای کاربر بازیابی میشن و مد اجرا برمیگرده به مد کاربر
  • برنامه کاربر به کارش ادامه میده
#define __SYSCALL_I386(nr, sym, qual) [nr] = sym,

const sys_call_ptr_t ia32_sys_call_table[] = {
  [0 ... __NR_syscall_compat_max] = &sys_ni_syscall,
  #include <asm/syscalls_32.h>
};

system call patameter handeling

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

اگر اشاره گری که برنامه پاس کرده مجاز نباشه دو رویکرد می تونیم در این مورد داشته باشیم :

  • اشاره گر رو تو چک کنه که توی فضای آدرس کاربر باشه یا
  • کلا اشاره گره چک نشه و اینکارو به MMU (Memorry managment unit ) بسپریم و از page fault hander در صورت غیر مجاز بودن اشاره گر استفاده کنیم

به نظر میاد گزینه دوم منطقی باشه ولی پیاده سازی سختی داره . چون page fault handler از آدرس غیر مجاز (fault address ) ، آدرس دستوری که باعث خطا شده (faulting address) و اطلاعات آدرس فضای کاربر برای تشخیص دادن استفاده می کنه. در این صورت این احتمالات هست :

  • در هنگام عملیات نوشتن ، درخواست پیجینگ (demand paging ) یا سوآپینگ :‌ در این صورت falut و faulting تو فضای کاربر هستن (پس اطلاعات کامل داریم )
  • اشاره گر غیر مجازی که تو سیستم کال استفاده شده : در این صورت fault تو فضای کاربر هست و faulting تو فضای کرنل
  • باگ کرنل : مثل بالایی

خب تو دو تای دومی ما اطلاعات خیلی کمی داریم تا بتونیم منشاء خطا رو تشخیص بدیم . برای حل این مشکل لینوکس از واسط هایی (مثل :copy_to_user()) استفاده می کنه تا به فضای کاربر دسترسی داشته باشه :‌

  • دستورالعمل هایی که به فضای کاربر دسترسی دارن توی یه جدول جمع آوری شده
  • وقتی page fault اتفاق می افته آدرس دستورالعملی که باعث خطا شده تو جدول چک میشه

از اونجایی که چک کردن fault address با کل فضای آدرس هزینه زیادی داره در مقابل چک کردنش با exception table . برا همین لینوکس از این روش که اپتیمایز تره استفاده می کنه .

Virtual Dynamic Shared Object (VDSO )

یه مکانیزم هست برای بهبود بخشیدن به سیستم کال ها . یک سری سیستم کال ها هستن که خیلی مورد استفاده هستن تو روش دیفالت هر باری که این سیستم کال ها نیاز داشته باشیم تغییر مد صورت میگیره که می تونی تاثیر منفی روی پرفورمنس بزاره . لینوکس با استفاده از vdso تعدادی از این سیستم کال ها رو به فضای کاربر می فرسته و برنامه ها می تونن بدون تغییر مد از کاربر به کرنل این سیستم کال ها رو فراخوانی کنن .

  • vdso یه حافظه ست تو فضای کاربر که توسط کرنل ایجاد میشه و یک سری توابع کرنل رو در اختیار میزاره و فرمتش ELF (Executable and Linkable Formt )
  • کتابخونه هایی مثل glibc اگه vdso در دسترس باشه ازش استفاده می کنن

یکی دیگه از پیاده سازی های جالب VDSO ، سیستم کال های مجازی هستن (vsyscalls) که مستقیما می تونن از سمت کاربر اجرا بشن . در واقع سیستم کال های مجازی بخشی از vdso هستن . برای مثال سیستم کال های getpid or gettimeofday می تونن با vsyscall پیاده سازی بشن .

می تونید سیستم کال های - مجازی - و vdso رو تو سورس لینوکس ببینید

linux/arch/x86/entry

refrence

قسمت بعدی در مورد Process Managment خواهد بود .

کپی بخش یا کل هر کدام از مطالب لینوکس ۹۸ تنها با کسب مجوز مکتوب امکان پذیر است.
وبلاگ لینوکس ۹۸ یک پروژه متن باز بوده و سورس آن در گیت‌هاب موجود است.