0% found this document useful (0 votes)
94 views18 pages

Lab2 Fall1402

Uploaded by

sajjad.ab2020
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
94 views18 pages

Lab2 Fall1402

Uploaded by

sajjad.ab2020
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 18

‫به نام خدا‬

‫آزمایشگاه سیستمعامل‬
‫پروژه دوم‪ :‬فراخوانی سیستمی‬
‫تاریخ تحویل‪ :‬یکشنبه ‪ 21‬آبان ‪1402‬‬

‫اهداف پروژه‬
‫● آشنایی با علت نیاز به فراخوانی سیستمی‬
‫● آشنایی با سازوکار و چگونگی صدا زده شدن فراخوانیهای سیستمی‪ 1‬در هسته ‪xv6‬‬
‫● آشنایی با افزودن فراخوانیهای سیستمی در هسته ‪xv6‬‬
‫● آشنایی با نحوه ذخیرهسازی پردازهها و ساختاردادههای مربوط به آن‬

‫مقدمه‬
‫هر برنامه در حال اجرا یک پردازه‪ 2‬نام دارد‪ .‬به این ترتیب یک سیستم رایانهای ممکن است در‬
‫آن واحد‪ ،‬چندین پردازه در انتظار سرویس داشته باشد‪ .‬هنگامی که یک پردازه در سیستم در حال‬
‫ِ‬

‫‪1‬‬
‫‪System Call‬‬
‫‪2‬‬
‫‪Process‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫اجرا است‪ ،‬پردازنده روال معمول پردازش را طی میکند‪ :‬خواندن یک دستور‪ ،‬افزودن مقدار‬
‫شمارنده برنامه‪ 3‬به میزان یک واحد‪ ،‬اجرای دستور و نهایتا ً تکرار حلقه‪ .‬در یک سیستم‬
‫رویدادهایی وجود دارند که باعث میشوند به جای اجرای دستور بعدی‪ ،‬کنترل از سطح کاربر‬
‫به سطح هسته منتقل شود‪ .‬به عبارت دیگر‪ ،‬هسته کنترل را در دست گرفته و به برنامههای سطح‬
‫‪4:‬‬
‫کاربر سرویس میدهد‬
‫‪ )۱‬ممکن است دادهای از دیسک دریافت شده باشد و به دالیلی الزم باشد بالفاصله آن داده از ثبات‬
‫مربوطه در دیسک به حافظه منتقل گردد‪ .‬انتقال جریان کنترل در این حالت‪ ،‬ناشی از وقفه‪ 5‬خواهد‬
‫بود‪ .‬وقفه به طور غیرهمگام با کد در حال اجرا رخ میدهد‪.‬‬
‫‪ )۲‬ممکن است یک استثنا‪ 6‬مانند تقسیم بر صفر رخ دهد‪ .‬در اینجا برنامه دارای یک دستور تقسیم‬
‫بوده که عملوند مخرج آن مقدار صفر داشته و اجرای آن کنترل را به هسته میدهد‪.‬‬
‫‪ )۳‬ممکن است برنامه نیاز به عملیات ممتاز داشته باشد‪ .‬عملیاتی مانند دسترسی به اجزای‬
‫سختافزاری یا حالت ممتاز سیستم (مانند محتوای ثباتهای کنترلی) که تنها هسته اجازه دسترسی‬
‫به آنها را دارد‪ .‬در این شرایط برنامه اقدام به فراخوانی فراخوانی سیستمی میکند‪ .‬طراحی‬
‫سیستمعامل باید به گونهای باشد که مواردی از قبیل ذخیرهسازی اطالعات پردازه و بازیابی‬
‫اطالعات رویدا ِد به وقوع پیوسته مثل آرگومانها را به صورت ایزولهشده از سطح کاربر انجام‬
‫دهد‪ .‬در این پروژه‪ ،‬تمرکز بر روی فراخوانی سیستمی است‪.‬‬
‫در اکثریت قریب به اتفاق موارد‪ ،‬فراخوانیهای سیستمی به طور غیرمستقیم و توسط توابع‬
‫کتابخانهای پوشاننده‪ 7‬مانند توابع موجود در کتابخانه استاندارد ‪ C‬در لینوکس یعنی ‪ glibc‬صورت‬
‫میپذیرد‪ 8.‬به این ترتیب قابلیتحمل‪ 9‬برنامههای سطح کاربر افزایش مییابد‪ .‬زیرا به عنوان مثال‬
‫چنانچه در ادامه مشاهده خواهد شد‪ ،‬فراخوانیهای سیستمی با شمارههایی مشخص میشوند که‬

