- PVSM.RU - https://www.pvsm.ru -
Добрый день/вечер/ночь/утро! Есть один экспериментальный курс по операционным системам. Есть он в Стэнфордском университете. Но часть материалов доступно всем желающим. Помимо слайдов доступны полные описания практических занятий.
Чем этот курс отличается от прочих других? Большая часть кода пишется самостоятельно и выполняется на вполне реальном современном железе. В качестве целевой платформы выбран Raspberry Pi 3 model B [1]. Т.е. достаточно актуальная архитектура AArch64. ARMv8 Cortex-A53, четыре ядра, 64-бита и вот это всё. В качестве основного языка программирования выбран Rust [2]. Который безопасный, быстрый, без GC и так далее. Его, Rust, предполагается изучать во время курса.
Тут есть про диски, файловые системы, операции ввода-вывода, потоки /процессы, планирование, виртуальную память, защита и безопасность, прерывания, параллелизм и синхронизацию. Как и в любом другом, уважающем себя курсе. Разница в актуальности материала и в количестве практики. Коддить придётся много.
Если вы хотели увидеть дословный перевод, то его не будет. Вместо этого я буду стараться сделать текст полезным и понятным. Например в тех местах, которые актуальны только для студентов Стэнфорда, я помещу информацию полезную остальным. Тут может быть немного сленга, чуточку несвязанных с оригиналом иллюстраций и небольшое количество дополнительных комментариев. В угоду читабельности тут не будет явных Примечаний Переводчика™. Текст можно считать художественным переводом или статьёй по мотивам. Я не сварщик — не обижусь.
Откуда я узнал про этот курс? Некто выложил сылочку на Hacker News [3]. Я случайно увидел и проникся. Немного сам тыкал материалы курса и в итоге решился это дело переводить.
В этой части будем настраивать малинку и необходимые инструменты. По итогам у нас будет малинка, мигающая светодиодом. Тут есть четыре основных этапа. Для начала нам требуется убедиться, что связь Pi с компьютером вполне себе работает. Запустим предварительно подготовленную программу. Во втором этапе разберёмся, как светодиодики подключать. Про макетную плату и проводки. На третьем этапе соберём няшный сишный код и запустим его на Pi. Установим кросскомпилятор aarch64-none-elf
и попробуем его в деле. И на четвёртом этапе перепишем это всё дело на Rust.
Парочка полезных ссылок:
Перед выполнением курса следует достать себе в непосредственное пользование unix-подобную операционную систему. Это может быть Linux, BSD или macOS с установленными git
, wget
, tar
, screen
и make
. Теоретически может заработать в Windows 10 с подсистемой linux, но никто не проверял наверняка. По крайней мере такая конфигурация не поддерживается. Т.е. для виндузятников нет готовых рецептов и рекомендуется установить Ubuntu LST [13] или Fedora [14].
Из железа нам потребуются:
В обсуждении на reddit есть ссылочки [15] на amazon с тем, что может потребоваться. Впрочем всё это можно купить в любом другом магазине. В том числе и в офлайне. Кроме этого всего можно докупить ещё каких либо компонентов на свой вкус.
Внимание: малинка чувствительна к электростатике. Старайтесь не трогать голыми руками контакты. Вас током не убьёт и даже не поцарапает, а вот саму малинку вполне себе может вывести из строя. Заземляйтесь.
Когда это всё есть в наличии можно стянуть код задания:
git clone https://web.stanford.edu/class/cs140e/assignments/0-blinky/skeleton.git 0-blinky
cd assignment0
make fetch
Не стесняйтесь исследовать содержимое самостоятельно.
Первое, что нам требуется сделать — это настройка CP2102-переходника. Он нужен для общения между компьютером и Pi. С одной стороны USB, с другой штук пять штырьков, посередине платка.
На линуксе всё должно заработать сразу из коробки. На маках потребуется установить драйвер. Скачиваем этот архив [16] и распаковываем. Запускаем SiLabsUSBDriverDisk.dmg
и соглашаемся с пунктами о продаже души по лицензии. После этого на подмонтированном томе запускаем Silicon Labs VPC Driver.pkg
. Устанавливаем и перезагружаемся.
Попробуйте вставить CP2102 в свободный USB-слот. Если всё работает, то должны появится соответствующие файлы в /dev
. В случае мака /dev/tty.SLAB_USBtoUART
. В случае линукса что-то вроде /dev/ttyUSB0
. Запишите — пригодится. Вынимаем переходник.
Теперь подключаем Raspberry Pi к CP2102. Вот таблица соответствия разъёмов:
Разъёмы на CP2102 | Разъёмы на Raspberry Pi |
---|---|
+5v | 4 |
GND | 6 |
RXD | 8 |
TXD | 10 |
Нумерация пинов на малинке (ещё есть интерактивная [17] версия):
Всё вместе будет выглядеть так (цвета проводов можно выбирать произвольно):
Важно: проверьте и перепроверьте соединения перед подключением этого всего к компьютеру. Нам нужна свежая малинка, а не подгоревшее варенье.
Если есть уверенность в правильности сопряжения малинки и переходника — можно таки подключить CP2102 к компу.
Raspberry Pi подгружает программки с microSD-карточки во время включения. Прямо сейчас мы разберёмся, как это готовить.
Для начала нам нужно скинуть на microSD-шку файлики из склонированного репозитория. А именно те, которые лежат в папке files/firmware
. Т.е. bootcode.bin
, config.txt
и start.elf
. Скопировать их следует в корень флеш-карты. Если вдруг в склонированном репозитории нет этих файликов — вы забыли про make fetch
.
Зачем нужны
bootcode.bin
,config.txt
иstart.elf
?
Это всё является загрузчиком для малинки.bootcode.bin
— первый загрузчик. Его задача — загрузитьstart.elf
. Который настраивает процессор в соответствии с содержимым файлаconfig.txt
. После этого он загружаетkernel8.img
и передаёт ему управление. Кстати где он?
Теперь копируем files/activity-led-blink.bin
из репо в корень флеш-карты и даём этому файлику имя kernel8.img
. Размонтируем карту и вытаскиваем. Убеждаемся, что малинка отключена. Затем вставляем карточку в малинку и подключаем малинку к питанию. Мы должны увидеть мигающий светодиод на малинке и на CP2102-переходнике. Мигание последнего означает, что там передаются некие данные.
Данные? Какие данные? Для того, чтоб посмотреть их нам нужно подключить эмулятор последовательного терминала к CP2102 и прочитать, что там происходит. Будем использовать screen [18] ибо он установлен и на Linux, и на macOS. Вспомните путь к устройству из папки /dev
и запустите
screen /dev/<имя> 115200
В Linux может потребоваться использовать sudo
для запуска этой команды. Впрочем можно добавить своего пользователя в группу dialout
и не писать перед этой командой sudo
постоянно:
sudo gpasswd --add <имя-пользователя> dialout
Так или иначе, но мы должны увидеть приветствия от малинки. Для выхода из screen
следует нажать <ctrl-a> k
, а затем ответить y
на предложение о выходе.
На этом этапе мы подключим 16-й вывод GPIO (физический контакт №36) малинки к светодиодику на макетке. Проверим его работу используя пердварительно подготовленный бинарник с прошивкой. Убедитесь что малинка отключена.
Как следует из названия, GPIO является общим механизмом передачи данных/сигналов между какими либо двумя устройствами через электрические контакты.
GPIO-выводы на малинке могут работать как входы или как выходы. Когда контакт является выходным, его можно включить или выключить. Под включением контакта подразумевается, что с него можно взять 3.3 вольта. Под выключением подразумевается, что через этот контакт ток не идёт. Когда же GPIO-контакт является входным, то малинка проверяет, есть ли на оном 3.3 вольта, либо же нет.
Эти контакты невероятно, умопомрачительно универсальны и их можно использовать для реализации огромного спектра различных функций. Подробности можно узнать в документации [6]. Документация не просто так прилагается. Её можно, а иногда необходимо, читать по ходу курса.
Начнём с построения вот такой схемки:
Если никогда не пользовали макетку, то рекомендуется почитать (или хотяб посмотреть картинки) вот в этом руководсве [5]. На нашей схеме мы подсоединяем светодиодик к контакту 3.3 вольта (вывод №1) и к контакту с нулевым потенциалом (за номером 14). Обратите внимание на правильность подключения светодиода. Более короткая его ножка должна быть подключена через резистор к пину 14 (нулевой потенциал, или земля (ground) по другому). После этого можно подключить малинку к питанию. Светодиод будет гореть (если всё подключено правильно). Если светодиод перевернуть, то он просто не будет гореть. Он в конце концов такой же диод [19], как и любые его друзья.
Если всё заработало с равномерно горящим светодиодом, то можно попробовать оным помигать. Отрубаем малинку от питания. Теперь переподключаем светодиод от пина 1 к пину 36 (GPIO 16) вот таким образом:
Опять вынимаем карту памяти. Копируем на неё files/gpio16-blink.bin
под именем kernel8.img
вместо старого с тем же именем. Ставим карточку обратно и поключаем малинку к питанию. Теперь светодиод должен безудержно мигать.
На этот раз мы будем писать на сях прогу, которая будет делать тоже, что и gpio16-blink.bin
. Для того, чтоб иметь возможность компилировать няшную сишечку под малинку нам нужен кросс-компилятор под aarch64-none-elf
.
Нам нужно установить GNU-тулчейн для под архитектуру aarch64-none-elf
(компилятор gcc и его компанию вроде objcopy).
Для начала стоит установить менеджер пакетов homebrew [20]. Если уже установлен, то эту часть можно пропустить.
xcode-select --install
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Теперь установим тулчейн aarch64-none-elf используя homebrew.
brew tap SergioBenitez/osxct
brew install aarch64-none-elf
Проверим, всё ли правильно установлено:
$ aarch64-none-elf-gcc --version
aarch64-none-elf-gcc (GCC) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
arch64-none-elf
в /usr/local/bin
:
wget https://web.stanford.edu/class/cs140e/files/aarch64-none-elf-linux-x64.tar.gz
tar -xzvf aarch64-none-elf-linux-x64.tar.gz
sudo mv aarch64-none-elf /usr/local/bin
/usr/local/bin/aarch64-none-elf/bin
к переменной окружения PATH
. Как именно — это зависит от вашего конкретного диструбутива Linux. В большинстве случаев следует добавить в ~/.profile
следующее:
PATH="/usr/local/bin/aarch64-none-elf/bin:$PATH"
aarch64-none-elf-gcc --version
Можно собрать самому из исходников, если такое желание возникнет. Подробнее вот тут [22].
Взаимодействие подавляющего большинства современных аппаратных устройств с ПО осуществляется через отображение его в память Memory-mapped I/O [23]. Суть такова: с устройствами можно общаться так, будто это просто некая часть памяти. При этом предоставляется спецификация о том, что будет происходить при записи или чтении определённых адресов в памяти. Адреса обычно разделяются на кусочки по 32 или 64 бита, которые называют регистрами. Регистры могут быть доступны только для чтения из них, для записи или для того и другого сразу.
Как мы узнаем, какие регистры и для чего использовать, и где в памяти они находятся? Производители различных устройств пишут документацию на эти самые устройства. Обычно их зовут даташитами (data sheet), мануалами (device manual), ну или просто документацией. Нет какого либо общего широко распространённого формата для документирования устройств. Иногда документация может быть недостаточной или её может не быть вообще. Умение читать и понимать аппаратную документацию это вполне себе полезный скилл и в некотором роде даже искусство.
Документацию на многие периферийные устройства, которые есть у Rasbperry Pi, можно найти в документе BCM2837 ARM Peripherals Manual [4]. Про GPIO можно почитать на 89 странице.
Падажжи, там же местами про BCM2835, а у нас BCM2837. Это норм?
Если открыть руководство, то там можно увидеть во многих местах упоминание BCM2835. Мы просто взяли руководство к нему и исправили некоторые ошибки. Ну и заголовок поменяли на BCM2837. BCM2837 и BCM2835 имеют одинаковые периферийные устройства с теми же относительными адресами в памяти. Основное отличие в общей конфигурации физической памяти. Базовый физический адрес периферийных устройств на BCM2837 —
0x3F000000
, в отличии от0x20000000
в BCM2835. Однако оба чипа отображают эти адреса на0x7E000000
. В кратце на BCM2837 "переферийный" адрес0x7EXXXXXX
будет находится на физическом адресе0x3FXXXXXX
. Приведённая документация изменена с учётом этого.
Для нашей задачи нам достаточно следующих регистров:
имя | адрес | описание | размер | чтение/запись |
---|---|---|---|---|
GPFSEL1 | 0x7E200004 | GPIO Function Select 1 | 32 бита | и то и другое |
GPSET0 | 0x7E20001C | GPIO Pin Output Set 0 | 32 бита | только запись |
GPCLR0 | 0x7E200028 | GPIO Pin Output Clear 0 | 32 бита | только запись |
Тащемто это непосредсвенно скопированно прямо из документации со страницы 90.
Теперь почитайте документацию для регистра GPFSELn
на страницах 91 и 92. Мы записываем в этот регистр для настройки пинов в качестве выходных или входных. Какое должно быть значение в каждом поле регистра GPFSEL1
для настройки вывода №16 GPIO, чтоб он стал выходом?
Теперь опять читаем документацию на странице 95. Про регистры GPSET0
и GPCLR0
. Мы записываем в регистр GPSET0
для включения контакта. А в GPCLR0
для выключения. Какое значение нам требуется записать в эти регистры для включения/выключения вывода 16?
В каталоге phase3/
репы есть заготовка кода для построения двоичного файла для малинки. Пока обойдёмся без объяснения того, зачем нужны crt0.S
, layout.ld
и Makefile
. Вместо этого сосредоточимся на blinky.c
. В нём вы обнаружите, что уже указаны адреса всех трёх необходимых нам регистров в верхней части. Кроме этого там есть парочка функций, которыми можно создать временную задержку. Задача состоит в том, чтоб дополнить функцию main
так, чтобы вывод №16 GPIO был настроен как выход, а затем то включался, то выключался для мигания светодиодом.
Когда код будет готов — его следует протестировать. Для начала скомпилируйте его, запустив make
, находясь в дириктории phase3/
. Если всё хорошо и нет ошибок, то создастся файлик blinky.bin
. Переименовываем его в kernel8.img
, копируем на microSD-карточку и запускаем это всё на малинке. Если уже есть работающий kernel8.img
— можно переходить к следующей фазе.
Подсказки:
Настройка/включение/выключение пинов могут быть реализованы за одну строку кода.
Тут пригодятся операторы
<<
,|
,&
и~
.В сишечке можно использовать шестнадцатеричную и двоичную формы. Для числа три что-то вроде
0x03
и0b011
соответственно.
В этот раз мы будем писать программу, подобную gpio16-blink.bin
, но уже на Rust. Код пишем в phase4/src/lib.rs
.
Для того, чтоб компилять программы на Rust, нам следует этот самый компейлятор установить. Помимо этого мы установим xargo
, который является обёрткой, связанной с менеджером пакетов cargo
. Xargo позволяет нам компейлировать наш код для Rasbperry Pi и всего такого.
rustup
. Убедитесь, что Rust был установлен корректно, запустив rustc --version
.rustup
и cargo
(который установился вместе с rustc на прошлом шаге) для установки ночной сборки Rust. Заодно и исходники стандартной библиотеки установим. И xargo
разумеется.
rustup default nightly-2018-01-09
rustup component add rust-src
cargo install xargo
$ rustc --version
rustc 1.25.0-nightly (b5392f545 2018-01-08)
$ xargo --version
xargo 0.3.10
cargo 0.25.0-nightly (a88fbace4 2017-12-29)
Теперь у нас есть вполне себе рабочий компилятор Rust.
Для написания кода в файле phase4/src/lib.rs
нужно знать по крайней мере следующие конструкции:
1) Вы можете читать из и писать в то, что находится за голыми указателями (*mut T
) при помощи методов read_volatile()
и write_volatile()
. Например у нас объявлено такое:
const A: *mut u32 = 0x12 as *mut u32;
const B: *mut u32 = 0x34 as *mut u32;
Мы можем записать 32-разрядное целое число без знака с адресом 0x12
в ячейку с адресом 0x34
примерно вот так:
B.write_volatile(A.read_volatile());
2) Локальные переменные обявляются при помощи конструкции let имя_переменной = некое_выражение;
.
Можно прочитать A
из предыдущего примера (т.е. значение, расположенное по адресу 0x12
) в переменную вот таким образом:
let value = A.read_volatile();
3) Вызвать функцию fn f(param: usize);
можно вот так: f(123);
.
4) Блок loop
можно использовать для бесконечного повторения чего либо:
loop {
do_this_again_and_again();
}
5) В Rust есть следующие побитовые операторы:
!
— инверсия<<
— сдвиг влево>>
— сдвиг вправо|
— битовое ИЛИ&
— битовый И.Теперь вы готовы помигать светодиодом из кода на Rust. Код пишем в phase4/src/lib.rs
. Переведите сишный код в аналогичный rust-код (в функции kmain
). Тут уже объявлены необходимые регистры и функция "сна", которая создаёт задержку на некоторое время. Используйте это всё.
Когда будете готовы проверить свою прогу, скомпилируйте её, запустив make
в каталоге phase4
. Если всё нормально, то создастся файл build/blinky.bin
, который переименовываем в kernel8.img
и кладём на microSD-карточку, которую затем вставляем в малинку. Когда светодиодик снова замигает — можно считать, что данная часть туториала завершена.
Автор: lain8dono
Источник [25]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/os/274853
Ссылки в тексте:
[1] Raspberry Pi 3 model B: https://www.raspberrypi.org/products/raspberry-pi-3-model-b/
[2] Rust: https://www.rust-lang.org/
[3] сылочку на Hacker News: https://news.ycombinator.com/item?id=16134618
[4] Документация по процессору BCM2837: https://web.stanford.edu/class/cs140e/docs/BCM2837-ARM-Peripherals.pdf
[5] Красочная исчерпывающая статья: https://learn.sparkfun.com/tutorials/how-to-use-a-breadboard
[6] Про использование GPIO в малинке: https://www.raspberrypi.org/documentation/usage/gpio-plus-and-raspi2/README.md
[7] Кратко про базовый синтаксис Rust: https://learnxinyminutes.com/docs/rust/
[8] Оффициальный учебник по Rust: https://doc.rust-lang.org/book/second-edition/
[9] русскоязычная версия: https://github.com/ruRust/rust_book_2ed
[10] Rusty Types for Solid Safety: https://web.stanford.edu/class/cs140e/notes/lec2/paper.pdf
[11] Шпаргалка по лайфтаймам в Rust: https://habrahabr.ru/post/322140/
[12] Шпаргалка по контейнерам в Rust: https://docs.google.com/presentation/d/1q-c7UAyrUlM-eZyTo1pd8SZ0qwA_wYxmPZVOQkoDmH4/present?token=AC4w5VgEyYo5AyyCd0GYMh-mP8O3hNlQQA%3A1518749570893&includes_info_params=1#slide=id.p
[13] Ubuntu LST: https://www.ubuntu.com/download/desktop
[14] Fedora: https://getfedora.org/en/workstation/download/
[15] ссылочки: https://www.reddit.com/r/cs140e/comments/7ql4fw/info_general_discussion/dsq6uhz/
[16] этот архив: https://www.silabs.com/documents/public/software/Mac_OSX_VCP_Driver.zip
[17] интерактивная: https://pinout.xyz/
[18] screen: https://www.gnu.org/software/screen/manual/screen.html#Overview
[19] диод: https://ru.wikipedia.org/wiki/%D0%94%D0%B8%D0%BE%D0%B4
[20] homebrew: https://brew.sh/
[21] aarch64-none-elf-linux-x64.tar.gz: https://web.stanford.edu/class/cs140e/files/aarch64-none-elf-linux-x64.tar.gz
[22] вот тут: https://wiki.osdev.org/GCC_Cross-Compiler
[23] Memory-mapped I/O: https://en.wikipedia.org/wiki/Memory-mapped_I/O
[24] https://rustup.rs/: https://rustup.rs/
[25] Источник: https://habrahabr.ru/post/349248/?utm_source=habrahabr&utm_medium=rss&utm_campaign=349248
Нажмите здесь для печати.