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

اما تمام حافظه ها به صورت رایگان برای استفاده در دسترس نیستند. برخی از بخش‌های حافظه برای عملکردهای سیستم و برخی دیگر ممکن است توسط دستگاه‌های سخت‌افزاری اشغال شده باشند. به همین دلیل دریافت نقشه حافظه سیستم بسیار مهم است.

مموری مپ چیست؟

اما نقشه حافظه چیست؟ نقشه حافظه نمایشی است (در مورد آن مانند یک جدول فکر کنید) که نشان می دهد حافظه فیزیکی چگونه در سیستم شما سازماندهی شده است. آدرس هر ناحیه حافظه، طول و نوع آن را نشان می دهد.

نوع 1 به این معنی است که منطقه برای استفاده آزادانه در دسترس است و نوع 2 به این معنی است که توسط سیستم شما رزرو شده است. نوع 3 به این معنی است که منطقه برای تنظیمات پیشرفته و رابط قدرت (ACPI 3.x) رزرو شده است. در حالی که ممکن است یک منطقه نوع 3 توسط سیستم استفاده نشود، می توان آن را بعداً بازیابی کرد.

استفاده از نقشه حافظه به شما این امکان را می دهد که منابع حافظه را بدون هیچ مشکلی مانند خرابی یا بی ثباتی سیستم با موفقیت مدیریت کنید.

راه هایی وجود دارد که می توانید حافظه موجود سیستم خود را شناسایی کنید. یکی با استفاده از بایوس و وقفه 15 ساعته. یکی دیگر از طریق انجام کاوش حافظه است.

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