‫‪3‬‬
‫‪Program Counter‬‬
‫ی‬
‫متفاوت برای این گذارها به‬ ‫‪ 4‬در ‪ xv6‬به تمایم این موارد ‪ trap‬گفته یمشود‪ .‬در حایل که در حقیقت در ‪ x86‬نامهای‬
‫کار یمرود‪.‬‬
‫‪5‬‬
‫‪Interrupt‬‬
‫‪6‬‬
‫‪Exception‬‬
‫‪7‬‬
‫‪Wrapper‬‬
‫ی‬ ‫ً‬ ‫ً‬
‫در ‪ ،glibc‬توابع پوشاننده غالبا دقیقا نام و پارامت ی‬
‫هات مشابه فراخواتهای سیستیم دارند‪.‬‬ ‫‪8‬‬
‫‪9‬‬
‫‪Portability‬‬
‫‪2‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫در معماریهای مختلف‪ ،‬متفاوت است‪ .‬توابع پوشاننده کتابخانهای‪ ،‬این وابستگیها را مدیریت‬
‫میکنند‪ .‬توابع پوشاننده ‪ 6xv‬در فایل ‪ usys.S‬توسط ماکروی ‪ SYSCALL‬تعریف شدهاند‪.‬‬
‫‪ )۱‬کتابخانههای (قاعدتا ً سطح کاربر‪ ،‬منظور فایلهای تشکیلدهنده متغیر ‪ ULIB‬در ‪Makefile‬‬
‫است) استفاده شده در ‪ 6xv‬را از منظر استفاده از فراخوانیهای سیستمی و علت این استفاده‬
‫بررسی نمایید‪.‬‬
‫تعداد فراخوانیهای سیستمی‪ ،‬وابسته به سیستمعامل و حتی معماری پردازنده است‪ .‬به عنوان‬
‫مثال در لینوکس‪ ،‬فریبیاسدی‪ 10‬و ویندوز ‪ ۷‬به ترتیب حدود ‪ ۵۰۰ ،۳۰۰‬و ‪ ۷۰۰‬فراخوانی‬
‫سیستمی وجود داشته که بسته به معماری پردازنده اندکی متفاوت خواهد بود [‪ .]1‬در حالی که‬
‫‪ xv6‬تنها ‪ ۲۱‬فراخوانی سیستمی دارد‪.‬‬
‫فراخوانی سیستمی سربارهایی دارد‪ )۱ :‬سربار مستقیم که ناشی از تغییر مد اجرایی و انتقال به‬
‫مد ممتاز بوده و ‪ )۲‬سربار غیرمستقیم که ناشی از آلودگی ساختارهای پردازنده شامل انواع‬
‫حافظههای نهان‪ 11‬و خط لوله‪ 12‬میباشد‪ .‬به عنوان مثال‪ ،‬در یک فراخوانی سیستمی ()‪ write‬در‬
‫‪۲‬‬
‫لینوکس تا حافظه نهان سطح یک داده خالی خواهد شد [‪ .]2‬به این ترتیب ممکن است کارایی به‬
‫‪۳‬‬

‫نصف کاهش یابد‪ .‬غالبا ً عامل اصلی‪ ،‬سربار غیرمستقیم است‪ .‬تعداد دستورالعمل اجرا شده به‬
‫ازای هر سیکل‪ (IPC)13‬هنگام اجرای یک فراخوانی سیستمی در بار کاری ‪2006 SPEC CPU‬‬
‫روی پردازنده ‪ Core i7‬اینتل در نمودار زیر نشان داده شده است [‪.]2‬‬

‫‪10‬‬
‫‪FreeBSD‬‬
‫‪11‬‬
‫‪Caches‬‬
‫‪12‬‬
‫‪Pipeline‬‬
‫‪13‬‬
‫‪Instruction per Cycle‬‬
‫‪3‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫مشاهده میشود که در لحظهای ‪ IPC‬به کمتر از ‪ ۰.۴‬رسیده است‪ .‬روشهای مختلفی برای‬
‫فراخوانی سیستمی در پردازندههای ‪ x86‬استفاده میگردد‪ .‬روش قدیمی که در ‪ 6xv‬به کار میرود‬
‫استفاده از دستور اسمبلی ‪ int‬است‪ .‬مشکل اساسی این روش‪ ،‬سربار مستقیم آن است‪ .‬در‬
‫پردازندههای مدرنتر ‪ x86‬دستورهای اسمبلی جدیدی با سربار انتقال کمتر مانند‬
‫‪ sysenter/sysexit‬ارائه شده است‪ .‬در لینوکس‪ glibc ،‬در صورت پشتیبانی پردازنده‪ ،‬از این‬
‫دستورها استفاده میکند‪ .‬برخی فراخوانیهای سیستمی (مانند()‪ gettimeofday‬در لینوکس)‬
‫فرکانس دسترسی باال و پردازش کمی در هسته دارند‪ .‬لذا سربار مستقیم آنها بر برنامه زیاد‬
‫‪14‬‬
‫خواهد بود‪ .‬در این موارد میتوان از روشهای دیگری مانند اشیای مجازی پویای مشترک‬
‫‪ )(vDSO‬در لینوکس بهره برد‪ .‬به این ترتیب که هسته‪ ،‬پیادهسازی فراخوانیهای سیستمی را در‬
‫فضای آدرس سطح کاربر نگاشت داده و تغییر مد به مد هسته صورت نمیپذیرد‪ .‬این دسترسی‬
‫نیز به طور غیرمستقیم و توسط کتابخانه ‪ glibc‬صورت میپذیرد‪ .‬در ادامه سازوکار اجرای‬
‫فراخوانی سیستمی در ‪ xv6‬مرور خواهد شد‪.‬‬
‫‪ )۲‬دقت شود فراخوانیهای سیستمی تنها روش دسترسی سطح کاربر به هسته نیست‪ .‬انواع این‬
‫روشها را در لینوکس به اختصار توضیح دهید‪ .‬میتوانید از مرجع [‪ ]3‬کمک بگیرید‪.‬‬

‫‪14‬‬
‫‪Virtual Dynamic Shared Objects‬‬
‫‪4‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫سازوکار اجرای فراخوانی سیستمی در ‪xv6‬‬

‫بخش سختافزاری و اسمبلی‬


