پنجشنبه، ۲۲ مرداد ۱۴۰۵

مدلهای حافظه در معماری چندهستهای نهفته مستقیماً بر سرعت، مصرف توان، هزینه، پیشبینیپذیری و مقیاسپذیری سیستم اثر میگذارند. طراح یک سیستم نهفته برخلاف طراح رایانهٔ همهمنظوره معمولاً برنامهها و الگوی دسترسی آنها به حافظه را از قبل میشناسد؛ بنابراین میتواند اندازه و سلسلهمراتب کش، نوع حافظه، معماری اشتراکی یا توزیعشده و سازوکار انسجام کش را دقیقاً متناسب با بار کاری انتخاب کند.
این فصل انواع حافظه از RAM، DRAM و SRAM تا حافظهٔ فلش، اسکرچپد و eDRAM را توضیح میدهد؛ سپس معماری کش، TLB، DMA، مدلهای UMA و NUMA، حافظهٔ توزیعشده، پروتکلهای انسجام کش و MESI، اشتراک کاذب و در نهایت حافظهٔ تراکنشی نرمافزاری و سختافزاری را بررسی میکند. ترجمهٔ حاضر، متن کامل فصل را با حفظ جزئیات فنی و با زبانی روان و قابل استفاده برای انتشار وب ارائه میدهد.
1. هیچ معماری حافظهای برای همهٔ سیستمهای نهفته بهترین نیست؛ تصمیم درست باید با سنجش واقعی برنامهٔ هدف گرفته شود.
2. کش معمولاً میانگین کارایی را بالا میبرد، اما در سامانههای بلادرنگ میتواند پیشبینیپذیری زمان دسترسی را کاهش دهد.
3. اسکرچپد، DMA و eDRAM ابزارهایی مهم برای کنترل توان، پهنای باند و زمان دسترسی هستند.
4. در معماریهای چندهستهای باید میان حافظهٔ اشتراکی، NUMA، حافظهٔ توزیعشده و ترکیبی از آنها انتخاب کرد.
5. پروتکلهای انسجام کش، از جمله MESI، جلوی خواندن نسخههای کهنهٔ داده را از کش هستههای مختلف میگیرند.
6. اشتراک کاذب، مهاجرت رشتهها و قفلگذاری نادرست میتوانند بخش بزرگی از مزیت چندهستهای را از بین ببرند.
7. حافظهٔ تراکنشی نوشتن کد همزمان را سادهتر میکند، اما هزینهٔ ثبت عملیات، تشخیص تعارض و تلاش مجدد را به همراه دارد.
8. مقدمه
9. انواع حافظه
10. معماری حافظه و سلسلهمراتب کش
11. سفارشیسازی کش، حافظهٔ مجازی، اسکرچپد، همپوشانی نرمافزاری، DMA، DRAM و حافظههای ویژه
12. ساختار حافظه در معماری چندهستهای: UMA، NUMA و حافظهٔ توزیعشده
13. انسجام کش و پروتکلهای مبتنی بر دایرکتوری، شنود و MESI
14. مسائل کارایی کش: اشتراک کاذب، پینگپنگ خط کش، وابستگی پردازندهای و قفلکردن کش
15. حافظهٔ تراکنشی نرمافزاری، سختافزاری و ترکیبی
16. جمعبندی و منابع
برنامههایی که قرار است روی سامانههای محاسباتی همهمنظوره اجرا شوند از قبل معلوم نیستند؛ بنابراین معماری حافظه باید آنقدر عمومی طراحی شود که بتواند مجموعهای از برنامههای ناهمگون با نیازهای متفاوت حافظه را پشتیبانی کند. در مقابل، سیستمهای نهفته برای اجرای مجموعهای از برنامههای ازپیشتعریفشده ساخته میشوند. الگوی دسترسی این برنامهها به حافظه را میتوان پیشاپیش تعیین کرد و از این اطلاعات برای سفارشیسازی معماری حافظه بهره گرفت؛ خواه هدف کاهش هزینه و اندازه باشد، خواه افزایش کارایی، کاهش مصرف توان یا حفظ قابلیت حمل نرمافزار.
طراحی معماری حافظه در سیستمهای نهفتهٔ چندهستهای حتی حساستر است، زیرا چند هستهٔ موجود در سیستم دادهها را با یکدیگر به اشتراک میگذارند. برای نمونه، نحوهٔ چیدمان سلسلهمراتب کش و پروتکل انسجام کش انتخابشده میتواند اثر بسیار زیادی بر کارایی کل سیستم داشته باشد.
بحث را با انواع حافظههای مورد استفاده در سیستمهای نهفته آغاز میکنیم. سپس معماری حافظه را با تأکید ویژه بر ساختار کش توضیح میدهیم. در یک سیستم نهفته میتوان تعداد سطوح کش، ظرفیت کش و اندازهٔ خط کش را با آزمایش و بنچمارکگیری قبلی از برنامهٔ هدف تعیین کرد. طراح همچنین میتواند پیکربندیهای جایگزینی مانند حافظهٔ SRAM اسکرچپد تحت کنترل DMA، حافظههای DRAM تعبیهشده و صفهای FIFO را انتخاب کند تا نیازهای متفاوت برنامه برآورده شود.
در ادامه، گزینههای معماری حافظهٔ چندهستهای معرفی میشوند. حافظه در چنین سامانهای میتواند شکلهای گوناگونی داشته باشد: از حافظهای که کاملاً میان همهٔ پردازندهها یا هستهها مشترک است تا حافظهای که بهطور کامل در اختیار یک هسته قرار دارد. روش اشتراک و تبادل اطلاعات میان هستهها نیز با پیکربندی حافظه تغییر میکند.
پس از آن مروری بر پروتکلهای انسجام کش خواهیم داشت و پروتکل MESI را با جزئیات توضیح میدهیم. پیش از جمعبندی نیز به حافظهٔ تراکنشی میپردازیم.
مهم است مشخص کنیم کدام نوع و پیکربندی حافظه برای برنامهٔ مورد نظر مناسبتر است و برنامه را طوری طراحی کنیم که از معماری انتخابشده بهشکلی کارآمد استفاده کند. هدف این فصل ارائهٔ تصویری جامع از گزینههای موجود است تا بتوانید برای سیستم خود تصمیم درستی بگیرید؛ زیرا یک معماری حافظهٔ واحد وجود ندارد که برای همهٔ سیستمهای نهفته به یک اندازه خوب عمل کند.
حافظه در یک سیستم نهفتهٔ ردهپایین یا میانرده معمولاً گرانتر و محدودتر از حافظهٔ رایانهٔ رومیزی، لپتاپ یا سیستم نهفتهٔ ردهبالاست. با توجه به تنوع حافظههای در دسترس، طراح باید تفاوت آنها را بشناسد تا نیاز دستگاه از نظر ظرفیت، چگالی ذخیرهسازی، هزینه، کارایی، توان، سهولت دسترسی و فرّار یا غیرفرّار بودن به کارآمدترین شکل برآورده شود.
بسته به اندازهٔ سیستم، حافظه میتواند بهصورت RAM فرّار برای نگهداری متغیرها و پشته و بهصورت حافظهٔ غیرفرّار مانند Flash یا ROM، دیسک مغناطیسی یا کارت حافظهٔ جداشدنی برای نگهداری دائمی دادهها وجود داشته باشد. رایجترین انواع حافظه در سیستمهای نهفته عبارتاند از موارد زیر.
RAM برای دسترسی پرسرعت به دادههای موقتی به کار میرود که هنگام اجرای برنامه مورد نیازند. هر کلمهٔ حافظه با مشخصکردن مستقیم نشانی آن قابل دسترسی است و زمان دسترسی به جایگاه فیزیکی کلمه وابسته نیست. RAM تا زمانی که برق دارد محتوای خود را حفظ میکند؛ بنابراین حافظهای فرّار است. ظرفیت ذخیرهسازی RAM نیز به اندازهٔ گذرگاه وابسته است.
DRAM حافظهای ارزان با چگالی ذخیرهسازی بالاست. چون اطلاعات را بهشکل خازنی نگه میدارد، باید با کمک کنترلگر DRAM بهطور دورهای نوسازی شود و این کار مصرف توان را افزایش میدهد. دسترسی به DRAM بر مبنای سطر، ستون و صفحه انجام میشود و به این ترتیب تعداد خطوط لازم در گذرگاه نشانی بهطور محسوسی کاهش مییابد.
SRAM زمان دسترسی بسیار کوتاهی دارد، اما تولید آن از DRAM گرانتر است. بسیاری از سیستمهای نهفته هر دو نوع را بهکار میگیرند: یک بلوک کوچک SRAM در مسیر بحرانی داده و یک بلوک بسیار بزرگتر DRAM برای سایر دادهها و کدها.
NVRAM در اصل SRAM همراه با پشتیبان باتری است و در نتیجه پس از قطع برق نیز دادههای خود را حفظ میکند. این حافظه در سیستمهای نهفته نسبتاً رایج است، اما وجود باتری هزینه را بالا میبرد. معمولاً از NVRAM برای نگهداری اطلاعات حیاتی سیستم استفاده میشود.
DPRAM نوعی SRAM با دو درگاه ورودی/خروجی است که هر دو به همان جایگاههای حافظه دسترسی دارند. این حافظه اغلب در پردازندههای دوهستهای برای پیادهسازی حافظهٔ مشترک به کار میرود.
EPROM یا حافظهٔ فقطخواندنیِ قابلبرنامهریزی الکتریکی، حافظهای غیرفرّار و یکباربرنامهپذیر است. میتوان آن را یک بار و با ایستگاههای برنامهریزی ویژه در محل برنامهریزی کرد. EEPROM نسخهٔ پاکشوندهٔ الکتریکی EPROM است و برخلاف EPROM میتوان آن را دوباره برنامهریزی کرد. EEPROM گران است و نوشتن در آن از نوشتن در RAM زمان بیشتری میبرد.
EEPROM باید برای حجم کمی از داده به کار رود که پس از قطع برق باید باقی بمانند، نه بهعنوان حافظهٔ اصلی سیستم. این حافظه از جهاتی به Flash شباهت دارد. تفاوت اصلی آن است که داده در EEPROM باید بایتبهبایت نوشته و پاک شود، در حالی که Flash نوشتن و پاککردن بلوکی را ممکن میکند و در نتیجه بسیار سریعتر است.
Flash تراشهای غیرفرّار، کمهزینه و پرچگال است که میتوان آن را بهصورت الکتریکی پاک و دوباره برنامهریزی کرد. دو گونهٔ اصلی آن NOR Flash و NAND Flash هستند. در NOR Flash میتوان هر کلمه را مستقل خواند یا نوشت و به همین دلیل برای ذخیرهٔ کد راهانداز مناسب است. NAND Flash از NOR متراکمتر و ارزانتر است، اما دسترسی به آن بلوکی است و برای ذخیرهٔ کدی که مستقیم اجرا میشود مناسب نیست. کاربرد اصلی NAND در کارت حافظه، فلش USB و درایو حالتجامد است.
کارت Secure Digital Multimedia Card فضای ذخیرهسازی غیرفرّار و ارزان در مقیاس گیگابایت فراهم میکند. این کارتها بسیار کوچکاند و در سامانههای قابلحملی مانند دوربین دیجیتال، کنسول بازی و تلفن همراه استفاده میشوند.
دیسک سخت یک وسیلهٔ ذخیرهسازی مغناطیسی، غیرفرّار و با دسترسی تصادفی است که میتواند حجیم باشد و برای خواندن جایگاههای حافظه به گردانندهٔ دیسک نیاز دارد. معمولاً از آن برای ذخیرهسازی انبوه، زمانی که محدودیت سختی برای اندازه وجود ندارد، استفاده میشود. دیسک سخت تنها در سیستمهای نهفتهٔ بزرگتر دیده میشود.
سیستم نهفته برای اجرای مجموعهای روشن و مشخص از برنامهها طراحی میشود. شناخت الگوی دسترسی این برنامهها به حافظه به طراح اجازه میدهد معماری حافظه را با هدف بهینهکردن مساحت تراشه، توان، کارایی و ماندگاری داده تنظیم کند.
سامانههای نهفتهٔ بلادرنگ معمولاً معماری حافظهٔ رویتراشهای ناهمگون دارند؛ یعنی ترکیبی از کش داده، کش دستور، دسترسی مستقیم به حافظه یا DMA، حافظهٔ اسکرچپد و حافظهٔ سفارشی را به کار میگیرند. در بخشهای بعد ویژگی هر کدام را بررسی میکنیم.
کش حافظهای کوچک است که بهجای DRAM کندتر، با SRAM پرسرعت ساخته میشود. کش به پردازنده یا هسته متصل است و با نگهداری نسخهای از تازهترین دادهها یا دستورهای استفادهشده، زمان دسترسی به جایگاههای پرتکرار حافظه را کاهش میدهد.
کش در حالت میانگین میتواند کارایی برنامه را بهتر کند، زیرا الگوی دسترسی برنامههای معمولی «محلیبودن ارجاع» دارد: اگر همان داده خیلی زود دوباره درخواست شود، محلیبودن زمانی رخ داده است؛ اگر دادهٔ درخواستی از نظر فیزیکی نزدیک دادهای باشد که از قبل در کش قرار دارد، محلیبودن مکانی داریم. دستورهای برنامه نیز معمولاً هر دو نوع محلیبودن را نشان میدهند و به همین علت کش دستور کارایی را بالا میبرد.
کش دادهها و دستورهای تازهاستفادهشده را در حافظهای کوچک و سریع نگه میدارد. پردازنده هنگام خواندن یا نوشتن یک نشانی ابتدا بررسی میکند که دادهٔ آن نشانی در کش موجود است یا نه. اگر موجود باشد، عملیات روی نسخهٔ داخل کش انجام میشود و نیازی به مراجعهٔ فوری به حافظهٔ اصلی نیست. این مسیر بسیار سریعتر است و در صورت استفادهٔ درست، کارایی برنامه را بهبود میدهد.
سختافزار کش تصمیم میگیرد هنگام ورود دادهٔ جدید کدام خط کش بیرون انداخته شود. کش میتواند کارایی را بهشدت افزایش دهد و برای بهبود حالت متوسط طراحی شده است، اما رفتار آن پیشبینیناپذیر و تا حد زیادی وابسته به الگوی دسترسی داده است. این ویژگی برای سامانههای بلادرنگ، که زمانبندی قابل پیشبینی یکی از نیازهای اصلی آنهاست، مسئلهساز میشود. به همین دلیل رایج است که بخشی از کش را قفل کنند یا برای قسمتهایی از کد که پیشبینیپذیری در آنها حیاتی است، کش را بهکلی غیرفعال کنند.
کوچکترین و سریعترین کش رایانه فایل ثباتهاست. استفاده از ثباتها را نرمافزار ــ معمولاً کامپایلر ــ کنترل میکند؛ کامپایلر ثباتها را برای نگهداری مقادیر خواندهشده از حافظهٔ اصلی و نتایج موقت اجرای برنامه اختصاص میدهد.
بیشتر پردازندهها دستکم سه کش دیگر نیز دارند: کش دستور فقطخواندنی برای سریعترکردن واکشی دستور، کش دادهٔ خواندنی/نوشتنی برای واکشی و ذخیرهٔ سریع داده، و بافر ترجمهٔ سریع یا TLB برای شتابدادن به تبدیل نشانی مجازی به فیزیکی، هم برای دستورها و هم برای دادهها.
کش داده معمولاً بهصورت سلسلهمراتبی از چند سطح، مانند L1 و L2 و سطوح بالاتر، سازماندهی میشود. شکل ۱ معماری حافظه و هزینهٔ تقریبی دسترسی در سطوح مختلف را نشان میدهد.

