Чи створює купа пам'яті (malloc / new) недетерміновану програму?


77

Кілька місяців тому я розпочав розробку програмного забезпечення для систем реального часу в C для космічних додатків, а також для мікроконтролерів з C ++. У таких системах існує принципове правило, що ніколи не слід створювати об'єкти купи (отже, не malloc / new), оскільки це робить програму недетермінованою . Я не зміг перевірити правильність цього твердження, коли люди мені це говорять. Отже, чи це правильне твердження?

Плутанина для мене полягає в тому, що, наскільки мені відомо, детермінізм означає, що запуск програми двічі призведе до точно того самого шляху виконання. На мою думку, це проблема з багатопотоковими системами, оскільки запуск однієї і тієї ж програми кілька разів може мати різні потоки, що запускаються в різному порядку кожного разу.


12
Динамічне розподіл і де-розподіл пам'яті страждає від проблеми фрагментації пам'яті, що не очікується в додатках реального часу.
Gaurav Pathak

22
В основному це відбувається так: програмістів ПК вчать використовувати malloc / new. Вони приходять до вбудованих систем, де купа не має жодного сенсу, оскільки вони побудовані з абсолютно іншою архітектурою та мисленням. Програміст для ПК засмучується, це не те, що вони думали про мене в школі! І я використовую купу на ПК протягом століть! Програміст ПК ігнорує всіх і продовжує розподіляти купу досі. Програма виходить як лайна, повна помилок та поганої роботи. Програміста ПК звільняють. Вбудований системний програміст бере на себе хаос. Програма переписується з нуля.
Лундін,

11
@ PeterA.Schneider Як фізику, який здобув ступінь кандидата наук з фізики частинок та атомів, мені вже дуже важко провести межу між детермінованим та недетермінованим у комп’ютерах; це тому, що ми можемо жити в недетермінованому всесвіті. Я розумію, що моє запитання може легко викликати круговий аргумент про те, чи є щось коли-небудь детермінованим, перш ніж називати його детермінованим за певних припущень. Але використання мови детермінізму тут обмежується тим, як його використовують вчені-комп’ютери, що практично не пов’язано з реальною випадковістю в природі.
Квантовий фізик,

8
@ PeterA.Schneider Тривіально робити недетерміновані речі майже у всіх сучасних комп'ютерах. Один простий спосіб - зауважте найменш значущий біт лічильника команд процесора в процедурі, яка чекає завершення читання диска. На це впливає зсувна турбулентність швидкості обертання диска. Ви можете зробити щось подібне з мережевим пакетом, на який впливають мікроскопічні коливання температури зони в кристалах кварцу, які впливають на перекіс між тактовою частотою мережевого інтерфейсу та тактовою частотою процесора. Ви можете виділити тепловий шум із аудіовходів. Є багато інших способів.
Девід Шварц,

5
@ PeterA.Schneider: Сьогодні в більшості процесорів x86 (Intel, починаючи з IvyBridge), є rdrandвбудована інструкція, яку ви можете виконати із звичайного процесу простору користувача. Це дає справжню апаратну випадковість від генератора теплових шумів, обумовленого AES (якщо NSA не послабила конструкцію ...). Звичайно, rdtscце також недетерміновано, як зазначає Девід, особливо враховуючи лише один процес, але хороший момент, що синхронізація між різними доменами годинника дає деяку реальну недетермінованість.
Пітер Кордес,

Відповіді:


71

У контексті систем реального часу детермінізм має більше, ніж повторюваний "шлях до виконання". Іншою необхідною властивістю є обмеження часу ключових подій. У жорстких системах реального часу подія, яка відбувається поза дозволеним інтервалом часу (або до початку цього інтервалу, або після його закінчення), представляє збій системи.

У цьому контексті використання динамічного розподілу пам’яті може спричинити детермінізм, особливо якщо програма має різну схему розподілу, вивільнення та перерозподілу. Терміни розподілу, вивільнення та перерозподілу можуть змінюватися з часом - і, отже, роблять терміни для системи в цілому непередбачуваними.


28
Зауважте, непередбачувані терміни не роблять систему нереальною. якщо розподіл займає rand()мілісекунди, а обмежений час більше RAND_MAX, система працює в режимі реального часу.
MSalters

