04 августа, 2008

uClinux - инструменты сборки


Итак, Вы теперь имеете систему разработки uClinux. Вкратце об <установке>

  • набор программ разработки uClinux должен быть установлен в /opt/uClinux

  • m68k-coff использовался для компиляции ядра. Он не связан со стандартной С библиотекой uC-libc.

  • ядро uClinux установленно в /opt/uClinux/linux

  • m68k-pic-coff - это PIC компилятор, который использовался для сборки бинарников пользовательского пространства.

  • стандартная С библиотека uC-libc должна быть скомпилирована как libc.a и crt0.o и скопирована в /opt/uClinux/m68k-pic-coff/lib, а их include файлы в /opt/uClinux/m68k-pic-coff/include

  • стандартная математическая библиотека uC-libm дложна быть скомпилирована как libm.a и math.h, включенные в /opt/uClinux/m68k-pic-coff/lib и /opt/uClinux/m68k-pic-coff/include соответственно.

  • m68k-pic-coffs’ LD заменен скриптом, который вызывает линковщик LD, затем COFF2FLT создает плоский бинарный модуль (Flat Binary Image) из coff файла

  • утилита genromfs должна быть собрана и установлена, и добавлена в path

  • Romdisk извлечен в /opt/uClinux/romdisk

  • коды пользовательского пространства uClinux установлены в /opt/uClinux/src

  • скрипт deftemplate.sh должен быть помещен в /opt/uClinux/

  • скрипт buildenv.sh должен быть помещен в /opt/uClinux/bin/ и добавлен в path



Есть две области разработки: средства ядра uClinux и средства пользовательского пространства. Так как ядро представлено для множества платформ, основным местом разработки будет пользовательское пространство. Как бы там ни было, ядро uClinux должно быть установлено первым.

Сборка ядра uClinux



Ядро должно быть сконфигурировано перед сборкой. Для тех, кто конфигурировал ядро для настольных ПК, эта процедура будет знакома, кроме конечно того, что uClinux не поддерживает загружаемые модули. Для других это будет испытанием.
Для начала перейдите в /opt/uClinux/linux и запустите конфигурационную утилиту

cd /opt/uClinux/linux
make menuconfig

Время конфигурации ядра. Так как Вы будете ссылаться на различные архитектуры и платформы, здесь нет особо выбора. Выберите Ваш процессор и плату из Platform Dependent Setup, и пропустите конфигурацию дополнительных параметров.

Как только вы закончили с конфигурационными дилемами, пришло время сборки. Просто запустите следующие команды, откиньтесь на спинку кресла и следите. Это может занять некоторое время, в зависимости от скорости Вашей системы.

make dep
make clean
make linux.bin

Make dep говорит make об установке зависимостей. Clean очистит все старые компоненты предыдущих сборок, а linux.bin соберет образ linux.bin, что является ядром в чистом бинарном виде.

По завершении Вы должны иметь парочку файлов в директории linux (/opt/uClinux/linux). Это будут linux.text, linux.data, linux.bin и карта системы (system map).

Процедура сборки ядра



Сбоорка ядра начнется со сборки отдельных компонентов/подсистем ядра. Как только это будет сделано, все объектные файлы подсистемы будут связаны (linked) используя секцию компоновочного файла (.LD), который будет находится в arch/$(ARCH)/platform/$(PLATFORM)/$(BOARD)/$(MODEL).ld, и запускать C asm код (crt0_rom.S) для создания файла linux.

LD -T (MODEL).ld crt0_$(MODEL).o [objs] -o linux

Компоновщик определяет секцию памяти, он сообщает компилятору сколько памяти доступно и где разместить разные куски кода. Загрузочный asm код - это код, который запускается прямо после reset vector или загрузчика (bootloader), который поднимает различные части микроконтроллера, такие как clock dividers, serial port, DRAM, SRAM, memory banks, watchdogs и т.д., которые должны быть установлены перед тем как микроконтроллер сможет запустить ядро linux. Также он поднимает стэк, обнуляет .bss сегмент, копирует .data сегмент в RAM и переходит к start_kernel() который является входной точкой в C Код.

Символьная/системная карта (symbol/system map) генерируется из файла linux. Это удобно для показа отладки, где каждая функция размещена в памяти.

NM $(LINUX) | grep -v '\(compiled\)\|\(\.o$$\)\|\( a \)' | sort > System.map

Linux.data, файл содержащий весь код сегмента данных, создан linux, удаляя все сегменты только-для-чтения и другие ненужные сегменты.

objcopy -O binary --remove-section=.romvec --remove-section=.text \
--remove-section=.ramvec --remove-section=.bss \
--remove-section=.eram linux linux.data