شکل ۱ ـ سلسلهمراتب حافظه
بافر Translation Lookaside Buffer که بهاختصار TLB نامیده میشود، نوعی کش برای افزایش سرعت تبدیل نشانی مجازی به نشانی فیزیکی در سامانههای دارای حافظهٔ مجازی است. TLB معمولاً با حافظهٔ محتوامحور یا CAM پیادهسازی میشود. کلید جستوجو در CAM نشانی مجازی و نتیجهٔ جستوجو نشانی فیزیکی است.
اگر نشانی درخواستی داخل TLB باشد، «اصابت TLB» رخ میدهد و نشانی فیزیکی بازیابیشده برای دسترسی به حافظه به کار میرود. اگر نشانی در TLB نباشد، «عدم اصابت TLB» رخ میدهد؛ در این حالت باید با مراجعه به جدول صفحه، نشانی مجازی را به فیزیکی تبدیل کرد که عملیاتی پرهزینه است. نگاشت تازه سپس در TLB ثبت میشود. ظرفیت TLB میتواند از 8 تا 4096 ورودی باشد. یک اصابت تقریباً 1 چرخهٔ کلاک زمان میبرد، در حالی که عدم اصابت ممکن است 10 تا 100 چرخه هزینه داشته باشد. هر هسته در سیستم چندهستهای TLB مخصوص خود را دارد.
اثر کش دستور بر کارایی معمولاً از اثر کش داده بیشتر است. هنگام واکشی دستور، پردازنده غالباً بیکار میماند؛ اما در زمان واکشی داده گاهی میتواند کار مفید دیگری انجام دهد. دستورها از نظر مکانی و زمانی نیز محلیبودن بیشتری دارند، پس نرخ اصابت کش دستور بالاتر است. جریان برنامه از الگوی دسترسی داده قابل پیشبینیتر است و پیشواکشی دستور سود بیشتری ایجاد میکند. نشانی هر دستور در طول اجرای برنامه ثابت میماند و همین موضوع رفتار کش دستور را پیشبینیپذیرتر میکند. برای رسیدن به بهترین کارایی، پردازنده باید مشغول محاسبه باشد، نه منتظر واکشی دستور یا داده از حافظه.
هنگام اجرای کد، کلمههای کد موجود در نشانیهای درخواستشده به کش دستور کپی میشوند تا هسته مستقیماً به آنها دسترسی داشته باشد. اگر یک بخش کد بارها استفاده شود، نگهداری آن در کش با حذف مراجعه به گذرگاه خارجی، گذردهی را افزایش میدهد. عدم اصابت خواندن در کش دستور معمولاً بیشترین تأخیر را ایجاد میکند، زیرا پردازنده ناچار است تا رسیدن دستور از حافظهٔ اصلی منتظر بماند.
میتوان سختافزار کش را طوری سفارشی کرد که دستورهای علامتگذاریشده را وارد کش نکند. طراح همچنین میتواند دستورهای حیاتی یا بخشهایی از کد را در کش قفل کند تا کارایی یک سیستم بلادرنگ قابل پیشبینی شود. خطهای قفلشده برای جا بازکردن به دستورهای تازه جایگزین نمیشوند.
برای دستیابی به بهترین نتیجه از کش دستور، کد تولیدشده باید تا حد ممکن کوچک باشد. طراح باید با چیدمان مناسب کد یا دستورهای صریح پیشواکشی به پردازنده کمک کند تصمیمهای بهتری برای پیشواکشی بگیرد.
سلسلهمراتب کش داده در پردازندههای معمولی از چند سطح تشکیل میشود. L1 یا کش اصلی، کشی کوچک و پرسرعت درون هستهٔ CPU است و معمولاً ظرفیتی بین 8 KB تا 64 KB دارد. کش L1 عموماً به دو بخش جدا تقسیم میشود: یکی برای دستور و دیگری برای داده.
L2 یا کش ثانویه از L1 بزرگتر، کندتر و ارزانتر است و برخلاف L1 معمولاً بیرون از هستهٔ CPU قرار میگیرد. اندازهٔ آن اغلب از 64 KB تا 4 MB است. بسیاری از پردازندهها یک کش L3 بزرگتر، کندتر و ارزانتر نیز درست پیش از حافظهٔ اصلی دارند. کشهای L2 و L3 ممکن است میان چند هسته مشترک باشند.
اگر دادهٔ درخواستی پردازنده یا هسته از قبل در کش باشد، «اصابت کش» رخ داده و درخواست با خواندن سریعتر کش پاسخ داده میشود. در «عدم اصابت» یا cache miss، داده باید دوباره محاسبه یا از محل اصلی و کندتر خود واکشی شود. درصد دسترسیهایی که به اصابت کش منتهی میشوند نرخ اصابت نام دارد و مستقیماً بر کارایی کل سیستم اثر میگذارد.
در کش چندسطحی ابتدا L1 بررسی میشود؛ اگر اصابت رخ دهد، پردازنده با سرعت بالا ادامه میدهد. در صورت عدم اصابت، کش بزرگتر L2 و سپس سطوح بعدی بررسی میشوند و تنها پس از آن حافظهٔ خارجی درگیر میشود. کنترلگر کش جزئیات واکشی دادهٔ لازم از نزدیکترین سطح دارای آن داده، یا در نهایت از حافظهٔ اصلی، را مدیریت میکند.
برای اصلاح دادهٔ ذخیرهشده در کش دو روش پایه وجود دارد. در روش write-through یا «نوشتن مستقیم»، داده همزمان در کش و حافظهٔ اصلی بهروزرسانی میشود. در روش write-back یا «پسنوشت»، فقط نسخهٔ داخل کش تغییر میکند و بلوک تغییریافته درست پیش از جایگزینی با بلوک دیگری در حافظهٔ اصلی نوشته میشود. پیادهسازی پسنوشت پیچیدهتر است، زیرا باید جایگاههای تغییرکرده را «کثیف» علامت بزند تا هنگام خروج از کش در حافظهٔ اصلی ثبت شوند.
اگر کش هستهای دیگر دادهٔ کثیف را درخواست کند، پاسخ ممکن است دو مراجعه به حافظه لازم داشته باشد: ابتدا کش دارندهٔ نسخهٔ اصلاحشده آن را در حافظهٔ اصلی مینویسد و سپس کش درخواستکننده داده را دریافت میکند. فرایند یکسان نگهداشتن کش هستههای مختلف «انسجام کش» نام دارد و در بخش مستقلی توضیح داده خواهد شد.
کنترلگر کش یک بلوک سختافزاری است که میتواند کد و داده را بهطور پویا میان حافظهٔ اصلی و کش جابهجا کند. داده یا کد ورودی جای محتوای قدیمی و کماستفادهٔ کش را میگیرد. سیاست جایگزینی کش الگوریتمی ابتکاری است که بر اساس نشانی خط در حافظهٔ اصلی تصمیم میگیرد کدام خط کش برای ورود خط جدید بیرون انداخته شود.
اگر هر خط بتواند در هر جای کش قرار گیرد، سیاست «کاملاً انجمنی» یا Fully Associative است. در سوی دیگر، اگر هر خط فقط یک جای مشخص داشته باشد، کش «نگاشت مستقیم» یا Direct Mapped نام دارد. بیشتر طراحیها میان این دو قرار میگیرند و N-way Set Associative هستند؛ یعنی هر ورودی حافظهٔ اصلی میتواند در یکی از N جایگاه مجموعه قرار گیرد. انجمنیبودن بیشتر احتمال اصابت را بالا میبرد، چون خط کش انتخابهای بیشتری برای استقرار دارد؛ اما جستوجو در جایگاههای بیشتر زمان و توان بیشتری میخواهد. بیشتر کشها 2راهه یا 4راههاند، زیرا افزایش انجمنیبودن فراتر از آن معمولاً اثر کمتری بر نرخ اصابت دارد.
شکل ۲ ـ روشهای انجمنیبودن کش
حافظهٔ کش به خطهای کش تقسیم میشود. خط کش اندازهٔ دادهای است که در هر نوبت از حافظه خوانده یا در آن نوشته میشود. اگر کش بتواند N خط را در خود جای دهد، رابطهٔ آن چنین است: N = اندازهٔ کش ÷ اندازهٔ خط کش.
نشانی حافظه، همانطور که در شکل ۳ دیده میشود، به سه بخش tag، index و block offset تقسیم میشود. index مشخص میکند داده در کدام خط یا مجموعهٔ کش قرار گرفته است؛ block offset جای دقیق داده را درون همان خط نشان میدهد؛ و tag شامل بیتهای پرارزش نشانی است که همراه خط ذخیره میشود تا کنترلگر بتواند نشانی کش را به یک نشانی یکتای حافظه مرتبط کند. بخش data خود داده را نگه میدارد و بیتهای flag در پروتکلهای انسجام کش استفاده میشوند.
برای نمونه، سامانهای با 256 MB حافظه و کش دادهٔ L1 با نگاشت مستقیم، اندازهٔ 8 KB و خطهای 64بایتی را در نظر بگیرید. تعداد خطها برابر است با 8K ÷ 64 = 128. برای نشانیدهی 256 MB به نشانی 28بیتی نیاز داریم. چون خط 64بایتی است، offset برابر log2(64) = 6 بیت میشود. index نیز log2(128) = 7 بیت است؛ پس tag برابر 28 - 7 - 6 = 15 بیت خواهد بود. هنگام دسترسی به یک نشانی، کنترلگر با index خط مناسب را پیدا و مقدار tag را مقایسه میکند تا بفهمد داده از قبل در کش هست یا نه.
در کش دادهٔ L1 چهارراهه، 128 خط به 128 ÷ 4 = 32 مجموعه تقسیم میشوند و index برابر log2(32) = 5 بیت است. در هر دسترسی باید 4 خط احتمالی بررسی شود و تقسیمبندی نشانی به 17 بیت tag، 5 بیت index و 6 بیت offset تغییر میکند.

