سخت‌افزار چگونه از فراخوانی توابع و داده‌های کاراکتری پشتیبانی می‌کند

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

فراخوانی رویهحافظه پشتهکدگذاری کاراکتر

~4 دقیقه مطالعه · آخرین به‌روزرسانی ۱۵ شهریور ۱۴۰۵

وقتی یک تابع فراخوانی می‌شود چه اتفاقی می‌افتد

فراخوانی یک Procedure (یک تابع یا زیرروال) به بیش از صرفاً پریدن به کد آن نیاز دارد. پردازنده باید به خاطر بسپارد بعداً کجا برگردد، آرگومان‌های ورودی را منتقل کند، یک نتیجه دریافت کند، و مطمئن شود مقادیر رجیستر کد فراخواننده به‌طور نامحسوس توسط رویه فراخوانده‌شده بازنویسی نمی‌شوند. RISC-V این کار را از طریق یک قرارداد مشخص‌شده انجام می‌دهد، نه سحر و جادوی سخت‌افزاری خاص.

دستور Jump-and-Link

RISC-V یک دستور اختصاصی برای فراخوانی رویه‌ها فراهم می‌کند که به‌طور خودکار موقعیت بازگشت را ذخیره می‌کند:

jal ra, ProcedureAddress

این دستور اجرا را به ProcedureAddress می‌پراند و هم‌زمان آدرس دستور بعدی را در یک رجیستر مشخص، معمولاً به نام ra (return address)، ذخیره می‌کند. وقتی رویه تمام می‌شود، از این آدرس ذخیره‌شده برای پریدن به عقب استفاده می‌کند:

jalr x0, 0(ra)

این کار کنترل را دقیقاً به دستوری که پس از فراخوانی اصلی می‌آید بازمی‌گرداند.

قراردادهای رجیستر برای انتقال داده

برای قابل‌پیش‌بینی نگه‌داشتن فراخوانی رویه‌ها در کامپایلرها و برنامه‌های مختلف، RISC-V از یک Register Convention ثابت پیروی می‌کند:

  • یک بازه مشخص از رجیسترها برای انتقال Argument Values به یک رویه رزرو شده است.
  • یک رجیستر مشخص برای بازگرداندن Result Value به فراخواننده رزرو شده است.
  • برخی رجیسترها Saved Registers نامیده می‌شوند، که یک رویه فراخوانده‌شده باید در صورت استفاده از آن‌ها، پیش از بازگشت آن‌ها را حفظ و بازیابی کند.
  • سایر رجیسترها Temporary Registers هستند، که یک رویه فراخوانده‌شده آزاد است بدون هیچ تعهدی برای بازیابی مقدار اصلی‌شان، آن‌ها را بازنویسی کند.

پشته: مدیریت فراخوانی‌های تودرتو و بازگشتی

یک مجموعه واحد از رجیسترها زمانی که رویه‌ها رویه‌های دیگر را فراخوانی می‌کنند، یا خودشان را به‌صورت بازگشتی فراخوانی می‌کنند، کافی نیست. برای مدیریت این موضوع، RISC-V از ناحیه‌ای از حافظه به نام Stack استفاده می‌کند، که از طریق یک رجیستر اختصاصی Stack Pointer مدیریت می‌شود.

پیش از اینکه یک رویه یک رجیستر ذخیره‌شده‌ای که نیاز به استفاده مجدد از آن دارد را بازنویسی کند، مقدار فعلی آن رجیستر را روی پشته push می‌کند؛ پیش از بازگشت، آن مقدار را با pop کردن بازیابی می‌کند:

رزرو فضای پشته:
addi sp, sp, -8

ذخیره مقدار یک رجیستر:
sd s0, 0(sp)

بازیابی آن بعداً:
ld s0, 0(sp)

آزادسازی فضای پشته:
addi sp, sp, 8

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

نمایش متن: کاراکترها و کدگذاری

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

یک دنباله از کاراکترها که متن را تشکیل می‌دهند String نامیده می‌شود، و معمولاً به‌صورت بایت‌های پیاپی در حافظه ذخیره می‌شود، یک بایت برای هر کاراکتر، که اغلب با یک مقدار پایان‌دهنده خاص که مشخص می‌کند رشته کجا تمام می‌شود، خاتمه می‌یابد.

چرا این قراردادها اهمیت دارند

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

نوشته و پژوهش‌شده توسط دکتر شاهین صیامی

مقالات مرتبط

سخت‌افزار چگونه تقسیم انجام می‌دهد: خارج‌قسمت، باقیمانده و موارد خاص

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

ادامه

سخت‌افزار چگونه اعداد را ضرب می‌کند: از منطق ساده تا مدارهای واقعی

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

ادامه

سخت‌افزار چگونه جمع و تفریق انجام می‌دهد و سرریز را تشخیص می‌دهد

سخت‌افزار چگونه جمع و تفریق انجام می‌دهد و سرریز را تشخیص می‌دهد

ادامه

آرایه‌ها در مقابل اشاره‌گرها در سطح سخت‌افزار

در C، آرایه‌ها و اشاره‌گرها اغلب قابل‌تعویض به‌نظر می‌رسند، و بسیاری از برنامه‌نویسان با آن‌ها به‌عنوان یک چیز واحد رفتار می‌کنند. با این حال در سطح سخت‌افزار، آن‌ها به توالی‌های دستوری قابل‌توجهی متفاوت با ویژگی‌های کارایی متفاوت کامپایل می‌شوند. این مقاله این دو رویکرد را با استفاده از اسمبلی RISC-V مقایسه می‌کند تا دقیقاً نشان دهد چرا کد مبتنی بر اشاره‌گر اغلب سریع‌تر است.

ادامه

از کد منبع تا یک فرآیند در حال اجرا: ترجمه و یک مثال کامل مرتب‌سازی

تبدیل یک برنامه C به چیزی که سیستم‌عامل واقعاً بتواند اجرا کند شامل چندین مرحله ترجمه مجزاست، که هرکدام یک فایل میانی متفاوت تولید می‌کنند. این مقاله کل این خط لوله را از کامپایلر تا loader مرور می‌کند، سپس مفاهیم این فصل را روی یک مثال کامل و واقعی به‌کار می‌برد: ترجمه گام‌به‌گام یک روال مرتب‌سازی C به اسمبلی RISC-V.

ادامه

مدیریت آدرس‌های بزرگ و همگام‌سازی در RISC-V

یک دستور ۳۲ بیتی نمی‌تواند یک ثابت بزرگ یا یک آدرس حافظه دور را مستقیماً درون خودش جا دهد، و چند پردازنده‌ای که حافظه را به اشتراک می‌گذارند نمی‌توانند بدون هماهنگی، یک داده یکسان را با امنیت به‌روزرسانی کنند. این مقاله توضیح می‌دهد RISC-V چگونه مقادیر فوری و آدرس‌های بزرگ را از قطعات کوچک‌تر می‌سازد، و دستورات اتمیک چگونه اجازه می‌دهند برنامه‌های موازی با امنیت همگام شوند.

ادامه