4
@MSalters - це правда. Система реального часу вимагає, щоб терміни були передбачувано обмеженими, а не щоб кожен час був передбачуваним заздалегідь.
Пітер

1
Будь ласка: чому ви тоді називаєте це "(не) детермінізмом"? Це просто операція без чітко визначеного часу виконання найгіршого випадку. Він стає "недетермінованим" лише у тому випадку, якщо на розподіл впливають, наприклад, інші процеси / частини програми, які взаємодіють із "зовні" (наприклад, очікування, коли хтось натисне кнопку).
Daniel Jour

Якщо це справді закрита система, ви можете назвати її детермінованою, але вона все одно буде хаотичною , тобто неможливо передбачити. З огляду на це, я сумніваюся, що це справді закрита система, оскільки це було б досить марно.
John Wu,

@DanielJour - визначення системи реального часу передбачає детермінований хронометраж подій (наприклад, це можна визначити, проаналізувавши властивості системи, що подія B БУДЕ відбутися між x та y мілісекундами після події A). Якщо процес запуску або реагування на події не має визначеного найгіршого часу виконання, то система не може задовольнити свої вимоги в реальному часі. У м'якій системі реального часу це може бути прийнятним до певної міри. У жорсткій системі реального часу це не так.
Пітер

40

Коментар, як зазначено, є неправильним.

Використання диспетчера купи з недетермінованою поведінкою створює програму з недетермінованою поведінкою. Але це очевидно.

Трохи менш очевидним є існування менеджерів купи з детермінованою поведінкою. Мабуть, найбільш відомим прикладом є розподілювач пулів. Він має масив із N * M байт та available[]маску з N бітів. Для розподілу він перевіряє перший доступний запис (бітовий тест, O (N), детермінована верхня межа). Щоб звільнити місце, він встановлює доступний біт (O (1)). malloc(X)округлить X до наступного найбільшого значення M, щоб вибрати правильний пул.

Це може бути не дуже ефективним, особливо якщо ваш вибір N і M занадто високий. І якщо ви виберете занадто низький рівень, ваша програма може вийти з ладу. Але обмеження для N і M можуть бути нижчими, ніж для еквівалентної програми без динамічного розподілу пам'яті.


циклічний буфер є різновидом цього розподільника, його навіть можна розподілити за допомогою одного malloc на початку виконання, тоді є й інші мутації, засновані на контексті - якщо ви заздалегідь знаєте, що вам потрібно N різних розмірів буфера, тоді ви можете створити розподільник, який є більш ефективним.
RSf

1
Альтернативний підхід до використання бітової маски для колектора пулу полягає у використанні зв’язаного списку, який має розподіл та звільнення O (1). Поки код ніколи не намагається виділити більше буферів певного розміру, ніж існує в пулі, однак, час буде повністю детермінованим, крім проблем кешування.
supercat

1
Більш загальний випадок - це розподіл заздалегідь, перш ніж потрібно детермінована поведінка. Подібно пам’яті, яка потрібна цьому розподільнику пулів :)
Ганс Пасант,

Я маю на меті порушити, здавалося б, поширену помилку серед вбудованих інженерів, що розподіл пам'яті довільного розміру (як, malloc()замість блоків фіксованого розміру) за своєю суттю не є детермінованим у часі або може призвести до необмеженої фрагментації. Як показано, наприклад, у "Передбачуваному розподілом часу в жорстких системах реального часу" [Herter 2014], передбачувані та ефективні алгоритми існують, але про них рідко говорять. Тут я впровадив один із ~ 500 рядків коду для вбудованої системи реального часу, з якою я брав участь: github.com/pavel-kirienko/o1heap
Павло Кірієнко

21

Ніщо в стандарті С11 або в n1570 не говорить, що mallocє детермінованим (або ні); а також жодної іншої документації, такої як malloc (3) на Linux. До речі, багато mallocреалізацій - це вільне програмне забезпечення .

