Обсуждение на форуме: http://forum.ubuntu.ru/index.php?topic=278844.0

English version of this wiki page is available on GitHub.

На GitHub’е есть английская версия этой вики-статьи (не дословный перевод, а вольное изложение идей).

Uefi-Boot — это простенький проект по организации загрузки Ubuntu на компьютерах с поддержкой UEFI без использования загрузчиков. Здесь описано полноценно работающее решение, но само собой для его реализации на другом компьютере могут потребоваться некоторые изменения. Поэтому я советую пользоваться предложенными командами и файлами вдумчиво, понимая и сверяя все действия в соответствии с вашей конфигурацией и настройками.

Как было указано в статье, UEFI поддерживает мультизагрузку (можно настроить несколько вариантов загрузки, которые выбираются из меню UEFI). Так же было отмечено, что все последние ядра в репозитории Ubuntu собираются с опцией UEFISTUB, которая позволяет загружать ядро непосредственно как EFI-приложение. В разделе Загрузка ядра из UEFI были поверхностно разобраны варианты реализации такой загрузки.

Эта статья посвящена последнему варианту (вариант монтирования ESP раздела как /boot). Здесь мы детально рассмотрим как реализовать этот вариант с автоматическим обновлением пунктов загрузки UEFI при установке новых и удалении старых ядер из системы.

Решение в деталях

Давайте разберём в деталях реализацию. Начинать мы будем из конфигурации, в которой система оказывается после установки в режиме UEFI. При этом во время установки /boot не был размещён на отдельном разделе. Если у вас /boot был вынесен в отдельный раздел, то действия будут чуточку отличаться,а после окончания настройки вы сможете удалить ваш boot-раздел — он больше не будет нужен.

Желательно, чтобы ESP-раздел был хотя бы 200–300 Мб (лучше 500), ведь в нём будут храниться наши ядра и их образы начальной инициализации (т. е. он и будет нашим /boot).

Монтируем ESP как /boot

Тут всё тривиально просто:

Вы можете не выполнять эти команды вручную, а воспользоваться скриптом, ссылка на который есть в конце статьи.

$ sudo -s
# mv /boot/*-generic* /boot/efi/             ! переносим ядра, их конфигурации initrd и т.п. в ESP раздел (он по умолчанию смонтирован в /boot/efi)
# umount /boot/efi                           ! размонтируем ESP раздел
# rm -rf /boot/*                             ! вычищаем /boot - это будет пустой каталог куда будет смонтирован ESP раздел
# sed -i 's/\/boot\/efi/\/boot/' /etc/fstab  ! вносим правки в /etc/fstab (вместо точки монтирования ESP раздела /boot/efi прописываем /boot
# mount /boot                                ! монтируем ESP раздел в каталог /boot

Тут все действия нужно производить из под рута — будьте крайне внимательны в записи и исполнении команд.

После этих изменений у вас и после перезагрузки ESP раздел будет смонтирован в /boot и в нем будут находится ядра и образы рамдисков периода начальной инициализации.

Утилита для обновления меню загрузки UEFI

Я написал эту утилиту на BASH, который практически не знаю, поэтому, наверняка, задачу можно было решить и более изящно, но я приведу тот код, который у меня заработал.

Конфигурационный файл

Вписывать фиксированные опции загрузки ядра и UUID корневого раздела и прямо в код утилиты, так же, как и полагаться только на автоматическое определение корневого раздела через информацию в /etc/fstab, — не слишком верное решение, довольно часто возникают ситуации, требующие не-дефолтовых настроек. И каждый раз корректировать скрипт принципиально неправильно. Поэтому настройки можно прописать руками в конфигурационном файле утилиты. Все настройки утилиты хранятся в /etc/uefiboot.conf (файл доступен на GitHub’е)1).

Вы можете его создать из любого текстового редактора, запущенного под рутом, или воспользоваться настроечным скриптом (ссылка на который есть в конце статьи). В этом файле вы можете задать ключи загрузки ядра (переменная OPTIONS) и устройство корневого раздела (переменная ROOT). Если этих параметров не задавать, то устройство корневого раздела будет взято из /etc/fstab (берется устройство с точкой монтирования '/'), а ключи загрузки ядра по умолчанию будут взяты ro quiet.