شکل ۳ ـ اجزای نشانی حافظه و خط کش
همهٔ بلوکهای یک کش مشخص اندازه و درجهٔ انجمنی یکسانی دارند، اما معمولاً کشهای «سطح پایینتر» مانند L1 کوچکترند و خطهای کش کوتاهتری دارند؛ در مقابل کشهای «سطح بالاتر» مانند L2 ظرفیت و خط کش بزرگتری دارند.
هنگام تعیین ظرفیت کل کش باید یک موازنه را در نظر گرفت: کش کوچکتر انرژی کمتری مصرف میکند و برای بیشتر برنامهها نرخ اصابت خوبی دارد، اما کش بزرگتر دامنهٔ برنامههایی را که نرخ اصابت مناسبی میگیرند افزایش میدهد و در عوض برای بسیاری از بارهای کاری انرژی اضافه هدر میدهد.
سیستم نهفته، برخلاف رایانهٔ همهمنظوره، در طول عمر خود معمولاً فقط مجموعهٔ کوچکی از برنامهها را اجرا میکند. پس بهتر است معماری دقیقاً برای همان برنامهها تنظیم شود. دو پارامتر اصلی در سفارشیسازی کش سیستم نهفته، اندازهٔ خط کش و ظرفیت کل کش هستند.
بهعنوان قاعدهٔ تجربی، اگر الگوی دسترسی حافظه محلیبودن مکانی بالایی دارد ــ یعنی منظم و متوالی است ــ خط کش بلندتر مناسبتر خواهد بود؛ زیرا تعداد دسترسیهای خارجتراشهای را کم میکند و با پیشواکشی عناصر نزدیک که احتمالاً بهزودی لازم میشوند از محلیبودن بهره میگیرد. اگر الگو نامنظم باشد، خط کوتاهتر بهتر است، چون با وارد نکردن دادههای غیرضروری ترافیک حافظهٔ خارجتراشه را کاهش میدهد. بیشترین اندازهٔ خط کش برابر اندازهٔ صفحهٔ حافظهٔ اصلی است. میتوان با تحلیل استفادهٔ مجدد از داده، تعداد اصابت و عدم اصابت را برای ظرفیتها و اندازههای مختلف خط کش پیشبینی و بر اساس نتیجه بهترین ترکیب را انتخاب کرد [1].
طراحان بعضی از سامانههای نهفتهٔ بلادرنگ کش را غیرفعال میکنند، چون زمان دسترسی بسته به اصابت یا عدم اصابت داده و دستور تغییر میکند و در نتیجه قابل پیشبینی نیست. در سختافزارهایی که پشتیبانی لازم را دارند، راهحل دیگر قفلکردن خطهای مشخص کش است. نرمافزار سیستم داده و دستور حیاتی را در کش بار میکند و به کنترلگر میگوید آنها را جایگزین نکند. به این ترتیب محتوای ضروری ثابت میماند و سازوکار عادی کش فقط محتوای کماهمیتتر را مدیریت میکند. عیب اصلی این است که پس از ثابتشدن داده و دستور، بازآرایی کش هزینهٔ زیادی خواهد داشت. مروری مناسب بر روشهای سفارشیسازی معماری حافظهٔ سیستم نهفته در منبع [2] آمده است.
کش در حالت متوسط کارایی را بهتر میکند، اما ممکن است برای سامانهٔ بلادرنگ با نیاز زمانی سخت و ثابت رفتار پیشبینیناپذیری ایجاد کند. گزینهٔ دیگر، کش تحت مدیریت نرمافزار است؛ روشی انعطافپذیر و کمهزینه که به نرمافزار اجازه میدهد تصمیم جایگزینی را هدایت کند [3].
برای مثال، میتوان به دستور دسترسی به حافظه صفتی به نام cache hint افزود تا مشخص شود داده باید در کش باقی بماند یا نه. ابتدا اجرای برنامهٔ نهفته ابزارگذاری میشود تا الگوی دسترسی حافظه به دست آید. پس از شناسایی محلیبودن داده، hintهای مناسب به دستورهای حافظهٔ برنامهٔ اصلی افزوده میشوند. این روش میتواند بدون پیشبینیناپذیری کش سختافزاری سنتی، کارایی را افزایش دهد.
حافظهٔ مجازی به کاربر اجازه میدهد داده را روی دیسک سخت نگه دارد، اما طوری از آن استفاده کند که گویی در حافظهٔ اصلی موجود است. برنامه به داده در یک فضای نشانی مجازی دسترسی دارد که به حافظه نگاشت شده؛ در حالی که دادهٔ واقعی روی دیسک قرار دارد و در زمان نیاز به حافظه منتقل میشود. به کمک حافظهٔ مجازی میتوان از فضای RAM بیشتری نسبت به ظرفیت فیزیکی سیستم استفاده کرد.
در برنامهٔ چندوظیفهای، هر وظیفه میتواند فضای نشانی مجازی مستقل خود را داشته باشد که «فضای نشانی مجزا» نامیده میشود. مدیریت حافظهٔ مجازی بر عهدهٔ سیستمعامل است و واحد مدیریت حافظه یا MMU در سختافزار از آن پشتیبانی میکند. با این حال، سیستمهای نهفتهٔ کوچکتر معمولاً حافظهٔ مجازی و MMU ندارند.
SRAM اسکرچپد گزینهای جایگزین برای ذخیرهسازی رویتراشهای است. این حافظهٔ داخلی کوچک و پرسرعت، داده، نتایج محاسبه و کارهای موقت را نگه میدارد. اسکرچپد از نظر توان، کارایی، مساحت و پیشبینیپذیری میتواند از کش سنتی بهتر باشد و به همین علت در سیستمهای بلادرنگ مانند برنامههای چندرسانهای و کنترلگرهای گرافیکی محبوب است. پردازندههای سیگنال دیجیتال یا DSP نیز معمولاً از اسکرچپد استفاده میکنند.
کد برنامه در سیستم نهفته از قبل معلوم است. طراح میتواند کد و دادهٔ حیاتی را شناسایی و با دقت در اسکرچپد قرار دهد. این تصمیم هنگام طراحی برنامه گرفته میشود و با دستورهای کامپایلر بهصورت دستی یا به کمک کامپایلر بهطور خودکار اجرا میشود. در کش، برعکس، نگاشت عناصر برنامه در زمان اجرا انجام میگیرد.
کارایی استفاده از اسکرچپد، مانند کش، به میزان محلیبودن ارجاع وابسته است. SRAM اسکرچپد دسترسی تکچرخهای تضمین میکند، اما دسترسی به کش تابع اصابت یا عدم اصابت است. فضای نشانی دادههای نگاشتشده به اسکرچپد نیز از فضای نشانی حافظهٔ اصلی جداست. طراح باید تصمیم بگیرد حافظهٔ رویتراشه چگونه میان کش داده و اسکرچپد تقسیم شود تا مجموع زمان دسترسی و اتلاف توان به کمینه برسد.
شکل ۴ یک هستهٔ پردازندهٔ نهفته را نشان میدهد که هم کش داده و هم اسکرچپد دارد [4]. گذرگاههای نشانی و دادهٔ CPU به کش داده، اسکرچپد و رابط حافظهٔ خارجی یا EMI متصلاند. با رسیدن درخواست حافظه، ابتدا کش داده جستوجو میشود؛ در صورت اصابت، داده را به CPU میدهد. اگر کش جواب ندهد، اسکرچپد بررسی میشود و در صورت اصابت، کنترل گذرگاه داده را میگیرد و داده را تحویل میدهد. اگر هیچکدام داده را نداشته باشند، EMI بلوکی هماندازهٔ خط کش را از حافظهٔ خارجی به کش و CPU منتقل میکند. این انتقال داده با DMA مدیریت میشود.
شکل ۴ ـ معماری حافظهٔ اسکرچپد
اسکرچپد را میتوان برای همپوشانیهای نرمافزاری نیز به کار برد که در بخش بعد توضیح داده میشود.
ممکن است یک پردازندهٔ نهفتهٔ بلادرنگ کش و کنترلگر کش داخلی نداشته باشد و در عوض SRAM رویتراشه را بهصورت اسکرچپد برای نگهداری کدهای پرتکرار از طریق software overlay استفاده کند.
هر بخش کدی که روی یک overlay نگاشت میشود دو فضا دارد: run space و live space. فضای live محل نگهداری بخش کد در حافظهٔ اصلی، زمانی است که کد اجرا نمیشود. فضای run بخشی از حافظهٔ داخلی است که کد هنگام اجرا در آن قرار میگیرد. نرمافزاری به نام overlay manager کد را از فضای live به فضای run منتقل میکند. ابزارهای linker و loader باید نمادهای overlay را برای بخشهای کد تولید کنند. این نمادها اطلاعات فضای run و live را در خود دارند و مدیر overlay با استفاده از آنها جابهجایی پویا را انجام میدهد. یک سیستم میتواند چند overlay با live spaceهای متفاوت اما run space مشترک داشته باشد.
برنامهنویس باید بخشهای کدی را که هرگز همزمان اجرا نمیشوند شناسایی کند تا این قابلیت قابل استفاده باشد. فاصلهٔ زمانی میان تعویض این بخشها باید زیاد باشد؛ وگرنه جابهجایی مکرر کارایی را پایین میآورد. اندازهٔ هر بخش overlay نیز نباید از فضای run موجود بیشتر باشد. overlay نرمافزاری معمولاً برای کد به کار میرود و استفاده از آن برای داده نادر است.
سیستم بلادرنگ کارآمد سیستمی است که CPU در بیشترین تعداد چرخهها کار مفید انجام دهد. در حالت عادی، CPU در تمام مدت یک عملیات خواندن یا نوشتن که مستلزم انتقال اطلاعات به حافظه یا از آن است، درگیر میماند.
DMA یک واحد جانبی است که حجم زیادی از داده را بدون استفادهٔ مستقیم از CPU به حافظه یا از حافظه منتقل میکند. DMA یکی از اجزای اصلی سیستمهای نهفتهٔ امروزی است. این واحد موازی با CPU کار میکند و محیطی شبیه چندپردازشی میسازد؛ در نتیجه پهنای باند مؤثر CPU و میزان استفاده از آن افزایش مییابد و طراحی سیستمهای سریعتر و کممصرفتر ممکن میشود.
در پردازندهٔ چندهستهای میتوان DMA را برای انتقال داده و MMU را برای نگاشت حافظه به کار گرفت تا محاسبه همزمان با انتقال داده ادامه یابد. انتقالی که CPU در آن دخالت ندارد «انتقال بدون کپی» یا zero-copy نامیده میشود.
DMA میتواند چهار نوع انتقال را انجام دهد:
17. حافظه به حافظه
18. حافظه به واحد جانبی
19. واحد جانبی به حافظه
20. واحد جانبی به واحد جانبی
در آغاز عملیات خواندن یا نوشتن، CPU کنترلگر DMA را از نیاز به انتقال آگاه میکند. پارامترهای راهاندازی معمول شامل نشانی پایهٔ مبدأ، نشانی پایهٔ مقصد، طول بلوک و این تصمیم است که پس از پایان انتقال وقفهای برای پردازنده تولید شود یا نه.
پس از مقداردهی اولیه، کنترلگر DMA سیگنال درخواست DMA را به CPU میفرستد و اجازهٔ استفاده از گذرگاه را میخواهد. CPU فعالیت جاری گذرگاه را به پایان میرساند، خروجیهای خود را از گذرگاه کنار میکشد و سیگنال تأیید DMA را برمیگرداند. کنترلگر سپس یک یا چند بایت را میخواند یا مینویسد و سیگنالهای نشانی، داده و کنترل را طوری هدایت میکند که گویی خود CPU است. در این مدت خروجیهای CPU سهحالته هستند. با پایان انتقال، کنترلگر گذرگاه را آزاد و درخواست DMA را غیرفعال میکند؛ CPU نیز تأیید را برمیدارد و کنترل گذرگاه را دوباره میگیرد. خود کنترلگر DMA هیچ پردازشی روی داده انجام نمیدهد.
عملیات DMA میتواند در حالت burst یا تکچرخهای اجرا شود. در حالت burst، کنترلگر تا انتقال همهٔ دادههای بافرشده، گذرگاه را در اختیار نگه میدارد. در حالت تکچرخهای پس از هر انتقال گذرگاه را آزاد میکند. روش دوم زمان دورماندن پردازنده از گذرگاه حافظه را کم میکند، اما برای هر انتقال باید توالی درخواست و تأیید گذرگاه تکرار شود. اگر حجم داده زیاد باشد، این سربار گذردهی کل سیستم را کاهش میدهد.
سادهترین روش استفاده از DMA انتخاب پردازندهای با کنترلگر DMA داخلی است. در این حالت نیازی به بافرهای خارجی گذرگاه نیست و زمانبندی نیز درست مدیریت میشود. کنترلگر داخلی میتواند داده را به حافظه و واحدهای جانبی رویتراشه منتقل کند؛ کاری که کنترلگر خارجی قادر به انجام آن نیست. چون دستدهی درون تراشه صورت میگیرد، ورود به حالت DMA و خروج از آن نیز معمولاً بسیار سریعتر است.
مدیریت داده با DMA انتخاب طبیعی برنامههای چندرسانهای است، زیرا این برنامهها با بافرهای بزرگ ویدئو، گرافیک و صدای فشرده یا غیرفشرده کار میکنند. DMA را میتوان همراه SRAM اسکرچپد رویتراشه برای ذخیرهٔ موقت داده به کار برد. حافظهٔ داخلی نیز میتواند میان کش L1 برای داده و دستور پرتکرار و اسکرچپد برای انتقالهای DMA تقسیم شود.
یک قاعدهٔ عملی چنین است: اگر تمام کد برنامه در حافظهٔ داخلی L1 جا میشود، کش دستور را غیرفعال و کد را در حافظهٔ داخلی قفل کنید. اگر کد جا نمیشود، کش دستور L1 را فعال کنید. اگر هنوز کارایی کافی نیست، خطهای مربوط به بخشهای حیاتی کد را قفل و باقی حافظهٔ داخلی را به اسکرچپد انتقالهای DMA اختصاص دهید.
DRAM در سیستم نهفته هم میتواند خارج از تراشه و هم داخل تراشه باشد. DRAM خارجی معمولاً حافظهٔ اصلی است که کد و دادهٔ برنامهٔ در حال اجرا را نگه میدارد. Embedded DRAM یا eDRAM، همانطور که از نامش پیداست، روی همان دای پردازنده یا ASIC یکپارچه میشود.
eDRAM از نظر کارایی، چگالی، پهنای باند و توان مزایایی دارد که آن را برای سیستمهای نهفته جذاب میکند. قراردادن حافظه روی تراشه، گذرگاههای بسیار عریضتر و سرعت کار بالاتر را ممکن میسازد. DRAM تقریباً چهار برابر SRAM مورد استفاده برای کش و اسکرچپد چگالی دارد. هرچند ساخت DRAM ممکن است گرانتر از SRAM باشد، در سطح کل سیستم مقرونبهصرفه است، زیرا چگالی بیشتر فضای تراشه را کاهش میدهد و هزینهٔ اضافه را جبران میکند.
سلولهای DRAM به نوسازی دورهای نیاز دارند و این موضوع پیچیدگی ایجاد میکند، اما کنترلگر نوسازی را میتوان در کنار eDRAM روی تراشه قرار داد. DRAMهای کالایی فقط در چگالیهای استانداردی مانند 4، 16 یا 64 مگابیت عرضه میشوند؛ در حالی که چگالی eDRAM را میتوان دقیقاً متناسب با نیاز سیستم انتخاب کرد. این قابلیت از هدررفت حافظه جلوگیری و مساحت و هزینه را کم میکند.
حالتهای دسترسی ویژهٔ eDRAM ساختار داخلی داده را بهتر به کار میگیرند و کارایی حافظه را افزایش میدهند. روشهای سنتز و کامپایل میتوانند با شناخت دقیق حالتهای دسترسی DRAM و اطلاع قبلی از برنامهٔ سیستم نهفته، کارایی و مصرف توان کل سامانه را بهبود دهند.
eDRAM در بسیاری از محصولات به کار رفته است؛ از جمله برخی نسلهای Apple iPhone، Sony PlayStation، Nintendo Wii و Microsoft Xbox 360.
علاوه بر ساختارهای عمومی مانند کش و اسکرچپد، حافظههای سفارشی دیگری وجود دارند که پروتکل دسترسی مشخصی را پیاده میکنند:
21. LIFO یا «آخرین ورودی، اولین خروجی» در بیشتر ریزپردازندهها و ریزکنترلگرها وجود دارد. وقتی اجرای یک روال برای رسیدگی سیستمعامل به کار فوریتری قطع میشود، نشانی دستور جاری در LIFO ذخیره میشود. پس از پایان کار فوری، سیستمعامل آخرین نشانی ثبتشده را برمیدارد و اجرا را از همان نقطه ادامه میدهد. رفتار LIFO همان پشتهٔ حافظه است.
22. FIFO یا «اولین ورودی، اولین خروجی» برای بافرکردن میان دستگاههایی با سرعت متفاوت و نیز برای نگهداری موقت داده تا زمان پردازش به کار میرود. این بافرها پهنای باند را بالا میبرند و در ارتباطات پرسرعت از گمشدن داده جلوگیری میکنند.
23. CAM یا حافظهٔ محتوامحور برای جستوجوی بسیار سریع ساخته شده است. در حافظهٔ عادی کاربر نشانی را میدهد و کلمهٔ داده را تحویل میگیرد؛ اما در CAM کاربر یک کلمهٔ داده ارائه میکند و حافظه تمام محتوای خود را میگردد. اگر داده موجود باشد، CAM یک یا چند نشانی محل ذخیره را برمیگرداند. مدار مقایسهٔ پرسرعت هزینهٔ ساخت، اندازهٔ فیزیکی و مصرف توان این حافظه را افزایش میدهد.
حافظهٔ سیستم چندهستهای را میتوان در دو دستهٔ کلی قرار داد: حافظهٔ اشتراکی و حافظهٔ توزیعشده. بسیاری از سامانههای مدرن ترکیبی از هر دو را به کار میگیرند.
در سیستم حافظهٔ اشتراکی، همهٔ پردازندهها یا هستهها دسترسی مستقیم به حافظهٔ مشترک دارند. برنامههایی که برای این مدل نوشته میشوند میتوانند مستقل از محل فیزیکی داده، همان نشانیهای منطقی حافظه را ببینند و بخوانند. وظایف موازی با اشاره به همان جایگاهها داده را به اشتراک میگذارند. این کار اشتراک داده را سریع و یکنواخت میکند، اما کنترل همگامسازی دادهٔ مشترک بر عهدهٔ برنامهنویس است.
مقیاسپذیری حافظهٔ اشتراکی محدود است، زیرا هستهها یک گذرگاه حافظه را مشترکاً استفاده میکنند و افزودن پردازنده، هسته یا ظرفیت حافظه، ترافیک همان گذرگاه را بالا میبرد. ساخت سامانههای بزرگ با این مدل نیز پرهزینه است. این ماشینها بر اساس یکنواختی زمان دسترسی به دو گروه UMA و NUMA تقسیم میشوند.
چندپردازندههای حافظهاشتراکی که با نام SMP یا چندپردازندهٔ متقارن نیز شناخته میشوند، اگر همهٔ پردازندهها دسترسی برابر و یکنواخت به حافظهٔ اصلی داشته باشند، ماشین UMA هستند. اگر چنین سامانهای کش داشته باشد، برای مدیریت دسترسی به جایگاههای حافظهٔ مشترک به پروتکل انسجام کش نیاز خواهد داشت. UMA دارای انسجام کش سختافزاری، Cache-Coherent UMA یا CC-UMA نامیده میشود.