Але mallocможе (і робить) невдачу, і його продуктивність невідома (типовий дзвінок mallocна моєму робочому столі займає практично менше мікросекунди, але я міг уявити дивні ситуації, коли це може зайняти набагато більше, можливо, багато мілісекунд на дуже завантаженому комп'ютер; читайте про обмолот ). І на моєму робочому столі Linux є ASLR (рандомізація макета адресного простору), тому запуск однієї програми двічі дає різні mallocадреси (у віртуальному адресному просторі процесу). До речі, тут є детермінована (за конкретних припущень, яку вам потрібно докласти), але практично марна mallocреалізація.

Детермінізм означає, що запуск програми двічі призведе до того самого шляху виконання

Це практично неправильно у більшості вбудованих систем, оскільки фізичне середовище змінюється; наприклад, програмне забезпечення, що керує ракетним двигуном, не може розраховувати, що тяга, або опір, або швидкість вітру тощо ... абсолютно однакові від одного запуску до наступного.

(тому я здивований, що ви вірите або хочете, щоб системи реального часу були детермінованими; вони ніколи не бувають! Можливо, вас турбує WCET , який все важче передбачити через кеші )

До речі, деякі "в реальному часі" або "вбудовані" системи реалізують власну malloc(або її варіант). Програми на C ++ можуть мати свої розподільники , які можна використовувати у стандартних контейнерах . Дивіться також це і те , тощо, тощо .....

А шари високого рівня вбудованого програмного забезпечення (згадайте автономний автомобіль та програмне забезпечення для його планування ), безсумнівно, використовують розподіл купи та, можливо, навіть техніку збору сміття (деякі з них є "в режимі реального часу"), але, як правило, не вважаються важливими для безпеки.


6
Я думаю, що OP мав на увазі сказати, що запуск програми двічі з точно однаковими входами повинен призводити до того самого шляху виконання або однакової спостережуваної поведінки (або будь-якого іншого визначення, яке є кращим).
Toby Speight

Але "спостережувана поведінка" суб'єктивна (що стосується налагодження printfз %pрезультатом malloc) і може призвести до бурхливих дискусій
Базиль Старинкевич

Планувальник автономного транспортного засобу (або насправді будь-якого іншого програмного забезпечення, що відповідає безпеці автомобілів) не використовуватиме розподіл купи або вивезення сміття. Динамічне розподіл пам'яті заборонено правилами MISRA .
dasdingonesin

Я використовував планування в розумінні ШІ. Все програмне забезпечення AI для планування використовує розподіл купи (і більшість з них використовують збір сміття та кодуються мовами AI дуже високого рівня, можливо, Lisp, Prolog тощо ...). Звичайно, вони не є критично важливими для безпеки шарами таких систем
Базиль Старинкевич

12

tl; dr: справа не в тому, що динамічне розподіл пам'яті за своєю суттю не є детермінованим (як ви визначили це з точки зору ідентичних шляхів виконання); це те, що це, як правило, робить вашу програму непередбачуваною . Зокрема, ви не можете передбачити, чи може розподілювач не спрацювати перед довільною послідовністю входів.

Ви можете мати недетермінований розподільник. Це насправді поширене поза вашим реальним часом, коли операційні системи використовують такі речі, як рандомізація розташування адрес. Звичайно, це зробить вашу програму недетермінованою.

Але це не цікавий випадок, тому припустимо ідеально детермінований розподільник: однакові послідовності розподілів та вивільнення завжди матимуть однакові блоки в одних і тих самих місцях, і ці розподіли та вивільнення завжди матимуть обмежений час роботи.

Тепер ваша програма може бути детермінованою: однаковий набір входів призведе до абсолютно однакового шляху виконання.

Проблема полягає в тому, що якщо ви розподіляєте та звільняєте пам’ять у відповідь на входи, ви не можете передбачити, чи розподіл коли-небудь провалиться (і помилка не є варіантом).

По-перше, ваша програма може втратити пам’ять. Отже, якщо йому потрібно працювати необмежено довго, врешті-решт розподіл не вдасться.

Але навіть якщо ви можете довести, що немає витоків, вам слід знати, що ніколи не існує послідовності введення, яка могла б вимагати більше пам'яті, ніж доступно.

Але навіть якщо ви можете довести, що програмі ніколи не буде потрібно більше пам'яті, ніж доступно, розподільник може, залежно від послідовності розподілів і звільнень, фрагментувати пам'ять і, отже, врешті-решт не зможе знайти суміжний блок для задоволення розподілу, навіть незважаючи на те, що в цілому достатньо вільної пам'яті.

Дуже важко довести, що немає послідовності входів, яка призвела б до патологічної фрагментації.

Ви можете розробити розподільники, щоб гарантувати, що не буде фрагментації (наприклад, шляхом виділення блоків лише одного розміру), але це створює суттєві обмеження для абонента і, можливо, збільшує обсяг пам'яті, необхідної через марнотратство. І той, хто телефонує, все одно повинен довести, що немає витоків, і що потрібна насичена верхня межа загальної пам'яті, незалежно від послідовності входів. Це навантаження настільки велике, що насправді спростити розробку системи таким чином, щоб вона не використовувала динамічне розподіл пам'яті.


OP згадує космічні програми (висока надійність) та програми на базі мікроконтролера. Справжньою великою проблемою для них є фрагментація купи та несподівані помилки розподілу, які можуть трапитися. Ймовірність виникнення цього типу помилок велика для невеликих просторів пам'яті, доступних на мікроконтролері. Щоб запобігти цьому, вся пам'ять повинна бути статично розподілена і ретельно відстежуватися максимальне використання стека.
uɐɪ

10

Справа із системами реального часу полягає в тому, що програма повинна суворо відповідати певним обмеженням обчислень та пам’яті, незалежно від пройденого шляху виконання (який все одно може значно відрізнятися залежно від вводу). То що в цьому контексті означає використання загального динамічного розподілу пам’яті (наприклад, malloc / new)? Це означає, що розробник в якийсь момент не може визначити точне споживання пам'яті, і було б неможливо сказати, чи отримана програма зможе відповідати вимогам як щодо пам'яті, так і щодо обчислювальної потужності.


4
Ну, стискання одного і того ж стисненого файлу завжди призводить до одного і того ж результату, але пропонуючи зберігати нестиснений результат, тип помилки не відповідає точці стиснення файлу;). Більш серйозно, планувальники маршрутів - це добре відомий приклад цілком детермінованих програм з невеликим вхідним простором (початкова та кінцева точки), матриця результатів яких занадто велика, занадто велика.
MSalters

