به نام خدا

سلام خدمت همه هموطنان عزیزم!

در این جلسه میخواهیم به مفاهیم سیستم عامل و کرنل و چیز های مورد نیاز بپزدازیم!

این جلسه در ادامه جلسه دهم قرار دارد که البته با اختلاف زمانی 2 سال منتشر میشود :)

راستش دو سال پیش که داشتم آموزش دهم رو میدادم که در مورد چگونگی نمایش کاراکتر در مود محافظت شده بود و همچنین قسمت نهم که فعال کردن A20 بود خیلی برام گنگ بود! امسال که دوباره برگشتم سمت سیستم عامل بیشتر برام مفهوم بود که چه کاری باید انجام داد و در واقع لب مطلب چی هست؟!

بنابراین تصمیم گرفتم که اون هارو توضیح بدم که برای شما هم بیشتر جا بیفته و هم یک مروری بشه!

ببینید در قسمت نهم که فعال کردن A20 بود اصلا چیز خاصی نداریم و ما خیلی بزرگ جلوه اش دادیم که این باعث میشد سخت به نظر برسه!

چیزی که ما بررسی کردیم این بود که چطور میشود برای دسترسی به 32 خط آدرس و گذر از مرز 20 بیتی، خط بیستم آدرس رو فعال کرد که انواع روش رو بررسی کردیم از جمله روش بایوس و... که گفتیم مطمئن ترین روش استفاده از کنترلر کیبورد هست! بنابراین برآن شدیم که این روش رو توضیح بدیم که البته در آن جلسه نشد خوب توضیح بدم بنابراین در این جلسه این رو توضیح میدم ببینید که واقعا هیچ چیز خاص و سختی نیست!

میکروکنترلر کیبورد کارش اینه که کنترل کیبورد رو بر عهده میگیره! (خیلی هم سخت نبود :)) خوب این میکرو مثل هر میکرو و... دارای رجیستر هایی هست که میشه درونش نوشت یا از خواند ولی ما چطور میتونیم به رجیستر های اون دسترسی داشته باشیم؟! 

کاری که بایوس برای ما کرده اینه که هر یک از رجیستر های میکروکنترلر های مختلف رو آدرس دهی کرده و شما میتونید با دو دستور اسمبلی in , out به اونا دسترسی داشته باشید مثلا شما با خواندن پورت 0x64 میتونید وضعیت کیبورد رو بخونید! که این دستور میتواند به صورت زیر باشد

in al,0x64

که در واقع مقدار پورت 0x64 رو میخونه (به اندازه یک بایت) و درون al قرار میده!

خوب شاید بپرسید که ما آدرس پورت های رجیستر های مختلف میکروکنترلر های مختلف رو از کجا باید پیدا کنیم! نگران نباشید هرجا که نیاز باشه ما جدول پورت های مربوطه رو در اختیار شما قرار میدیم!

که در اینجا برای میکروکنترلر کیبورد جدول زیر رو داریم:

Port Mapping
Port Read/Write Descripton
0x60 Read Read Input Buffer
0x60 Write Write Output Buffer
0x64 Read Read Status Register
0x64 Write Send Command to controller

خوب همون 0x64 که در بالا گفتم رو میتونید در جدول فوق مشاهده کنید!

همانطور که ملاحظه میکنید برای آدرس 0x64 دو نوع خواندن/نوشتن تعریف شده که اگه شما اون رو بخونید بهتون وضعیت رو میده و اگه بنویسید شما یک دستور  رو برای میکرو ارسال کردید! دلیل این هم کاهش حجم هست و اینا با هم قاطی نمیشوند چون وضعیت یک رجیستر فقط خواندنی هست در حال یک دستور فقط ارسال میشود و شما نمیتوانید یک دستور را بخوانید! همچنین از پورت 0x60 برای خواندن و نوشتن در بافر استفاده میشود!