Файл Linux.text также содержит весь text segment код (fixed code и переменные/строки)

objcopy -O binary --remove-section=.ramvec --remove-section=.bss \
--remove-section=.data --remove-section=.eram \
--set-section-flags=.romvec=CONTENTS,ALLOC,LOAD,READONLY,CODE linux linux.text

Сегменты .Text и .Data конкатенируются для синтеза linux.bin

cat linux.text linux.data > linux.bin

Мы закончили с linux.bin, что является бинарным кодом ядра. Теперь он может быть загружен в память и запущен. Однако будет сбой после попытки распаковки ROM filesystem и монтирования нодов устройства (device nodes), содержащихся внутри romfs.

Сборка ядра потребуется единожды до приминения каких-либо модификаций ядра. На каждой сборке системы romfs будет конкатенироваться с linux.bin.

ФС постоянной памяти (ROM filesystem)



Так как есть одна общая исходая директория для ядра linux, Вы можете сложить проекты ФСы rom и бинарников пространства пользователя. Вы можете иметь одно общее ядро на главном сервере, но иметь множество разработчиков, работающих над одной системой и использующих их собственные rom ФС.

Для установки рабочей среды всего лишь нужно создать директорию в подходящем месте, обычно /home/<username>/ для многопользовательской системы и запустить buildenv

cd /home/cpeacock/
mkdir ucsimm
buildenv

buildenv соберет среду разработки uClinux, создавая вначале Makefile в Вашей директории. Теперь если Вы наберете make, среда разработки будет скопирована из директории /opt/uClinux/, и создастся файл image.bin.

make

Теперь директория должна иметь такую структуру

Makefile deftemplate.sh image.bin linux romdisk romdisk.img romdisk.map src

Эта среда содержит бинарники пользовательского пространства в ФС ROM, которая монтирована ядром во время заргузки. Каждый исполняемый файл описанный в /src/makefile скомпилирован и его плоский бинарнкик помещен в /src/bin. Файл deftemplate.sh запущен для копирования конкретных бинарников в дерево виртуального диска постоянной памяти (romdisk), который будет представлять базу нашего romfs образа. Заметим, что скрипт deftemplate только копирует бинаркники в дерево romdisk. Он не удаляет их(бинарники), поэтому чтобы сделать это Вы должны раскамментировать соответствующую строку в скрипте deftemplate и вручную удалить файл из дерева rom диска.

Дерево rom диска также включает конфигурационные файлы и структуру директорий ФС rom

/bin /dev /etc /htdocs /lib /proc ramfs.img /sbin /tmp /usr /var

ФС romdisk.img автоматически генерируется из дерева используя

genromfs -v -V "ROM Disk" -f romdisk.img -d romdisk 2> romdisk.map

romdisk.img затем конкатенируется с /linux/linux.bin (бинарник ядра мы собирали ранее) для создания

cat linux/linux.bin romdisk.img > image.bin

image.bin - файл, который содержит ядро linux плюс ROM FS. Этот образ укомплектован и готов для закачки в Вашу uClinux Систему. Для лучшего представления процесса сборки, типичная карта памяти 2.0.38 uCsimm показана на рисунке ниже.



Память загрузчика (bootloader flash) уже присутствует в системе разработки, которую мы используем (в нашем случае uCsimm). Это модуль dragonball от Lineo. Сегменты ROM Vectors, .TEXT и .DATA созданы как часть компиляции ядра. Эти три секции составляют файл linux.bin, который может быть найдет в /opt/uClinux/linux.

Бинарники скомпилированы как плоские исполняемые и помещены в директорию rom диска. Затем Genromfs пакует это в romfs.img, который конкатенирован с linux.bin для предоставления файла image.bin. Это повысит или понизит размер, в зависимости от того какие файлы Вы включили в ФС ROM. Единственное ограничение накладывается на максимальное пространство вашей системы.

image.bin - это бинарный файл, который загружается в систему разработки через загрузчик (bootloader).

Настройка romfs



В настоящий момент мы собрали достаточно для загрузки image.bin и благополучно зарегистрировались в целевую систему uClinux. Однако мы используем стандартный IP адрес и другие параметры прямо из коробки.

Войдите в директорию с Вашим диском rom (romdisk). Вы должны иметь структуру, похожую на эту:

/bin /dev /etc /htdocs /lib /proc ramfs.img /sbin /tmp /usr /var

Как в любой системе linux, критические конфигурационные файлы могут быть найдены в /etc

inetd.conf inittab issue passwd rc resolv.conf services

Стандартный rc файл выглядит так:

#!/bin/sh
#
# system startup.

# set up the hostname
/bin/hostname uCsimm

# attach the interfaces
/sbin/ifattach
/sbin/ifattach \
--addr 192.168.1.200 \
--mask 255.255.255.0 \
--net 192.168.1.0 \
--gw 192.168.1.100 eth0

# expand the ramdisk
/sbin/expand /ramfs.img /dev/ram0

# mount ramdisk, proc and nfs
/bin/mount -t ext2 /dev/ram0 /var
/bin/mount -t proc proc /proc
/bin/mount -t nfs 192.168.1.11:/home/jeff/kit /usr

# start up the internet superserver
/sbin/inetd &

# that's it... success
exit 0

Вам нужно будет изменить имя хоста (hostname), IP адрес, маску, сетевые параметры и параметры шлюза. Также нужно изменить монтирование nfs, /bin/mount -t nfs 192.168.1.11:/home/jeff/kit /usr

Монтирование NFS тома для Разработки



NFS или Network File System - это путь экспортирования и монтирования директорий в UNIX системах. Это может быть очень удобно во время разработки, позволяя легко выводить Вашу рабочую директорию на uClinux платформу. Это дает возможность компилировать код в linux системе и запускать его на сетевом диске embedded системы без копирования flat бинарников или прошивки новой ROMFS. Можете представить себе какое огромное количество времени Вы экономите.

С утилитой флэш-загзузки/сетевой загрузки для целевой платформы Вы можете производить обновления флэша через кабель. Это также ускорит разработку. Пересылка 1MB image.bin файла через 115,200 serial link не так быстро.

Перед использованием NFS потребуется установить песочницу для разработки, чтобы извлечь нужные Вам директории. Рекомендуется провести некоторое время без внешнего подключения к интернет. NFS exports определены в файле /etc/exports. Надо редактировать от рута.

Типичная запись вывода имеет такой вид

/home (ro)

Это выведет домашнии директории (home) для всех с правами только чтение. Если Вы имеете чувствительную информацию в домашней директории, Вы можете явно указать на среду разработки или уточнить, какие машины имеют доступ к директории вывода, а именно:

/home/cpeacock/ucsimm uCSimm(ro)

где uCsimm - имя хоста uClinux системы. Оно должно быть включено в /etc/hosts, или же должен быть указан явный IP адрес.

После замены директории вывода нужно перезагрузить компьютер (windows way :) ) или перезапустить NFS демоны. Вот так:

/etc/rc.d/init.d/nfs stop
/etc/rc.d/init.d/nfs start

(на многих linux системах)

Как только это сделано, модифицирйуте файл rc и включите сюда данные о монтировании, в моем случае

mount -t nfs 192.168.0.1:/home/cpeacock/ucsimm /usr

Вы также можете зарегистрироваться в вашу uClinux систему и запустить это в коммандной строке. Это удобно после того, как uClinux система приведена в порядок, и теперь нужно быстро сделать некоторые вещи или reimage.

ФС оперативной памяти (RAM FileSystem)



uClinux имеет RAM ФС (ramdisk) для временных областей /tmp и /var в отсутствии жесткого диска (hard disk drive, HDD). romdisk текущиего дистрибутива uClinux идет с ramfs на 256кбайт. /tmp в корне - это символическая ссылка на /var/tmp. var - это точка монтирования RAM ФС.

ramfs распакована и монтирована через скрипт запуска rc,

# expand the ramdisk
/sbin/expand /ramfs.img /dev/ram0

# mount ramdisk, proc and nfs
/bin/mount -t ext2 /dev/ram0 /var

Она содержит ext2 ФС, которая сжата при помощи алгоритма Zero Run Length Encoding (ZRTE). Она распаковывается при помощи утилиты распаковки в блочное RAM устройство /dev/ram0. Как только этот процесс завершен, она монтирована как ext2 ФС с точкой монтирования /var.

Если нужно исправить ссылку на tmp, мы можем просто добавить команду в rc скрипте для создания директории tmp в var после монтирования ramfs. Как бы то ни было, увеличить размер ramdisk будет не так легко. Для этого нужно создать новый ramfs.img. Для некоторых приложений даже необходим больший размер ramdisk. Вы возможно захотите журналировать данные в файл, который затем будет скачан, используя анонимный FTP, HTTP, или другое.

Создаем новую ramfs



Создание новой ramfs - достаточно простая задача. Одна проблема, на которую следует обратить внимание - это порядок следования байт в вашей системы (endianness).