1
Здається, це не відповідає на запитання, яке було задано; це відповідає на інше питання. Нагадуємо, питання полягало в тому, "чи використання кучі робить програму недетермінованою?" Це не відповідає на це запитання. Це може відповісти на запитання "чи проблематичне використання купи у системах реального часу?", Але це вже інше питання.
DW

7

Так це правильно. Для тих програм, про які ви згадали, все, що може статися, має бути детально зазначено. Програма повинна обробляти найгірший сценарій відповідно до специфікації і виділити саме стільки пам'яті, ні більше, ні менше. Ситуації, коли "ми не знаємо, скільки вхідних даних ми отримуємо", не існує. Найгірший сценарій вказаний із фіксованими числами.

Ваша програма повинна бути детермінованою в тому сенсі, що вона може впоратися з усім, найгіршим сценарієм.

Сама мета купи полягає в тому, щоб дозволити декільком не пов'язаним між собою програмам спільно використовувати оперативну пам'ять, наприклад у ПК, де кількість запущених програм / процесів / потоків не є детермінованою. Цей сценарій не існує в системі реального часу.

Крім того, купа не є детермінованою за своєю природою, оскільки сегменти з часом додаються або видаляються.

Більше інформації тут: https://electronics.stackexchange.com/a/171581/6102


2
"детермінований у тому сенсі, що він може впоратися з усім, до найгіршого сценарію" - Це не те, що означає детерміноване. Не все, що є поганою технікою, не є детермінованим.
DW

@DW Якщо ви вказали, що ваша програма повинна мати змогу обробляти 100 речей, тоді ви розробляєте це і очікуєте детермінованої поведінки для всіх випадків до 100. Якщо ви вийдете за межі вказаних обмежень, всі ставки вимкнені, а результат невизначений . Це насправді означає детермінованість. Альтернативою може бути не встановлення верхньої межі та розподілу купи. Тоді пункт, коли програма піде на заваду, неможливо визначити тоді. Це залежить від розміру купи, фрагментації купи та багатьох речей. Подібним чином, якщо ви дозволяєте "будь-яку кількість вводу" замість детермінованого максимуму.
Лундін,

5

