Взето от Every 7.8μs your computer’s memory has a hiccup

...
Взето от Every 7.8μs your computer’s memory has a hiccup
Коментари Харесай

Оперативната памет на вашия компютър лагва на всеки 7,8 μs

Взето от Every 7.8μs your computer’s memory has a hiccup

По време на моето посещаване в Музея по компютърна история в Маунтин Вю, видях античен пример на феритна памет.

Не знам, по какъв начин тъкмо работи тази памет – дали феритните пръстени се въртят (не, не се въртят) и за какво през всеки пръстен минават три проводника? Усетих, че не знам и по какъв начин тъкмо работи актуалната динамична оперативна памет.



Известно е, че в динамичната RAM, всеки обичай се запаметява във тип на заряд (или неналичието на заряд) в дребен кондензатор в DRAM чипа. За да не се изгубят записаните данни се постанова тяхното непрестанно възобновяване, познато повече като освежаване на динамичната памет. Процесът на освежаване в действителност е четене на всеки обичай и по-късно неговият запис – битовете се прочитат и презаписват. По време на опресняването, паметта е заета и не може да извършва общоприетите интервенции четене и запис.

И незабавно породи въпросът, а дали не е допустимо по чисто софтуерен път да измерим това закъснение на DRAM паметта, по време на което се прави опресняването?
Какво ще използваме
Всяка DIMM плочка е формирана от кафези, подредени в редове и колони. Редовете и колоните от своя страна образуват страници на паметта, а всички кафези са разграничени в няколко области.

Конфигурацията на паметта в компютърната система може да бъде тествана да вземем за пример с командата decode-dimms. Ето един образец:
$ decode-dimms Size 4096 MB Banks x Rows x Columns x Bits 8 x 15 x 10 x 64 Ranks 2
Но напълно не е належащо да се задълбочаваме в работата на цялата плочка памет. За нашата задача е задоволително да разберем работа на единствено една клетка, тъй като ни интересува единствено процеса по опресняването.

Ще се ръководим от два авторитетни източника на информация:
Учебникът по освежаване на DRAM на университета на щата ЮтаПерфектната документи на 1 Gb чип на Micron
Всеки обичай в динамичната памет с случаен достъп наложително би трябвало да се опреснява. Обикновено това става на всеки 64 ms (параметърът Static Refresh). Това е прекомерно скъпа интервенция за една компютърна система, която ще погълне прекалено много запаси. За да се избегне едно дълго прекъсване на работата на всеки 64 ms, процесът е разграничен на 8192 по-малки интервенции на опресняването. При всяка една от тях контролерът на паметта изпраща команди за опресняването на DRAM. След приемането на тази директива, чипът обновява 1/8192 от своите кафези. Да пресметнем: 64 ms/8192 = 7812,5 ns или 7,81 μs.

Може да кажем, че:
Командата за възобновяване се извършва на всеки 7812,5 ns. Тя носи името tREFIПроцесът по прочитането и презаписа на клетките лишава известно време, след което чипът още веднъж може да прави общоприетите четене и запис. Това е параметърът tRFC, който при доста чипове е 75 ns, като в документите на Micron tRFC е 120 ns.
Има и други фактори. Ако паметта е гореща (над 85°C), кондензаторите се разреждат по-бързо, времето за предпазване на данните пада, и времето на Static Refresh понижава двойно до 32 ms. Съответно, tREFI пада до 3906,25 ns.

Типичният DRAM чип е ангажиран с опресняването през немалка част от своето време – от 0,4 до 5%. Освен това, забележителна част от потреблението на електрическа сила на DRAM чиповете се дължи точно на процеса на освежаване.

Има и друго: по време на опресняването се блокира работата на целия чип. Тоест, всеки един обичай на динамичната памет е блокиран за над 75 ns на всеки 7182 ns. Хайде да го измерим.
Подготовката на опита
За да можем да измерим тези интервенции с наносекундна акуратност е нужен доста компактен цикъл. Това може да стане благодарение на програмния език C и наподобява по следния метод:

for (i = 0; i <...; i++) { // Perform memory load. Any load instruction will do *(volatile int *) one_global_var; // Flush CPU cache. This is relatively slow _mm_clflush(one_global_var); // mfence is needed, otherwise sometimes the loop // takes very short time (25ns instead of like 160). I // blame reordering. asm volatile( " mfence " ); // Measure and record time clock_gettime(CLOCK_MONOTONIC, &ts); }
Целият сорс код може да бъде взет от GitHub

Кодът е напълно банален: четене на паметта, изчистване на данните от кеша на централния процесор, премерване на времето.

В моя компютър се появи следната информация:

# време, дължина на цикъла
3101895733, 134
3101895865, 132
3101896002, 137
3101896134, 132
3101896268, 134
3101896403, 135
3101896762, 359
3101896901, 139
3101897038, 137

Обикновено цикълът се извършва за към 140 ns, само че понякога скача до към 360 ns. А от време на време се появяват странни резултати от над 3200 ns.

За страдание, очевидно в тези данни има доста много звук и е прекомерно мъчно да се забележи задръжката, обвързвана с циклите по освежаване на DRAM паметта.
Бързото видоизменение на Фурие
В един миг бях осенен от забавна мисъл. Всъщност ние желаеме да записваме събитие с закрепена дължина и е разумно да подадем данните в FFT логаритъм (Fast Fourier transform или бързото видоизменение на Фурие). По този метод ще научим главните честоти.

Съвсем не съм първият, досетил се за това. Марк Сибърн с гениалната накърнимост Rowhammer употребява тази техника още през 2015 година. Но даже със сорс кода на Марк, всичко се оказа много комплицирано, само че открих метод.

Първоначално е нужна подготовка на данните. FFT работи с входни данни с еднакъв период за тяхното приемане. След това би трябвало да се изреже шума. Аз реализирах най-хубав резултат при следните обработки на входния сигнал:
Малките смисли (под 1,8 от средното) на итерациите на цикъла се отстраняват и се заменят с нули. Не желаеме шумВсички останали показания се заменят с единици, тъй като нас не ни интересува амплитудата на задръжката, провокирана от някой външен звук
Спрях се на стъпка на дискретизация 100 ns, само че е уместно всяко число до честотата на Найкуист – двойно по-високата от предстоящата периодичност

Алгоритъмът наподобява по следния метод:
UNIT=100ns A = [(timestamp, loop_duration),...] p = 1 for curr_ts in frange(fist_ts, last_ts, UNIT): while not(A[p-1].timestamp <= curr_ts < A[p].timestamp): p += 1 v1 = 1 if avg*1.8 <= A[p-1].duration <= avg*4 else 0 v2 = 1 if avg*1.8 <= A[p].duration <= avg*4 else 0 v = estimate_linear(v1, v2, A[p-1].timestamp, curr_ts, A[p].timestamp) B.append( v )
Този логаритъм генерира един много отегчителен вектор:
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...]
Всъщност, векторът е огромен – към 200 000 данни. Но точно с тези данни към този момент може да се употребява FFT!
C = numpy.fft.fft(B) C = numpy.abs(C) F = numpy.fft.fftfreq(len(B)) * (1000000000/UNIT)
Изглежда елементарно, нали? Получават се два вектора:
Векторът C съдържа комплексните цифри на честотните съставни елементи. Тук не се интересуваме от комплексните цифри и можем да ги изгладим с командата abs()Векторът F съдържа маркерите, показващи какъв честотен интервал в кое място на вектора C се намира. Трябва да го възстановяваме до херц посредством умножение на честотата на дискретизация.
Резултатът образува следната диаграма:


Оста Y няма никакви измервателни единици, тъй като игнорирахме времето на задръжката. Но напълно ясно се виждат честотните пикове. Да ги разгледаме по-отблизо:


Ясно се виждат първите три пика. От тях можем да извлечем базовите честоти:

127850.0
127900.0
127950.0
255700.0
255750.0
255800.0
255850.0
255900.0
255950.0
383600.0
383650.0

Пресмятаме: 1000000000 (ns/s)/ 127900 (Hz) = 7818,6 ns.

Да, първият пик на честотата е точно това, което търсехме и корелира с времето на освежаване.

Останалите пикове на честоти 256 KHz, 384 KHz и 512 KHz са хармониките на нашата базова периодичност 128 KHz. Това е общоприет непряк резултат при потреблението на FFT с правоъгълни импулси.

За облекчение на опита, вършим скрипт за командния ред. Ето по какъв начин наподобява той на моя сървър:
~/2018-11-memory-refresh$ make gcc -msse4.1 -ggdb -O3 -Wall -Wextra measure-dram.c -o measure-dram./measure-dram | python3./analyze-dram.py [*] Verifying ASLR: main=0x555555554890 stack=0x7fffffefe2ec [ ] Fun fact. I did 40663553 clock_gettime()`s per second [*] Measuring MOVQ + CLFLUSH time. Running 131072 iterations. [*] Writing out data [*] Input data: min=117 avg=176 med=167 max=8172 items=131072 [*] Cutoff range 212-inf [ ] 127849 items below cutoff, 0 items above cutoff, 3223 items non-zero [*] Running FFT [*] Top frequency above 2kHz below 250kHz has magnitude of 7716 [+] Top frequency spikes above 2kHZ are at: 127906Hz 7716 255813Hz 7947 383720Hz 7460 511626Hz 7141
Да отбележа, че кодът не е идеален, При съществуване на проблеми се предлага изключването на Turbo Boost.

След този опит можем да създадем два значими извода.
Видяхме, че данните от ниско редовно равнище мъчно се проучват и са много шумни и се постанова потреблението на бързото видоизменение на Фурие с нищожно филтрирани данни.Но най-главното е, че показахме точното премерване на хардуерен развой в потребителска среда. Именно този способ докара до разкриването на неповторимата накърнимост Rowhammer и на процедура е осъществен в офанзивите Meltdown/Spectre и още веднъж употребен при новата версия на Rowhammer за ECC паметите.

Разбира се, това са основите и доста неща останаха отвън рамките на тази публикация. Темата е добре развита в следните материали:
Изобразяване на L3 кеш паметта в процесорите Sandy BridgeКак се съпоставя физическия адрес на компютърната система в редовете и колоните на RAM чиповетеКак Хану Хартикаинен (Hannu Hartikainen) хакна DDR SO-DIMM паметта и я накара да работи с друга скорост
И най-после, ето едно доста положително пояснение на работата на феритната памет в PDP-11

*Допълнителните материали, изброени малко нагоре, ще разгледаме в идващите статии
Източник: kaldata.com

СПОДЕЛИ СТАТИЯТА


Промоции

КОМЕНТАРИ
НАПИШИ КОМЕНТАР