Що спільного між ядром Linux та iOS
Природно, у великих системах завжди знайдуться загальні концепції. Наприклад, важко уникнути використання базових алгоритмів та структур даних. Таких як списки, масиви, дерева. Але я хочу розповісти не про це.
iOS є об'єктно орієнтованою штукою. Десь нижче Objective-C (а скоро ми зможемо говорити вже і про Swift) залягають величезні пласти необ'єктного коду, і під ними – Unix (а точніше BSD) система. І на тому рівні у Linux та iOS багато спільного. Але я й не про це.
Порівняємо основні структури ядра Linux з об'єктно-орієнтованою частиною iOS.
1. Основні структури
В обох системах є кілька фундаментальних структур. Наприклад, в iOS це будуть:
Рядки (NSSrting); Массиви (NSArray); Колекції (NSSet); Словники (NSDictionary); Уявлення числових примітивів (NSNumber); Скаляри (NSRange). Я залишаю за дужками різні варіації всіх цих типів (NSSet/NSMutableSet/NSCountedSet та інше).
Усі ці типи даних реалізовані як класи. Легко помітити, що тут немає кількох фундаментальних структур: зв'язкових списків (linked lists) та бінарних дерев (binary tree). Немає їх з тієї простої причини, що вони інкапсульовані в інші типи даних. Так, NSArray можна використовувати замість зв'язкового списку, а NSDіctionary замість бінарного дерева, не особливо піклуючись про внутрішню їхню реалізацію.
Добре. А які основні типи ми можемо знайти в ядрі Linux? Тут ситуація виглядає з точністю навпаки. Важко виділити будь-які стандартні для ядра типи даних. Найбільші претенденти на це звання:
Подвійний зв'язковий перелік, визначений у файлі include/linux/list.h; Червоно-чорні дерева, визначені вфайл include/linux/rbtree.h; Радиксні дерева (radix tree), визначені у файлі include/linux/radix-tree.h; Бітові масиви (bit arrays), визначені у файлі include/linux/bitmap.h; Ну а також семафори та спинлоки, які не присутні в iOS у явному вигляді - тобто, їх не потрібно алоцювати, вони приховані в методах.
Немає жодної вимоги використати саме ці реалізації. Якщо ви чимось незадоволені, то можете написати свою реалізацію чого завгодно. Її, щоправда, можуть прийняти модератори, якщо спробуєте завантажити свій пэтч. У всякому разі, його не приймуть модератори, якщо не буде дуже вагомої причини реалізовувати це замість використання вже написаних структур.
Отже, на перший погляд ми маємо два ортогональні підходи до побудови системи. iOS пропонує досить чистий об'єктно-орієнтований підхід, і Apple намагається як тільки може приховати нутрощі об'єктів від кінцевого програміста. Ядро Linux, навпаки, визначає дуже базові примітиви, залишаючи програміста розбиратися з ними. Грубо кажучи, iOS це блокове будівництво, а ядро Linux надає у ваше розпорядження іноді цеглу, а іноді просто глину та піч для випалу. Однак і цілі програмування в двох системах абсолютно різні: ніхто не очікує від розробника ядра створення інтерфейсу користувача, так само як ніхто і не чекає від програміста в iOS написання підтримки чіпа і шини даних.
2. То що спільного між двома цими системами?
Референси. Підрахунок посилань на об'єкти.
Донедавна iOS вимагала від програміста вручну зменшувати лічильник об'єктів. Це робилося за допомогою виклику стандартного методу retain для його збільшення, release для зменшення. В останніх версіях Objective-C в цьому немає потреби,система робить це автоматично.
Лічильник об'єктів - абсолютно об'єктно-орієнтована річ: у тебе є кілька процесів, які спільно використовують об'єкт. Для простоти уявімо, що це об'єкт тільки для читання, тобто жоден із процесів не може його змінити. Але питання: коли цей об'єкт повинен бути звільнений? І ким?
У системах з автоматичним керуванням пам'яттю кожен об'єкт має внутрішній лічильник, який збільшується кожного разу, коли хтось отримує посилання на цей об'єкт. Коли хтось перестає використовувати це посилання (наприклад, присвоюючи Nil), внутрішній лічильник об'єкта зменшується.
Вибачаюсь за занудство, але як це відбувається. Зі свого коду я створюю новий рядок в iOS:
Після виконання цього коду утворився об'єкт типу NSString. Змінна s тримає посилання об'єкт, і це об'єкт має внутрішній лічильник, рівний 1.
Після виконання цього рядка коду змінна s2 також посилається на той самий об'єкт. І внутрішній лічильник рефересів цього об'єкта дорівнюватиме 2.
Тепер змінна s більше не посилається на об'єкт і внутрішній лічильник референсів в об'єкті дорівнює 1.
А тепер повернемося до ядра Linux. І відразу відкриємо файл incluce/linux/kref.h У цьому файлі ми можемо бачити генерну реалізацію саме цього механізму - лічильник референсів. Файл не надто великий, але необхідний.
Ядро Linux з погляду паралельних процесів – це дуже цікава штука. Якщо ти забув про синхронізацію – твій код дуже швидко впаде. Разом з усім іншим ядром, до речі. Найчастіше для цього потрібні частки секунди. Іноді секунди. Якщо ти забув про те, що ядро є реінтерантним, і твій код може бути перерваний у будь-який момент часу — твій код впаде. Немає ніякого способу запобігти цьому — навіть заборонапереривань не допоможе вирішити проблеми синхронізації. Big Lock, що колись існував у ядрі, і дозволяв зупинити все, крім твого процесу, був видалений з ядра роки тому, оскільки ним користувалися. Ні, серйозно, це досить дивна ситуація: ти маєш чудовий спосіб вирішити всі свої проблеми з синхронізацією, але тобі кажуть: не треба це використовувати, це погана карма. Але в тебе ніколи немає часу, а Big Lock чудово запобігає падінню твого коду. Тож у якийсь момент Лінус вольовим зусиллям видалив цей механізм.
Так ось, якщо зробити пошук по ядру, наприклад, за функцією kref_init, то ви побачите, що він використовується більш ніж у двохстах місцях. Що для ядра чимало. Як же працює kref і навіщо він потрібен?
Відповідаючи на друге питання: він потрібен для того ж, для чого потрібний і лічильник референсів в об'єктах iOS – це механізм синхронізації управління об'єктами з погляду управління пам'яттю. Звичайно, в ядрі доводиться продавати всі способи видалення об'єкта своїми руками, тут немає збирача сміття, як в iOS (точніше кажучи, він є, але в іншому місці, робить зовсім інші речі, і він ніяк не пов'язаний з kref).
3. Висновок
Жодної моралі в цій статті немає.
Ні для кого не новина, що ядро Linux багато в чому адаптувало об'єктно-орієнтовану парадигму. Тим не менш, модератори ядра не дозволяють розробникам ускладнювати його (ядро) і підтримують інфраструктуру на рівні найпростіших примітивів. Філософія тут полягає в тому, щоб "keep it simple" - дозволь розробнику самому зварити свій суп із сирої грудинки, не пропонуй йому супний набір або курячий порошок.
iOS, у свою чергу, йде шляхом приховування деталей реалізації від розробників. Якогось моменту в iOS з'явивсяARC, Automatic Reference Counting, система, яка стежить за лічильником посилань об'єктів та знищує їх автоматично. У найближчому майбутньому, схоже, об'єкти будуть автоматично не тільки знищуватися, а й створюватися, на це вказують останні тенденції — в деяких випадках вже сьогодні можна опустити виклик alloc (наприклад, [NSSting stringWithFormat] працює так само, як [[NSString alloc] initWithFormat :]).
Доброго всім дня. Дякую за увагу.
Хардкорна конфа за С++. Ми запрошуємо лише профі.