‫جهت فراخوانی سیستمی در ‪ xv6‬از روش قدیمی پردازندههای ‪ x86‬استفاده میشود‪ .‬در این‬
‫روش‪ ،‬دسترسی به کد دارای سطح دسترسی ممتاز (در اینجا کد هسته) مبتنی بر مجموعه‬
‫توصیفگرهایی موسوم به ‪ Gate Descriptor‬است‪ .‬چهار نوع ‪ Gate Descriptor‬وجود دارد‬
‫که ‪ xv6‬تنها از ‪ Trap Gate‬و ‪ Interrupt Gate‬استفاده میکند‪ .‬ساختار این ‪ Gate‬ها در شکل‬
‫زیر نشان داده شده است [‪.]4‬‬

‫این ساختارها در ‪ 6xv‬در قالب یک ساختار هشت بایتی موسوم به ‪ struct gatedesc‬تعریف‬
‫شدهاند (خط ‪ .)۸۵۵‬به ازای هر انتقال به هسته (فراخوانی سیستمی و هر یک از انواع وقفههای‬

‫‪5‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫سختافزاری و استثناها) یک ‪ Gate‬در حافظه تعریف شده و یک شماره تله‪ 15‬نسبت داده میشود‪.‬‬
‫این ‪Gate‬ها توسط تابع ()‪ tvinit‬در حین بوت (خط ‪ )۱۲۲۹‬مقداردهی میگردند‪Interrupt .‬‬
‫‪ Gate‬اجازه وقوع وقفه در پردازنده حین کنترل وقفه را نمیدهد‪ .‬در حالی که ‪Trap Gate‬‬
‫اینگونه نیست‪ .‬لذا برای فراخوانی سیستمی از ‪ Trap Gate‬استفاده میشود تا وقفه که اولویت‬
‫بیشتری دارد‪ ،‬همواره قابل سرویسدهی باشد (خط ‪ .)۳۳۷۳‬عملکرد ‪Gate‬ها را میتوان با‬
‫بررسی پارامترهای ماکروی مقداردهنده به ‪ Gate‬مربوط به فراخوانی سیستمی بررسی نمود‪:‬‬
‫پارامتر ‪ T_SYSCALL[idt]t :۱‬محتوای ‪ Gate‬مربوط به فراخوانی سیستمی را نگه میدارد‪.‬‬
‫آرایه ‪( idt‬خط‪ )۳۳۶۱‬بر اساس شماره تلهها اندیسگذاری شده است‪ .‬پارامترهای بعدی‪ ،‬هر یک‬
‫بخشی از ‪T_SYSCALL[idt]t‬را پر میکنند‪.‬‬
‫پارامتر ‪ :۲‬تعیین نوع ‪ Gate‬که در اینجا ‪ Trap Gate‬بوده و لذا مقدار یک دارد‪.‬‬
‫پارامتر ‪ :۳‬نوع قطعه کدی که بالفاصله پس از اتمام عملیات تغییر مد پردازنده اجرا میگردد‪ .‬کد‬
‫کنترلکننده فراخوانی سیستمی در مد هسته اجرا خواهد شد‪ .‬لذا مقدار ‪ 3<<SEG_KCODE‬به‬
‫ماکرو ارسال شده است‪.‬‬
‫پارامتر ‪ :۴‬محل دقیق کد در هسته که ‪ vectors[T_SYSCALL]t‬است‪ .‬این نیز بر اساس شماره‬
‫تلهها شاخصگذاری شده است‪.‬‬
‫پارامتر ‪ :۵‬سطح دسترسی مجاز برای اجرای این تله‪ DPL_USER .‬است‪ .‬زیرا فراخوانی سیستمی‬
‫توسط (قطعه) کد سطح کاربر فراخوانی میگردد‪.‬‬
‫‪ )۳‬آیا باقی تلهها را نمیتوان با سطح دسترسی ‪ DPL_USER‬فعال نمود؟ چرا؟‬
‫به این ترتیب برای تمامی تلهها ‪ idt‬مربوطه ایجاد میگردد‪ .‬به عبارت دیگر پس از اجرای‬
‫()‪ tvinit‬آرایه ‪ idt‬به طور کامل مقداردهی شده است‪ .‬حال باید هر هسته پردازنده بتواند از‬
‫اطالعات ‪ idt‬استفاده کند تا بداند هنگام اجرای هر تله چه کد مدیریتی باید اجرا شود‪ .‬بدین منظور‬
‫تابع ()‪ idtinit‬در انتهای راهاندازی اولیه هر هسته پردازنده‪ ،‬اجرا شده و اشارهگر به جدول ‪idt‬‬
‫را در ثبات مربوطه در هر هسته بارگذاری مینماید‪ .‬از این به بعد امکان سرویسدهی به تلهها‬
‫فراهم است‪ .‬یعنی پردازنده میداند برای هر تله چه کدی را فراخوانی کند‪.‬‬