نکته دیگه ای که باید بهش توجه داشته باشید اینه که پردازنده با سرعت چند گیگاهرتز میخواهد با یک میکروکنترلر چند مگاهرتزی ارتباط برقرار کند در نتیجه شما نباید دستورات خود را پشت سر هم و بلافاصله به میکرو ارسال کنید چون در این صورت امکان از دست رفتن دستورات وجود دارد! برای جلوگیری از این وضعیت ما باید رجیستر Status میکرو را چک کنیم که ببینیم آیا کارش تمام شده یا خیر؟!

رجیستر وضعیت میکروکنترلر کیبورد به شکل زیر است:

  • Bit 0: Output Buffer Status
    • 0: Output buffer empty, dont read yet
    • 1: Output buffer full, please read me :)
  • Bit 1: Input Buffer Status
    • 0: Input buffer empty, can be written
    • 1: Input buffer full, dont write yet
  • Bit 2: System flag
    • 0: Set after power on reset
    • 1: Set after successfull completion of the keyboard controllers self-test (Basic Assurance Test, BAT)
  • Bit 3: Command Data
    • 0: Last write to input buffer was data (via port 0x60)
    • 1: Last write to input buffer was a command (via port 0x64)
  • Bit 4: Keyboard Locked
    • 0: Locked
    • 1: Not locked
  • Bit 5: Auxiliary Output buffer full
    • PS/2 Systems:
      • 0: Determins if read from port 0x60 is valid If valid, 0=Keyboard data
      • 1: Mouse data, only if you can read from port 0x60
    • AT Systems:
      • 0: OK flag
      • 1: Timeout on transmission from keyboard controller to keyboard. This may indicate no keyboard is present.
  • Bit 6: Timeout
    • 0: OK flag
    • 1: Timeout
    • PS/2:
      • General Timeout
    • AT:
      • Timeout on transmission from keyboard to keyboard controller. Possibly parity error (In which case both bits 6 and 7 are set)
  • Bit 7: Parity error
    • 0: OK flag, no error
    • 1: Parity error with last byte

 

بیت های مهم رو بلد کردیم که بیشتر توجه کنید بهش! 

نکته مهمی که در آموزش منبع هم بهش اشاره نشده اینه که بافر ورودی و خروجی از کیه؟! بافر خروجی میکروکنترلر برای CPU ورودی به حساب میاد! ولی خوب توجه کنید که این بافر ها منسوب به میکروکنترلر هستند! به عنوان مثال بیت صفر نشان دهنده وضعیت بافر خروجی هست! یعنی الان مثلا میکرو کارش تموم شده و در بافر خروجی یک چیزی نوشته ما با چک کردن این بیت متوجه این میشیم که بافر خروجی پر هست و محتویاتی داخلش هست! پس اونو میخونیم :)

خوب حالا برای درک بهتر شما بگید اگه ما بخواهیم یک دستوری برای میکرو بفرستیم باید کدام بافر رو چک کنیم؟ ورودی یا خروجی؟! 

جواب : از آنجا که ورودی و خروجی منسوب به میکرو هست باید بافر ورودی رو چک کنیم که آیا خالی هست یا پر؟ بافر که پر باشه یعنی میکروکنترلر هنوز در حال کار روی دستور قبلی هست و ما باید منتظر باشیم پس برای انتظار یک روتین مینویسیم!

این روتین بافر ورودی رو برای ارسال یک دستور چک میکند!

wait_input:
        in      al,0x64		; read status register
        test    al,2		; test bit 2 (Input buffer status)
        jnz     wait_input	; jump if its not 0 (not empty) to continue waiting

گفتیم که با نوشتن در پورت 0x64 میتوانیم دستورات خود را برای میکروکنترلر ارسال کنیم! حالا سوال اینجاست که چه دستوری؟ و دستورات چه چیزی هستند!

بنابراین ما لیست زیر را به این منظور اینجا آورده ایم:

Keyboard Controller Commands
Keyboard Command Descripton
0x20 Read Keyboard Controller Command Byte
0x60 Write Keyboard Controller Command Byte
0xAA Self Test
0xAB Interface Test
0xAD Disable Keyboard
0xAE Enable Keyboard
0xC0 Read Input Port
0xD0 Read Output Port
0xD1 Write Output Port
0xDD Enable A20 Address Line
0xDF Disable A20 Address Line
0xE0 Read Test Inputs
0xFE System Reset
Mouse Command Descripton
0xA7 Disable Mouse Port
0xA8 Enable Mouse Port
0xA9 Test Mouse Port
0xD4 Write to mouse

همانظور که میبنید دو دستور 0xDD و 0xDF به منظور فعال کردن و غیر فعال کردن A20 به کار میرود اما متاسفانه این دو دستور ساده در همه میکروکنترلر ها موجود نیست در نتیجه ما نمیتوانیم از این دو دستور برای سیستم عامل خودمون استفاده کنیم (چون قصد نداریم یک سیستم عامل به دردنخور بسازیم!)

خوب قبل از اینکه ادامه بدیم یک نکته رو بگم:

طبق گفته سایت منبع : گیت A20 یک گیت OR الکتریکی واقعی هست که به پایه P21 میکروکنترلر 8042 متصل شده (میکروکنترلر صفحه کلید) (که البته به نظر من این گیت باید AND باشه که بتونه اینکار رو انجام بده نه OR حالا مهم نیست!) برای فعال کردن A20 بلاشک باید این پایه میکروکنترلر رو فعال کنیم تا گیت ما فعال شه یعنی ما باید output port میکروکنترلر رو تغییر بدیم!

کاری که باید بکنیم اینه که OutPut Port فعلی رو بخونیم و پایه ای که به گیت A20 وصل هست رو 1 کنیم!

برای اینکار در جدول بالا به دستور 0xD0 توجه کنید! کاری که این دستور انجام میده اینه که  OutPut Port رو برامون میخونه! این خیلی خوبه!

ولی خوبه ما چطور چیزی رو که خوند دریافت کنیم؟

جواب اینه که این میکرو چیزی رو که خوند در بافر خروجی اش قرار میده و ما با خوندن اون بافر مقدار  OutPut Port رو به دست آوردیم و این خیلی عالیه s02

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

  1. دستور خواندن  OutPut Port رو براش بفرستیم که ببینیم وضعیت پایه ها چجوریه؟!
  2. از اونجایی که میکرو این چیزی رو که خوند در بافر خروجی اش قرار میده ما باید منتظر فعال شدن بیت "خروجی پر است" در رجیستر وضعیت باشیم
  3. حالا بافر خروجی رو میخونیم!
  4. حالا بیت مربوط به پایه متصل به گیت A20 رو یک میکنیم که این گیت فعال بشه!
  5. حالا باید به میکرو یک دستور ارسال کنیم که بگیم ما میخواهیم که  OutPut Port بفرستیم و تو اون رو روی پایه های خروجی ست کن! پس اول این دستور رو میفرستیم!
  6. حالا  OutPut Port جدید رو میفرستیم!
  7. تمام الان گیت A20 فعال شد :)

حالا بریم سراغ کدش!

1 - ارسال دستور خواندن Output Port : 

mov     al,0xD0
out     0x64,al

2- انتظار برای پر شدن بافر خروجی:

call wait_output

3- خواندن بافر خروجی:

in al,0x60

حالا مقدار پورت های خروجی توی دست ماست و ما باید بیت مربوط به A20 رو فعال کنیم اما بیت مربوطه کدام بیت است! به عبارت زیر توجه کنید:

  • Bit 0: System Reset
    • 0: Reset computer
    • 1: Normal operation
  • Bit 1: A20
    • 0: Disabled
    • 1: Enabled
  • Bit 2-3: Undefined
  • Bit 4: Input buffer full
  • Bit 5: Output Buffer Empty
  • Bit 6: Keyboard Clock
    • 0: High-Z
    • 1: Pull Clock Low
  • Bit 6: Keyboard Data
    • 0: High-Z
    • 1: Pull Data Low

 