شکل ۵ ـ چندپردازنده با دسترسی یکنواخت به حافظه
ماشین NUMA اغلب از دو یا چند بلوک SMP تشکیل میشود که به یکدیگر متصلاند. حافظهٔ یک بلوک از بلوک دیگر دیده و قابل دسترسی است، اما چون بلوکها از طریق گذرگاه به هم متصلاند، زمان دسترسی با توجه به فاصلهٔ فیزیکی حافظه از CPU درخواستکننده تغییر میکند. اگر انسجام کش بهصورت سختافزاری وجود داشته باشد، این معماری CC-NUMA نام میگیرد.

شکل ۶ ـ چندپردازنده با دسترسی غیریکنواخت به حافظه
در سیستم حافظهٔ توزیعشده، همهٔ هستهها یا پردازندهها دسترسی یکسانی به حافظه ندارند. هر حافظه محلیِ یک پردازنده است و فقط همان پردازنده مستقیماً به آن دسترسی دارد. نشانیهای حافظهٔ یک پردازنده به حافظهٔ پردازندهٔ دیگر نگاشت نمیشوند؛ بنابراین فضای نشانی سراسری وجود ندارد.
برای دسترسی به حافظهٔ پردازندهای دیگر باید از شبکهٔ ارتباطی استفاده کرد. زمان دسترسی به فاصله و سرعت شبکهٔ انتقال داده وابسته است و میتواند بسیار متفاوت باشد. تغییر حافظهٔ محلی یک فرایند اثری بر فرایندهای دیگر ندارد؛ پس نیازی به همگامسازی حافظه و پرداخت سربار انسجام کش نیست. این مدل مقیاسپذیر است، زیرا افزودن پردازنده و حافظهٔ جدید بهسادگی انجام میشود.