‫‪15‬‬
‫‪Trap Number‬‬
‫‪6‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫یکی از راههای فعالسازی هر تله استفاده از دستور >‪ int <trap no‬میباشد‪ .‬لذا با توجه به این‬
‫که شماره تله فراخوانی سیستمی ‪ ۶۴‬است (خط ‪ ،)۳۲۲۶‬کافی است برنامه‪ ،‬جهت فراخوانی‬
‫فراخوانی سیستمی دستور ‪ int 64‬را فراخوانی کند‪ int .‬یک دستورالعمل پیچیده در پردازنده‬
‫‪( x86‬یک پردازنده ‪ )CISC‬است‪ .‬ابتدا باید وضعیت پردازه در حال اجرا ذخیره شود تا بتوان پس‬
‫از فراخوانی سیستمی وضعیت را در سطح کاربر بازیابی نمود‪ .‬اگر تله ناشی از خطا باشد (مانند‬
‫خطای نقص صفحه‪ 16‬که در فصل مدیریت حافظه معرفی میگردد)‪ ،‬کد خطا نیز در انتها روی‬
‫پشته قرار داده میشود‪ .‬حالت پشته (سطح هسته‪ )17‬پس از اتمام عملیات سختافزاری مربوط به‬
‫دستور ‪( int‬مستقل از نوع تله با فرض ‪ Push‬شدن کد خطا توسط پردازنده) در شکل زیر نشان‬
‫داده شده است‪ .‬دقت شود مقدار ‪ esp‬با ‪ Push‬کردن کاهش مییابد‪.‬‬

‫‪ )۴‬در صورت تغییر سطح دسترسی‪ ss ،‬و ‪ esp‬روی پشته ‪ Push‬میشود‪ .‬در غیراینصورت‬
‫‪ Push‬نمیشود‪ .‬چرا؟‬
‫گام ‪ ،int‬بردار تله یا همان کد کنترلکننده مربوط به فراخوانی سیستمی اجرا میگردد‬
‫در آخرین ِ‬
‫که در شکل زیر نشان داده شده است‪.‬‬

