Нутрощі андроїда (розкриваємо робота) - (шляхти)

Нутрощі андроїда (розкриваємо робота) - (шляхти)

Як працює Android

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

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

Крок перший. U-BOOT і таблиця розділів

Отримавши управління, u-boot перевіряє таблицю розділів і передає управління ядру, прошитому в розділ з ім'ям boot, після чого ядро ​​витягує в пам'ять RAM-образ з того ж розділу та почне завантажувати або Android, або консолі відновлення. NAND-пам'ять в Android-пристроях поділена на шість умовно обов'язкових розділів:

  • boot - містить ядро ​​і RAM-диск, зазвичай має розмір в районі 16 Мб;
  • recovery - консоль відновлення, складається з ядра, набору консольних додатків і файлу налаштувань, розмір 16 Мб;
  • system - містить Android, в сучасних девайсах має розмір не менше 1 Гб;
  • cache - призначений для зберігання кеш даних, також використовується для збереження прошивки в ході OTA-оновлення і тому має розмір, подібний до розмірів розділу system;
  • userdata - містить налаштування, додатки і дані користувача, йому відводиться все залишився NAND-пам'яті;
  • misc - містить прапор, який визначає, в якому режимі повинна завантажуватися система: Android або recovery.

У термінології Linux RAM-диск - це свого роду віртуальний жорсткий диск, який існує тільки в оперативній пам'яті. На ранньому етапі завантаження ядро ​​витягує вміст диска з образу і підключає його як кореневу файлову систему (rootfs).

Особливо цікавий розділ misc. Існує припущення, що спочатку він був створений для зберігання різних налаштувань незалежно від основної системи, але в даний момент використовується тільки для однієї мети: вказати завантажувачу, з якого розділу потрібно вантажити систему - boot або recovery. Цю можливість, зокрема, використовує додаток ROM Manager для автоматичної перезавантаження системи в recovery з автоматичною же установкою прошивки. На її ж основі побудований механізм подвійної завантаження Ubuntu Touch, яка прошиває завантажувач Ubuntu

в recovery і дозволяє управляти тим, яку систему вантажити в наступний раз. Стер розділ misc - завантажується Android, заповнив даними - завантажується recovery. тобто Ubuntu Touch.

Частина коду завантажувача, яка визначає таблицю розділів:

Крок другий. розділ boot

Якщо в розділі misc не варто прапор завантаження в recovery, u-boot передає управління коду, розташованому в розділі boot. Це не що інше, як ядро ​​Linux; воно знаходиться на початку розділу, а відразу за ним слід упакований за допомогою архіваторів cpio і gzip образ RAM-диска, що містить необхідні для роботи Android каталоги, систему ініціалізації init і інші інструменти. Ніякої файлової системи на розділі boot немає, ядро ​​і RAM-диск просто слідують один за одним. Вміст RAM-диска таке:

  • data - каталог для монтування однойменного розділу;
  • dev - файли пристроїв;
  • proc - сюди монтується procfs;
  • sbin - набір підсобних утиліт і демонів (adbd, наприклад);
  • res - набір зображень для charger (див. нижче);
  • sys - сюди монтується sysfs;
  • system - каталог для монтування системного розділу;
  • charger - додаток для відображення процесу зарядки;
  • build.prop - системні настройки;
  • init - система ініціалізації;
  • init.rc - налаштування системи ініціалізації;
  • ueventd.rc - налаштування демона uventd, що входить до складу init.

Це, якщо можна так висловитися, скелет системи: набір каталогів для підключення файлових систем з розділів NAND-пам'яті і система ініціалізації, яка займеться решти роботою по завантаженню системи. Центральний елемент тут - додаток init і його конфіг init.rc, про яких у всіх подробицях я розповім пізніше. А поки хочу звернути увагу на файли charger і ueventd.rc, а також каталоги sbin, proc і sys.

Файл charger - це невеликий додаток, єдине завдання якого в тому, щоб вивести на екран значок батареї. Він не має ніякого відношення до Android і використовується тоді, коли пристрій підключається до зарядник в вимкненому стані. В цьому випадку завантаження Android не відбувається, а система просто завантажує ядро, підключає RAM-диск і запускає charger. Останній виводить на екран іконку батареї, зображення якої у всіх можливих станах зберігається в звичайних PNG-файлах всередині каталогу res.

Файл ueventd.rc є конфиг, що визначає, які файли пристроїв в каталозі sys повинні бути створені на етапі завантаження системи. У заснованих на ядрі Linux системах доступ до заліза здійснюється через спеціальні файли всередині каталогу dev, а за їх створення в Android відповідає демон ueventd, що є частиною init. У нормальній ситуації він працює в автоматичному режимі, приймаючи команди на створення файлів від ядра, але деякі файли необхідно створювати самостійно. Вони перераховані в ueventd.rc.

Каталог sbin в стоковому Android зазвичай не містить нічого, крім adbd, тобто демона ADB, який відповідає за налагодження системи з ПК. Він запускається на ранньому етапі завантаження ОС і дозволяє виявити можливі проблеми на етапі ініціалізації ОС. У кастомних прошивках в цьому каталозі можна знайти купу інших файлів, наприклад mke2fs, яка може знадобитися, якщо розділи необхідно переформатувати в ext3 / 4. Також модератори часто поміщають туди BusyBox, за допомогою якого можна викликати сотні Linux-команд.