شکل ۷ ـ چندپردازنده با حافظهٔ توزیعشده
در طراحی تراشهٔ چندهستهای باید ساختار کش با دقت انتخاب شود. آیا کش باید میان هستهها مشترک باشد یا هر هسته کش خصوصی داشته باشد؟ اگر سلسلهمراتب کش وجود دارد، سطوح مختلف چگونه مدیریت میشوند؟ و برای عملکرد درست دستگاه، سازگاری دادهٔ کش و حافظه چگونه حفظ میشود؟
کش مشترک میان چند هسته اجازه میدهد داده مستقیماً به اشتراک گذاشته شود و تغییر یک هسته بدون سازوکار اضافی انسجام در اختیار هستههای دیگر قرار گیرد. چون بهجای یک کش برای هر هسته یک کش در سطح تراشه داریم، میتوان آن را بزرگتر ساخت و دادهٔ بیشتری نگه داشت. با این حال، اگر رشتههای هستههای مشترک دادههای بزرگی از فضاهای نشانی کاملاً متفاوت بخوانند، کش مشترک سود چندانی ندارد.
کش خصوصی رقابت رشتهها برای فضای یک کش واحد را کاهش میدهد و چون به هسته نزدیکتر است، سرعت دسترسی بیشتری دارد. کش L1، که پایینترین سطح است، معمولاً خصوصی طراحی میشود تا تأخیر دسترسی مکرر به داده و دستور همان هسته کم بماند.
بالاترین سطح کش، درست پیش از حافظهٔ اصلی، بهتر است یک کش مشترک بزرگ باشد تا تعداد مراجعه به حافظهٔ اصلی را کاهش دهد. برای نمونه، یک تراشهٔ هشتهستهای با سه سطح میتواند برای هر هسته یک L1، برای هر جفت هسته یک L2 و برای همهٔ هستهها یک L3 مشترک داشته باشد؛ همان ساختاری که در شکل ۸ دیده میشود.

شکل ۸ ـ ساختار کش در یک تراشهٔ چندپردازنده
در ساختار کش خصوصی ممکن است یک رشته دادهای را در کش هستهٔ خود تغییر دهد، اما هنوز آن را در حافظهٔ اصلی ننوشته باشد. همزمان رشتهای روی هستهٔ دیگر به همان داده نیاز پیدا میکند. چنین وضعیتی را پروتکل انسجام کش مدیریت میکند.
سیستم چندهستهای ممکن است چند سطح کش داشته باشد. برای حفظ سازگاری و اعتبار دادهها باید از پروتکل انسجام کشِ فراهمشده بهوسیلهٔ پردازنده استفاده کرد.
برای روشنشدن مسئله، چهار هسته با کش خصوصی را در نظر بگیرید. متغیری به نام foo در حافظهٔ اصلی قرار دارد و مقدار اولیهٔ آن 2 است.

شکل ۹ ـ وضعیت نخست مثال انسجام کش
هستههای 1 و 3 مقدار foo را میخوانند و نسخهای از آن را در کش خود نگه میدارند.

شکل ۱۰ ـ کپیشدن foo در دو کش
اکنون هستهٔ 3 مقدار جدید 3 را در foo مینویسد. اگر کش از نوع write-through باشد، حافظهٔ اصلی نیز به مقدار 3 بهروزرسانی میشود؛ اما کش هستهٔ 1 هنوز مقدار قدیمی 2 را دارد. اگر هستهٔ 1 دوباره foo را بخواند، پاسخ نادرست و کهنهای از کش خود میگیرد. پروتکل انسجام کش دقیقاً برای جلوگیری از چنین وضعیتی است.

شکل ۱۱ ـ ایجاد نسخهٔ کهنه در کش
پروتکلهای انسجام کش دو خانوادهٔ بزرگ دارند: مبتنی بر دایرکتوری و مبتنی بر شنود یا snooping. مناسببودن هر خانواده به توزیع حافظه، نوع اتصال میان اجزا، پهنای باند گذرگاه و ویژگیهای ارتباطی معماری بستگی دارد.
پروتکل شنودگر معمولاً سریعتر است، به شرطی که یک گذرگاه مشترک با پهنای باند کافی وجود داشته باشد؛ زیرا همهٔ تراکنشهای داده را همهٔ پردازندهها میبینند. هر درخواست برای تمام گرهها پخش میشود. با بزرگشدن سیستم، اندازهٔ گذرگاه و پهنای باند مورد نیاز نیز تقریباً به همان نسبت رشد میکند؛ بنابراین snooping مقیاسپذیر نیست.
طرح دایرکتوری معمولاً کندتر است، زیرا در مسیر بحرانی عدم اصابت کش باید دایرکتوری نیز خوانده شود و پیام مسیر ارتباطی اضافهای را طی کند. این روش به حافظهٔ اختصاصی برای دایرکتوری نیز نیاز دارد و هزینهٔ سیستم را افزایش میدهد. مزیت آن مصرف بسیار کمتر پهنای باند است، چون پیامها نقطهبهنقطهاند و برای همه پخش نمیشوند. به همین دلیل در سیستمهای نهفتهٔ چندهستهای بزرگتر غالباً طرح دایرکتوری ترجیح داده میشود.
پروتکل دایرکتوری دادههای مشترک را در ساختار اضافهای به نام directory ردیابی میکند. این ساختار مانند جدول جستوجو عمل میکند و به هر پردازنده میگوید کدام کش نسخهٔ یک داده را دارد و وضعیت سازگاری آن چیست. همهٔ درخواستهای بارگذاری یا بهروزرسانی حافظه از فیلتر دایرکتوری میگذرند. وقتی یک ورودی تغییر کند، دایرکتوری کشهای دیگری را که همان ورودی را دارند بهروزرسانی یا نامعتبر میکند.
در چندپردازندهٔ متقارن کوچک، دایرکتوری میتواند متمرکز باشد. در سامانهٔ NUMA با نشانی مشترک یا چندپردازندهٔ حافظهتوزیعشدهٔ بزرگتر، دایرکتوری را میتوان توزیع کرد.
طرح مبتنی بر دایرکتوری از snooping مقیاسپذیرتر است، چون برای ارتباط به یک گذرگاه مشترک وابسته نیست. دایرکتوری همهٔ پردازندههایی را که یک بلوک یا خط کش را نگه میدارند ثبت میکند و با پیامهای نقطهبهنقطه انسجام را حفظ میکند. بنابراین میتوان از هر شبکهٔ نقطهبهنقطهٔ مقیاسپذیر استفاده کرد.
پروتکلهای دایرکتوری سه دستهٔ اصلی دارند:
دایرکتوری Full-map
در طرح Full-map هر پردازنده میتواند نسخهای از هر بلوک داده را در کش خود داشته باشد. هر ورودی دایرکتوری برای هر پردازنده یک بیت در مجموعهٔ Sharers دارد و یک بیت dirty نیز مالکیت انحصاری را نشان میدهد. مقدار 1 در بیت یک پردازنده یعنی نسخهای از داده در کش آن پردازنده است. اگر بیت dirty برابر 1 باشد، فقط بیت یک پردازنده در Sharers فعال خواهد بود.
هر خط کش نیز دو بیت وضعیت دارد: یکی معتبر یا نامعتبر بودن خط و دیگری نوشتهشدن یا dirty بودن آن را نشان میدهد.
مشکل اصلی Full-map این است که حافظهٔ لازم برای دایرکتوری خوب مقیاس نمیشود. تعداد بیتهای Sharers با تعداد پردازندهها و اندازهٔ دایرکتوری با ظرفیت حافظهٔ اصلی رشد میکند. همچنین با بزرگشدن چندپردازنده، پهنای باند و حافظهٔ لازم برای یک دایرکتوری متمرکز میتواند به گلوگاه تبدیل شود.
دایرکتوری Limited
در طرح Limited برای حل مشکل اندازه، تعداد پردازندههایی که میتوانند در مجموعهٔ Sharers ثبت شوند مستقل از تعداد کل پردازندهها محدود میشود. عیب این راه آن است که دیگر هر پردازنده نمیتواند نسخهٔ هر بلوک دلخواه را در کش خود نگه دارد.
دایرکتوری Chained
طرح Chained مشکل دوم Full-map را با نسخهای توزیعشده از همان ایده حل میکند. نسخههای مشترک داده با زنجیرهای از اشارهگرهای دایرکتوری ردیابی میشوند که میان کش همهٔ پردازندهها توزیع شدهاند [5].
هر بار که پردازنده بلوکی را در کش بار میکند یا در یک خط کش مینویسد، پیامی به دایرکتوری متمرکز یا توزیعشده میفرستد. نتیجهٔ پیام یا بهروزرسانی دایرکتوری است یا تولید پیامهای بیشتری برای انجام درخواست.
دایرکتوری وضعیت هر بلوک حافظه را نگه میدارد. هر بلوک در یکی از سه حالت Invalid، Shared یا Exclusive است و درخواستها بر اساس حالت فعلی چنین مدیریت میشوند.
حالت Invalid یا نامعتبر
هیچ پردازندهای داده را در کش ندارد.
24. Read miss: داده از حافظه به کش خوانده و حالت بلوک Shared میشود.
25. Write miss: داده از حافظه به کش آورده و در کش پردازنده نوشته میشود، اما حافظه بهروز نمیشود. حالت بلوک Exclusive میشود و بردار بیت Sharers هویت مالک را نشان میدهد.
حالت Shared یا مشترک
دستکم یک پردازنده داده را در کش دارد و حافظهٔ اصلی بهروز است.
26. Read miss: داده از حافظه به کش درخواستکننده میرود و پردازنده به بردار Sharers افزوده میشود.
27. Write miss: داده در کش درخواستکننده بار و نوشته میشود، اما حافظه بهروز نمیشود. برای همهٔ پردازندههای موجود در Sharers پیام invalidate فرستاده میشود. درخواستکننده بهعنوان تنها عضو مجموعه ثبت و حالت بلوک Exclusive میشود.
حالت Exclusive یا انحصاری
یک پردازنده که «مالک» نام دارد داده را در اختیار دارد و نسخهٔ حافظهٔ اصلی قدیمی است.
28. Read miss: درخواست داده برای مالک فرستاده میشود. مالک داده را به دایرکتوری میدهد؛ دایرکتوری آن را در حافظه مینویسد و برای پردازندهٔ درخواستکننده نیز میفرستد. حالت مالک در دایرکتوری از Exclusive به Shared تغییر میکند، چون هنوز نسخهای خواندنی دارد، و درخواستکننده نیز به Sharers افزوده میشود.
29. Data write-back: مالک در حال جایگزینکردن بلوک است و آن را پس مینویسد. حافظه بهروز، Sharers خالی و حالت بلوک Invalid میشود.
30. Write miss: بلوک مالک تازهای پیدا میکند. پیامی برای مالک قبلی ارسال میشود؛ او مقدار بلوک را به دایرکتوری میدهد و دایرکتوری آن را به درخواستکننده میفرستد. بردار Sharers پس از حذف مالک قدیمی، هویت مالک جدید را ثبت میکند و حالت همچنان Exclusive میماند.
در همهٔ پروتکلهای دایرکتوری باید توجه داشت که ارتباط آنی نیست و تأخیر از گرهای به گرهٔ دیگر فرق میکند. بنابراین ممکن است در یک لحظه چند برداشت متفاوت از حافظه وجود داشته باشد. طراح باید این شرایط رقابتی را بشناسد و مدیریت کند.
بیشتر پردازندههای چندهستهای تجاری از طرح snooping استفاده میکنند. در این روش همهٔ کشها گذرگاه مشترک میانکش را زیر نظر دارند تا اعلان تغییر دادهٔ مشترک در کشهای دیگر را ببینند. دو روش پایه برای نوشتن وجود دارد.
Write-through و پروتکل نامعتبرسازی
در write-through همهٔ نوشتنهای کش در حافظهٔ اصلی نیز ثبت میشوند، حتی اگر داده در کش بماند. هر خط کش دو حالت معتبر یا نامعتبر دارد. اگر هستهای دیگر دادهٔ همان خط را تغییر دهد، خط نامعتبر میشود.
در مثال foo، بهمحض آنکه هستهٔ 3 مقدار تازهای بنویسد، نسخهٔ موجود در کش هستهٔ 1 نامعتبر خواهد شد. وقتی هستهٔ 1 دوباره foo را بخواند، cache miss رخ میدهد و نسخهٔ بهروز از حافظهٔ اصلی واکشی میشود. این پروتکل ساده است، اما کند عمل میکند و ترافیک زیادی روی گذرگاه میسازد. گاهی برای کاهش ترافیک از بافر نوشتن استفاده میشود.

