Подробный гайд: DXE в контексте UEFI и загрузки компьютера - Часть 2
Вторая часть статьи, продолжает тему DXE в UEFI и переходит от базовых понятий к более практическим механизмам: диспетчеризации, событиям, памяти, runtime, безопасности и отладке.
15. Архитектурные протоколы DXE
Архитектурные протоколы — это базовые интерфейсы, без которых платформа не может корректно перейти к следующим этапам загрузки. Их установка часто является обязательной для нормального завершения DXE и начала BDS.
К наиболее важным архитектурным протоколам обычно относятся:
- EFI_CPU_ARCH_PROTOCOL
Управление процессором: прерывания, кэш, флаги, базовые CPU-операции.
- EFI_TIMER_ARCH_PROTOCOL
Таймерный сервис, на котором строятся события и таймеры UEFI.
- EFI_METRONOME_ARCH_PROTOCOL
Источник тактов для коротких задержек и калибровки времени.
- EFI_VARIABLE_ARCH_PROTOCOL
Доступ к переменным UEFI.
- EFI_VARIABLE_WRITE_ARCH_PROTOCOL
Подтверждает, что платформа поддерживает запись переменных.
- EFI_RUNTIME_ARCH_PROTOCOL
Инфраструктура для runtime-драйверов и сервисов, доступных после загрузки ОС.
- EFI_SECURITY_ARCH_PROTOCOL
Базовая политика безопасности загрузки.
- EFI_SECURITY2_ARCH_PROTOCOL
Расширенный механизм проверки образов и безопасности.
- EFI_WATCHDOG_TIMER_ARCH_PROTOCOL
Watchdog-таймер, который может перезагрузить систему при зависании.
- EFI_RESET_ARCH_PROTOCOL
Механизм сброса системы.
- EFI_REAL_TIME_CLOCK_ARCH_PROTOCOL
Доступ к часам реального времени.
- EFI_FIRMWARE_VOLUME2_PROTOCOL
Доступ к Firmware Volume, чтение файлов и секций.
- EFI_DEVICE_PATH_PROTOCOL
Универсальное описание путей к устройствам.
- EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL
Распределение ресурсов для PCI/PCIe при инициализации шины.
Важно понимать:
DXE Dispatcher внимательно следит за появлением таких протоколов. Если один из критичных архитектурных протоколов не установлен, зависимые драйверы могут так и не запуститься, а система зависнет на этапе DXE или не перейдёт к BDS.
16. DXE Dispatcher изнутри
DXE Dispatcher — это не просто загрузчик модулей, а механизм управления зависимостями и порядком инициализации.
Его работу можно описать так:
1. Поиск Firmware Volumes
DXE Core узнаёт, какие Firmware Volumes доступны: из HOB, через протоколы или через вложенные образы.
2. Сканирование файлов
Внутри каждого Firmware Volume Dispatcher ищет файлы подходящих типов: драйверы, приложения, вложенные FV.
3. Чтение Depex
Для каждого драйвера анализируется выражение зависимости.
4. Проверка условий
Если все требуемые протоколы установлены, драйвер допускается к загрузке.
5. Загрузка образа
PE/COFF-образ загружается в память, выполняются релокации.
6. Запуск точки входа
Вызывается entry point драйвера.
7. Повторный цикл
Если драйвер установил новые протоколы, Dispatcher снова проверяет, какие драйверы теперь можно запустить.
Отдельно стоит упомянуть a priori file. Это специальный файл внутри Firmware Volume, который задаёт список драйверов для приоритетной загрузки. Он не заменяет Depex, но помогает жёстко задать порядок для критичных модулей.
Практический смысл a priori file:
- сначала поднять переменные;
- затем безопасность;
- затем chipset-драйверы;
- затем консоль;
- затем ACPI и SMBIOS.
Если драйвер не запускается, Dispatcher обычно сохраняет информацию об этом. Повторная попытка возможна, если изменились условия, но полагаться на «само заработает» не стоит.
17. Depex на практике
Depex — один из главных инструментов управления запуском DXE-драйверов.
Простейшие случаи
Запуск без условий:
[Depex]
TRUE
Запрет запуска:
[Depex]
FALSE
Запуск только при наличии протокола:
[Depex]
gEfiPciRootBridgeIoProtocolGuid
Логические условия
Несколько протоколов:
[Depex]
gEfiPciRootBridgeIoProtocolGuid AND gEfiVariableArchProtocolGuid
Альтернатива:
[Depex]
gEfiSomeProtocolGuid OR gEfiOtherProtocolGuid
Отрицание:
[Depex]
NOT gEfiSomeProtocolGuid
Порядок запуска
Иногда нужно не просто дождаться протокола, а повлиять на порядок:
[Depex]
BEFORE gEfiSomeDriverProtocolGuid
или:
[Depex]
AFTER gEfiSomeDriverProtocolGuid
Такие конструкции полезны, если платформа требует строгой последовательности, но использовать их нужно аккуратно: они делают систему менее гибкой.
Типичные ошибки Depex
- Драйвер использует протокол, но не указывает его в Depex.
- Depex слишком жёсткий и блокирует запуск на других платформах.
- Используется
TRUE, хотя драйвер реально зависит от нескольких сервисов. - Драйвер рассчитывает на протокол, который появляется только в BDS.
- Применяется
BEFORE/AFTERбез реальной необходимости.
Хорошее правило:
если драйвер не может работать без протокола — этот протокол должен быть в Depex.
18. События, таймеры и уведомления в DXE
DXE — событийно-ориентированная среда. Вместо «спящих» циклов и бесконечного ожидания используются события.
Основные механизмы:
- создание событий;
- ожидание событий;
- проверка события;
- таймеры;
- уведомления о появлении протоколов;
- уведомления о завершении boot services.
Создание события
Пример создания события по GUID:
EFI_EVENT Event;
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
MyNotifyFunction,
NULL,
&gEfiEventExitBootServicesGuid,
&Event
);
Такое событие может использоваться, например, для корректного завершения работы перед ExitBootServices.
Таймеры
Таймер создаётся через событие и SetTimer:
Status = gBS->SetTimer (
TimerEvent,
TimerPeriodic,
TIMER_INTERVAL
);
Варианты:
- одноразовый таймер;
- периодический таймер;
- относительный таймер;
- абсолютный таймер.
Protocol Notify
Если нужно дождаться появления протокола, лучше использовать protocol notify, а не опрос в цикле.
В EDK II часто применяют:
EfiCreateProtocolNotifyEvent (
&gEfiSomeProtocolGuid,
TPL_CALLBACK,
MyProtocolNotify,
NULL,
&Registration
);
Это намного корректнее, чем делать LocateProtocol в бесконечном цикле.
19. TPL: уровни приоритета и безопасная обработка
TPL — Task Priority Level. Это механизм приоритетов в UEFI, который помогает управлять тем, когда могут выполняться события и обработчики.
Основные уровни:
TPL_APPLICATIONTPL_CALLBACKTPL_NOTIFYTPL_HIGH_LEVEL
Смысл TPL:
- код с более высоким TPL временно блокирует события с более низким приоритетом;
- обработчики не должны выполняться слишком долго;
- нельзя бесконечно удерживать высокий приоритет;
- события должны быть короткими и предсказуемыми.
Пример повышения приоритета:
EFI_TPL OldTpl;
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
// критичный участок
gBS->RestoreTPL (OldTpl);
Практическое правило
- для большинства уведомлений подходит
TPL_CALLBACK; - для важных системных уведомлений используют
TPL_NOTIFY; TPL_HIGH_LEVELследует применять только в действительно критичных местах.
Если обработчик события делает слишком много работы, он может ухудшить стабильность всей загрузки.
20. Работа с памятью в DXE
DXE активно использует сервисы памяти UEFI.
Основные операции:
AllocatePagesFreePagesAllocatePoolFreePoolGetMemoryMap
Типы памяти
Очень важно правильно выбирать тип памяти.
Часто используемые типы:
EfiBootServicesCode— код, нужный только до загрузки ОС;EfiBootServicesData— данные, нужные только до загрузки ОС;EfiRuntimeServicesCode— runtime-код;EfiRuntimeServicesData— runtime-данные;EfiACPIReclaimMemory— ACPI-данные, которые ОС может освободить после прочтения;EfiACPIMemoryNVS— память, которая должна сохраняться;EfiReservedMemoryType— зарезервированная область.
Почему это важно
После ExitBootServices операционная система получает memory map.
Если вы ошиблись с типом памяти, ОС может освободить или перезаписать область, которая всё ещё нужна firmware.
Например:
- если runtime-структура лежит в
EfiBootServicesData, она может быть потеряна; - если ACPI-таблицы размещены неправильно, ОС может их не увидеть;
- если S3-данные размещены неверно, восстановление из сна может сломаться.
Memory Map и Map Key
Перед ExitBootServices загрузчик ОС вызывает GetMemoryMap и получает MapKey. Если после этого memory map меняется, ExitBootServices может завершиться ошибкой. Тогда загрузчик должен получить новую карту и повторить попытку.
Для DXE-разработчика это означает:
нельзя без необходимости менять memory map в самый последний момент.
21. DXE_RUNTIME_DRIVER: жизнь после ExitBootServices
Если модуль должен продолжать работать после загрузки ОС, его оформляют как runtime-драйвер.
В INF это обычно:
MODULE_TYPE = DXE_RUNTIME_DRIVER
Что важно понимать
После ExitBootServices:
- Boot Services больше недоступны;
- нельзя использовать
LocateProtocol; - нельзя создавать обычные события через boot services;
- нельзя полагаться на boot-память;
- доступны только Runtime Services.
Virtual Address Change
Операционная система может вызвать SetVirtualAddressMap, чтобы перевести firmware-указатели в виртуальное адресное пространство. Runtime-драйвер должен корректно обработать это событие.
Пример регистрации события:
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
VirtualAddressChangeCallback,
NULL,
&gEfiEventVirtualAddressChangeGuid,
&mVirtualAddressChangeEvent
);
Внутри обработчика обычно вызывают преобразование указателей:
EfiConvertPointer (0, (VOID **)&MyPointer);
Частые ошибки runtime-драйверов
- Использование Boot Services после
ExitBootServices. - Хранение данных в памяти неправильного типа.
- Отсутствие обработки смены виртуальных адресов.
- Попытка использовать протоколы, которые не рассчитаны на runtime.
- Слишком сложная логика внутри runtime-обработчиков.
Runtime-код должен быть максимально простым, надёжным и предсказуемым.
22. Переменные UEFI: от инициализации до защищённой записи
Переменные UEFI — один из главных механизмов сохранения состояния платформы между перезагрузками.
Основные сервисы:
GetVariable
SetVariable
GetNextVariableName
QueryVariableInfo
Атрибуты переменных
Пример базовых атрибутов:
EFI_VARIABLE_NON_VOLATILEEFI_VARIABLE_BOOTSERVICE_ACCESSEFI_VARIABLE_RUNTIME_ACCESSEFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS
Пример установки переменной:
Status = gRT->SetVariable (
L"MyVar",
&gMyVendorGuid,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
sizeof (Data),
&Data
);
Где живут переменные
В зависимости от платформы переменные могут храниться в:
- SPI-flash;
- отдельной NVRAM-области;
- энергонезависимой памяти контроллера;
- защищённой области, обрабатываемой SMM.
Безопасные переменные
Для Secure Boot используются authenticated variables:
PKKEKdbdbx
Их изменение требует соблюдения политики безопасности. Просто так перезаписать ключи из обычного кода нельзя.
На практике запись таких переменных часто проходит через:
- проверку подписи;
- проверку прав;
- SMM-обработчик;
- защищённое хранилище;
- механизм FTW для атомарности.
Variable Policy
В современных EDK II-платформах часто есть дополнительная политика переменных, которая может:
- запрещать создание некоторых переменных;
- ограничивать размер;
- требовать авторизацию;
- блокировать изменение после определённого этапа загрузки.
23. Secure Boot в DXE
Secure Boot — это политика доверия к загружаемым образам.
В DXE он опирается на:
- сервис переменных;
- Security Architecture Protocol;
- Security2 Architecture Protocol;
- проверку PE/COFF-образов;
- базы ключей и хэшей.
Основные объекты
- PK — Platform Key
Корень доверия для управления ключами.
- KEK — Key Exchange Key
Ключи, которым разрешено обновлять базы подписей.
- db — Signature Database
Разрешённые подписи и хэши.
- dbx — Forbidden Signature Database
Запрещённые подписи и хэши.
Как происходит проверка
Упрощённо:
- BDS или другой код вызывает загрузку образа.
- Перед запуском срабатывает security-механизм.
- Образ проверяется по
dbиdbx. - Если проверка успешна — образ разрешается.
- Если нет — возвращается ошибка безопасности.
Типичный статус:
EFI_SECURITY_VIOLATION
Что проверяется
Могут проверяться:
- загрузчики ОС;
- UEFI-приложения;
- UEFI-драйверы;
- option ROM;
- capsule-образы;
- некоторые recovery-компоненты.
Точная политика зависит от реализации платформы.
Практический вывод
Если вы разрабатываете собственный DXE-драйвер или загрузчик, вам нужно:
- либо подписать образ;
- либо добавить его хэш в
db; - либо отключить Secure Boot на этапе разработки.
24. Measured Boot, TPM и журнал событий
Measured Boot и Secure Boot — разные вещи.
- Secure Boot решает, можно ли выполнять образ.
- Measured Boot решает, что факт выполнения образа нужно зафиксировать.
Если включён measured boot, платформа измеряет важные этапы загрузки и записывает результаты в TPM.
Что может измеряться
- firmware-образ;
- PEI-модули;
- DXE-драйверы;
- option ROM;
- загрузчик;
- параметры конфигурации;
- ключевые переменные.
PCR
Результаты измерений обычно расширяются в регистрах TPM — PCR.
Типично:
- PCR0 — базовый firmware;
- PCR2 — option ROM и расширение платформы;
- PCR4 — загрузчик;
- другие PCR — дополнительные события.
Набор PCR и их назначение зависят от платформы и политики TCG.
Event Log
Кроме самих PCR, обычно ведётся журнал событий — TPM Event Log.
В нём можно посмотреть:
- что измерялось;
- в каком порядке;
- какой хэш был получен;
- какие компоненты участвовали.
Этот журнал затем может использоваться для attestation — проверки состояния платформы.
25. PCI(e), Option ROM и UEFI Driver Model
Инициализация PCI/PCIe — одна из самых заметных задач DXE.
Основные участники
- PCI Root Bridge Driver
Предоставляет доступ к корневому мосту.
- PCI Bus Driver
Выполняет перечисление устройств.
- PCI Host Bridge Resource Allocation Protocol
Управляет ресурсами.
- EFI_PCI_IO_PROTOCOL
Интерфейс доступа к конкретному PCI-устройству.
- EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL
Доступ к PCI-пространству через root bridge.
Что делает PCI Bus Driver
- сканирует шины;
- назначает bus number;
- распределяет BAR;
- включает device;
- создаёт handle для устройств;
- устанавливает PCI I/O Protocol;
- подключает драйверы;
- при необходимости загружает UEFI Option ROM.
Option ROM
Option ROM — это firmware-образ, расположенный на плате расширения.
Он может содержать:
- legacy BIOS-код;
- UEFI-драйвер;
- оба варианта сразу.
В современном UEFI-режиме нас интересует прежде всего UEFI-часть.
Перед выполнением UEFI Option ROM платформа может проверить:
- подпись;
- хэш;
- политику Secure Boot;
- допустимость выполнения.
Если платформа запрещает неподписанные option ROM, устройство может не появиться до загрузки ОС.
26. Накопители: Block I/O, Partition и файловые системы
Чтобы загрузить ОС, firmware должна уметь работать с накопителями.
Основные уровни
Host Controller Driver
↓
Device Driver
↓
EFI_BLOCK_IO_PROTOCOL
↓
Partition Driver
↓
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
Block I/O
EFI_BLOCK_IO_PROTOCOL даёт поблочный доступ к устройству:
- чтение блоков;
- запись блоков;
- получение информации о носителе;
- сброс устройства;
- обработка смены носителя.
Disk I/O
EFI_DISK_IO_PROTOCOL позволяет читать и писать байтовые диапазоны поверх блочного устройства.
Partition Driver
Он распознаёт:
- GPT;
- MBR;
- El Torito;
- другие схемы, если реализованы.
После этого создаются дочерние handle для разделов.
Файловые системы
Для UEFI чаще всего важна FAT-файловая система, потому что EFI System Partition обычно использует FAT.
Через EFI_SIMPLE_FILE_SYSTEM_PROTOCOL можно:
- открыть том;
- открыть файл;
- прочитать файл;
- записать файл;
- получить информацию о файле.
Практический смысл
Для загрузки ОС firmware должна дойти до состояния, когда:
- контроллер накопителя инициализирован;
- раздел найден;
- файловая система доступна;
- загрузочный файл можно прочитать и запустить.
27. Консоль и графика: GOP, ConOut и boot logo
Пользователь видит результат работы DXE и BDS через консоль и графику.
Основные протоколы
- EFI_SIMPLE_TEXT_INPUT_PROTOCOL
Ввод с клавиатуры.
- EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
Текстовый вывод.
- EFI_GRAPHICS_OUTPUT_PROTOCOL
Графический framebuffer.
GOP
GOP заменяет старые видеорежимы и предоставляет:
- разрешение;
- формат пикселя;
- адрес framebuffer;
- режимы отображения.
Именно GOP часто используется для:
- логотипа загрузки;
- простого boot splash;
- текстового вывода;
- раннего отображения до запуска видеодрайвера ОС.
ConIn, ConOut, ErrOut
В UEFI есть переменные:
ConInConOutErrOut
Они описывают, какие устройства считаются консолью ввода, вывода и ошибок.
Платформа может, например, переключить консоль между:
- serial-портом;
- USB-клавиатурой;
- встроенным дисплеем;
- внешним GPU;
- виртуальной консолью.
Почему это важно для DXE
Если консоль не инициализирована вовремя:
- не будет видно setup-меню;
- не отобразятся ошибки;
- boot-меню может зависнуть;
- пользователь решит, что система не отвечает.
28. Сеть в DXE: PXE, HTTP Boot и Load File
DXE может реализовать полноценный сетевой стек.
Уровни сетевого стека
Типичная структура:
Network Controller
↓
SNP
↓
MNP
↓
ARP / IP4 / IP6
↓
UDP / TCP
↓
DHCP
↓
TFTP / HTTP Boot
↓
Load File Protocol
Основные протоколы
- SNP — Simple Network Protocol
- MNP — Managed Network Protocol
- ARP
- IP4 / IP6
- UDP / TCP
- DHCP4 / DHCP6
- HTTP / TLS
- PXE / TFTP
- Load File Protocol
Как происходит сетевая загрузка
- BDS выбирает сетевую загрузочную запись.
- Сетевой контроллер подключается.
- Выполняется DHCP или автоконфигурация.
- Получается адрес загрузчика.
- Образ скачивается.
- Загрузчик запускается.
Практическая деталь
Сетевой стек — один из самых тяжёлых компонентов DXE.
Если сетевая загрузка не нужна, её часто отключают для ускорения boot и уменьшения поверхности атаки.
29. Типичные ошибки и диагностика DXE-модулей
DXE-ошибки часто проявляются как «зависание до ОС», «нет дисков», «не работает консоль» или «Secure Boot violation». Ниже — типовые проблемы.
Драйвер не запускается
Причины:
- модуль не добавлен в FDF;
- драйвер не попал в нужный FV;
- Depex не выполняется;
- нет нужного протокола;
- entry point вернул ошибку;
- неверный
MODULE_TYPE.
Что проверять:
- firmware-образ;
- debug-лог;
- секцию
[Depex]; - наличие протокола в системе;
- статус точки входа.
Драйвер запускается, но протокол не появляется
Причины:
- ошибка внутри драйвера;
- протокол устанавливается на неправильный handle;
- установка протокола завершилась ошибкой;
- драйвер ожидал ресурсы, которых нет.
Что проверять:
- статус
InstallProtocolInterface; - корректность handle;
- наличие зависимостей;
- порядок запуска.
Система зависает после инициализации устройства
Причины:
- бесконечный цикл;
- долгий опрос устройства;
- конфликт драйверов;
- устройство не отвечает;
- неверная работа с прерываниями.
Что проверять:
- таймауты;
- debug-печать;
- watchdog;
- статусы операций;
- поведение на другом железе.
Ошибка Secure Boot
Причины:
- неподписанный образ;
- хэш отсутствует в
db; - образ в
dbx; - нарушена политика загрузки.
Что проверять:
- состояние Secure Boot;
SetupMode;dbиdbx;- подпись образа;
- журнал measured boot, если доступен.
Переменная не читается из ОС
Причины:
- неверные атрибуты;
- переменная не была сохранена как non-volatile;
- нет runtime-доступа;
- переменная удалена политикой;
- NVRAM повреждена.
Что проверять:
- атрибуты
SetVariable; - GUID переменной;
- наличие
EFI_VARIABLE_RUNTIME_ACCESS; - размер данных;
- логи firmware.
30. Чек-лист разработчика DXE
Ниже — практический чек-лист, который помогает избежать большинства типовых ошибок.
Базовая структура модуля
- [ ] Правильный
MODULE_TYPE - [ ] Уникальный
FILE_GUID - [ ] Корректный
ENTRY_POINT - [ ] Модуль добавлен в DSC
- [ ] Модуль добавлен в FDF
- [ ] Все библиотеки разрешены в DSC
- [ ] Протоколы и GUID объявлены в INF
Depex
- [ ] Драйвер зависит только от реально нужных протоколов
- [ ] Нет скрытых зависимостей
- [ ] Нет лишнего
TRUE, если нужны протоколы - [ ]
BEFORE/AFTERиспользуется осознанно - [ ] Порядок запуска не ломает другие платформы
События и таймеры
- [ ] Нет бесконечных циклов ожидания
- [ ] Используются события вместо busy wait
- [ ] Обработчики короткие
- [ ] TPL выбран адекватно
- [ ] Таймеры корректно останавливаются
Память
- [ ] Тип памяти соответствует времени жизни данных
- [ ] Runtime-данные не лежат в boot memory
- [ ] Нет утечек памяти
- [ ] Указатели проверяются перед использованием
- [ ] Буферы имеют правильный размер
Runtime
- [ ] Модуль не использует Boot Services после
ExitBootServices - [ ] Обработчик Virtual Address Change реализован
- [ ] Все указатели преобразуются корректно
- [ ] Runtime-память выделена правильно
- [ ] Логика минимальна и надёжна
Переменные
- [ ] Указаны правильные атрибуты
- [ ] Non-volatile переменные действительно сохраняются
- [ ] GUID уникален
- [ ] Размер данных контролируется
- [ ] Обработка ошибок
SetVariable/GetVariableприсутствует
Безопасность
- [ ] Образ подписан или хэш добавлен в
db - [ ] Нет обхода Secure Boot в debug-режиме без необходимости
- [ ] Входные данные проверяются
- [ ] Нет небезопасных строковых операций
- [ ] Чувствительные данные не пишутся в debug-лог
- [ ] SMM-границы не нарушаются
Отладка
- [ ] Есть debug-печать на ключевых этапах
- [ ] Проверяются все статусы
- [ ] Используются коды ошибок
- [ ] Можно получить serial/debug log
- [ ] Есть возможность проверить handle database через shell
- [ ] Есть возможность проверить переменные и протоколы
Итог второй части
Во второй части мы разобрали DXE уже как рабочую инженерную среду:
- как устроены архитектурные протоколы;
- как DXE Dispatcher принимает решения о запуске драйверов;
- как писать корректные Depex;
- как работают события, таймеры и TPL;
- как правильно обращаться с памятью;
- чем отличаются runtime-драйверы;
- как устроены переменные, Secure Boot и measured boot;
- как DXE работает с PCI, накопителями, графикой и сетью;
- какие ошибки встречаются чаще всего и как их диагностировать.
Если первая часть объясняла что такое DXE и зачем он нужен, то вторая часть показывает как DXE реально работает изнутри и на что смотреть при разработке и отладке.
Мы делимся этой технической информацией, чтобы помочь вам в решении задач — используйте её с пониманием. Статья носит рекомендательный характер, поэтому, пожалуйста, применяйте описанные методы осмотрительно.