همانطور که از جدول بالا پیداست بیت مربوط به A20 بیت 1 است پس ما باید این بیت رو فعال کنیم

4- فعال کردن بیت مربوط به گیت A20:

or al,2 ; 00000010b

5- ارسال دستور نوشتن  Output Port : 

push eax
mov     al,0xD1
out     0x64,al

برای اینکه مقدار  Output Port که این همه زحمت براش کشیدیم از دست نرود آنرا در استک ذخیره میکنیم!

6- ارسال مقدار  Output Port جدید:

خوب حالا نوبت به مرحله آخر رسیده! کافیه چیزی رو که پوش کردیم حالا پاپ کنیم و برای میکروکنترلر بفرستیم:

pop eax
out 0x60,al

البته یه چیزی هم بگم که برای جلوگیری از هر مشکلی بهتره قبل از ارسال دستورات چک کنیم که آیا بافر خروجی خالی هست یا نه؟

حالا گیت A20 فعال شد و ما الان به 4 گیگ رم دسترسی داریم!

این از این!

حالا موضوع دیگه که به آموزش جلسه 8 برمیگرده مود 32 بیتی هست و اینکه چطور وارد مود 32 بیتی بشیم ؟!

» وارد شدن به دنیای 32 بیتی:

باورتون میشه اگه بگم (با این همه آموزش جلسه 8 ولی) وارد شدن به مود 32 بیتی فقط و فقط با ست کردن 1 بیت از رجیستر cr0 هست!!

cr0 رجیستری به شکل زیر هست:

  • Bit 0 (PE) : Puts the system into protected mode
  • Bit 1 (MP) : Monitor Coprocessor Flag This controls the operation of the WAIT instruction.
  • Bit 2 (EM) : Emulate Flag. When set, coprocessor instructions will generate an exception
  • Bit 3 (TS) : Task Switched Flag This will be set when the processor switches to another task.
  • Bit 4 (ET) : ExtensionType Flag. This tells us what type of coprocessor is installed.
    • 0 - 80287 is installed
    • 1 - 80387 is installed.
  • Bit 5 : Unused.
  • Bit 6 (PG) : Enables Memory Paging.

که مهمش رو درشت کردیم! اما بیت مد نظر ما بیت 0 هست که مارو وارد دنیای 32 بیتی میکنه!

و ما با تکه کد زیر میتونیم وارد مود 32 بیتی بشیم:

mov		eax, cr0			; set bit 0 in CR0-go to pmode
or		eax, 1
mov		cr0, eax

اما مد 32 بیتی دنیای دیگه داره و ما نمیتونیم انقدر بی مقدمه واردش شیم s03

این مثل این میمونه که شما میتونید وارد یک کلاس درس شید ولی اگه وارد شید که به درد نمیخوره یا شماره بیرون میندازن یا خودتون هیچ چیزی یاد نمیگیرید و هیچ کاری نمیتونید بکنید و.... برای اینکه بتونید از کلاس درس استفاده بکنید باید ویژگی هایی داشته باشید مثلا مقدمات اون درس رو بلد باشید و... در نتیجه وارد کلاس شدن عملیه ولی به چه دردی میخوره؟

وارد مود 32 بیتی شدن به همین آسونیه ولی چون شما مقدماتش رو فراهم نکردید در همون لحظه اول سیستم عامل کرش میکنه! چرا؟

از آنجا که تایمر داره هر چند میلی ثانیه به چند میلی ثانیه یک وقفه به CPU میده و CPU هم میره در جدول IVT و طبق آدرسی که اونجاست تابع Handler مربوط به وقفه ساعت رو اجرا میکنه! به محض ورود به مد 32 بیتی دیگه این جدول موجود نیست بلکه پای IDT وسط میاد و آدرس توابع Handler در این جدول قرار میگرند که به دلیل عدم فراهم کردن این جدول به محض ورود به مود 32 بیتی سیستم کرش میکنه (چون IDT نداریم که این بره آدرس تابع مربوط به Handler تایمر رو بر داره و اجرا کنه!)

