این آموزش در ادامه سری آموزش های "راهنمای معماری های x86 و x86_64 برای برنامه نویسان سیستمی" می باشد. در این قسمت در مورد مباحثی صحبت می کنیم که پیش نیاز برای آموزش های بعدی می باشد. این مباحث عبارت اند از رجیسترهای سیستمی، مدهای عملکرد، و سطوح دسترسی.
یکسری موضوعات که قبل از ادامه مطلب باید به آنها اشاره کنم:
مدهای عملکرد در پرازنده های اینتل:
(64-bit Mode)
وقتی کامپیوتر خود را روشن یا ریست می کنیم اولین مدی که داخلش قرار می گیریم Real Mode است. این مدی است که کدهای ۱۶ بیتی میتوانند داخلش اجرا شوند مثل کد برنامه های DOS و BIOS و مربوط به دوره ای می شود که پردازنده 8086 وجود داشت، و همچنان با تفاوت اندکی در پردازنده های امروزی نیز پشتیبانی می شود.
ویژگی های Real Mode:
220 = 1M ناحیه از حافظه را آدرس دهی کرد. فرمول محاسبه آدرس فیزیکی بر اساس آدرس منطقی به صورت ((segment << 4) + offset) که در واقع از شیفت کردن segment به اندازه ۴ بیت به سمت چپ و جمع شدن آن با offset به دست می آید.هانطور که گفتم این مد کمی با مدی که در پردازنده های امروزی استفاده می شود فرق دارد. در این مد تغییر کرده امکان دسترسی به حافظه بیشتر از 1M وجود دارد (در این حالت همچنان از آدرس دهی منطقی استفاده می شود). در واقع تکنیکی وجود دارد که می توان از امکانات مدیریت حافظه در مد محافظه شده (Protected Mode) بهره برد و به نواحی دیگر حافظه دسترسی پیدا کرد. اگر با اصطلاحاتی مثل Voodoo Mode یا Unreal Mode روبرو شدید بدانید منظور همین Real Mode تغییر کرده در پردازنده های امروزی است.
بعد از Real Mode که بالا اشاره کردیم اولین کاری که یک سیستم عامل (۳۲ بیتی و ۶۴ بیتی) انجام می دهد سوییچ شدن به Protected Mode است. در این مود خیلی از محدودیت هایی که در Real Mode وجود داشت دیگر جود ندارد.
برخی ویژگی های Protected Mode:
در پست قبلی گفته بودم برای برخی مباحث از سورس FreeBSD برای شرح بهتر مساله استفاده میکنم. به همین جهت کدهایی که مربوط به فعال کردن Protected Mode است را اینجا قرار داده ام.
برای سوییچ شدن به Protected Mode به ترتیب کارهای زیر باید انجام شوند:
همانطور که می بینید قبل سوییچ شدن یکسری کار دیگر هم انجام می شود که غیر فعال کردن وقفه ها و مقداردهی جدول GDT است. برای اینکه بتوان یک محیط قابل استفاده داشت بعد سوییچ شدن به Protected Mode باید خیلی چیزهای دیگر هم مقدار دهی شوند ولی فعلا به آنها موضوعات نمی پردازم.
مسیر کد freebsd/sys/i386/i386/mpboot.s
NON_GPROF_ENTRY(bootMP) .code16 cli /*** مرحله ۱ ***/ CHECKPOINT(0x34, 1) /* First guarantee a 'clean slate' */ xorl %eax, %eax movl %eax, %ebx movl %eax, %ecx movl %eax, %edx movl %eax, %esi movl %eax, %edi /* set up data segments */ mov %cs, %ax mov %ax, %ds mov %ax, %es mov %ax, %fs mov %ax, %gs mov %ax, %ss mov $(boot_stk-bootMP), %esp /* Now load the global descriptor table */ lgdt MP_GDTptr-bootMP /*** مرحله ۲ ***/ /* Enable protected mode */ movl %cr0, %eax orl $CR0_PE, %eax /*** مرحله ۳ ***/ movl %eax, %cr0 /* * make intrasegment jump to flush the processor pipeline and * reload CS register */ pushl $0x18 pushl $(protmode-bootMP) lretl /*** مرحله ۴ ***/
کد بالا یکسری کارهای اضافی دیگه مثل مقدار دهی رجیسترها و سگمنت های عمومی هم کرده است.
Virtual-8086 Mode مدی است که وقتی داخل Protected Mode هستیم می توانیم به آن سوییچ کنیم. این مد در واقع محیطی معادل Real Mode ایجاد می کند و این امکان را می دهد تا همزمان دو تا برنامه که یکی برای Protected Mode و یکی برای Real Mode نوشته شده است در کنار هم در Protected Mode اجرا شوند. برای مثال در سیستم عامل ویندوز در صورت اجرای یک برنامه تحت DOS یک پروسه به نام ntvdm.exe اجرا می شود که در واقع ماشین مجازی فایل های ۱۶ بیتی است که از Virtual-8086 Mode برای فراهم کردن این قابلیت استفاده کرده است.
برای سورییچ شدن به این مد کافیه مقدار VM از رجیستر EFLAGS به یک مقدار دهی بشود. یک نکته در مورد سوییچ شدن به این مد این است که مقدار EFLAGS.VM را نمی توان حین اجرای یک برنامه تغییر داد و حتما باید در هنگام ایجاد یک Task انجام شوم.
این مدی است که فقط به درد توسعه دهنده های firmware میخورد و کاربردهایش بیشتر مربوط به سخت افزار می شود مثل مدیریت پاور، نظارت بر عملکرد سخت افزارها و در مواقعی تغییر وضیعتشان. و پردازنده در صورت به وجود آمدن وقفه SMI (System Management Interrupt) به این مد سوییچ می کند. SMM این ویژگی ها دارد:
NMI (Non-maskable Interrupt) هم نمی توانند وقفه ای در اجرای کد داخل SMM بیاندازند.همانطور که بالاتر اشاره کردم این مد خیلی بدرد یک برنامه نویس سیستمی سطح سیستم عامل ممکنه نخورد، ولی با این حال به نظرم داشتن دانش در موردش لازمه به خاطر اینکه
مد IA-32e یک مد توسعه یافته تر مدهای موجود در معماری ۳۲ بیتی است و تقریبا تمام امکانات مد ۳۲ بیتی را نیز دارد. این مود خود به دو مد دیگر تقسیم شده مد سازگاری (Compatibility Mode) و مد ۶۴ بیتی (64-bit Mode) است. در این مد سیستم عامل باید ۶۴ بیتی باشد. در ادامه در مورد مدها توضیح خواهم داد.
نکته: با وجودی که می گوییم معماری ۶۴ بیتی است ولی از سمت سخت افزار پردازنده فقط ۵۲ بیت را می تواند آدرس دهی کند. و از طرفی آدرس مجازی که قابل دسترسی است ۴۸ بیت است و بیت ۴۸ در بیت های ۴۸-۶۳ کپی می شود. به این نوع آدرس دهی Canonical Addresses گفته می شود.
برای سوییچ شدن به IA-32e اول باید در Protected Mode بود بعد از این مد به IA-32e سوییچ کرد. مراحل به صورت زیر است.
PAE (Physical Address Extensions) (مقدار بیت PAE از رجیستر CR4 به یک تغییر کند)کدی که به IA-32e سوییچ می کند. مسیر کد freebsd/sys/amd64/amd64/mpboot.S
...
... /*** مرحله ۱و۲ ***/
/* * At this point, we are ruing in 32 bit legacy protected mode. */ .code32 protmode: mov $bootdata-gdt, %eax mov %ax, %ds /* Turn on the PAE bit for when paging is enabled */ mov %cr4, %eax orl $CR4_PAE, %eax mov %eax, %cr4 /*** مرحله ۳ ***/ /* * Enable EFER.LME so that we get long mode when all the prereqs are * in place. In this case, it turns on when CR0_PG is finally enabled. * Pick up a few other EFER bits that we'll use need we're here. */ movl $MSR_EFER, %ecx rdmsr orl $EFER_LME | EFER_SCE, %eax wrmsr /*** مرحله ۴ ***/ /* * Point to the embedded page tables for startup. Note that this * only gets accessed after we're actually in 64 bit mode, however * we can only set the bottom 32 bits of %cr3 in this state. This * means we are required to use a temporary page table that is below * the 4GB limit. %ebx is still our relocation base. We could just * subtract 3 * PAGE_SIZE, but that would be too easy. */ leal mptramp_pagetables-mptramp_start(%ebx),%eax movl (%eax), %eax mov %eax, %cr3 /*** مرحله ۵ ***/ /* * Finally, switch to long bit mode by enabling paging. We have * to be very careful here because all the segmentation disappears * out from underneath us. The spec says we can depend on the * subsequent pipelined branch to execute, but *only if* everything * is still identity mapped. If any mappings change, the pipeline * will flush. */ mov %cr0, %eax orl $CR0_PG, %eax /*** مرحله ۶ ***/ mov %eax, %cr0
برای محافظت از ناحیه های حافظه و همچنین محدود کردن اجرای دستورات سیستمی چهار سطح دسترسی وجود دارد که به صورت Rin0-Ring3 نامگذاری شده اند. هر چی عدد سطح بزرگتر باشد دسترسی کمتر است. در نتیجه سطح Ring3 دسترسی کمتری نسبت به Ring0 دارد. این سطوح را میتوان به صورت شکل زیر ترسیم کرد. هر چی از حلقه مرکزی به بیرون بیاییم دسترسی نیز کمتر می شود.