شکل ۱۲ ـ پروتکل نامعتبرسازی کش
راه جایگزین، پروتکل update است. در این روش همهٔ تغییرهای دادهٔ کششده روی گذرگاه میانکش پخش میشوند. برای هر نوشتن یک رویداد update ساخته میشود، حتی اگر همان متغیر چند بار پیاپی نوشته شود؛ در نتیجه update از invalidate کندتر است. اگر هستهٔ 3 چند بار foo را تغییر دهد، invalidate فقط برای نخستین نوشتن یک رویداد میفرستد، چون نسخههای دیگر از همان ابتدا نامعتبر شدهاند؛ اما update برای هر تغییر یک پیام تازه پخش میکند.

شکل ۱۳ ـ پروتکل بهروزرسانی کش
Write-back
در write-back نوشتن کش بلافاصله به حافظهٔ اصلی منتقل نمیشود، مگر آنکه کش دیگری به همان خط نیاز داشته باشد. متداولترین پروتکل پسنوشت MESI است.
نام MESI از چهار حالت Modified، Exclusive، Shared و Invalid گرفته شده است. وضعیت هر خط کش با دو بیت پرچم اضافی نگهداری میشود. این پروتکل بر مدل مالکیت داده استوار است؛ یعنی تنها یک کش میتواند نسخهٔ dirty یا تغییریافته را در اختیار داشته باشد. کشی که خط را تغییر میدهد، کشهای دیگر را از وقوع تغییر باخبر میکند، اما لزوماً خود داده را نمیفرستد.
چهار حالت MESI چنیناند:
31. Modified یا اصلاحشده: خط معتبر است و فقط در کش جاری وجود دارد. تغییر کرده اما هنوز در حافظهٔ اصلی نوشته نشده و dirty است. پسنوشتن دادهٔ تغییریافته به حافظه، حالت خط را به Exclusive تغییر میدهد.
32. Exclusive یا انحصاری: خط معتبر و فقط در کش جاری است، اما clean و با نسخهٔ حافظهٔ اصلی یکسان است. اگر کش دیگری آن را بخواند، حالت Shared میشود؛ اگر هستهٔ جاری در آن بنویسد، Modified خواهد شد.
33. Shared یا مشترک: خط معتبر است و علاوه بر کش جاری، دستکم در یک کش دیگر نیز وجود دارد. نسخهها با حافظهٔ اصلی یکساناند. اگر خط کنار گذاشته شود، حالت آن Invalid میشود.
34. Invalid یا نامعتبر: خط کش دادهٔ معتبر حافظه ندارد.
در آغاز همهٔ خطهای کش خالی و Invalid هستند. اگر دادهای فقط برای خواندن بار شود و هیچ هستهٔ دیگری آن را نداشته باشد، حالت Exclusive میشود؛ در غیر این صورت حالت کش تازه و همهٔ کشهای دارندهٔ آن داده Shared خواهد شد.
در write miss، حالت Modified میشود، داده برای نوشتن در خط کش بار میشود و مطابق توضیح منبع، حافظه نیز همان زمان بهروزرسانی میشود. خطهای Modified و Exclusive در مالکیت کشی هستند که آنها را نگه میدارد و میتوان آنها را بدون اطلاع کشهای دیگر تغییر داد.
اگر هستهٔ دومی بخواهد در خطی بنویسد که در اختیار هستهٔ اول است، هستهٔ اول محتوای خط را میفرستد و نسخهٔ محلی خود را Invalid میکند. این عملیات Request For Ownership یا RFO نام دارد. کش دوم مالک خط میشود و حالت آن Modified خواهد بود.
اگر خط Shared باشد و هستهٔ محلی آن را بخواند، حالت عوض نمیشود و خواندن از کش انجام میگیرد. اگر هسته بخواهد روی همان خط Shared بنویسد، حالت به Modified تغییر میکند و با پیام RFO به همهٔ هستهها اعلام میشود تا نسخههایشان را Invalid کنند. مطابق متن منبع، برای خود این تغییر حالت عملیات جداگانهای روی گذرگاه لازم نیست. اگر خط از ابتدا Exclusive باشد، نوشتن نیازی به اعلان روی گذرگاه میانکش ندارد.
جدول ۱ ـ پروتکل انسجام کش MESI
حالت خط کش | Modified (M) | Exclusive (E) | Shared (S) | Invalid (I) |
خط معتبر است؟ | بله | بله | بله | خیر |
وضعیت نسخهٔ حافظه | قدیمی | معتبر | معتبر | - |
نسخه در کش دیگر؟ | خیر | خیر | ممکن است | - |
عمل هنگام خواندن | Read hit؛ بدون بهروزرسانی از حافظه | Read hit؛ بدون بهروزرسانی از حافظه | Read hit؛ بدون بهروزرسانی از حافظه | Cache miss؛ بارگیری از حافظه؛ اگر کش دیگری خط را ندارد E و در غیر این صورت S |
عمل هنگام نوشتن | Write hit؛ کش بهروز میشود و حافظه فوراً تغییر نمیکند | Write hit؛ کش بهروز میشود، حافظه تغییر نمیکند و حالت M میشود | Write hit؛ کش بهروز و نسخههای دیگر نامعتبر میشوند؛ حالت فنی درست M است | Write miss؛ داده بار میشود و حالت M خواهد شد |
یادداشت فنی: در ردیف «نوشتن روی خط Shared»، جدول چاپی منبع حالت مقصد را E نوشته است؛ اما توضیح متنی همان فصل و رفتار استاندارد MESI، گذار Shared به Modified را بیان میکنند. در جدول فارسی، مقدار فنی درست M درج شده و اختلاف منبع بهصراحت ثبت شده است.
حالتهای MESI در جدول ۱ و شکل ۱۴ خلاصه شدهاند. تغییر حالت با گوشدادن یا snoopکردن هستهها به رویدادهایی انجام میشود که هستههای دیگر پخش میکنند. نشانی خط مورد نظر روی گذرگاه نشانی میانکش دیده میشود تا هر کش بتواند محتوای خود را بهصورت انجمنی برای یافتن خط متناظر جستوجو کند.

شکل ۱۴ ـ نمودار حالت پروتکل MESI
هزینههای ویژهٔ سیستم چندهستهای از همین گذارها روشن میشوند. پرکردن کش پرهزینه است و علاوه بر آن باید پیامهای RFO نیز کنترل شوند. گذار MESI تا زمانی که همهٔ هستهها فرصت پاسخگویی به پیام را پیدا نکنند کامل نمیشود؛ پس کندترین پاسخ ممکن، سرعت پروتکل انسجام را تعیین میکند. هر بار که RFO لازم باشد، کار کندتر میشود.
دو وضعیت اصلی نیازمند RFO است:
35. رشتهای از یک هسته به هستهٔ دیگر مهاجرت کرده و خطهای کش آن باید یکبار به هستهٔ تازه منتقل شوند.
36. یک خط کش واقعاً بهطور همزمان در دو هسته لازم است.
پهنای باند محدود مورد نیاز برای اجرای پروتکل انسجام، همزمانی را محدود میکند. برنامه باید طوری طراحی شود که پردازندهها و هستههای مختلف تا حد ممکن کمتر به همان جایگاههای حافظه دسترسی بزنند؛ در نتیجه پیامهای کمتری روی گذرگاه میانکش فرستاده میشود.
برخی پردازندهها حالت پنجمی به نام Shared Modified اضافه میکنند و پروتکل حاصل را MOESI مینامند. کش در این حالت نسخهٔ جاری را برای خطهای Shared کشهای دیگر میفرستد، اما آن را در حافظهٔ اصلی پس نمینویسد.
یک برنامه ممکن است چند رشته داشته باشد که همزمان روی ساختار دادهٔ مشترکی کار میکنند. تازهترین بخشهای ساختار داده که هر رشته به آنها دست زده در کش هستهٔ اجراکنندهٔ همان رشته قرار دارند. بعضی الگوهای دسترسی در چنین وضعیتی رفتاری ایجاد میکنند که کارایی برنامه را پایین میآورد.
اشتراک کاذب زمانی رخ میدهد که رشتههای پردازندههای متفاوت، متغیرهایی را تغییر دهند که در یک خط کش واحد قرار گرفتهاند.

شکل ۱۵ ـ اشتراک کاذب
فرض کنید حلقهٔ زیر موازی و تکرارهای آن میان دو رشته تقسیم شدهاند:
for (i = 0; i < N; i++)
{
C[i] = A[i] + B[i];
}
اگر زمانبندی round-robin باشد، همهٔ تکرارهای زوج به Thread 0 و همهٔ تکرارهای فرد به Thread 1 میرسند. Thread 0 روی Core 0 و Thread 1 روی Core 1 اجرا میشود. فرض کنید یک خط کش بتواند هشت مقدار از A، B یا C را نگه دارد. در آغاز، مقادیر A(0..7)، B(0..7) و C(0..7) برای هر دو رشته در سه خط کش بار میشوند.
خواندن A و B باعث نامعتبرشدن کش نمیشود. اما وقتی Thread 0 در C(0) مینویسد، خط متناظر در Core 1 نامعتبر میشود، چون خط مشترک است. Core 1 ناچار است آن خط را دوباره بار کند که عملیاتی پرهزینه است. وقتی Thread 1 در عضو فرد متناظر، یعنی C(1)، مینویسد، خط Core 0 نامعتبر و دوباره بار میشود و این رفتوبرگشت ادامه پیدا میکند. این همان اثر «پینگپنگ خط کش» است و میتواند افت کارایی شدیدی ایجاد کند.
یادداشت ترجمه: متن چاپی در جملهٔ دوم دوباره C(0) را ذکر کرده است؛ با توجه به تقسیم زوج/فرد حلقه، C(1) منظور منطقی مثال است.
دو رشته در واقع روی دادههای مستقل کار میکنند، اما چون دادهها در یک خط کش قرار دارند، کش آنها را مشترک تلقی میکند. این پدیده اشتراک کاذب نام دارد. همچنین هر هسته فقط از نیمی از مقادیر خطهای A و B استفاده میکند و ظرفیت خط بهطور کامل به کار گرفته نمیشود.
هنگام موازیسازی باید این نکته را در نظر گرفت. در مثال بالا راه بهتر آن است که محدودهٔ حلقه به دو نیم تقسیم شود: نیمهٔ اول به Thread 0 و نیمهٔ دوم به Thread 1 برسد. در این حالت اشتراک خط کش فقط در مرز میان دادههای دو رشته رخ میدهد و خطهای A و B نیز کاملاً استفاده میشوند؛ بنابراین کارایی بیشتر میشود.
ساختار کش و نحوهٔ تخصیص رشتهها به هستهها بر کارایی پردازندهٔ چندهستهای اثر دارد. ساختار شکل ۸ را در نظر بگیرید. اگر برنامه دو رشته دارد که مقدار زیادی داده را به اشتراک میگذارند، بهتر است آنها روی هستههایی مانند Core 0 و Core 1 قرار گیرند که یک L2 مشترک دارند. در این حالت تغییرها از مسیر کش منتقل میشوند و مراجعه به حافظهٔ کندتر کاهش مییابد.
اگر دو رشته هیچ دادهای را مشترکاً استفاده نمیکنند و از کش بزرگتر سود میبرند، بهتر است روی هستههایی مانند Core 0 و Core 2 اجرا شوند که L2 مشترک ندارند؛ البته به شرطی که برنامهٔ دیگری Core 1 و Core 3 را اشغال نکرده باشد.
بیشتر کتابخانههای سیستم دستورهایی برای processor affinity دارند و سیستمعامل را وادار میکنند رشته را روی یک هسته یا مجموعهٔ مشخصی از هستهها اجرا کند. مزیت جانبی این است که رشته در اجراهای بعدی نیز روی همان هسته زمانبندی میشود و شاید بتواند از دادههای باقیمانده در کش اجرای قبلی دوباره استفاده کند. affinity همچنین میتواند استفادهٔ انحصاری از هستههای مشخص را به فرایندها یا رشتههای حیاتی بدهد.
برنامههای سیستم نهفته از قبل مشخصاند و میتوان الگوی دسترسی حافظهٔ آنها را تحلیل کرد. بر این اساس، کد و دادهٔ حیاتی به خطهای معینی از کش اختصاص داده و همان خطها قفل میشوند. خط قفلشده برای ورود خط جدید بیرون انداخته نمیشود و باقی کش بهروش عادی کار میکند.
بحث مدیریت حافظه در پردازندهٔ چندهستهای بدون حافظهٔ تراکنشی کامل نیست.
روش سنتی حل مسئلهٔ همگامسازی داده در حافظهٔ اشتراکی، استفاده از قفل است تا بخشهای بحرانی کد بدون دخالت رشتههای دیگر اجرا شوند. این روش دشوار و مستعد خطاست و مشکلات شناختهشدهای مانند بنبست و شرایط رقابتی ایجاد میکند که بازتولید و اشکالزدایی آنها بسیار سخت است. برنامهنویس باید تداخل عملیات روی دادهٔ مشترک را حتی در بخشهایی از کد که از هم دور و ظاهراً نامرتبطاند در نظر بگیرد.
انتظار رشتهها برای آزادشدن قفل میتواند افت کارایی بزرگی ایجاد کند. قفل همچنین ممکن است به وارونگی اولویت منجر شود؛ وضعیتی که رشتهٔ پراولویت ناچار است منتظر رشتهٔ کماولویت بماند تا قفل را آزاد کند.
برای نمونه، هنگام درج یک گره در فهرست پیوندی مشترک میان چند رشته، رشتهٔ درجکننده باید پیش از تغییر اشارهگرها قفل را بگیرد.