Начнем с обнуления блочного устройства ram. Позже мы будем использовать утилиту сжатия в целях получения более сжатого образа. Вам также нужно выбрать размер диска ramdisk. Большой ramdisk позволит Вам записывать большие журналы и временные файлы, но с затратами на системной RAM. Это зависит от того, какие приложения Вы используете на uClinux системе.

# dd if=/dev/zero of=/dev/ram0 bs=1k count=1024
1024+0 records in
1024+0 records out

Далее сделаем ext2fs. Флаг -v включает вербальный режим, так что мы сможем увидеть некоторую статистику. По умолчанию 5% блоков зарезервировано для суперпользователя. Мы это выключим, используя -m0. Мы также отключим любые другие дополнительные возможности ФС ext2, используя -Onone. На пост 2.2 ядерных системах, опции sparse_super и filetype автоматически включены при создании ext2fs. Однако обе эти возможности некорректно поддерживаются ядром pre 2.2, и как результат mount сообщает о проблеме при монтировании ФС на системе 2.0.38 uClinux.

# mke2fs -vm0 -Onone /dev/ram0 1024
mke2fs 1.19, 13-Jul-2000 for EXT2 FS 0.5b, 95/08/09
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
128 inodes, 1024 blocks
0 blocks (0.00%) reserved for the super user
First data block=1
1 block group
8192 blocks per group, 8192 fragments per group
128 inodes per group

Writing inode tables: done
Writing superblocks and filesystem accounting information: done

На следующем шаге мы добавим дополнительные папки и файлы к ramdisk. Это необязательная процедура, поэтому Вы можете ее пропустить. По умолчанию при создании ФС ext2 добавляется директория lost+found. Она появится в /var/lost+found. Также рекоммендуется создать папку tmp. Чтобы добавить файлы и папки, для начала монтируйте ФС как ext2.

# mount -t ext2 /dev/ram0 /mnt/ramdisk
# cd /mnt/ramdisk
# mkdir tmp

Как только Вы закончили, отмонтируйте ФС для обеспечения чистоты монтирования в будущем

# umount /mnt/ramdisk

Теперь время переместить ФС из блочного ram устройства в локальный файл. Копируем ФС байт-к-байту в ramfsraw.img. Для 1MB ramfs, этот файл будет размером в 1MB.

# dd if=/dev/ram of=ramfsraw.img bs=1k count=1024
1024+0 records in
1024+0 records out

Будет очень неэффективно добавлять файл размером 1Mb к romdisk в его текущей форме, и так как большая часть ФС свободна, мы можем очень эффективно сжать, используя ZRLE (Zero Run Length Encoding). Вы можете поискать документ, описывающий процесс “making a file with holes”.

ZRLE записывает блоки с ненулевыми данными, прибавляя к ним длину блока и ее позицию в файле. Так как в основном файл состоит из нулей, утилита обрезает их. Утилита распаковки просто обнулит всю длину распаковываемого файла, и затем скопирует блоки данных обратно в подходящие ячейки.

Сжатие может быть выполнено при помощи утилиты, называемой holes. Существует пара версий, в зависимости от порядка байт (endian) и/или размера блоков. Вы можете скачать исходные коды с ftp://ftp.beyondlogic.org/uClinux/ramfsutils.tar.gz и скомпилировать их. Эти бинарники запустятся на Linux платформе с обратным следованием байт (little endian platforms).

# /holes/holes ramfsraw.img >ramfs.img
Length of original file 1048576

Листинг двух файлов покажет длину сжатия. Теперь мы можем добавить его на наш romdisk, не затрачивая пространства.

# ls ram* -l
-rw-r--r-- 1 root root 2639 Mar 4 16:00 ramfs.img
-rw-r--r-- 1 root root 1048576 Mar 4 15:59 ramfsraw.img

Вы также можете заметить что сгенерированный ramfs.img меньше, чем uClinux ramfs.img (3340). Еще одна причина почему Вы должны обновлять и генерировать самостоятельно. (По некоторым причинам, размер блока run length на запасной ramfs не должна быть больше 200-300 байт даже если expand.c размещает буфер в 2048 байт при распаковке образа).

Тестируем RAM filesystem

Можно протестировать новую ramfs на Вашей uClinux платформе, сначала отмонтируя существующую ramfs, а затем распаковывая и монтируя новую ramfs.

/bin/umount /var
/sbin/expand /ramfs.img /dev/ram0
/bin/mount -t ext2 /dev/ram0 /var

Также Вы возможно захотите протестировать ее на Вашем настольном linux. Копирование файла expand.c из /src/init/ и перекомпиляция для x86 linux приведет к endianness проблеме. Исправьте define в ntohl(), или используйте прекомпилированный бинарник для x86 отсюда (ramfsutils.tar.gz).