خوب اینجوری هم که نمیشه بزارید حداقل یک ثانیه توی مود 32 بیتی باشیم!

برای اینکار خوب یه راه حل خوبی هست! ما تا زمانی که IDT رو درست نکردیم وقفه ها رو غیرفعال میکنیم که دیگه تایمر نتونه وقفه بده که سیستم در اثر نبودن IDT کرش کنه!

خوب این عملیه و میشه اینکار رو کرد خیلی راحت و با یک دستور اسمبلی cli میشه وقفه رو غیرفعال کرد!

ولی مشکل دیگه ای وجود داره اونم GDT هست! GDT که مخفف Global Descriptor Table هست فضای حافظه ram رو توضیف میکنه! یعنی اینکه میگه کجای رم قابل خواندن و کجاش برای نوشتنه! دیتا ها کجان و کد ها کجان و...

نکته ای که هست اینه که ممکنه این GDT کمی گنگ باشه ولی خیلی خیلی مهمه و زود ازش نگذرید که فکر کنید خوب اینو کپی میکنم بقیه سیستم عامل رو خودم مینویسم نه هرگز اینجور نیست چون هیچ وقت نمیتونید GDT رو کنار بگذارید! 

البته اصلا هم سخت نیست!

کاری که شما باید بکنید اینه که براساس یک فرمتی (یه چیز مثل Struct در C) ما باید جدول خودمون رو توصیف کنیم و بسازیم! بعدش اونو به پردازنده بدیم!

پس اون قدرهام سخت نیست!

فرم زیر همون فرمتی هست که ما باید توضیفگر خودمون رو از روش بسازیم:

  • Bits 56-63: Bits 24-32 of the base address
  • Bit 55: Granularity
    • 0: None
    • 1: Limit gets multiplied by 4K
  • Bit 54: Segment type
    • 0: 16 bit
    • 1: 32 bit
  • Bit 53: Reserved-Should be zero
  • Bits 52: Reserved for OS use
  • Bits 48-51: Bits 16-19 of the segment limit
  • Bit 47 Segment is in memory (Used with Virtual Memory)
  • Bits 45-46: Descriptor Privilege Level
    • 0: (Ring 0) Highest
    • 3: (Ring 3) Lowest
  • Bit 44: Descriptor Bit
    • 0: System Descriptor
    • 1: Code or Data Descriptor
  • Bits 41-43: Descriptor Type
    • Bit 43: Executable segment
      • 0: Data Segment
      • 1: Code Segment
    • Bit 42: Expansion direction (Data segments), conforming (Code Segments)
    • Bit 41: Readable and Writable
      • 0: Read only (Data Segments); Execute only (Code Segments)
      • 1: Read and write (Data Segments); Read and Execute (Code Segments)
  • Bit 40: Access bit (Used with Virtual Memory)
  • Bits 16-39: Bits 0-23 of the Base Address
  • Bits 0-15: Bits 0-15 of the Segment Limit

مثلا مشخص کنیم که توضیف گر ما کجای حافظه رو قرار توضیف کنه! (مقادیر Base و Limit) این توصیفگر ما برای توصیف کد هست یا داده؟ کسی که به این کد دسترسی پیدا میکنه باید چه سطح دسترسی داشته باشه؟ و...

نکته ای که باید بهش توجه کرد اینه که توی GDT توصیفگر اول باید حتما پوچ باشه (همش صفر باشه)  و دوتا توصیفگر بعدی باید توصیفگر کد و توصیفگر داده باشند!

ببینید برای درک بهتر GDT بزارید کمی بیشتر توضیح بدم:)