Навіть якщо ваш розподілювач купи має повторювану поведінку (однакова послідовність розподілу та безкоштовні дзвінки дають однакову послідовність блоків, отже (сподіваємось) однаковий внутрішній стан купи), стан купи може різко змінюватися, якщо змінити послідовність викликів , що потенційно може призвести до фрагментації, яка призведе до непередбачуваних помилок розподілу пам'яті.

Причиною розподілу купи є неприйняття прямо заборонених у вбудованих системах, особливо. критично важливих систем, таких як наведення літаків або космічних кораблів або системи життєзабезпечення, немає можливості перевірити всі можливі зміни послідовності викликів malloc / free, які можуть відбутися у відповідь на внутрішньо асинхронні події.

Рішення полягає в тому, щоб кожен обробник виділив одну пам'ять для своєї мети, і це вже не має значення (принаймні, що стосується використання пам'яті), в якому порядку викликаються ці обробники.


3

Проблема використання купи у жорсткому програмному забезпеченні реального часу полягає у тому, що розподіл купи може не вдатися. Що ви робите, коли у вас не вистачає купи?

Ви говорите про космічні програми. У вас є досить жорсткі вимоги щодо безвідмовної роботи. Ви не повинні мати можливості витоку пам’яті, тому недостатньо для запуску принаймні коду безпечного режиму. Ви не повинні впасти. Не можна кидати винятки, які не мають блоку catch. У вас, ймовірно, немає ОС із захищеною пам’яттю, тому одна збійна програма може теоретично вивести все.

Можливо, ви взагалі не хочете використовувати купу. Переваги не перевищують загальновиробничі витрати.

Недетермінованість, як правило, означає щось інше, але в цьому випадку найкраще читати - вони хочуть, щоб уся поведінка програми була повністю передбачуваною.


2

Представити RTOS від Integrity від GHS:

https://www.ghs.com/products/rtos/integrity.html

та LynxOS:

http://www.lynx.com/products/real-time-operating-systems/lynxos-178-rtos-for-do-178b-software-certification/

LynxOS та Integrity RTOS належать до програмного забезпечення, що використовується в космічних програмах, ракетах, літаках тощо, оскільки багато інших не затверджені та не сертифіковані органами влади (наприклад, FAA).

https://www.ghs.com/news/230210r.html

Щоб задовольнити жорсткі критерії космічних додатків, Integrity RTOS фактично забезпечує формальну перевірку, тобто математично доведену логіку, що їх програмне забезпечення поводиться відповідно до специфікації.

Серед цих критеріїв наведемо тут:

https://en.wikipedia.org/wiki/Integrity_(operating_system)

і тут:

Цілісність Green Hills Динамічне виділення пам'яті

це:

введіть тут опис зображення

Я не фахівець у формальних методах, але, можливо, однією з вимог до цієї перевірки є усунення невизначеності в термінах, необхідних для розподілу пам'яті. У RTOS вся подія точно планується в мілісекундах один від одного. А при динамічному розподілі пам’яті завжди виникають проблеми із необхідними термінами.

Математично вам дійсно потрібно довести, що все працювало, виходячи з найбільш фундаментальних припущень про час та обсяг пам'яті.

І якщо ви думаєте про альтернативи купи пам’яті: статична пам’ять . Адреса фіксована, розмір призначений фіксований. Позиція в пам'яті фіксована. Тому дуже легко міркувати про достатність пам'яті, надійність, доступність тощо.


1
Технічно, як закрита система, вона є детермінованою, але також хаотичною, тобто. неможливо передбачити.
John Wu

2

Коротка відповідь

Існує деякий вплив на значення даних або їх розподіл статистичної невизначеності, наприклад, сцинтиляторного пристрою першого або другого рівня, який може випливати з невідтворюваної кількості часу, на який вам, можливо, доведеться чекати malloc/free .

Найгірший аспект полягає в тому, що вони не пов'язані з фізичним явищем ні з апаратним забезпеченням, а якось із станом пам'яті (та її історії).

У такому випадку ваша мета - відновити вихідну послідовність подій за даними, на які впливають ці помилки. На відновлену / здогадану послідовність також впливатимуть помилки. Не завжди ця ітерація буде збіжною на стабільному рішенні; не сказано, що це буде правильно; ваші дані вже не незалежні ... Ви ризикуєте логічним замиканням ...