پیش نیازها

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

  • یک سیستم عامل لینوکس

  • آشنایی با زبان اسمبلی

  • یک ویرایشگر متن به انتخاب شما

  • یک شبیه ساز نصب شده است. برای این مثال من از QEMU استفاده می کنم.

  • اسمبلر FASM نصب شده است

  • Git برای شبیه سازی مخزن (https://github.com/nikolaospanagopoulos/memoryMapBoot)

چند کلمه در مورد BIOS int 15h

در حالت Real، BIOS وقفه های زیادی را ارائه می دهد که با سخت افزار تعامل دارند و می توانند اطلاعاتی را در اختیار شما قرار دهند.

وقفه هایی وجود دارد که می تواند به گرفتن نقشه حافظه کمک کند، اما قوی ترین آنها int15h با عملکرد E820h است (اعداد هگزا دسیمال! بسیار مهم است که به خاطر بسپارید. اعداد اعشاری کار نمی کنند). این روش یک نقشه حافظه دقیق ارائه می دهد که می توانید با استفاده از آن به طور ایمن تعیین کنید که کدام مناطق از حافظه می توانند برای کارهای حیاتی مانند تنظیم صفحه بندی، تخصیص حافظه و موارد دیگر استفاده شوند.

در این مقاله خواهید دید که چگونه می توانید از این وقفه برای دریافت نقشه حافظه دقیق سیستم خود استفاده کنید.

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

بررسی حافظه و اینکه چرا باید از آن اجتناب کنید

کاوش حافظه است process دسترسی دستی به حافظه فیزیکی و تعیین اینکه آیا در دسترس است یا نه. مسئله این است که همه حافظه ها برای دسترسی مستقیم طراحی نشده اند.

دسترسی به بخش‌هایی از حافظه که نباید به آن‌ها دسترسی داشته باشید می‌تواند باعث رفتار غیرقابل پیش‌بینی مانند:

  • خرابی سیستم: مقداری از حافظه برای ساختارهای بایوس، دستگاه‌های سخت‌افزاری و غیره محفوظ است. دسترسی به این مناطق می‌تواند منجر به خرابی سیستم یا بی‌ثباتی سیستم شود.

  • تخریب حافظه: دسترسی به مناطق حافظه رزرو شده می تواند منجر به خراب شدن آن مناطق شود. این می تواند دوباره باعث خرابی، بی ثباتی، خرابی و غیره شود

پیشنهاد می‌کنیم بخوانید:  TypeScript: یک ویژگی را از یک شی حذف کنید

بنابراین، باید از بررسی حافظه خودداری کنید زیرا این یک خطر غیرضروری برای توسعه هسته شما است process.

کد

مرحله 1: برای Call int 15h آماده شوید

در این قسمت، شما اساسا محیط مورد نیاز برای فراخوانی int 15h را راه اندازی خواهید کرد. رجیسترهای هدف عمومی باید به گونه ای ذخیره شوند که هیچ داده مهمی وجود نداشته باشد روی آنها در طول فراخوانی وقفه گم می شوند. سپس رجیسترها bp، ebx پاک می شوند تا بتوان آنها را به مقادیر اولیه خود تنظیم کرد.

مقدار “SMAP” در ذخیره می شود edx برای اطمینان از فرمت صحیح که بایوس باز می گردد، ثبت نام کنید. در نهایت، ما راه اندازی 0xe820 عملکرد و درخواست داده های نقشه حافظه.

pusha
mov di, 0x0504        ; Set DI register for memory storage
xor ebx, ebx          ; EBX must be 0
xor bp, bp            ; BP must be 0 (to keep an entry count)
mov edx, 0x534D4150   ; Place "SMAP" into edx | The "SMAP" signature ensures that the BIOS provides the correct memory map format
mov eax, 0xe820       ; Function 0xE820 to get memory map
mov dword [es:di + 20], 1 ; force a valid ACPI 3.X entry | allows us to get additional information (extended attributes)
mov ecx, 24           ; Request 24 bytes of data
  • این pusha دستور همه رجیسترهای هدف عمومی را به پشته هل داد تا مقادیر آنها در طول تماس وقفه ذخیره شود. آنها را می توان پس از تماس وقفه بازیابی کرد تا از فساد سایر مناطق جلوگیری شود.

  • این mov di, 0x0504 دستورالعمل، رجیستر di را روی 0×0504 تنظیم می کند (جایی که ورودی های نقشه حافظه ذخیره می شوند).

  • xor ebx, ebx دستور xor از عملگر xor برای پاک کردن رجیستر ebx استفاده می کند. برای شروع بازیابی ورودی ها باید روی 0 تنظیم شود.

  • xor bp, bp در اینجا از همان عملگر xor برای تنظیم bp روی 0 استفاده کنید. این کار ورودی های حافظه شما را پیگیری می کند.

  • mov edx, 0x534D4150 این دستورالعمل ذخیره خواهد شد 0x534D4150 (رشته ASCII “SMAP”) در ثبات edx. این اطمینان حاصل می کند که BIOS فرمت صحیح را برای نقشه حافظه شما برمی گرداند.

  • mov eax, 0xe820 این دستورالعمل تابع 0xe280 را تنظیم می کند که نقشه حافظه را به همراه int15h دریافت می کند.

  • mov dword [es:di + 20], 1 این دستورالعمل یک ورودی معتبر ACPI (پیکربندی پیشرفته و رابط قدرت) 3.x را مجبور می کند. به این ترتیب BIOS اطلاعات اضافی را در قالب ویژگی های اضافی ارائه می دهد.

  • mov ecx, 24 این دستورالعمل از بایوس 24 بایت حافظه می خواهد. این اندازه ای است که ورودی های ACPI 3.x باید شامل اطلاعات اضافی باشد.

مرحله 2: با int15h تماس بگیرید

در اینجا، در نهایت می توانید برای واکشی نقشه حافظه، وقفه را فراخوانی کنید. باید بررسی کنید که عملکرد توسط BIOS پشتیبانی می‌شود و داده‌های معتبر در حال واکشی هستند. همچنین باید با تنظیم مجدد “SMAP” در فرمت صحیح اطمینان حاصل کنید edx ثبت نام کنید.

    int 0x15                 ; using interrupt
    jc short .failed         ; carry set روی first call means "unsupported function"
    mov edx, 0x534D4150      ; Some BIOSes apparently trash this register? lets set it again
    cmp eax, edx             ; روی success, eax must have been reset to "SMAP"
    jne short .failed
    test ebx, ebx            ; ebx = 0 implies list is only 1 entry long (worthless)
    je short .failed
  • int 0x15 این دستورالعمل وقفه 0×15 را فراخوانی می کند.

  • jc short .failed پرچم حمل است که تنظیم شده است. این بدان معنی است که عملکرد پشتیبانی نمی شود و تماس ناموفق است. به کنترل کننده خطای ما می رود.

  • mov edx, 0x534D4150 دوباره “SMAP” را تنظیم کنید زیرا برخی از BIOS ها این رجیستر را پس از تماس خراب می کنند.

  • cmp eax, edx در صورت موفقیت آمیز بودن تماس، روی موفقیت، BIOS مقدار “SMAP” را در eax برمی گرداند.

  • jne short .failed اگر این کار را نکرد، به این معنی است که تماس ناموفق بوده و به برچسب مدیریت خطا ما می‌رود.

  • test ebx, ebx این دستورالعمل بررسی می کند که آیا ebx بعد از اولین تماس 0 است یا خیر. این بدان معنی است که نقشه حافظه فقط شامل یک ورودی است. این ورودی احتمالاً نامعتبر است، بنابراین به برچسب رسیدگی به خطا می‌رود.

پیشنهاد می‌کنیم بخوانید:  اصول Git را بیاموزید - یک کتابچه راهنمای روی وظایف توسعه روزانه

مرحله 3: از طریق ورودی های حافظه حلقه بزنید

پس از اولین فراخوانی موفق، باید در هر ورودی نقشه حافظه حلقه بزنید.

در حلقه، شما دوباره int 15h را فراخوانی می‌کنید تا تمام ورودی‌های حافظه بعدی را در حالی که طول هر ورودی و سایر ویژگی‌ها را بررسی می‌کنید، دریافت کنید. اگر معیارها را برآورده کند، شمارنده را افزایش می دهید و ورودی را ذخیره می کنید. این تا زمانی ادامه می یابد که هیچ ورودی برای آن باقی نمانده باشد process.

    jmp short .jmpin
.e820lp:
    mov eax, 0xe820          ; eax, ecx get trashed روی every int 0x15 call
    mov dword [es:di + 20], 1 ; force a valid ACPI 3.X entry
    mov ecx, 24              ; ask for 24 bytes again
    int 0x15
    jc short .e820f          ; carry set means "end of list already reached"
    mov edx, 0x534D4150      ; repair potentially trashed register
.jmpin:
    jcxz .skipent            ; skip any 0 length entries (If ecx is zero, skip this entry (indicates an invalid entry length))
    cmp cl, 20               ; got a 24 byte ACPI 3.X response?
    jbe short .notext
    test byte [es:di + 20], 1 ;if bit 0 is clear, the entry should be ignored
    je short .skipent         ; jump if bit 0 is clear 
.notext:
    mov eax, [es:di + 8]     ; get lower uint32_t of memory region length
    or eax, [es:di + 12]     ; "or" it with upper uint32_t to test for zero and form 64 bits (little endian)
    jz .skipent              ; if length uint64_t is 0, skip entry
    inc bp                   ; got a good entry: ++count, move to next storage spot
    add di, 24               ; move next entry into buffer
.skipent:
    test ebx, ebx            ; if ebx resets to 0, list is complete
    jne short .e820lp
  • .e820lp یک برچسب برای حلقه زدن در هر ورودی نقشه حافظه است.

خطوط بعدی برای فراخوانی int15h برای دریافت ورودی حافظه بعدی استفاده می شود:

  • jc short .e820f اگر پرچم حمل تنظیم شده باشد به این معنی است که به انتهای لیست رسیده ایم.

  • jcxz .skipent اگر ثبات ecx 0 باشد، به این معنی است که طول ورودی حافظه نامعتبر است. بنابراین کد از آن عبور می کند.

  • cmp cl, 20 بررسی می کند که آیا ورودی حافظه یک ورودی معتبر ACPI 3.x است یا خیر. (طول آن 24 بایت خواهد بود). اگر اینطور نیست، کد به آن می رود .notext.

  • test byte [es:di + 20], 1 بررسی می کند که آیا بیت 0 در ویژگی های توسعه یافته ورودی حافظه تنظیم شده است یا خیر، که نشان دهنده یک ورودی معتبر است. اگر واضح باشد، ورودی از قلم افتاده است.

  • mov eax, [es:di + 8] 32 بیت پایینی از طول منطقه حافظه را دریافت می کند و سپس آن را با استفاده از عملگر یا با 32 بیت بالایی ترکیب می کنیم. اگر طول کل 0 باشد، ورودی حذف می شود.

  • inc bp افزایش تعداد ورودی

  • add di, 24 نشانگر di را به سمت ورودی حافظه بعدی به جلو حرکت می دهد. هر ورودی 24 بایت است.

مرحله 4: پایان مدیریت ورودی های حافظه

در نهایت، می توانید تعداد ورودی را ذخیره کنید. و با استفاده از popa دستورالعمل، شما تمام رجیسترهای هدف عمومی را به مقادیر قبلی خود بازیابی خواهید کرد. اگر خطایی در طول process، کد به آن می رود .failed برچسب که تابع رسیدگی به خطای ما است.

.e820f:
    mov [mmap_ent], bp       ; store the entry count
    clc                      ; there is "jc" روی end of list to this point, so the carry must be cleared

    popa
    ret
.failed:
    stc                      ; "function unsupported" error exit
    ret
  • mov [mmap_ent], bp تعداد ورودی را ذخیره می کند.

  • clc پرچم حمل را پاک می کند زیرا از قبل تنظیم شده است.

  • popa همه رجیسترهای هدف عمومی را از پشته باز می‌گرداند.

  • .failed ما از این برچسب برای رسیدگی به خطا استفاده می کنیم.

در اینجا یک ویدیو از حساب YouTube من است که در آن کد بالا را پیاده سازی و توضیح می دهم:

پایان

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

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

به کدنویسی ادامه دهید!