همونطور که از اسمش پیداست GDT یک جدول هست!  که میتونه یه تعداد محدودی درایه داشته باشه (خوب قاعدتا توی هر جدول یک چیزی نوشته میشه دیگه اینجام همینجوریه) حالا این درایه های جدول چیا هستند؟! همون چیزایی هستند که قرار حافظه رو توضیف کنند! شاید بپرسید ما که یک چیز بیشتر نمیخوایم توصیف کنیم چه نیازی به جدول بود؟ نه دلیلش اینه که خوب ممکنه شما هرجایی رو برای یک چیزی توصیف کنید! که البته LDT اینجوری درکش راحت تره پس بزارید LDT رو توضیح بدم بعدش به GDT تعمیم بدم! LDT هم دقیقا مثل GDT میمونه ولی ما فعلا کارش نداریم! هر برنامه که قراره روی سیستم عامل ما اجرا بشه برا خودش یک LDT داره! خوب هر درایه در این جدول LDT یک توصیفگر هست خوب برنامه ما نباید به همه حافظه دسترسی داشته باشه و فقط باید بتونه به دیتا و کد های خودش دسترسی داشته باشه! پس هر برنامه یک توصیفگر خودش رو میخواد ولی برای حرفه ای تر شدن پردازنده هر برنامه میتونه به چند قطعه تقسیم بشه و هر قطعه برای خودش یک توصیفگر داشته باشه و LDT شامل توصیفگر این قطعه هاست!

GDT هم تقریبا همینجوری توی این جدول میتونید تعداد محدودی توصیف کننده داشته باشید! ولی اینتل گفته اولین توصیف کننده ی این جدول حتما باید پوچ یا نال باشه! (دلیلش رو باید از خود اینتل بپرسید)

بعدش هم باید کد و دیتا رو بزارید که GDT شما کامل بشه!

حالا باید آدرس GDT و سایز اون رو به پردازنده اعلام کنید!

برای اینکار باید آدرس اولین توصیفگر که در GDT توصیفگر پوچ هست و همراه اندازه توصیفگر ها (که ما فعلا سه تا توصیفگر پوچ و کد و داده داریم ) رو به پردازنده اعلام کنیم! برای اینکار باید از جدول زیر کمک بگیریم 

GDTR Register
Bits 0...15 (GDT Limit) Bits 16...47 (GDT Base Address)

 

یعنی اینتل یک رجیستر مخصوص نگه داری اندازه و آدرس GDT تعبیه کرده که ما باید اطلاعات مربوط به GDT رو در اون بریزیم!

برای اینکه اطلاعات مربوطه رو در این رجیستر قرار بدیدم از یک دستور خاص به عنوان LGDT یعنی Load GD Table استفاده میکنیم!

حالا طبق جدول بالا که گفتیم هر بیت یک توصیفگر (یک درایه از GDT) چه معنی میده ما میایم و برای کد و دیتای خودمون یک توصیفگر میسازیم! نکته اینجاست که ما کل حافظه رو توصیف میکنیم و دسترسی کامل به حافظه رو برای خودمون اخذ میکنیم (هرچی نباشه که ما کرنل هستیم) اگه اشتباهی کنیم دیگه نمیتونیم به اونجای حافظه بریم! حالا شاید بپرسید که خوب چه کاری بود ما GDT درست کردیم این که مثل قبلش شد! باید بگم بله تقریبا فرقی نمیکنه ولی این قانونشه و پارتی هم با کسی نداره و ما هم برای استفاده از حافظه باید اونو توصیف کنیم!

توصیف ما به این شکله :

; Global Descriptor Table
gdt_start:
; null discriptor
    dd 0
    dd 0
; code discriptor
    dw 0xFFFF ; limit
    dw 0    ; base
    db 0    ; base (16-23)
    db 10011010b ; acsess
    db 11001111b ; granularity
    db 0    ; base high
; data discriptor
    dw 0xFFFF ; limit
    dw 0    ; base
    db 0    ; base (16-23)
    db 10010010b ; acsess
    db 11001111b ;granularity
    db 0    ; base high
gdt_end:

همانطور که میبینید توصیفگر پوچ همش 0 هست! و توصیفگر کد و دیتا هم فقط در یک بیت با هم تفاوت دارند که اون بیت هم اینه -> این توصیفگر دیتا هست یا توصیفگر کد هست؟ که قطعا برای کد و دیتا هرکدوم باید فرق کنه!

همانطور که در کد بالا مشاهده میکنید ما base رو صفر گرفتیم و limit رو هم حداکثر (همش f هست :)) (در همین حد سواد دارم :)) یعنی میخواهیم کل حافظه رو توصیف کنیم!(توجه کنید که هم بیت های base یا همه بیت های limit پشت سر هم نیستند و جدا از هم هستند، دلیل این هم سازگاری بازگشتی هست - یعنی توی cpu های قدیمی که نبوده)

دو تا بایت مهم هستند که یکیش granularity و دیگری access هست!

در زیر توضیح access بایت رو از منبعش آوردم و چون ساده هست فارسیش نکردم:

  • Bit 0 (Bit 40 in GDT): Access bit (Used with Virtual Memory). Because we don't use virtual memory (Yet, anyway), we will ignore it. Hence, it is 0
  • Bit 1 (Bit 41 in GDT): is the readable/writable bit. Its set (for code selector), so we can read and execute data in the segment (From 0x0 through 0xFFFF) as code
  • Bit 2 (Bit 42 in GDT): is the "expansion direction" bit. We will look more at this later. For now, ignore it.
  • Bit 3 (Bit 43 in GDT): tells the processor this is a code or data descriptor. (It is set, so we have a code descriptor)
  • Bit 4 (Bit 44 in GDT): Represents this as a "system" or "code/data" descriptor. This is a code selector, so the bit is set to 1.
  • Bits 5-6 (Bits 45-46 in GDT): is the privilege level (i.e., Ring 0 or Ring 3). We are in ring 0, so both bits are 0.
  • Bit 7 (Bit 47 in GDT): Used to indicate the segment is in memory (Used with virtual memory). Set to zero for now, since we are not using virtual memory yet

و همچنین برای بایت granularity طبق سایت منبع داریم:

  • Bit 0-3 (Bits 48-51 in GDT): Represents bits 16-19 of the segment limit. So, lessee... 1111b is equal to 0xf. Remember that, in the first two bytes if this descriptor, we set 0xffff as the first 15 bites. Grouping the low and high bits, it means we can access up to 0xFFFFF. Cool? It gets better... By enabling the 20th address line, we can access up to 4 GB of memory using this descriptor. We will look closer at this later...
  • Bit 4 (Bit 52 in GDT): Reserved for our OS's use--we could do whatever we want here. Its set to 0.
  • Bit 5 (Bit 53 in GDT): Reserved for something. Future options, maybe? Who knows. Set to 0.
  • Bit 6 (Bit 54 in GDT): is the segment type (16 or 32 bit). Lessee.... we want 32 bits, don't we? After all-we are building a 32 bit OS! So, yeah--Set to 1.
  • Bit 7 (Bit 55 in GDT): Granularity. By setting to 1, each segment will be bounded by 4KB.

خوب اینجا لازم میبینم که یک بیت رو توضیح بدم:

راستش اگه دقت کنید میبنید که کل limit ما 20 بیت هست! یعنی اگه ما بخوایم کل حافظه رو توصیف کنیم base رو میزاریم روی 0 و اگه بخواهیم limit رو با f پر کنیم میشه 0xFFFFF یعنی نهایتا 1 مگابایت! خوب برای اینکه ما بتونیم به 4 گیگ حافظه دسترسی داشته باشیم باید limit رو میکردن 32 بیتی و چون نتونستن اومدن یک بیت به نام Granularity که بیت 55 از توصیفگر ما هست! میگه اندازه limit در 4k ضرب شه و اون limit ما باشه! اگه دقت کنید 4 کیلوبایت میشه 2 به توان 12 و اگه این عدد یعنی 2 به توان 12 رو در 2 به توان 20 (حداکثر limit) ضرب کنیم میشه 2 به توان 32 که یعنی ما تونستیم 4 گیگ حافظه رو توصیف کنیم:)