شکل ۱۶ ـ درج گرهٔ فهرست پیوندی با قفل
حافظهٔ تراکنشی یا TM با هدف سادهکردن نوشتن کد چندرشتهای مقیاسپذیر، کارآمد و درست، بدون قفل صریح، طراحی شده است. برنامهنویس مشخص میکند چه عملیاتی باید اتمی باشد و سیستم تصمیم میگیرد این اتمیبودن چگونه اجرا شود.
برنامهٔ موازی میتواند گروهی از دستورهای خواندن و نوشتن را بهصورت اتمی اجرا کند، مشابه تراکنش پایگاه داده. TM خوشبینانه است و فرض میکند تراکنش موفق خواهد شد. این نگاه همزمانی را افزایش میدهد، زیرا رشته لازم نیست از پیش منتظر منبعی بماند و چند رشته میتوانند همزمان و ایمن بخشهای جداگانهٔ یک ساختار داده را تغییر دهند؛ حتی اگر در روش قفلگذاری کل آن ساختار زیر یک قفل قرار میگرفت.
در سازوکار قفل، رشته فقط به احتمال آنکه رشتهای دیگر همزمان همان کار را انجام دهد قفل میگیرد. در TM، تراکنش تنها زمانی دوباره اجرا میشود که واقعاً تغییر همزمانی روی دادهٔ مشترک رخ داده باشد. تراکنشها برخلاف قفلها چند خوانندهٔ همزمان را بهطور خودکار مجاز میکنند.
TM سه دستور اصلی برای کنترل وضعیت تراکنش دارد:
37. Commit تلاش میکند تغییرها را دائمی کند. این کار فقط زمانی موفق است که هیچ تراکنش دیگری جایگاهی از مجموعهدادهٔ تراکنش را تغییر نداده و هیچ تراکنش دیگری جایگاهی از write set آن را نخوانده باشد. اگر commit شکست بخورد، همهٔ تغییرهای write set کنار گذاشته میشوند.
38. Abort همهٔ بهروزرسانیهای write set را دور میریزد.
39. Validate وضعیت تراکنش را آزمایش میکند. اگر تراکنش جاری abort نشده باشد true و در غیر این صورت false برمیگرداند.
توسعهدهنده میتواند بخشهایی از برنامه را که دادهٔ مشترک را تغییر میدهند atomic علامت بزند. هر بلوک اتمی درون یک تراکنش اجرا میشود: یا کل بلوک اجرا میشود یا هیچیک از عملیاتش اثر نهایی ندارد. داخل بلوک میتوان متغیر مشترک را هر چند بار که لازم است خواند یا نوشت، بدون آنکه قفلی گرفته شود.
در پایان بلوک و هنگام commit، سامانهٔ TM بررسی میکند که آیا دادهٔ مشترک از شروع عملیات اتمی تاکنون در حافظه تغییر کرده است یا نه. اگر تغییر نکرده باشد، مقدار تازه ثبت میشود. اگر تغییر کرده باشد، تراکنش abort و کار رشته rollback میشود. برنامه معمولاً بدون دخالت برنامهنویس عملیات را دوباره امتحان میکند تا موفق شود یا به حد ازپیشتعیینشدهٔ تلاش ناموفق برسد.
همان مثال درج گره را میتوان با TM بهصورت یک بلوک اتمی نوشت.

شکل ۱۷ ـ درج گره با حافظهٔ تراکنشی
چند رشته میتوانند بدون نگرانی از همگامسازی، گرههایی را در فهرست درج کنند. اگر روی قسمتهای متفاوت فهرست کار کنند، همهٔ تراکنشها commit میشوند و موازیسازی کامل به دست میآید. در نسخهٔ قفلدار، صرفنظر از محل درج، هر بار فقط یک رشته میتواند گره اضافه کند.
TM محدودیتهایی نیز دارد. عملیاتی که قابل لغو و بازگردانی نیست نمیتواند در یک تراکنش معمولی قرار گیرد؛ ورودی/خروجی نمونهٔ رایج چنین عملیاتی است. در عمل بافرهایی ساخته میشوند تا عملیات برگشتناپذیر را در صف نگه دارند و پس از پایان تراکنش اجرا کنند.
حافظهٔ تراکنشی را میتوان در نرمافزار یا سختافزار پیادهسازی کرد.
STM معنا و رفتار تراکنشی را در کتابخانهٔ زمان اجرا یا زبان برنامهنویسی فراهم میکند و به پشتیبانی سختافزاری اندکی نیاز دارد؛ معمولاً یک عملیات اتمی compare-and-swap یا معادل آن کافی است. در سادهترین شکل، تراکنش نرمافزاری بلوکی اتمی از کد است که از دید منطقی در یک لحظه رخ میدهد. با رسیدن به پایان بلوک، تراکنش در صورت امکان commit میشود و در غیر این صورت abort و دوباره اجرا خواهد شد.
هر متغیر مشترک اطلاعات version دارد تا تغییرهای تراکنشهای اتمی ردیابی شوند. در مثال شکل ۱۸، Thread T1 نخستین تراکنش اتمی و Thread T2 تراکنش دوم را اجرا میکند. هر دو رشته transaction log مخصوصی دارند که ابتدا خالی است. متغیرهای var1 تا var4 نیز مقدار و version اولیه دارند.

شکل ۱۸ ـ وضعیت آغازین حافظهٔ تراکنشی
فرض کنید T1 و T2 همزمان اجرا و var1 تا var4 را میخوانند. پیش از بهروزرسانی، هر رشته همهٔ متغیرهای درگیر و version آنها را در log خود ثبت میکند.

شکل ۱۹ ـ وضعیت پس از خواندن متغیرها
اکنون T2 مقادیر var3 و var4 را تغییر میدهد و برای commit تلاش میکند. مدیر تراکنش بررسی میکند version فعلی این دو متغیر با version ثبتشده در log T2 یکسان باشد. با فرض برابری، تراکنش در حافظه ثبت و مقدار و version هر دو متغیر بهروز میشود.

شکل ۲۰ ـ وضعیت پس از بهروزرسانی T2
سپس T1 اجرای تراکنش خود را تمام میکند و میخواهد مقدارهای تازهٔ var1 و var2 را commit کند. مدیر تراکنش versionهای var1 تا var4 را با log T1 مقایسه میکند و میبیند versionهای var3 و var4 تغییر کردهاند؛ بنابراین تراکنش T1 abort و دوباره اجرا میشود. فرض میکنیم در تلاش بعدی تعارضی وجود ندارد و تراکنش موفق میشود.