‫‪16‬‬
‫‪Page Fault‬‬
‫‪ 17‬دقت شود با توجه به اینکه قرار است تله در هسته مدیریت گردد‪ ،‬پشته سطح هسته نیاز است‪ .‬این پشته پیش از‬
‫اجرای هر برنامه سطح کاربر‪ ،‬توسط تابع ‪ )(switchuvm‬برای اجرا هنگام وقوع تله در آن برنامه آماده یمگردد‪.‬‬
‫‪7‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫‪.globl vector64‬‬
‫‪vector64:‬‬
‫‪pushl $0‬‬
‫‪pushl $64‬‬
‫‪jmp alltraps‬‬
‫در اینجا ابتدا یک کد خطای بیاثر صفر و سپس شماره تله روی پشته قرار داده شده است‪ .‬در‬
‫انتها اجرا از کد اسمبلی ‪ alltraps‬ادامه مییابد‪ .‬حالت پشته‪ ،‬پیش از اجرای کد ‪ alltraps‬در‬
‫شکل زیر نشان داده شده است‪.‬‬

‫‪Ss‬‬
‫‪Esp‬‬
‫‪eflags‬‬
‫‪Cs‬‬ ‫‪esp‬‬
‫‪Eip‬‬
‫‪error code‬‬
‫‪trapno‬‬

‫)‪(empty‬‬

‫‪ alltraps‬باقی ثباتها را ‪ Push‬میکند‪ .‬به این ترتیب تمامی وضعیت برنامه سطح کاربر پیش‬
‫از فراخوانی سیستمی ذخیره شده و قابل بازیابی است‪ .‬شماره فراخوانی سیستمی و پارامترهای‬
‫آن نیز در این وضعیت ذخیره شده‪ ،‬حضور دارند‪ .‬این اطالعات موجود در پشته‪ ،‬همان قاب تله‬
‫هستند که در پروژه قبل مشابه آن برای برنامه ‪ initcode.S‬ساخته شده بود‪ .‬حال اشارهگر به‬
‫باالی پشته (‪ )esp‬که در اینجا اشارهگر به قاب تله است روی پشته قرار داده شده (خط ‪)۳۳۱۸‬‬
‫و تابع )(‪ trap‬فراخوانی میشود‪ .‬این معادل اسمبلی این است که اشارهگر به قاب تله به عنوان‬
‫‪8‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫پارامتر به )(‪ trap‬ارسال شود‪ .‬حالت پشته پیش از اجرای )(‪ trap‬در شکل زیر نشان داده شده‬
‫است‪.‬‬

‫بخش سطح باال و کنترلکننده زبان سی تله‬


‫تابع ‪ trap‬ابتدا نوع تله را با بررسی مقدار شماره تله چک میکند (خط ‪ .)۳۴۰۳‬با توجه به این‬
‫که فراخوانی سیستمی رخ داده است تابع ‪ syscall‬اجرا میشود‪ .‬پیشتر ذکر شد فراخوانیهای‬
‫سیستمی‪ ،‬متنوع بوده و هر یک دارای شمارهای منحصربهفرد است‪ .‬این شمارهها در فایل‬
‫‪ syscall.h‬به فراخوانیهای سیستمی نگاشت داده شدهاند (خط ‪ .)۳۵۰۰‬تابع ()‪ syscall‬ابتدا وجود‬
‫فراخوانی سیستمی فراخوانی شده را بررسی نموده و در صورت وجود پیادهسازی‪ ،‬آن را از‬
‫جدول فراخوانیهای سیستمی اجرا میکند‪ .‬جدول فراخوانیهای سیستمی‪ ،‬آرایهای از اشارهگرها‬

‫‪9‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫به توابع است که در فایل ‪ syscall.c‬قرار دارد (خط ‪ .)۳۶۷۲‬هر کدام از فراخوانیهای سیستمی‪،‬‬
‫خود‪ ،‬وظیفه دریافت پارامتر را دارند‪ .‬ابتدا مختصری راجع به فراخوانی توابع در سطح زبان‬
‫اسمبلی توضیح داده خواهد شد‪ .‬فراخوانی توابع در کد اسمبلی شامل دو بخش زیر است‪:‬‬
‫(گام ‪ )۱‬ایجاد لیستی از پارامترها بر روی پشته‪ .‬دقت شود پشته از آدرس بزرگتر به آدرس‬
‫کوچکتر پر میشود‪.‬‬
‫ترتیب ‪ Push‬شدن روی پشته‪ :‬ابتدا پارامتر آخر‪ ،‬سپس پارامتر یکی مانده به آخر و در نهایت‬
‫پارامتر نخست‪.‬‬
‫مثالً برای تابع )‪(a,b,c‬ا‪ f‬کد اسمبلی کامپایل شده منجر به چنین وضعیتی در پشته سطح کاربر‬
‫میشود‪:‬‬
‫‪esp+8‬‬ ‫‪C‬‬
‫‪esp+4‬‬ ‫‪B‬‬
‫‪esp‬‬ ‫‪A‬‬
‫(گام ‪ )۲‬فراخوانی دستور اسمبلی معادل ‪ call‬که منجر به ‪ Push‬شدن محتوای کنونی اشارهگر‬
‫دستورالعمل (‪ )eip‬بر روی پشته میگردد‪ .‬محتوای کنونی مربوط به اولین دستورالعمل بعد از‬
‫تابع فراخوانیشده است‪ .‬به این ترتیب پس از اتمام اجرای تابع‪ ،‬آدرس دستورالعمل بعدی که باید‬
‫اجرا شود روی پشته موجود خواهد بود‪.‬‬
‫مثالً برای فراخوانی تابع قبلی پس از اجرای دستورالعمل معادل ‪ call‬وضعیت پشته به صورت‬
‫زیر خواهد بود‪:‬‬
‫‪esp+12‬‬ ‫‪c‬‬
‫‪esp+8‬‬ ‫‪b‬‬
‫‪esp+4‬‬ ‫‪a‬‬
‫‪esp‬‬ ‫‪Ret Addr‬‬
‫در داخل تابع ‪ f‬نیز میتوان با استفاده از اشارهگر ابتدای پشته به پارامترها دسترسی داشت‪ .‬مثالً‬
‫برای دسترسی به ‪ b‬میتوان از ‪ 8+esp‬استفاده نمود‪ .‬البته اینها تنها تا زمانی معتبر خواهند‬
‫بود که تابع ‪ f‬تغییری در محتوای پشته ایجاد نکرده باشد‪.‬‬

‫‪10‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫در فراخوانی سیستمی در ‪ xv6‬نیز به همین ترتیب پیش از فراخوانی سیستمی پارامترها روی‬
‫پشته سطح کاربر قرار داده شدهاند‪ .‬به عنوان مثال چنانچه در پروژه یک آزمایشگاه دیده شد‪،‬‬
‫برای فراخوانی سیستمی ()‪ sys_exec‬دو پارامتر ‪ $argv‬و ‪ $init‬و آدرس برگشتی صفر به‬
‫ترتیب روی پشته قرار داده شدند (خطوط ‪ ۸۴۱۰‬تا ‪ .)۸۴۱۲‬سپس شماره فراخوانی سیستمی که‬
‫در ‪ SYS_exec‬قرار دارد در ثبات ‪ eax‬نوشته شده و ‪ int $T_SYSCALL‬جهت اجرای تله‬
‫فراخوانی سیستمی اجرا شد‪ sys_exec)( .‬میتواند مشابه آنچه در مورد تابع ‪ )(f‬ذکر شد به‬
‫پارامترهای فراخوانی سیستمی دسترسی پیدا کند‪ .‬به این منظور در ‪ xv6‬توابعی مانند ()‪argint‬‬
‫و()‪ argptr‬ارائه شده است‪ .‬پس از دسترسی فراخوانی سیستمی به پارامترهای مورد نظر‪ ،‬امکان‬
‫اجرای آن فراهم میگردد‪.‬‬
‫‪ )۵‬در مورد توابع دسترسی به پارامترهای فراخوانی سیستمی به طور مختصر توضیح دهید‪ .‬چرا‬
‫در()‪ argptr‬بازه آدرسها بررسی میگردد؟ تجاوز از بازه معتبر‪ ،‬چه مشکل امنیتی ایجاد میکند؟‬
‫در صورت عدم بررسی بازهها در این تابع‪ ،‬مثالی بزنید که در آن‪ ،‬فراخوانی‬
‫سیستمی()‪ sys_read‬اجرای سیستم را با مشکل روبرو سازد‪.‬‬
‫شیوه فراخوانی فراخوانیهای سیستمی جزئی از واسط باینری برنامههای کاربردی‪ ) (ABI18‬یک‬
‫سیستمعامل روی یک معماری پردازنده است‪ .‬به عنوان مثال در سیستمعامل لینوکس در معماری‬
‫‪ ،xv6‬پارامترهای فراخوانی سیستمی به ترتیب در ثباتهای ‪ edi، esi، edx، ecx،ebx‬و ‪ebp‬‬
‫قرار داده میشوند‪ 19.‬ضمن این که طبق این ‪ ،ABI‬نباید مقادیر ثباتهای ‪ edi، esi،ebx‬و ‪ebp‬‬
‫پس از فراخوانی تغییر کنند‪ .‬لذا باید مقادیر این ثباتها پیش از فراخوانی فراخوانی سیستمی در‬
‫مکانی ذخیره شده و پس از اتمام آن بازیابی گردند تا ‪ ABI‬محقق شود‪ .‬این اطالعات و شیوه‬
‫‪20.‬‬
‫فراخوانی فراخوانیهای سیستمی را میتوان در فایلهای زیر از کد منبع ‪ glibc‬مشاهده نمود‬
‫‪sysdeps/unix/sysv/linux/i386/syscall.S‬‬
‫‪sysdeps/unix/sysv/linux/i386/sysdep.h‬‬

‫‪18‬‬
‫‪Application Binary Interface‬‬
‫حداکت شش پار ی‬
‫امت ارسال یمگردد‪.‬‬ ‫ر‬ ‫‪ 19‬فرض این است که‬
‫مستها مربوط به ‪ glibc-2.26‬است‪.‬‬
‫‪ 20‬ر‬
‫‪11‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫به این ترتیب در لینوکس برخالف ‪ xv6‬پارامترهای فراخوانی سیستمی در ثبات منتقل میگردند‪.‬‬
‫یعنی در لینوکس در سطح اسمبلی‪ ،‬ابتدا توابع پوشاننده پارامترها را در پشته منتقل نموده و سپس‬
‫پیش از فراخوانی فراخوانی سیستمی‪ ،‬این پارامترها ضمن جلوگیری از از دست رفتن محتوای‬
‫ثباتها‪ ،‬در آنها کپی میگردند‪.‬‬
‫در هنگام تحویل سواالتی از سازوکار فراخوانی سیستمی پرسیده میشود‪ .‬دقت شود در مقابل‬
‫‪ ،ABI‬مفهومی تحت عنوان واسط برنامهنویسی برنامه کاربردی(‪ (API21‬وجود دارد که شامل‬
‫مجموعهای از تعاریف توابع (نه پیادهسازی) در سطح زبان برنامهنویسی بوده که واسط قابلحمل‬
‫سیستمعامل(‪ (POSIX22‬نمونهای از آن است‪ .‬پشتیبانی توابع کتابخانهای سیستمعاملها از این‬
‫تعاریف‪ ،‬قابلیتحمل برنامهها را افزایش میدهد‪ 23.‬مثالً امکان کامپایل یک برنامه روی لینوکس‬
‫و ‪ iOS‬فراهم خواهد شد‪ .‬جهت آشنایی بیشتر با ‪ POSIX‬و پیادهسازی آن در سیستمعاملهای‬
‫لینوکس‪ ،‬اندروید و ‪ iOS‬میتوان به مرجع [‪ ]5‬مراجعه نمود‪.‬‬

‫بررسی گامهای اجرای فراخوانی سیستمی در سطح کرنل توسط ‪gdb‬‬

‫در این قسمت با توجه به توضیحاتی که تا االن داده شدهاست‪ ،‬قسمتی از روند اجرای یک سیستمکال‬
‫را در سطح هسته بررسی خواهیدکرد‪ .‬ابتدا یک برنامه ساده سطح کاربر بنویسید که بتوان از‬
‫یک نقطه‬ ‫طریق آن‪ ،‬فراخوانیهای سیستمی ()‪ getpid‬در ‪ xv6‬را اجرا کرد‪.‬‬
‫توقف(‪ )breakpoint‬در ابتدای تابع ‪ syscall‬قرار دهید‪ .‬حال برنامه سطح کاربر نوشتهشده را‬
‫اجرا کنید‪ .‬زمانی که به نقطه توقف برخورد کرد‪ ،‬دستور ‪ bt‬را در ‪ gdb‬اجرا کنید‪ .‬توضیح‬
‫کاربرد این دستور‪ ،‬تصویر خروجی آن و تحلیل کامل تصویر خروجی را در گزارشکار ثبت‬
‫کنید‪.‬‬

‫حال دستور ‪(down‬توضیح کارکرد این دستور را نیز در گزارش ذکر کنید) را در ‪ gdb‬اجرا‬
‫کنید‪ .‬محتوای رجیستر ‪ eax‬را که در ‪ tf‬میباشد‪ ،‬چاپ کنید‪ .‬آیا مقداری که مشاهده میکنید‪،‬‬

‫‪21‬‬
‫‪Application Programming Interface‬‬
‫‪22‬‬
‫‪Portable Operating System Interface‬‬
‫‪ 23‬توابع پوشاننده فراخواتهای سیستیم ی‬
‫بخش از ‪ POSIX‬هستند‪.‬‬
‫‪12‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫برابر با شماره فراخوانی سیستمی ()‪ getpid‬میباشد؟ علت را در گزارش کار توضیح دهید‪.‬‬

‫چند بار دستور ‪ c‬را در ‪ gdb‬اجرا کنید تا در نهایت‪ ،‬محتوای رجیستر ‪ ،eax‬شماره فراخوانی‬
‫سیستمی ()‪ getpid‬را در خود داشتهباشد‪.‬‬

‫دقت کنید میتوانید در ابتدا دستور ‪ layout src‬را اجرا کنید تا کد ‪ c‬در ترمینال ‪ gdb‬نشان‬
‫دادهشود و شاید در تحلیل مراحل‪ ،‬کمکتان کند‪.‬‬

‫ارسال آرگومانهای فراخوانیهای سیستمی‬

‫تا اینجای کار با نحوه ارسال آرگومانهای فراخوانیهای سیستمی در سیستمعامل ‪ xv6‬آشنا شدید‪.‬‬
‫در این قسمت به جای بازیابی آرگومانها به روش معمول‪ ،‬از ثباتها استفاده میکنیم‪ .‬فراخوانی‬
‫سیستمی زیر که در آن تنها یک آرگومان ورودی از نوع ‪ int‬وجود دارد را پیادهسازی کنید‪.‬‬

‫)‪● int find_digital_root(int n‬‬

‫در این قسمت بـه جـای بـازیابی آرگومان هـا بـه روش معمول‪ ،‬از ثباتها استفاده میکنیم‪ .‬در این‬
‫فـراخـوانی‪ ،‬ریشه دیجیتال عدد ورودی را محاسبه کنید‪ .‬برای مثال در صورتی که عدد ورودی‬
‫‪ 284‬باشد‪ ،‬شما باید عدد ‪ 5‬را در خروجی چاپ کنید‪.‬‬

‫عدد ‪ n‬باید مثبت باشد و در غیر این صورت فراخوانی سیستمی عددی منفی برگردانده و برنامه‬
‫سطح کاربر این را به کاربر اطالع میدهد‪.‬‬

‫توضیح‪ :‬ریشه دیجیتال یک عدد‪ ،‬حاصل جمع مکرر ارقام آن عدد است تا اینکه حاصل به یک‬
‫تک رقم برسد‪ .‬در مثال گفته شده‪ ،‬در ابتدا ‪ 2+8+4=14‬میشود و سپس ‪ 1+4=5‬ریشه دیجیتال‬
‫عدد است‪.‬‬

‫دقت داشته باشید که از ثبات برای ذخیره مقدار آرگومان استفاده میکنیم نه برای آدرس محل‬
‫قرارگیری آن‪ .‬ضمن این که پس از اجرای فراخوانی‪ ،‬باید مقدار ثبات دست نخورده باقی بماند‪.‬‬

‫پیادهسازی فراخوانیهای سیستمی‬

‫‪13‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫در این آزمایش با پیادهسازی فراخوانیهای سیستمی‪ ،‬اضافهکردن آنها به هسته ‪ xv6‬را فرا‬
‫میگیرید‪ .‬در این فراخوانیها که در ادامه توضیح داده میشود‪ ،‬پردازشهایی انجام میشود که‬
‫از سطح کاربر قابل انجام نیست‪ .‬تمامی مراحل کار باید در گزارش کار همراه با فایلهایی‬
‫که آپلود میکنید موجود باشند‪.‬‬

‫نحوه اضافه کردن فراخوانیهای سیستمی‬


‫برای انجام این کار لینک و مستندات زیادی در اینترنت و منابع دیگر موجود است‪ .‬شما باید چند‬
‫فایل را برای اضافه کردن فراخوانیهای سیستمی در ‪ xv6‬تغییر دهید‪ .‬برای این که با این فایلها‬
‫بیشتر آشنا شوید‪ ،‬پیادهسازی فراخوانیهای سیستمی موجود را در ‪ xv6‬مطالعه کنید‪ .‬این فایلها‬
‫شامل ‪ syscall.c ،syscall.h ،user.h‬و ‪ ...‬است‪ .‬گزارشی که ارائه میدهید باید شامل تمامی‬
‫مراحل اضافه کردن فراخوانیهای سیستمی و همینطور مستندات خواستهشده در مراحل بعد‬
‫باشد‪.‬‬

‫نحوه ذخیره اطالعات پردازهها در هسته‬


‫ت یک پردازه دیگر توسط هسته ساخته میشوند‪.‬‬
‫پردازهها در سیستمعامل ‪ xv6‬پس از درخواس ِ‬
‫در این صورت هسته نیاز دارد تا اولین پردازه را خودش اجرا کند‪ .‬هسته ‪ xv6‬برای نگهداری‬
‫هر پردازه یک ساختار داده ساده دارد که در یک لیست مدیریت میشود‪ .‬هر پردازه اطالعاتی‬
‫که توسط آن شناخته میشود‪ ،‬پردازه والد و غیره را در ساختار خود‬ ‫‪24‬‬
‫از قبیل شناسه واحد خود‬
‫دارد‪ .‬برای ذخیرهکردن اطالعات بیشتر‪ ،‬میتوان دادهها را به این ساختار داده اضافه کرد‪.‬‬

‫‪ .1‬پیادهسازی فراخوانی سیستمی کپی کردن فایل‬


‫در این قسمت فراخوانی سیستمی طراحی کنید که دو نام میگیرد و فایل با نام اول را کپی‬
‫کرده و با نام دوم ذخیره میکند‪.‬‬
‫)‪● int copy_file(const char* src, const char* dest‬‬

‫‪24‬‬
‫‪PID‬‬
‫‪14‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫برای تست این فراخوانی سیستمی‪ ،‬یک برنامهی سطح کاربر بنویسید و فراخوانی سیستمی‬
‫گفته شده را فراخوانی کنید و نتیجه را نشان دهید‪ .‬عمل کپی کردن باید به طور کامل در‬
‫کرنل و نه در برنامه سطح کاربر انجام شود‪.‬‬
‫برای تولید فایل‪ ،‬میتوانید از دستور ‪ echo‬استفاده کنید و یا فایل ‪ README‬را کپی کنید‪.‬‬
‫این فراخوانی سیستمی در صورت موفقیت ‪ 0‬و در غیر این صورت ‪ -1‬ریترن میکند‪ .‬در‬
‫صورت خطا در برنامه سطح کاربر به کاربر اطالع داده میشود‪.‬‬
‫توجه کنید که یک فایل نباید به خودش کپی شود و در این صورت ‪ -1‬ریترن میشود‪.‬‬
‫اگر نام دوم داده شده از قبل وجود داشته باشد‪ ،‬میتوانید به دلخواه یا خطا گرفته و یا آن را‬
‫‪ override‬کنید‪.‬‬

‫‪ .2‬پیادهسازی فراخوانی سیستمی تعداد ‪ uncle‬های پردازه‬


‫در این قسمت‪ ،‬فراخوانی سیستمی را طراحی کنید که تعداد ‪ uncle‬های یک پردازه را‬
‫برگرداند‪ .‬منظور از ‪ uncle‬یک پردازه‪ sibling ،‬های پردازه پدر آن پردازه می باشد‪.‬‬
‫)‪● int get_uncle_count(int‬‬
‫برای تست‪ ،‬برنامه ای در سطح کاربر بنویسید که با استفاده از ()‪ fork‬سه فرزند ساخته‬
‫شود و سپس برای یک کدام از آن ها یک فرزند ایجاد شود‪ .‬در نهایت‬
‫()‪ get_uncle_count‬را برای آن فراخوانی کنید و خروجی را نمایش دهید‪.‬‬

‫‪ .3‬پیادهسازی فراخوانی سیستمی طول عمر پردازه‬


‫در این قسمت‪ ،‬مدت زمان زندگی یک پردازه از زمان به وجود آمدن تا زمان صدا کردن‬
‫این فراخوانی سیستمی ‪ ،‬محاسبه شود‪.‬‬
‫)‪● int get_process_lifetime(int‬‬
‫برای تست این فراخوانی سیستمی‪ ،‬برنامه ای در سطح کاربر بنویسید که با استفاده از )(‪fork‬‬
‫یک پردازه فرزند ایجاد شود و پس از ‪ 10‬ثانیه از بین برود‪ .‬طول عمر پردازه فرزند و پدر‬
‫را نمایش دهید‪.‬‬

‫‪15‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫‪16‬‬
‫آزمایشگاه سیستم عامل – پروژه ‪ – ۲‬پاییز ‪۱۴۰۲‬‬

‫نکاتی در رابطه با فراخوانیهای سیستمی‬

‫● برای این که بتوانید فراخوانیهای سیستمی خود را تست کنید الزم است که یک برنامه‬
‫سطح کاربر بنویسید و در آن فراخوانیها را صدا بزنید‪ .‬برای این که بتوانید برنامه‬
‫سطح کاربر خود را درون ‪ Shell‬اجرا کنید‪ ،‬باید تغییرات مناسبی را روی ‪Makefile‬‬
‫انجام دهید تا برنامه جدید کامپایل شود و به فایلسیستم ‪ xv6‬اضافه شود‪.‬‬
‫● برای ردیابی روال فراخوانیها‪ ،‬پیغامهای مناسبی در جاهای مناسب چاپ کنید‪.‬‬
‫● برای نمایش اطالعات در سطح هسته از )(‪ cprintf‬استفاده کنید‪.‬‬

‫سایر نکات‬

‫● آدرس مخزن و شناسه آخرین تغییر خود را در محل بارگذاری در سایت درس‪ ،‬بارگذاری‬
‫نمایید‪.‬‬
‫● تمام مراحل کار را در گزارش کار خود بیاورید‪.‬‬
‫● همه افراد باید به پروژه آپلود شده توسط گروه خود مسلط باشند و لزوما ً نمره افراد یک گروه‬
‫با یکدیگر برابر نیست‪.‬‬
‫● در صورت مشاهده هرگونه مشابهت بین کدها یا گزارش دو گروه‪ ،‬به هر دو گروه نمره ‪۰‬‬
‫تعلق میگیرد‪.‬‬
‫● فصل سه کتاب ‪ xv6‬میتوان کمککننده باشد‪.‬‬
‫● هر گونه سوال در مورد پروژه را فقط از طریق فروم درس مطرح کنید‪.‬‬

‫موفق باشید‬

‫‪17‬‬
۱۴۰۲ ‫ – پاییز‬۲ ‫آزمایشگاه سیستم عامل – پروژه‬

‫مراجع‬

[1] “System Call.” [Online]. Available:


https://en.wikipedia.org/wiki/System_call.

[2] L. Soares and M. Stumm, “FlexSC: Flexible System Call


Scheduling with Exception-less System Calls,” in Proceedings of
the 9th USENIX Conference on Operating Systems Design and
Implementation, 2010, pp. 33–46.

[3] C.-C. Tsai, B. Jain, N. A. Abdul, and D. E. Porter, “A Study of


Modern Linux API Usage and Compatibility: What to Support
when You’Re Supporting,” in Proceedings of the Eleventh
European Conference on Computer Systems, 2016, p. 16:1--
16:16.

[4] “Intel{®} 64 and IA-32 Architectures Software Developer's


Manual, Volume 3: System Programming Guide,” 2015.

[5] V. Atlidakis, J. Andrus, R. Geambasu, D. Mitropoulos, and J.


Nieh, “POSIX Abstractions in Modern Operating Systems: The
Old, the New, and the Missing,” in Proceedings of the Eleventh
European Conference on Computer Systems, 2016, p. 19:1--
19:17.

18

You might also like