در سیستم عامل های امروزی فقط از دو تا از این سطوح استفاده می شود Ring0 و Ring3 که از دید سیستم عامل به این سطوح به ترتیب سطح کرنل و سطح کاربر گفته می شود. حالا این ها که گفیم اصلا به چه دردی می خورد. با استفاده از این سطوح سیستم عامل می تواند مشخص کند که چه ناحیه از حافظه متعلق به سطح کاربر است و چه ناحیه هایی مربوط به کرنل. بعد از این عمل پردازنده در اجرای دستورات (مثلا دستوری از Ring3) سطح دسترسی ناحیه حافظه را بررسی می کند اگر دستور سعی کند آدرسی (مثلا آدرسی از Ring0) را که مربوط به حلقه پایین تر باشد را بخواند یا بنویسد که در این صورت دسترسی کافی ندارد پردازنده اجازه ادامه اجرا به کد نمی دهد و اصطلاحا یک exception تولید میکند. اینجوری امنیت سیستم و کد های سیستم عامل تامین می شود.
اینجا چهار سطح وجود دارد چرا از سطوح دیگر استفاده نمی شود؟ قبلا گفته بودیم به دو طریق می توان حافظه را مدیریت کرد Segmentation و Paging. در حالت Segmentation امکان استفاده از هر چهار سطح وجود دارد. ولی در حالت Paging فقط امکان استفاده از دو سطح را داریم. به این دلیل دو سطح دیگر امروزه استفاده چندانی ندارد مگر یک موارد خاصی که یک نرم افزار سیستمی از این سطوح استفاده کند.
اصلا چرا این سطوح اضافی معرفی شدند؟ وجود سطح اضافه این امکان را به سیستم عامل میدهد تا بخش هایی از کد خود را که احتمال بروز خطا در آن بیشتر است یا امنیت پایینتری دارد را در سطوحی با دسترسی پایین تر اجرا کند. برای مثال می توان گفت تقریبا 70% تا 80% یک سیستم عامل مربوط می شود به درایورهای مربوط به ارتباط با دیوایس ها. در حالتی که سیستم عامل از دو سطح استفاده کند و در صورت سوء استفاده از یک باگ داخل یکی از درایورها امنیت کل سیستم به خطر میافتد به دلیل اینکه در سیستم عامل های امروزی کد درایورها هم در سطح هسته سیستم عامل یعنی Ring0 اجرا می شوند. در حالی که با داشتن یک سطح اضافه می شود کد درایورها را در سطح با دسترسی پایین تر اجرا کرد و به این صورت سیستم عامل نسبت به سوء استفاده از باگ درایور مصون می ماند. این ویژگی خوبه ولی سیستم عامل های امروزی هیچ کدوم از این ویژگی برخوردار نیستن و دلیلش هم بیشتر بخاطر کارایی (performance) سیستم است به دلیل سوییچ های زیادی که بین سطح ها اتفاق می افتد. اگر اطلاعات بیشتر در این زمینه می خواهید می توانید در مورد سیستم عامل های با معماری microkernel تحقیق کنید.
نکته: بالاتر در مورد مد SMM توضیح دادم این مد از Ring0 که در واقع سطح سیستم عامل می شود دسترسی بالاتری دارد و بصورت غیر رسمی به "Ring -2" معروف است.
پردازنده های معماری های ۳۲ بیتی و ۶۴ بیتی رجیسترهای سیستمی مختلفی دارند. در این آموزش من فقط در مورد رجیستر های CR0-CR4 و یکی رجیسترهای MSR به نام IA32_EFER صحبت می کنم که در این آموزش از آنها مثال زدم.
در شکل زیر این رجیسترها را می توانید ببینید. خانه هایی که خاکستری هستند رزرو می باشند و نباید داخل آنها مقداری نوشته شود.