اینم از این بیت که به خاطر این بیت مهم کل بایت رو به این اسم نامگذاری کردیم :)

حالا که توصیف کردیم باید آدرس GDT و اندازه اونم به دست بیاریم:

toc:
    dw   gdt_end-gdt_start-1 ; limit of GDT - size of 
    dd   gdt_start  ; base Adress of GDT

حالا باید اینو به پردازنده معرفی کنیم!

برای اینکار از دستور زیر استفاده میکنیم!

lgdt [toc]

دیگه تموم شد! اینم از GDT خیالمون از اینم راحت شد!

سه عنصر اصلی رو توضیح دادیم فعال کردن A20 و ساخت GDT و رفتن به مد 32 بیتی! 

حالا میتونید با خیال راحت وقفه ها رو غیرفعال کنید و وارد مد 32 بیتی بشید :)

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

یه نکته که بگم خوشحال شید قراره کرنل رو با C++ بنویسیم! 

توی آموزش منبع با استفاده از ویژوال استودیو میاد و کرنل رو با زبان C++ مینویسه و با همون به صورت exe کامپایل میکنه!

توجه کنید که هرچند فرمت exe مربوط به ویندوز است اما کد های اصلی برنامه قراره روی cpu اجرا بشه پس مهم نیست که فرمتش چیه!

ما الان باید بیایم کرنل رو که فرمت exe داره در حافظه لود کنیم!

 

» لود کردن کرنل و اجرای کرنل:

خوب کاری که باید بکنیم اینه که کرنل رو از روی دیسک (فلاپی) بخونیم و در حافظه بریزیم! بعد اجرا کنیم!

کمی فکر کنید! اگه ما بخواهیم در مد 32 بیتی اینکار رو انجام بدیم که وقفه نداریم (دیگه در مد 32 بیتی بایوسی در کار نیست - اگه قبلا نگفته بودم ببخشید این نکته خیلی مهمه و باید بهش توجه داشته باشید) اگر هم بخواهید در مد 16 بیتی اینکار رو بکنید که شما هنوز کمتر از 1 مگ حافظه دارید و نمیتونید کرنل رو در آدرس بیش از 1 مگ قرار بدید و همچنین در مد 16 بیتی نمیتونید بپرید به کرنل چون هنوز A20 فعال نشده و وارد مد 32 بیتی نشدیم!

پس بهتره که قسمت لودکردن از فلاپی روی حافظه رو در قسمت 16 بیتی که به بایوس دسترسی داریم انجام بدیم (و اونو در آدرسی لود کنیم و بعدش در قسمت 32 بیتی (هنوزم به زبان اسمبلی) کرنل خودمون رو که هنوز توی حافظه است از اونجایی که لود شده بیاریم روی آدرس 1مگ بریزیم و حالا اجراش کنیم!)

 

قسمت لود کردن کرنل مثل قسمت لود کردن بوتلدر stage2 میمونه که در قسمت های اولیه رفتیم و جدول FAT رو لود کردیم و...

اینجام همینکار هارو برای KERNEL32.EXE انجام میدیم! تا اینا که هیچی بعدش هم وقتی وارد مد 32 بیتی شدیم اون رو به مکان 1 مگ حافظه منتقل میکنیم و اجرا میکنیم! اینم که هیچی!

در نتیجه این چیز جدیدی نداشت!

فقط یک نکته مهم وجود داره که چون فرمت خروجی کرنل ما exe خواهد بود باید به تابع main بریم نه اینکه از همون اول فایل exe بپریم چون اولاش یک چیزی به نام فایل هدر داره که توضیحاتی در مورد فایل نوشته و ما باید از اونا بپریم که شما با سرچ کردن Header File of exe میتونید تشخیص بدید که نقطه شروع برنامه کجاست و به اونجا بپرید.

موفق باشید

یا علی مدد...!