Если корневой раздел системы расположен на подразделе BTRFS (установщик Ubuntu размещает корень на подразделе @ btrfs, даже не спрашивая у вас), то в переменную ROTFLAGS нужно указать параметр rootflags=subvol=@, чтобы ядро смогло найти корень. Если переменная не определена то имя подтома с корнем будет взято из /etc/fstab.

Дополнительно в конфигурационный файл добавлен параметр K_SUFFIX — это необходимо для работы с подписанными ядрами из пакета linux-signed-generic, в котором поставляются ядра с подписью ключом Canonical. Подпись необходима для организации загрузки в режиме Secure boot. Если пользоваться ядрами из пакета linux-signed-generic, то в список ключей db должен быть добавлен сертификат с ключом Canonical (сертификат с этим ключом можно найти на GitHub’е). А в опцию загрузки должно добавляться ядро с суффиксом .efi.signed. Если вы организовали подпись ядер своим ключом, и решили использовать иной суффикс, то в переменную K_SUFFIX нужно будет его прописать.

Использование пакета linux-signed-generic имеет странный нюанс, ядра из этого пакета, почему то, не вызывают триггеры ядра (которые используются в нашем решении для обновления пунктов загрузки UEFI). Поэтому не удаляйте пакет с обычными ядрами (linux-generic) - его ядра при установке/удалении нужные триггеры активизируют исправно.

Утилита uefiboot-update

Собственно скрипт утилиты располагается в /usr/bin/uefiboot-update, его код на GitHub’е:

Код достаточно примитивный: после определения необходимых параметров, сначала мы вычищаем все пункты меню с названиями вида «Ubuntu….», а затем добавляем в порядке возрастания версии ядра новые пункты меню. Важность порядка добавления в том, что самый последний добавленный пункт становится самым первым в приоритете загрузки. И важно, что бы первым в приоритете стояла загрузка самого свежего ядра.

Приоритет конечно можно поменять отдельно, но раз уж мы все пункты Ubuntu* зачистили, то и создавать их надо все, а раз так, то создание в нужном порядке избавляет нас от лишних действий по заданию нужного приоритета.

В скрипте предусмотрена инициализация переменных настройки ROOT, OPTINS и ROOTFLAGS, которая обеспечивает работу утилиты без дополнительных настроек в большинстве стандартных ситуаций. Но, например, для настройки загрузки системы в режиме SecureBoot нужно будет обязательно указать значение переменной K_SUFFIX в файле /etc/uefiboot.conf.

Инициализация синхронизации пунктов загрузки

Самый простой способ инициализировать синхронизацию — воспользоваться теми же средствами, которыми пользуется grub для обновление своей конфигурации.

Ядро — это довольно важный компонент системы и на его установку/удаление завязано немало процессов. Для того, чтобы упростить процесс взаимодействия с процессом изменения версий ядер были предусмотрены триггеры ядра — это полная аналогия скриптов DEB-пакета: пред/постинсталляция и пред/постудаление, но лежат они в соответствующих папках /etc/kernel,а не в deb-пакете ядра. Таким образом, любое приложение, которому надо прореагировать на появление нового или удаление старого ядра могут сделать символьные ссылки на свои утилиты из соответствующих каталогов или просто разместить там свои скрипты (собственно именно так и поступает grub). Скрипты эти будут автоматически вызываться при наступлении событий связанных установкой или удалением ядер.

Нас будут интересовать каталоги /etc/kernel/postinst.d и /etc/kernel/postrm.d, именно в них мы создадим символьные ссылки на нашу утилиту.

ln -s /usr/bin/uefiboot-update /etc/kernel/postinst.d/uefiboot-update
ln -s /usr/bin/uefiboot-update /etc/kernel/postrm.d/uefiboot-update

Теперь пункты загрузки UEFI будут автоматически обновляться после установки нового ядра или после удаления одного из ранее установленных.

Подчистим систему

Т. к. мы настроили все для загрузки без GRUB/Shim, то необходимо вычистить все, что связано с этими загрузчиками из системы (при обновлении GRUB будет создавать свою запись в пунктах меню UEFI):

# apt-get purge grub* shim os-prober