У процесі завантаження Android відображає три різних завантажувальних екрану: перший з'являється відразу після натискання кнопки живлення і прошитий в ядро ​​Linux, другий відображається на ранніх етапах ініціалізації і записаний в файл /initlogo.rle (сьогодні майже не використовується), останній запускається за допомогою програми bootanimation і міститься у файлі /system/media/bootanimation.zip.

Каталог proc для Linux стандартний, на наступних етапах завантаження init підключить до нього procfs, віртуальну файлову систему, яка надає доступ до інформації про всі процеси системи. До каталогу sys система підключить sysfs, що відкриває доступ до інформації про залізо і його налаштувань. За допомогою sysfs можна, наприклад, здати пристрій у сон або змінити використовуваний алгоритм енергозбереження.

Файл build.prop призначений для зберігання низькорівневих налаштувань Android. Пізніше система обнулить ці настройки і перезапише їх значеннями з недоступного поки файлу system / build.prop.

Крок другий, альтернативний. розділ recovery

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

За допомогою скриптів, наприклад, можна зробити так, щоб після завантаження recovery автоматично знайшов на карті пам'яті потрібні прошивки, встановив їх і перезавантажився в Android. Ця можливість використовується інструментами ROM Manager, autoflasher, а також механізмом автоматичного поновлення CyanogenMod і інших прошивок.

Кастомниє рекавери також підтримують скрипти бекапа, розташовані в каталозі /system/addon.d/. Перед прошивкою recovery перевіряє наявність скриптів і виконує їх перед тим, як зробити прошивку. Завдяки таким скриптів gapps не зникають після установки нової версії прошивки.

Крок третій. ініціалізація

Кожен блок визначає стадію завантаження або, висловлюючись мовою розробників Android, дія. Блоки відокремлені один від одного директивою on, за якою слідує ім'я дії, наприклад on early-init або on post-fs. Блок команд буде виконаний тільки в тому випадку, якщо спрацює однойменний тригер. У міру завантаження init буде по черзі активувати тригери early-init, init, early-fs, fs, post-fs, early-boot і boot, запускаючи таким чином відповідні блоки команд.

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

Найбільш примітний з додаткових конфігов носить ім'я initrc.імя_ устройства.rc де ім'я змінної визначається автоматично на основі вмісту файлу ro.hardware. Це переносних залежний конфігураційний файл, який містить блоки команд, специфічні для конкретного пристрою. Крім команд, що відповідають за тюнінг ядра, він також містить приблизно таку команду:

Вона означає, що тепер init повинен підключити всі файлові системи, перераховані у файлі ./fstab.імя_устройства, який має наступну структуру:

Зазвичай в ньому містяться інструкції по підключенню файлових систем з внутрішніх NAND-розділів до каталогів / system (ОС), / data (настройки додатків) і / cache (кешовані дані). Однак, злегка змінивши цей файл, ми можемо змусити init завантажити систему з карти пам'яті. Для цього достатньо розбити карту пам'яті на три-чотири розділи: 1 Гб / ext4, 2 Гб / ext4, 1 Гб / ext4 і простір, що залишився fat32. Далі необхідно визначити імена розділів карти пам'яті в каталозі / dev (для різних пристроїв вони відрізняються) і замінити ними оригінальні імена пристроїв у файлі fstab.

Сучасний Android включає в себе десятки служб, але дві з них мають особливий статус і визначають весь життєвий цикл системи.

Крок четвертий. Zygote і App_process

На певному етапі завантаження init зустріне в кінці конфіга приблизно такий блок:

Це опис служби Zygote, ключового компонента будь Android-системи, який відповідальний за ініціалізацію, старт системних служб, запуск і зупинку для користувача додатків і багато інших завдань. Zygote запускається за допомогою невеликого додатки / system / bin / app_process, що дуже добре видно на наведеному вище шматку конфі-га. Завдання app_proccess - запустити віртуальну машину Dalvik, код якої розташовується в розділяється бібліотеці /system/lib/libandroid_runtime.so, а потім поверх неї запустити Zygote.

Після цього Zygote відкриває сокет / dev / socket / zygote і йде в сон, чекаючи дані. В цей час запущений раніше Activity Manager посилає широкомовний Интент Intent.CATEGORY_HOME, щоб знайти додаток, що відповідає за формування робочого столу, і віддає його ім'я Zygote через сокет. Останній, в свою чергу, Форкал і запускає додаток поверх віртуальної машини. Вуаля, у нас на екрані з'являється робочий стіл, знайдений Activity Manager і запущений Zygote, і статусний рядок, запущена system_server в рамках служби Status Bar. Після тапа по іконці робочий стіл пошле Интент з ім'ям цього додатка, його прийме Activity Manager і передасть команду на старт програми демона Zygote.

Все це може виглядати дещо незрозуміло, але найголовніше - запам'ятати три прості речі:

Багато в чому Android сильно відрізняється від інших ОС, і з наскоку в ньому не розібратися. Однак, якщо зрозуміти, як все працює, відкриваються просто безмежні можливості. На відміну від iOS і Windows Phone, операційка від гугла має дуже гнучку архітектуру, яка дозволяє серйозно змінювати її поведінку без необхідності писати код. У більшості випадків достатньо підправити потрібні конфіги і скрипти.