این رجیسترها در همه مدهای مربوط به معماری ۳۲ بیتی و مد سازگاری به اندازه ۳۲ بیت است. و در مد ۶۴ بیت به اندازه ۶۴ بیت است.
من در مورد همه بیت ها توضیح نمی دهم و برای توضیحات بیشتر لازمه مستندات پردازنده را ببینید.
CR0: این رجیستر یکسری وضعیت های مربوط به پردازنده را نگه می دارد. در تمام مقادیر یک به معنی فعال کردن و صفر به معنی غیر فعال می باشد.
PE (Protection Enable): این بیت برای فعال کردن Protected Mode است.PG (Paging): برای فعال کردن Paging می باشد CR1: رزرو
CR2: در صورت بروز خطای page-fault این رجیستر آدری خطی دستوری که این خطا را ایجاد کرهد نگه می دارد.
CR3: در مدیریت حافظه Paging این رجیستر آدرس فیزیکی اولین جدول را در خود نگه می دارد (توضیحات بیشتر در آموزش بعدی)
CR4: این رجیستر برای فعال یا غیر فعال کردن یکسری امکانات ارائه شده توسط پردازنده است.
پردازنده های ذکر شده تعداد خیلی زیادی رجیستر به نام MSR دارند. این رجیسترها برای تغییر یا گرفتن یکسری وضعیت ها از پردازنده است. ما در این آموزش از یکی از رجیسترها به نام IA32_EFER استفاده کردیم. به هر کدام از این رجیسترها یک عدد نسبت داده شده.
در مثالی که بالا ذکر کردم مقدار رجیستر IA32_EFER برابر مقدار هگزادسیمال 0xc0000080 است و برای فعال کردن IA-32e مقداربیت 8 که IA-32e Mode Enable است را تغییر داده ایم. در تصویر زیر ساختار این رجیستر و مقدار آن قابل مشاهذه است.

1. اطلاعات در مورد SMRAMC در مستندات (datasheet volume 2) ارائه شده که از این لینک قابل دسترسی هستند
برچسب:
نویسنده: آریا مرادی