Но оказывается, этого не всегда достаточно. Любое ядро в репозитории Ubuntu имеет grub в зависимостях (зависимость типа рекомендованные — recommends) и вот, в некоторых случаях,2) grub пытается установится вместе с ядром (причем ставится почему-то grub-pc). Чтобы этого не происходило, нужно apt дать четкую и постоянную инструкцию не ставить рекомендованные.

Сделать это можно, записав значение APT::Install-Recommends "false"; в файл (новый) /etc/apt/apt.conf.d/zz-no-recommends:

# echo 'APT::Install-Recommends "false";' > /etc/apt/apt.conf.d/zz-no-recommends

Финальная настройка

Теперь осталось запустить утилиту обновления пунктов меню загрузки UEFI

# uefiboot-update

И последний штрих - уменьшение или вовсе обнуление тайм-аута меню загрузки UEFI (обычно, значение по умолчанию для этого тайм-аута 5-10 секунд, столько нам совсем не нужно

# efibootmgr -t1  # устанавливаем тайм-аут в 1 секунду

или

# efibootmgr -T   # Вовсе удаляем тайм-аут (в некоторых пршивках лучше установить его в 0, а не удалять) 

Автоматизированная установка и настройка

На GitHub’е есть установочно-настроечный скрипт, который выполняет все необходимые подготовительные операции (перемонтирование ESP в /boot), копирует все файлы на нужные места, а также создает нужные ссылки и удаляет загрузчики. Однако я не советую исполнять его бездумно — стоит проверить, что команды соответствуют конфигурации вашей системы.

Плюсы полученного решения

  • Из процесса загрузки исключены все загрузчики - ядро ОС загружается непосредственно из UEFI. Это приводит к не слишком значительному, но, вместе с тем, заметному убыстрению процесса загрузки (обычно экономия - 0.5 - 1.5 секунды).
  • Как и при использовании GRUB/Shim нет никакой необходимости следить за обновлениями ядер - при установке нового ядра система автоматически будет загружать его при следующей загрузке.

Минусы такого решения

Описав подробно решение я не могу не упомянуть о некоторых сложностях с которыми можно столкнутся. Ниже перечислены «тяжелые» ситуации, с которыми вам, скорее всего, и не придется столкнуться, но не упомянуть о них — совершенно не допустимо.

  • Пункты загрузки UEFI доступны из меню самого UEFI, и тут у разных производителей могут быть свои реализации этого меню. Попадаются и такие, которые вообще ничего не показывают или не дают выбора. Это не так критично, если у вас первым пунктом правильно прописана загрузка единственной ОС на вашем компьютере, но может стать проблемой, если вы используете несколько ОС или вам, по какой-то причине, нужно загрузится в UFIShell или просто загрузится с более старым ядром (после неудачной установки обновлений, например). Есть ещё производители, которые жёстко прописывают один единственный пункт загрузки в UEFI (как ни странно — это ссылка на загрузчик Windows). На таком оборудовании описанное решение можно будет реализовать только с значительными изменениями. В частности из-за такого ограничения нет возможности прописать несколько вариантов загрузки для нескольких версий ядра.
  • Некоторые прошивки UEFI имеют в меню своей конфигурации пункт сбросить настройки, и при этом сбрасываются все пункты меню загрузки (как часть настроек). Пользователь может воспользоваться сбросом для сброса неправильных настроек в других пунктах настроек UEFI, а в результате получит не загружающуюся машину. Особо стоит упомянуть про VirtualBox, он при закрытии виртуальной машины просто сбрасывает пункты загрузки и приходится загружать систему вручную через UEFIShell.

Для VirtualBox есть решение: необходимо добавить строку fs0:\EFI\ubuntu\grubx64.efi (или аналогичную) в файл startup.nsh в корне EFI раздела.

  • И самая неприятная опасность — это сбои при записи пунктов загрузки в NVRAM. Запуск утилиты обновления пунктов загрузки осуществляется автоматически при установке нового или сносе старого ядра. Причем так уж реализованы эти триггеры ядра, что порой пункты загрузки обновляются по несколько раз. NVRAM-у не страшна частая запись (это не флеш-память с лимитированным числом циклов записи), однако сбой при записи может привести к печальным последствиям. Я умудрился потерять питание на своем нетбуке (у него сдохла батарея и он у меня работал от сети), во время обновления пунктов загрузки UEFI. И нетбук практически «окирпичился» :-\ — загрузить я его смог только через UEFIShell вбивая команды загрузки ядра и его параметров руками. Но на этом неприятности не кончились, утилита efibootmgr напрочь отказалась записывать пункты загрузки выдавая сообщение, что на устройстве недостаточно места!!! Я попробовал удалить ненужные переменные, но это не помогло. Гуглеж дал совет загрузить ядро с урезанным лимитом на объем свободного NVRAM (по умолчанию, резервируется половина объема NVRAM для совместимости с некоторыми кривыми вариантами реализации UEFI), но перед этим я решил сбросить настройки BIOS/UEFI до фабричных. И, о чудо, — всё заработало!

Анализ ситуации показал, что и в этом случае виновата кривизна прошивки UEFI - она не смогла распознать кривую запись переменных в nvram. По стандарту, любая кривизна в NVRAM должна восприниматься как сигнал к обнулению NVRAM до factory default. Но моя прошивка не отловила глюки в записях и не обнулила NVRAM с нарушенной внутренней структурой. Справедливости ради надо заметить, что я умудрился отловить крайне маловероятную ситуацию сбоя именно в момент записи в NVRAM, которая, к тому же, наложилась на кривость в реализации UEFI.

Нужно отметить, что большинство неприятностей с UEFI, возникают от кривых реализаций UEFI. И эти кривости, так или иначе, но вылезают боком для любых ОС или загрузчиков, использующих UEFI.

Восстановление сброшенных или искаженных настроек пунктов загрузки UEFI

Если есть возможность загрузиться в UEFIShell

  1. грузимся в UEFIShell (либо через консоль настройки UEFI, либо через пункт меню загрузки)
  2. переходим в ESP раздел: fs0:
  3. набираем буквы vml и жмем TAB пока в строке ввода не появится нужное (последнее) ядро, допустим это будет vmlinuz-3.19.0-23-generic
  4. вводите пробел и начинаете набирать initr и снова жмете TAB пока не появится полное имя соотвествующего нашему ядру образа рамдиска. В нашем примере это должно быть initrd.img-3.19.0-23-generic
  5. после этого стрелочками переводим курсов перед initrd.img-3.19.0-23-generic и вписываем: root=/dev/sda2 ro quiet initrd=
    Вместо /dev/sda2 - пропишите раздел где у вас корневая ФС. По окончании этих манипуляций у вас будет строка такого вида:
    fs0:\> vmlinuz-3.19.0-23-generic root=/dev/sda2 ro quiet initrd=initrd.img-3.19.0-23-generic
    Жмем Enter и ждем когда загрузится Ubuntu, после чего идем в терминал и выполняем
    sudo uefiboot-update

    Все. Пункты меню UEFI восстановлены.

Если возможности загрузиться в UEFIShell нет

Потребуется LiveUSB/CD с образом Ubuntu который может загружаться EFI-режиме.

  1. грузимся с LiveUSB/CD в UEFI-режиме.
  2. устанавливаем утилиту efibootmgr (пакет утилиты есть на диске, поэтому даже соединение с Internet настраивать не нужно)
    sudo apt-get install efibootmgr

  3. Прописываем верный пункт загрузки через утилиту efibootmgr командой типа этой:

    sudo efibootmgr -cl vmlinuz-3.19.0-23-generic -u "root=/dev/sda2 ro quiet initrd=initrd.img-3.19.0-23-generic" -L Ubuntu

    Версии ядер пропишите свои, как и укажите свой раздел корневой ФC вместо /dev/sda2. После перезагрузки идем в терминал и выполняем

    sudo uefiboot-update

    Всё. Пункты меню UEFI восстановлены.

Копилефт

Предложенный набор файлов и решений распространяется под GPL. Берите, пользуйтесь, изменяйте под себя.

Ссылки

Проект на GitHub’е: https://github.com/slytomcat/UEFI-Boot

1) Я даю тут ссылки на GitHub, т.к. там лежат самые последние, обновлённые версии
2) В частности, если у вас установлен synaptic и в его настройках указано ставить рекомендованные