Корневая ФС на NFS



Монтирование директории разработки uClinux в /usr поможет Вам ускорить процесс разработки. Это позволит разработчику компилировать бинарники m68k в linux системе и мгновенно иметь их через монтированный NFS том.

Может случится ситуация когда Вы захотите модифицировать другие файлы на FLASH, например, конфигурационные файлы /etc для инетрнет демонов, http web servers. One option is the flash and burn method - изменить файл, пересобрать образ, закачать образ в uClinux систему, задержать воздух и надеяться на то, что изменения верны.

Наиболее скоростной подход и, таким образом, лучший вариант решения проблемы заключается в монтировании корневой директории uClinux как NFS том, которая вытягивается с машины разработки. Это позволит модифицировать любой файл в ФС и иметь их быстродоступными на uClinux системе. Ясно, что это сохранит львинную долю времени.

Установка корневой ФС NFS заставляет немного подумать над ядром. Для начала редактируем файл setup.c в /arch/{arch}/kernel/ и добавляем следующие строки для установки деталей о NFS сервере и хосте в буффер командной строки ядра.

ROOT_DEV = MKDEV(BLKMEM_MAJOR,0);

+ #ifdef CONFIG_ROOT_NFS
+ strcpy(command_line,
+ "root=/dev/nfs "
+ "nfsroot=192.168.0.2:/ucsimm/romdisk "
+ "nfsaddrs=192.168.0.200:192.168.0.2:192.168.0.1:255.255.255.0:"
+ "ucsimm:eth0:none");
+ #endif

/* Keep a copy of command line */
*cmdline_p = &command_line[0];



memcpy(saved_command_line, command_line, sizeof(saved_command_line));

saved_command_line[sizeof(saved_command_line)-1] = 0;

Формат этих параметров

root=/dev/nfs

Используется для доступа к корню NFS. Это не настоящий том, а псевдо-NFS-том.

nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>]

server-ip - это IP адрес NFS сервера, от которого Вы хотите монтировать ФС. Диретория root конкретизирует имя директории на NFS сервере для монтирования корня. Дальше идет запятая и другие страндартные параметры NFS. Парамерты необязательны. Дополнительные параметры могут быть найдены в файле nfsroot.txt как часть документации, которую Вы получаете с ядром.

nfsaddrs=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>

client IP - это IP адрес, который вы хотите присвоить embedded системе uClinux. Обычно присваивается ifattach в rc файле, но это не будет больше доступно локально, Вы должны предоставить эти подробности здесь. server-ip опять конкретизирует IP адрес NFS сервера - шлюз и маска должны быть self explanatory, hostname - это имя Вашей локальной машины, device указывает какой интерфейс устанавливать, тогда как autoconf указывает должны ли BOOTP либо rarp быть использованы.

Эти три параметра связаны вместе. Отделите пробелами каждую команду.

Как только изменения сделаны, и сохранены, начните конфигурировать ядро uClinux для поддержки корневой NFS. Опции можно найти в меню Filesystems - выбрать "NFS filesystem support" и "Root file system on NFS" обе. Затем пересобрать ядро. Как только ФС rom будет монтирована с сервера NFS, уже не нужно генерировать ROM ФС и конкатенировать ее с файлом linux.bin. Просто заргузите файл linux.bin как есть.

Убедитесь что директория, которую Вы указали в exported корректна и сбросьте uClinux. Если все пройдет успешно, загрузка должна быть похожа на

eth0: Attempting TP

eth0: using 10Base-T (RJ-45)

Root-NFS: Got file handle for /ucsimm/romdisk via RPC

VFS: Mounted root (nfs filesystem).

и дать пользователю зарегистироваться в системе как обычно. Если NFS сервер отсутствует, последует следующее сообщение (uClinux будет пытаться соединиться бесконечное количество раз, пока NFS сервер не появится в сети)

eth0: Attempting TP
eth0: using 10Base-T (RJ-45)
NFS server 192.168.0.2 not responding, still trying.

Вы также можете получить сообщение ниже, если exports некорректен или Вы пытаетесь получить директории с другого сервера. В этом случае проверьте файл exports и перезапустите NFS демон. После kernel panic Вам потребуется перезапустить uClinux перед попыткой соединиться снова.

eth0: Attempting TP
eth0: using 10Base-T (RJ-45)
Root-NFS: Server returned error 13 while mounting /ucsimm/romdisk
VFS: Unable to mount root fs via NFS, trying floppy.
VFS: Cannot open root device 02:00
Kernel panic: VFS: Unable to mount root fs on 02:00