В связи с загруженностью специалистов, несколько лет назад мы вынуждены были отдать одну разработку контрагентам. Разработка велась на модуле Mars ZX3 фирмы Enclustra, в котором используется SOC ARM+FPGA Zynq-7020. Для сборки Linux использовался BSP от Enclustra (bsp-xilinx), который был немного модифицирован.
В процессе тестирования разработанного программного обеспечения, мы сразу же столкнулись с отказами ПО при выключении питания. При анализе, обнаружилось, что команды конфигурирования, отправляемые на устройство по сети, записывались в файлы, которые, при сбое питания, иногда оказывались пустыми или отсутствовали совсем. Это вынудило нас пересмотреть идеологию построения переданной нам сборки Linux. Сам процесс построения системы хорошо описан на сайте изготовителя модуля, поэтому не буду на нем останавливаться. Опишу только то, что позволило решить стоящую перед нами задачу повышения надежности и предотвращения отказов.
На модуле Mars ZX3 установлены микросхемы QSPI Flash и NAND Flash. В нашем случае, загрузка модуля происходит с QSPI Flash, в которую был записан U-Boot. Так как обе микросхемы используют одинаковые выводы Zynq-7020, после загрузки U-Boot переключает выводы на NAND Flash, на котором в отдельных разделах записаны загрузочный скрипт, Device Tree, ядро Linux, файловая система ubifs, переменные окружения. При этом все разделы, кроме раздела с переменными окружения были резервированы (то есть таких разделов было по два). Ниже приведен фрагмент файла Device Tree, где показано, как был разбит NAND Flash в варианте, переданном нам контрагентами:
Вторые разделы предполагалось использовать при сбое основных разделов. Для этого в Linux должен был быть реализован процесс, контролирующий целостность системы и, при сбое, записывающий в переменную окружения значение, предписывающую запускать систему с резервных разделов. Данный алгоритм предполагалось реализовать в будущем.
Конфигурационные данные записывались в две папки, для резервирования, но это не спасло ситуацию. Предполагается, что при отсутствии конфигурационных файлов, они создаются вновь автоматически, с параметрами по умолчанию.
Проблема заключается в том, что NAND Flash записывает данные постранично, а стирание происходит блоками. Таки образом, если при записи данных произойдет сбой питания, то будут испорчены не только эти данные, но может повредиться и файловая система. Запуск резервной системы может только отложить возникновение проблемы. Хотя в этом случае можно и реализовать восстановление основных разделов из резервных.
Мы решили пойти другим путем, смонтировав rootfs как Read-Only файловую систему, а файлы конфигурации записывать в отдельные разделы data и backup, которые монтировались для чтения и записи. В этом случае надобность в резервных разделах отпадает, но мы оставили их на будущее, так как объем памяти позволял нам это сделать. При необходимости, их можно удалить.
В итоге, было сделано следующее разбиение NAND Flash на разделы:
При прошивке NAND Flash с помощью команды U-Boot мы стираем разделы nand-data и nand-data-backup:
В загрузочном скрипте Linux мы реализовали монтирование корневой файловой системы как Read-Only, заменив в файле /etc/inittab в сборке Linux строку:
на
Добавили загрузочный скрипт в папку /etc/init.d/, который монтирует разделы nand-data и nand-data-backup для чтения и записи. В случае ошибки при монтировании (при первой загрузке или если повреждена файловая система), эти разделы форматируются и вновь монтируются. В корневой файловой системе должны быть предварительно созданы папки /mnt/data/ и /mnt/backup/.
При загрузке, если конфигурационные файлы в папке /mnt/data/ отсутствуют, они загружаются из папки /mnt/backup/. Если конфигурационные файлы отсутствуют и в /mnt/backup/, они автоматически создаются из ПО с параметрами по умолчанию. Если файлы присутствуют в /mnt/data/, но отсутствуют в /mnt/backup/, они копируются из /mnt/data/ в /mnt/backup/. Все эти операции выполняет пользовательское ПО.
На следующем этапе, для повышения надежности мы отказались от записи конфигурации в файл по каждой команде. Вся конфигурация теперь хранится в оперативной памяти и, при необходимости, по отдельной команде может быть сохранена в файлах в папках /mnt/data/ и /mnt/backup/.
Если в процессе работы потребуется внести изменения в корневую файловую систему, не перепрошивая устройство, можно из консоли перемонтировать систему для чтения и записи командой
Выполнить изменения и, затем, перемонтировать в Read-Only:
В процессе тестирования разработанного программного обеспечения, мы сразу же столкнулись с отказами ПО при выключении питания. При анализе, обнаружилось, что команды конфигурирования, отправляемые на устройство по сети, записывались в файлы, которые, при сбое питания, иногда оказывались пустыми или отсутствовали совсем. Это вынудило нас пересмотреть идеологию построения переданной нам сборки Linux. Сам процесс построения системы хорошо описан на сайте изготовителя модуля, поэтому не буду на нем останавливаться. Опишу только то, что позволило решить стоящую перед нами задачу повышения надежности и предотвращения отказов.
На модуле Mars ZX3 установлены микросхемы QSPI Flash и NAND Flash. В нашем случае, загрузка модуля происходит с QSPI Flash, в которую был записан U-Boot. Так как обе микросхемы используют одинаковые выводы Zynq-7020, после загрузки U-Boot переключает выводы на NAND Flash, на котором в отдельных разделах записаны загрузочный скрипт, Device Tree, ядро Linux, файловая система ubifs, переменные окружения. При этом все разделы, кроме раздела с переменными окружения были резервированы (то есть таких разделов было по два). Ниже приведен фрагмент файла Device Tree, где показано, как был разбит NAND Flash в варианте, переданном нам контрагентами:
partition@nand-linux {
label = "nand-linux";
reg = <0x0 0x500000>;
};
partition@nand-device-tree {
label = "nand-device-tree";
reg = <0x500000 0x100000>;
};
partition@nand-bootscript {
label = "nand-bootscript";
reg = <0x600000 0x100000>;
};
partition@nand-linux-second {
label = "nand-linux-second";
reg = <0x700000 0x500000>;
};
partition@nand-device-tree-second {
label = "nand-device-tree";
reg = <0xC00000 0x100000>;
};
partition@nand-bootscript-second {
label = "nand-bootscript";
reg = <0xD00000 0x100000>;
};
partition@nand-rootfs {
label = "nand-rootfs";
reg = <0xE00000 0xF500000>;
};
partition@nand-rootfs-second {
label = "nand-rootfs";
reg = <0x10300000 0xF500000>;
};
partition@boot-env {
label = "nand-env";
reg = <0x1F800000 0x100000>;
};
Вторые разделы предполагалось использовать при сбое основных разделов. Для этого в Linux должен был быть реализован процесс, контролирующий целостность системы и, при сбое, записывающий в переменную окружения значение, предписывающую запускать систему с резервных разделов. Данный алгоритм предполагалось реализовать в будущем.
Конфигурационные данные записывались в две папки, для резервирования, но это не спасло ситуацию. Предполагается, что при отсутствии конфигурационных файлов, они создаются вновь автоматически, с параметрами по умолчанию.
Проблема заключается в том, что NAND Flash записывает данные постранично, а стирание происходит блоками. Таки образом, если при записи данных произойдет сбой питания, то будут испорчены не только эти данные, но может повредиться и файловая система. Запуск резервной системы может только отложить возникновение проблемы. Хотя в этом случае можно и реализовать восстановление основных разделов из резервных.
Мы решили пойти другим путем, смонтировав rootfs как Read-Only файловую систему, а файлы конфигурации записывать в отдельные разделы data и backup, которые монтировались для чтения и записи. В этом случае надобность в резервных разделах отпадает, но мы оставили их на будущее, так как объем памяти позволял нам это сделать. При необходимости, их можно удалить.
В итоге, было сделано следующее разбиение NAND Flash на разделы:
partition@nand-linux {
label = "nand-linux";
reg = <0x0 0x500000>;
};
partition@nand-device-tree {
label = "nand-device-tree";
reg = <0x500000 0x100000>;
};
partition@nand-bootscript {
label = "nand-bootscript";
reg = <0x600000 0x100000>;
};
partition@nand-linux-second {
label = "nand-linux-second";
reg = <0x700000 0x500000>;
};
partition@nand-device-tree-second {
label = "nand-device-tree";
reg = <0xC00000 0x100000>;
};
partition@nand-bootscript-second {
label = "nand-bootscript";
reg = <0xD00000 0x100000>;
};
partition@nand-rootfs {
label = "nand-rootfs";
reg = <0xE00000 0x9600000>;
};
partition@nand-rootfs-second {
label = "nand-rootfs";
reg = <0xA400000 0x9600000>;
};
partition@nand-data {
label = "nand-data";
reg = <0x13A00000 0x5F00000>;
};
partition@nand-data-backup {
label = "nand-data-backup";
reg = <0x19900000 0x5F00000>;
};
partition@boot-env {
label = "nand-env";
reg = <0x1F800000 0x100000>;
};
При прошивке NAND Flash с помощью команды U-Boot мы стираем разделы nand-data и nand-data-backup:
u-boot>nand erase.part nand-data
u-boot>nand erase.part nand-data-backup
В загрузочном скрипте Linux мы реализовали монтирование корневой файловой системы как Read-Only, заменив в файле /etc/inittab в сборке Linux строку:
::sysinit:/bin/mount -o remount,rw /
на
::sysinit:/bin/mount -o remount,ro /
Добавили загрузочный скрипт в папку /etc/init.d/, который монтирует разделы nand-data и nand-data-backup для чтения и записи. В случае ошибки при монтировании (при первой загрузке или если повреждена файловая система), эти разделы форматируются и вновь монтируются. В корневой файловой системе должны быть предварительно созданы папки /mnt/data/ и /mnt/backup/.
#!/bin/sh
#
# datafs Mount datafs.
#
umask 077
start() {
printf "Starting mount datafs..."
/usr/sbin/ubiattach /dev/ubi_ctrl -m 8
/usr/sbin/ubiattach /dev/ubi_ctrl -m 9
mount_datafs_err=0;
mount_datafs_backup_err=0;
/bin/mount -t ubifs ubi1:datafs /mnt/data
mount_datafs_err=$?
if [ $mount_datafs_err -ne 0 ]; then
/usr/sbin/ubidetach -p /dev/mtd8
/usr/sbin/ubiformat /dev/mtd8 -y
/usr/sbin/ubiattach /dev/ubi_ctrl -m 8
/usr/sbin/ubimkvol /dev/ubi1 -N datafs -s 81MiB
/bin/mount -t ubifs ubi1:datafs /mnt/data
mount_datafs_err=$?
fi
/bin/mount -t ubifs ubi2:datafs /mnt/backup
mount_datafs_backup_err=$?
if [ $mount_datafs_backup_err -ne 0 ]; then
/usr/sbin/ubidetach -p /dev/mtd9
/usr/sbin/ubiformat /dev/mtd9 -y
/usr/sbin/ubiattach /dev/ubi_ctrl -m 9
/usr/sbin/ubimkvol /dev/ubi2 -N datafs -s 81MiB
/bin/mount -t ubifs ubi2:datafs /mnt/backup
mount_datafs_backup_err=$?
fi
printf "Mount datafs: "
if [ $mount_datafs_err -ne 0 ]; then
echo "Error"
else
echo "OK"
fi
printf "Mount backup datafs: "
if [ $mount_datafs_backup_err -ne 0 ]; then
echo "Error"
else
echo "OK"
fi
touch /var/lock/datafs
}
stop() {
/bin/umount /mnt/data
/bin/umount /mnt/backup
rm -f /var/lock/datafs
}
restart() {
stop
sleep 1
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
restart
;;
*)
echo "Usage: $0 {start|stop|restart|reload}"
exit 1
esac
exit 0
При загрузке, если конфигурационные файлы в папке /mnt/data/ отсутствуют, они загружаются из папки /mnt/backup/. Если конфигурационные файлы отсутствуют и в /mnt/backup/, они автоматически создаются из ПО с параметрами по умолчанию. Если файлы присутствуют в /mnt/data/, но отсутствуют в /mnt/backup/, они копируются из /mnt/data/ в /mnt/backup/. Все эти операции выполняет пользовательское ПО.
На следующем этапе, для повышения надежности мы отказались от записи конфигурации в файл по каждой команде. Вся конфигурация теперь хранится в оперативной памяти и, при необходимости, по отдельной команде может быть сохранена в файлах в папках /mnt/data/ и /mnt/backup/.
Если в процессе работы потребуется внести изменения в корневую файловую систему, не перепрошивая устройство, можно из консоли перемонтировать систему для чтения и записи командой
mount -o remount,rw /
Выполнить изменения и, затем, перемонтировать в Read-Only:
mount -o remount,ro /
dlinyj
Правильно понимаю, что статья описывает как вы сделали rootfs в режим ro и сделали резервное копирование с перемонтированием файловой системы?
Просто rootfs в ro — это нормальная практика, а все временные файлы размещать в tmpfs. Сохранять только ценные логи (в идеале копированием по крону, либо по критической ошибке). Думал, что nand с ubifs и иже с ними уже никто не использует, чтобы не тратить ресурсы процессора на поддержку файловой системы.
murad1974 Автор
Не совсем так, вопрос не в том, где хранить временные файлы. Наше программное обеспечение является конфигурируемым. Конфигурация осуществяляется командами, передаваемыми по сети. После перезагрузки устройства, конфигурация должна сохраниться. Я описал наше решение как сделать так, чтобы при сбоях питания, наша система не оказалась повреждена и продолжила работу в обычном режиме. На nand устанавливается rootfs (в свой раздел ro), а также выделяются отдельные разделы для хранения конфигурации, которыми мы и управляем. Даже в варианте, который Вы привели, при сохранении логов или копировании с использованием cron, файловая система может повредиться, если в момент записи выключится питание.
dlinyj
Не работал с UBIFS, но разве у неё нет возможности избежать таких ситуаций? У jffs2 совершенно точно была табличная возможность организации так, что в случае сбоя при записи востанавливается предыдущая копия rootfs. Что гарантирует стабильность работы. У роутеров логи пишутся в nand и как-то же они работают и даже не в ro.
Прошу меня правильно понять, ваша статья полезна и интересна, но разве это не велосипед?
murad1974 Автор
Я написал о реальных сбоях, с которыми мы столкнулись. Надежность для наших изделий является критически необходимой. Да, ubifs, как и jffs2 имеют механизмы восстановления данных, но они не дают 100% гарантии. На хабре кстати была статья об этом.
dlinyj
Спасибо за ссылку.