Довша відповідь

Ви сказали: "Я не зміг перевірити правильність цього твердження, коли люди мені це говорять" .
Я спробую навести вам суто гіпотетичну ситуацію / кейс.

Давайте уявимо, що ви маєте справу з ПЗЗ або з деякими сцинтиляторними тригерами 1-го та 2-го рівня в системі, яка повинна економити ресурси (ви перебуваєте в космосі).
Швидкість придбання буде встановлена ​​таким чином, щоб фон був на x%рівні MAXBINCOUNT.

  • Там сплеск, у вас є стрибок підрахунку та переповнення лічильника сміття.
    Я хочу всього цього: ви перемикаєтесь на максимальну швидкість придбання і закінчуєте свій буфер.
    Ви перейдете на звільнення / виділите більше пам'яті, тим часом закінчите додатковий буфер.
    Що ти робитимеш?

    1. Ви продовжуватимете протидіяти ризикуючи переповненням (другий рівень намагатиметься правильно розрахувати хронометраж пакетів даних), але в цьому випадку ви підете занижувати кількість за цей період?
    2. ви зупините лічильник, вводячи дірку в часовому ряді ?

    Зверніть увагу, що:

    • Чекаючи розподілу, ви втратите перехідне (або принаймні його початок).
    • Що б ви не робили, це залежить від стану вашої пам'яті, і вона не відтворюється.
  • Тепер замість цього сигнал змінюється maxbincountна максимальній швидкості збору, дозволеній вашим обладнанням, і подія довша, ніж зазвичай.
    Ви закінчуєте простір і просите більше ... тим часом, ви стикаєтесь з тією ж проблемою вище.
    Переповнення та систематична вершина враховує недооцінку чи діри в часових рядах?

Давайте перейдемо на другий рівень (він може бути і на тригері 1-го рівня).

Від апаратного забезпечення ви отримуєте більше даних, ніж можете зберегти або передати.
Ви повинні згрупувати дані в часі або просторі (2x2, 4x4, ... 16x16 ... 256x256 ... масштабування пікселів ...).

Невизначеність від попередньої проблеми може вплинути на розподіл помилок .
Існують налаштування CCD, для яких у вас є пікселі межі з відліком, близьким до maxbincount(це залежить від "де" ви хочете бачити краще).
Тепер ви можете прийняти душ на вашому ПЗЗ або одне велике місце з однаковою загальною кількістю підрахунків, але з іншою статистичною невизначеністю (частина, яка вводиться часом очікування) ...

Так, наприклад, там, де ви очікуєте лоренцевого профілю, ви можете отримати його згортку за допомогою гауссового (Фойгта), або якщо другий справді домінує у брудного гауссова ...


-3

Компроміс є завжди. Саме робоче середовище програми та завдання, які вона виконує, повинні бути основою для прийняття рішення про використання HEAP чи ні.

Об’єкт купи є ефективним, коли потрібно розподілити дані між кількома викликами функцій. Вам просто потрібно передати вказівник, оскільки купа є загальнодоступною. Є і недоліки. Деякі функції можуть звільнити цю пам’ять, але деякі посилання можуть існувати і в інших місцях.

Якщо пам'ять купи не звільняється після завершення роботи, і програма продовжує виділяти більше пам'яті, в якийсь момент HEAP закінчиться з пам'яттю і вплине на детермінований характер програми.


1
"Вам просто потрібно передати вказівник, оскільки купа є загальнодоступною." І це відрізняється від .dataі чим .bss...?
Лундін,

5
Можна створити глобальні змінні стека ... де проблема в цьому? Їх також можна передавати між функціями.
Квантовий фізик

2
Здається, це не відповідає на запитання, яке було задано; це відповідає на інше питання. Нагадуємо, питання полягало в тому, "чи використання кучі робить програму недетермінованою?" Це не відповідає на це запитання. Це може відповісти на питання "чи є використання купи хорошою ідеєю в системах реального часу?", Але це вже інше питання. Остання фраза починає рухатись у цьому напрямку, але ви не кажете, як це впливає на детермінований характер програми чи чому, і не відповідаєте на питання.
DW
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.