شکل ۲۱ ـ وضعیت پس از اجرای دوباره و commit شدن T1
کد TM هم از دید کد TM و هم از دید کد عادی اتمی است، اما فقط تعارض میان دو قطعهٔ TM تشخیص داده میشود؛ زیرا اطلاعات version فقط برای بلوکهای TM نگهداری میشود. اگر کد عادی متغیری را در میانهٔ بلوک TM تغییر دهد، این تغییر ممکن است تشخیص داده نشود و سیستم ناسازگار شود.
ثبت عملیات load، update و store و نیز بررسی version سربار دارد. میزان همگامسازی برنامه و تعداد commitهای ناموفق بر کارایی اثر میگذارند. یک سامانهٔ TM خوب باید تراکنشهای نامحدود از نظر فضا و زمان را نیز پشتیبانی کند؛ یعنی اندازهٔ بلوک اتمی یا مدت اجرای آن از پیش محدود نباشد.
ممکن است برنامهنویس مجبور شود سازوکار fallback داشته باشد تا اگر تراکنش پس از چند تلاش موفق نشد، بخش بحرانی با قفل سنتی اجرا شود. زبان برنامهنویسی نیز باید پایش دسترسیهای حافظه برای ثبت log و معناهای کامل TM، مانند تراکنشهای تودرتو، را پشتیبانی کند.
استانداردهای C و C11 که در سال 2011 منتشر شدند پشتیبانی محدودی از STM اضافه کردند [6].
پیادهسازی HTM معمولاً از کش پردازنده و پروتکل انسجام کش برای تشخیص و مدیریت تعارض میان تراکنشهای سختافزاری استفاده میکند. پروتکل انسجام، دسترسیهای داخل تراکنش را ردیابی میکند. اگر تراکنش abort شود، خطهای کشی که نوشتنهای موقت HTM را نگه میدارند کنار گذاشته میشوند.
سختافزار نسخهٔ داده را مدیریت و تعارض را بهصورت شفاف دنبال میکند. چون تمام عملیات در سختافزار رخ میدهد، HTM معمولاً سربار کمتری از STM دارد. با این حال، اندازهٔ تراکنش سختافزاری به منابع فیزیکی لازم برای نگهداری تراکنش محدود است؛ در حالی که نرمافزار میتواند تراکنشهای بزرگتر و طولانیتر را پوشش دهد.
جزئیات بیشتر HTM در فصل مربوط به همگامسازی سختافزاری کتاب آمده است.
حافظهٔ تراکنشی ترکیبی با هماهنگکردن HTM و STM از تراکنشهای اتمی با اندازهٔ محدود پشتیبانی میکند. تراکنشهایی که از محدودیتهای سختافزار فراتر نمیروند با HTM و بقیه با STM اجرا میشوند.
سیستم نهفتهٔ چندهستهای با رایانهٔ چندهستهای همهمنظوره تفاوت دارد، زیرا برای برنامهٔ مشخصی طراحی میشود. معمولاً میتوان الگوی دسترسی همان برنامه به حافظه را از قبل به دست آورد و معماری را از نظر هزینه، مصرف توان، کارایی و ماندگاری داده دقیقاً تنظیم کرد. با توجه به پیچیدگی دستگاه باید چند موازنهٔ مهم سنجیده شود:
آیا حافظه میان هستهها مشترک است یا توزیعشده؟ اگر مشترک است، همهٔ هستهها زمان دسترسی یکسان دارند یا تأخیر به محل حافظه وابسته است؟ آیا هستهها کش دارند و آیا انسجام کش در همهٔ هستهها حفظ میشود؟
آیا وجود کش واقعاً سیستم را بهتر میکند؟ سلسلهمراتب آن چگونه چیده میشود؟ چند سطح لازم است؟ کش داخل یا خارج تراشه است؟ ظرفیت کش و اندازهٔ خط چقدر باشد؟ کش داده و دستور جدا باشند؟ آیا برای زمانبندی بلادرنگ باید خطهایی قفل شوند؟ انسجام با سختافزار مدیریت شود یا نرمافزار و کدام پروتکل مناسبتر است؟
آیا حافظههایی مانند SRAM اسکرچپد و eDRAM برای دستگاه سودمندند؟ آیا LIFO، FIFO یا CAM لازم است؟
آیا میتوان حافظهٔ تراکنشی را در سختافزار یا نرمافزار پشتیبانی کرد و محدودیت اندازه و کارایی آن چیست؟
برای یافتن معماری بهینه باید برنامهٔ نهفته را با پیکربندیهای مختلف حافظه بهطور دقیق تحلیل و بنچمارک کرد. این فصل خلاصهای از حافظهها و معماریهای رایج در پردازندهٔ نهفته و رایانهٔ رومیزی و مزایا و معایب هر رویکرد ارائه کرد. معماری یکسان و همهکارهای وجود ندارد؛ طراح سیستم نهفته باید نیازهای همان سیستم را ارزیابی و حافظه را متناسب با آن تنظیم کند.
Leslie Lamport (1979), How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs— مقالهٔ بنیادی درباره سازگاری ترتیبی یا Sequential Consistency.
Sarita V. Adve و Kourosh Gharachorloo (1996), Shared Memory Consistency Models: A Tutorial — آموزش جامع مدلهای سازگاری حافظه اشتراکی.
Kourosh Gharachorloo و همکاران (1990), Memory Consistency and Event Ordering in Scalable Shared-Memory Multiprocessors — معرفی و بررسی Release Consistency در پردازندههای چندگانه.
Mark S. Papamarcos و Janak H. Patel (1984), A Low-Overhead Coherence Solution for Multiprocessors with Private Cache Memories — از منابع اصلی پروتکل انسجام کش MESI.
Daniel J. Sorin، Mark D. Hill و David A. Wood (2011), A Primer on Memory Consistency and Cache Coherence — مرجع جامع درباره تفاوت سازگاری حافظه و انسجام کش.
Peter Sewell و همکاران (2010), x86-TSO: A Rigorous and Usable Programmer’s Model for x86 Multiprocessors — مدل دقیق ترتیب حافظه در معماری x86.
Hans-J. Boehm و Sarita V. Adve (2008), Foundations of the C++ Concurrency Memory Model — مبانی علمی مدل حافظه و همزمانی در زبان C++.
Arm, Arm Architecture Reference Manual for A-profile Architecture — مرجع رسمی ترتیب حافظه، کش، موانع حافظه و عملیات اتمیک در Arm.
Arm, Learn the Architecture: Armv8-A Memory Systems — راهنمای آموزشی سیستم حافظه، انواع حافظه و Memory Ordering در Armv8-A.
Arm, Learn the Architecture: Introducing AMBA CHI — معرفی پروتکل اتصال داخلی منسجم برای سامانههای چندهستهای.
Intel, Intel 64 and IA-32 Architectures Software Developer’s Manuals — مستندات رسمی Memory Ordering، عملیات قفلشده و پردازش چندهستهای در معماری Intel.
Intel, False Sharing — توضیح رسمی پدیده False Sharing، روش تشخیص و راهکارهای کاهش آن.
RISC-V International, RVWMO Memory Consistency Model — تعریف رسمی مدل حافظه ضعیف RISC-V.
RISC-V International, A Extension for Atomic Instructions — مستندات عملیات اتمیک و ترتیبهای Acquire، Release و Sequentially Consistent.
ISO/IEC JTC1/SC22/WG14, N1570: Programming Languages — C — پیشنویس نهایی استاندارد C11 شامل مدل حافظه، اتمیکها و <stdatomic.h>.
ISO/IEC JTC1/SC22/WG21, N3337: C++ International Standard — مرجع استاندارد همزمانی، اتمیکها و ترتیبهای حافظه در C++.
Linux Kernel Documentation, Linux Kernel Memory Barriers — راهنمای جامع موانع حافظه، وابستگیها، کش و ترتیب دسترسیها.
Linux Kernel Documentation, Dynamic DMA Mapping Guide — راهنمای رسمی ارتباط DMA با کش، حافظه منسجم و همگامسازی CPU و دستگاه.
Zephyr Project, Symmetric Multiprocessing — مستندات SMP و مدیریت انسجام حافظه در سامانههای چندهستهای نهفته.
FreeRTOS, Symmetric Multiprocessing with FreeRTOS — راهنمای رسمی اجرای وظایف و زمانبندی در پردازندههای چندهستهای مبتنی بر FreeRTOS.
مدل حافظه مجموعهای از قوانین است که مشخص میکند عملیات خواندن و نوشتن حافظه با چه ترتیبی برای هستههای مختلف قابل مشاهده هستند. این مدل به برنامهنویس میگوید چه زمانی تغییرات ایجادشده توسط یک هسته در اختیار هستههای دیگر قرار میگیرند.
در یک سیستم چندهستهای، چند پردازنده ممکن است همزمان به دادههای مشترک دسترسی داشته باشند. اگر ترتیب دسترسیها و روش همگامسازی بهدرستی مدیریت نشود، برنامه با خطاهایی مانند Race Condition، خواندن دادهٔ قدیمی یا رفتار غیرقابلپیشبینی روبهرو خواهد شد.
انسجام کش یا Cache Coherence تضمین میکند هستهها درباره مقدار یک محل حافظه اختلاف دائمی نداشته باشند. مدل حافظه یا Memory Consistency دامنه گستردهتری دارد و ترتیب مشاهده عملیات مربوط به چند محل حافظه را مشخص میکند؛ بنابراین وجود انسجام کش بهتنهایی برای اجرای صحیح برنامه کافی نیست.
در مدل Sequential Consistency نتیجه اجرای برنامه باید به شکلی باشد که انگار عملیات تمام هستهها در یک ترتیب سراسری واحد اجرا شدهاند. این مدل درک سادهتری دارد، اما محدودکردن بازآرایی عملیات میتواند هزینهٔ اجرایی و سختافزاری بیشتری ایجاد کند.
در مدل حافظه ضعیف، پردازنده و کامپایلر اجازه دارند بعضی عملیات حافظه را برای افزایش کارایی بازآرایی کنند. برنامهنویس باید با عملیات اتمیک، قفلها و موانع حافظه مشخص کند کدام دسترسیها باید با ترتیب قطعی انجام شوند.
بازآرایی ممکن است توسط کامپایلر، پردازنده، بافرهای نوشتن یا سیستم کش انجام شود. اگر وابستگی قابل مشاهدهای وجود نداشته باشد، سیستم میتواند ترتیب واقعی اجرای دستورها را تغییر دهد؛ حتی اگر ترتیب آنها در کد منبع متفاوت باشد.
سختافزار با استفاده از پروتکلهای انسجام کش، نسخههای مختلف یک داده را در کش هستهها ردیابی میکند. هنگامی که یک هسته دادهای را تغییر میدهد، نسخههای موجود در کش سایر هستهها بهروزرسانی یا نامعتبر میشوند.
MESI یکی از پروتکلهای رایج انسجام کش است. هر خط کش در این پروتکل یکی از چهار وضعیت Modified، Exclusive، Shared یا Invalid را دارد و جابهجایی میان این وضعیتها تعیین میکند کدام هسته اجازه خواندن یا تغییر داده را دارد.
خیر. MESI انسجام نسخههای کششده یک داده را مدیریت میکند، اما ترتیب منطقی عملیات برنامه یا دسترسی همزمان چند هسته به یک ساختار داده را تضمین نمیکند. برای این موارد همچنان به عملیات اتمیک و سازوکارهای همگامسازی نیاز است.
مانع حافظه دستوری است که اجرای برخی عملیات خواندن و نوشتن را تا کاملشدن عملیات قبلی محدود میکند. این دستور از بازآرایی نامطلوب در مرزهای حساس برنامه جلوگیری میکند و ترتیب مشاهده دادهها را میان هستهها کنترل میکند.
عملیات Acquire اجازه نمیدهد دسترسیهای بعدی پیش از آن مشاهده شوند و عملیات Release مانع عبور دسترسیهای قبلی از آن میشود. ترتیب Sequentially Consistent علاوه بر این محدودیتها، یک ترتیب سراسری قویتر برای عملیات اتمیک ایجاد میکند، اما معمولاً هزینه بیشتری دارد.
عملیات اتمیک عملیاتی است که از دید سایر هستهها بهصورت یک واحد غیرقابلتقسیم اجرا میشود. شمارندههای مشترک، فلگهای وضعیت، صفهای بدون قفل و برخی الگوریتمهای همزمان از کاربردهای رایج آن هستند.
خیر. volatile معمولاً مانع حذف یا ادغام بعضی دسترسیها توسط کامپایلر میشود، اما اتمیکبودن، انسجام حافظه و ترتیب بینرشتهای لازم را تضمین نمیکند. در C و C++ باید برای ارتباط میان هستهها از atomic، قفل یا primitiveهای همگامسازی مناسب استفاده شود.
Race Condition زمانی رخ میدهد که نتیجه برنامه به ترتیب زمانی دسترسی همزمان چند هسته یا رشته به داده مشترک وابسته باشد. این خطاها ممکن است فقط در بار پردازشی بالا یا شرایط زمانی خاص ظاهر شوند و به همین دلیل تشخیص آنها دشوار است.
False Sharing زمانی اتفاق میافتد که دو هسته دادههای مستقلی را تغییر میدهند، اما آن دادهها در یک خط کش قرار دارند. در نتیجه خط کش مرتباً میان هستهها جابهجا یا نامعتبر میشود و بدون وجود اشتراک منطقی واقعی، کارایی سیستم کاهش پیدا میکند.
میتوان دادههایی را که توسط هستههای متفاوت تغییر میکنند در خطوط کش جداگانه قرار داد. استفاده از Alignment، Padding و طراحی مناسب ساختار داده از روشهای رایج کاهش False Sharing است؛ البته اندازه واقعی خط کش باید از مستندات پردازنده بررسی شود.
حافظه مشترک برای چند هسته قابل دسترسی است و ارتباط میان آنها را سادهتر میکند، اما به همگامسازی نیاز دارد. حافظه محلی معمولاً سریعتر و قابلپیشبینیتر است، ولی انتقال داده میان هستهها باید بهصورت صریح انجام شود.
کنترلکننده DMA ممکن است مستقیماً با حافظه اصلی کار کند و از کش پردازنده عبور کند. در سیستمهای فاقد DMA منسجم، نرمافزار باید پیش یا پس از انتقال، عملیات پاکسازی یا نامعتبرسازی کش و موانع حافظه لازم را اجرا کند.
در سیستمهای بلادرنگ فقط سرعت متوسط مهم نیست؛ زمان اجرای قابلپیشبینی نیز اهمیت دارد. رقابت بر سر کش، موانع حافظه، قفلها و ترافیک انسجام میتوانند تأخیر ایجاد کنند و باید در تحلیل بدترین زمان اجرای سیستم در نظر گرفته شوند.
انتخاب راهکار مناسب باید بر اساس معماری پردازنده، زبان برنامهنویسی، سیستمعامل یا RTOS، اشتراک داده و نیازهای بلادرنگ انجام شود. بهترین رویکرد این است که از همگامسازی سطحبالا و مطمئن استفاده شود و مدلهای ضعیفتر فقط در بخشهایی به کار روند که صحت و کارایی آنها با آزمایش و مستندات معماری تأیید شده است.