Ослепительное разнообразие приложений

Более богатая среда выполнения

Мы уже предоставили пользовательским программам доступ к IOE в виде системных вызовов и файлов, а также выполнили низкоуровневую инкапсуляцию через NDL. Однако для некоторых более сложных программ программирование напрямую с использованием NDL всё ещё довольно затруднительно. Чтобы лучше поддерживать разработку и выполнение таких сложных программ, нам необходимо предоставить библиотеки более высокого уровня.

Мультимедийная библиотека

В Linux существует целый ряд GUI-программ, разработанных с использованием библиотеки SDL. В Navy есть библиотека miniSDL, которая предоставляет некоторые API, совместимые с SDL, благодаря чему эти GUI-программы можно легко перенести в Navy. Код miniSDL находится в каталоге navy-apps/libs/libminiSDL/ и состоит из 6 модулей:

  • timer.c: управление таймером
  • event.c: обработка событий
  • video.c: интерфейс отрисовки
  • file.c: абстракция файлов
  • audio.c: воспроизведение аудио
  • general.c: общие функции, включая инициализацию, обработку ошибок и т.д.

Мы можем использовать NDL для поддержки низкоуровневой реализации miniSDL, чтобы miniSDL предоставляла пользовательским программам более богатую функциональность — тогда мы сможем запускать в Navy более сложные программы. API в miniSDL называются так же, как в SDL, и вы можете обратиться к RTFMоткрыть в новом окне, чтобы узнать точное поведение этих API. Кроме того, большая часть API в miniSDL не реализована, поэтому лучше придумать способ, чтобы программа предупреждала вас, когда используется нереализованный API — иначе вам будет сложно понять вызванное этим непредсказуемое поведение сложных программ.

Обязательно изучите поведение SDL API через RTFM

В методичке мы лишь кратко опишем назначение этих API — обязательно обратитесь к руководству SDL, чтобы понять их точное поведение.

Арифметика с фиксированной точкой

В логике некоторых программ используются вещественные числа. Современные реальные компьютерные системы, как правило, оснащены FPU, поэтому разработчики обычно тоже выбирают числа с плавающей точкой для представления таких чисел. Однако стандарт чисел с плавающей точкой слишком сложен для учебной компьютерной системы, особенно если учесть ситуацию с самодельными процессорами: корректно реализовать FPU в железе — крайне трудная задача. Поэтому во всей системе Project-N мы не планируем вводить числа с плавающей точкой: в NEMU нет FPU, выполнение операций с плавающей точкой в AM является неопределённым поведением (UB), Nanos-lite считает регистры с плавающей точкой не входящими в контекст, а Navy не предоставляет среду выполнения, связанную с числами с плавающей точкой (при компиляции Newlib мы определили макрос NO_FLOATING_POINT).

Если мы сможем реализовать логику программы другим способом, то у всех этих крутых программ появится шанс запуститься на спроектированном вами самими процессоре. На самом деле числа с плавающей точкой — не единственный способ представления вещественных чисел, для этого подходят и числа с фиксированной точкой! Более того, арифметику с фиксированной точкой можно реализовать через целочисленную арифметику, а значит, мы можем реализовать логику вещественных чисел с помощью инструкций целочисленной арифметики, не вводя FPU в железо для запуска таких программ. Такая система арифметики называется арифметикой с фиксированной точкойоткрыть в новом окне.

В Navy предоставляется библиотека fixedptc, специально предназначенная для арифметики с фиксированной точкой. По умолчанию библиотека fixedptc использует 32-битное целое число для представления вещественных чисел в формате «24.8» (см. navy-apps/libs/libfixedptc/include/fixedptc.h): целая часть занимает 24 бита, дробная — 8 бит. Можно также считать, что десятичная точка вещественного числа всегда зафиксирована слева от 8-го двоичного разряда. В библиотеке определён тип fixedpt для представления чисел с фиксированной точкой — как видно, по сути это тип int32_t.

31  30                           8          0
+----+---------------------------+----------+
|sign|          integer          | fraction |
+----+---------------------------+----------+

Таким образом, для вещественного числа a его представление в типе fixedpt — это A = a * 2^8 (с усечением дробной части результата). Например, вещественные числа 1.2 и 5.6, приближённо представленные типом FLOAT, выглядят так:

1.2 * 2^8 = 307 = 0x133
+----+---------------------------+----------+
| 0  |             1             |    33    |
+----+---------------------------+----------+


5.6 * 2^8 = 1433 = 0x599
+----+---------------------------+----------+
| 0  |             5             |    99    |
+----+---------------------------+----------+

На самом же деле вещественные числа (истинные значения), которые представляют эти два значения типа fixedpt, равны:

0x133 / 2^8 = 1.19921875
0x599 / 2^8 = 5.59765625

Для отрицательных вещественных чисел мы используем противоположное значение соответствующего положительного числа, например, представление -1.2 в типе fixedpt:

-(1.2 * 2^8) = -0x133 = 0xfffffecd

В PA2 мы рассказали об архитектуре native в AM: благодаря абстракции AM API мы можем сначала запускать написанные нами программы на native, что позволяет эффективно отделять аппаратные (NEMU) баги от программных. А можем ли мы добиться похожего эффекта в Navy?

Ответ определённо да, потому что подобный эффект — это подарок, который даёт нам представление о компьютере как об абстрактном слое. Среда выполнения, предоставляемая Navy, включает libos, libc (Newlib), некоторые специальные файлы, а также различные библиотеки, ориентированные на приложения. Первые три мы называем «средой выполнения, связанной с операционной системой», в то время как библиотеки, ориентированные на приложения, слабо связаны с операционной системой — в рамках этого обсуждения мы можем даже отнести их к категории приложений Navy. Подобно тому, как в AM с помощью возможностей Linux native реализуется API AM, мы также можем с помощью возможностей Linux native реализовать вышеупомянутую среду выполнения, чтобы поддерживать запуск тех же приложений Navy и тестировать их по отдельности. Так мы добиваемся разделения среды выполнения, связанной с операционной системой, и приложений Navy.

В Navy мы предоставляем специальную ISA под названием native для реализации вышеупомянутого разделения. Её отличия от других ISA заключаются в следующем:

  • При компоновке она обходит libos и Newlib, позволяя приложению линковаться напрямую с glibc Linux
  • Функциональность специальных файлов, таких как /dev/events, /dev/fb, реализуется через некоторые механизмы Linux native (см. navy-apps/libs/libos/src/native.cpp)
  • Приложения, скомпилированные под Navy native, можно запускать напрямую и отлаживать с помощью gdb (см. navy-apps/scripts/native.mk), в то время как приложения, скомпилированные под другие ISA, могут работать только при поддержке Nanos-lite

Хотя native в Navy и native в AM называются одинаково, механизмы у них разные: система, работающая на AM native, нуждается в таких слоях абстракции, как AM, Nanos-lite, libos, libc, для поддержки упомянутой среды выполнения. ARCH=native в AM соответствует ISA=am_native в Navy; а в Navy native вышеупомянутая среда выполнения реализуется напрямую средствами Linux native.

В каталоге, где находится bmp-test, можно выполнить make ISA=native run, чтобы скомпилировать bmp-test под Navy native и сразу запустить его, а также отладить через make ISA=native gdb. Таким образом, вы можете отдельно протестировать в среде Linux native весь код Navy, кроме libos и Newlib (например, NDL и miniSDL). Исключением является dummy в Navy: поскольку он напрямую вызывает системные вызовы через _syscall_(), такой код не может напрямую работать в Linux native, так как в Linux этого системного вызова не существует (либо у него другой номер).

Волшебство LD_PRELOAD

bmp-test должен открыть файл по пути /share/pictures/projectn.bmp, но в Linux native файла по этому пути не существует. Тем не менее нам всё равно удалось успешно запустить bmp-test — знаете ли вы, как это достигается? Если вам интересно, можете поискать в интернете информацию о LD_PRELOAD.

Wine, WSL и совместимость сред выполнения

Мы можем реализовать среду выполнения Navy через Linux native, что позволяет приложениям Navy работать в Linux native. А можем ли мы реализовать среду выполнения других операционных систем — например, предоставить в Linux среду выполнения, совместимую с Windows, чтобы поддерживать запуск Windows-приложений в Linux?

Именно таким проектом является Wineоткрыть в новом окне — он реализует API, связанные с Windows, через среду выполнения Linux. Другой проект противоположного направления — WSLоткрыть в новом окне: он реализует API Linux через среду выполнения Windows, поддерживая запуск программ Linux в Windows, при этом WSL ещё и модифицирует ядро Windows, чтобы обеспечить специальную поддержку программ Linux. Однако полноценные среды выполнения Linux и Windows слишком сложны, поэтому некоторые программы со сложными зависимостями от среды выполнения до сих пор с трудом идеально работают под Wine или WSL — настолько, что WSL2 отказался от технического подхода «совместимости среды выполнения» и вместо этого перешёл на виртуальную машину для полноценного запуска системы Linux. Среда же выполнения Navy, напротив, очень проста — мы реализовали её менее чем в 300 строках native.cpp. Но если вы поймёте заложенные в ней концепции, вы также поймёте, как работают близкие вам технологии вроде WSL.

Приложения в Navy

Имея эти библиотеки функций, мы можем запускать в Navy больше программ. Для запуска Chinese Paladin потребуется реализовать довольно много функций, поэтому сначала запустим несколько простых программ, чтобы протестировать вашу реализацию.

NSlider (NJU Slider)

NSlider — это простейшее демонстрационное приложение в Navy, представляющее собой слайд-плеер с поддержкой перелистывания страниц. На втором конкурсе Loongson Cup в 2018 году команда Nanjing University, запустив NSlider на разработанном ими самими процессоре с внеочередным исполнением, достигла цели «показать слайды на созданной ими самими полностековой компьютерной системе прямо во время финальной защиты проекта».

Теперь вы тоже можете запустить NSlider на созданной вами самими компьютерной системе, но для начала нужно реализовать API SDL_UpdateRect(). Модуль отрисовки SDL вводит понятие Surface, которое можно рассматривать как холст с различными атрибутами — подробности о значениях полей структуры Surface можно узнать через RTFM. Назначение SDL_UpdateRect() — синхронизировать указанную прямоугольную область холста с экраном.

Запустите NSlider

Мы предоставляем скрипт, который конвертирует PDF-версию слайдов с соотношением сторон 4:3 в изображения BMP и копирует их в navy-apps/fsimg/. Вам нужно предоставить подходящий PDF-файл, а затем действовать согласно соответствующему README. При конвертации вы можете столкнуться с проблемами — решите их самостоятельно.

Затем реализуйте SDL_UpdateRect() в miniSDL. Если реализация верна, при запуске NSlider отобразится первый слайд. Вероятно, это ваше первое знакомство с API SDL, поэтому вам понадобится RTFM, а также RTFSC, чтобы понять поведение уже существующего кода.

Обратите внимание на размер образа ramdisk

Мы линкуем содержимое образа ramdisk в сегмент данных Nanos-lite, а пользовательскую программу загружаем в область памяти около 0x3000000 (x86) или 0x83000000 (mips32 или riscv32) — это подразумевает допущение: размер образа ramdisk не должен превышать 48 МБ. Если это допущение нарушается, содержимое ramdisk может быть перезаписано, что приведёт к труднообъяснимым ошибкам. Поэтому следите за размером образа ramdisk и не помещайте туда слишком много или слишком больших файлов.

Запустите NSlider (2)

Реализуйте в miniSDL функцию SDL_WaitEvent(), которая используется для ожидания события. Вам нужно упаковать события, предоставляемые NDL, в события SDL и вернуть их приложению — понять формат событий SDL можно, прочитав код NSlider. После правильной реализации вы сможете листать страницы в NSlider; способ перелистывания уточните через RTFSC.

Загрузочное меню — ещё одна программа с относительно простым поведением: она показывает меню, в котором пользователь может выбрать, какую программу запустить. Для его работы вам также нужно реализовать в miniSDL два API, связанных с отрисовкой:

  • SDL_FillRect(): заполнить указанную прямоугольную область холста заданным цветом
  • SDL_BlitSurface(): скопировать указанную прямоугольную область одного холста в заданную позицию другого холста

Загрузочное меню также отображает некоторые английские шрифты — информация об этих шрифтах хранится в формате BDF, а Navy предоставляет библиотеку libbdf для разбора формата BDF, генерации пиксельной информации соответствующих символов и упаковки её в Surface SDL. После реализации SDL_BlitSurface() мы сможем удобно выводить на экран пиксельную информацию строк.

Запустите загрузочное меню

После правильной реализации указанных выше API вы увидите загрузочное меню, в котором можно перелистывать страницы. Однако при попытке выбрать пункт меню возникнет ошибка — работа загрузочного меню также требует поддержки некоторых системных вызовов, о которых мы расскажем далее. Пока что достаточно протестировать miniSDL с помощью загрузочного меню.

NTerm (NJU Terminal)

NTerm — это эмулятор терминала, реализующий базовую функциональность терминала, включая ввод и удаление символов, а также получение команд. Терминал обычно используется вместе с Shell: команды, полученные от терминала, передаются Shell для обработки, а Shell выводит информацию обратно в терминал. NTerm поставляется с очень простым встроенным Shell (см. builtin-sh.cpp), который по умолчанию игнорирует все команды. NTerm также может взаимодействовать с внешними программами, но это выходит за рамки курса ICS, и в PA мы не будем использовать эту возможность.

Чтобы запустить NTerm, вам также нужно реализовать два API miniSDL:

  • SDL_GetTicks(): по функциональности похож на NDL_GetTicks(), но с одним дополнительным небольшим требованием — подробности см. RTFM
  • SDL_PollEvent(): в отличие от SDL_WaitEvent(), он немедленно возвращает управление, если в данный момент нет никаких событий

Запустите NTerm

После правильной реализации указанных выше API вы увидите, что курсор NTerm мигает с частотой раз в секунду, и сможете вводить символы. Чтобы NTerm мог запускать другие программы, вам также нужно реализовать несколько системных вызовов, о которых мы расскажем далее.

Реализуйте встроенную команду echo

Разбор команд во встроенном Shell очень похож на разбор команд, который вы реализовывали для простого отладчика в PA1, а Newlib в Navy уже предоставляет функции стандартной библиотеки. Желающие могут реализовать встроенную команду echo.

Flappy Bird

Один из энтузиастов разработал игру Flappy Bird на основе библиотеки SDL — sdlbirdоткрыть в новом окне, и мы без труда перенесли её в Navy. Выполнив make init в каталоге navy-apps/apps/bird/, вы склонируете перенесённый проект с github. Этот перенесённый проект по-прежнему можно запустить в Linux native: достаточно выполнить make run в каталоге navy-apps/apps/bird/repo/ (возможно, потребуется установить некоторые библиотеки — уточните через STFW). При таком способе запуска не линкуется ни одна библиотека Navy, поэтому вы даже услышите звуковые эффекты и сможете играть, кликая мышью.

Чтобы запустить Flappy Bird в Navy, вам также нужно реализовать один API из другой библиотеки — SDL_image: IMG_Load(). Эта библиотека реализована на основе библиотеки декодирования изображений из проекта stbоткрыть в новом окне и служит для упаковки декодированных пикселей в структуру Surface SDL, чтобы приложение могло легко отображать изображения на экране. Указанный API принимает путь к файлу изображения, а затем упаковывает информацию о пикселях изображения в структуру Surface SDL и возвращает её. Один из способов реализации этого API:

  1. Открыть файл с помощью файловых операций libc и получить размер файла size
  2. Выделить область памяти buf размером size
  3. Прочитать весь файл в buf
  4. Вызвать STBIMG_LoadFromMemory() с параметрами buf и size — функция вернёт указатель на структуру SDL_Surface
  5. Закрыть файл, освободить выделенную память
  6. Вернуть указатель на структуру SDL_Surface

Запустите Flappy Bird

Реализуйте IMG_Load() и запустите Flappy Bird в Navy. По сути, это упражнение на файловые операции. Кроме того, по умолчанию Flappy Bird использует высоту экрана 400 пикселей, а высота экрана NEMU по умолчанию — 300 пикселей. Чтобы запустить Flappy Bird в NEMU, нужно изменить SCREEN_HEIGHT в navy-apps/apps/bird/repo/include/Video.h на 300.

По умолчанию Flappy Bird также попытается открыть звуковую карту для воспроизведения звуковых эффектов; miniSDL по умолчанию заставляет связанные с аудио API возвращать 0 или NULL, программа сочтёт соответствующую операцию неудачной, но всё равно сможет работать без звуковых эффектов.

Кроме того, Flappy Bird — это проект, подходящий для чтения: чтобы разобраться в нём, не требуется много специальных знаний, а правила игры легко усваиваются, после чего можно изучить, как игровые эффекты реализованы в коде.

Применение принципа «компьютер — это абстрактный слой»: перенос и тестирование

При переносе игры мы последовательно запускаем её в четырёх окружениях:

  • Чистый Linux native: никак не связан с компонентами Project-N, используется для того, чтобы убедиться, что сама игра действительно корректно работает. После смены версий библиотек или изменения кода игры мы сначала тестируем на Linux native.
  • native в Navy: заменяем библиотеки Linux native на библиотеки Navy, проверяем, работает ли игра корректно при поддержке библиотек Navy.
  • native в AM: заменяем системные вызовы и glibc Linux на Nanos-lite, libos и Newlib, проверяем, работает ли игра корректно при поддержке Nanos-lite и его среды выполнения.
  • NEMU: заменяем настоящее железо на NEMU, проверяем, работает ли игра корректно при поддержке NEMU.

С помощью такого метода мы можем быстро найти уровень абстракции, на котором находится баг. Возможность так действовать обеспечивается благодаря выводу «компьютер — это абстрактный слой»: мы можем заменить всё, что находится ниже определённого уровня абстракции, на надёжную реализацию, сначала независимо протестировать ненадёжную реализацию одного уровня абстракции, а затем поочерёдно подставлять и тестировать ненадёжные реализации других уровней абстракции. Однако это требует, чтобы весь написанный вами код был переносимым, иначе замена уровней абстракции окажется невозможной.

PAL (Chinese Paladin)

Оригинальная версия Chinese Paladin была разработана для платформы Windows, поэтому она не может работать в GNU/Linux (знаете почему?), а также не может работать в Navy-apps. Энтузиасты разработали кроссплатформенную версию Chinese Paladin на основе библиотеки SDL — проект называется SDLPALоткрыть в новом окне. Мы уже перенесли SDLPAL в Navy: выполнив make init в каталоге navy-apps/apps/pal/, вы склонируете перенесённый проект с github. Как и в случае с Flappy Bird, этот перенесённый проект по-прежнему можно запустить в Linux native: распакуйте файлы данных Chinese Paladin (ссылку мы опубликовали в объявлении в группе курса) и поместите их в каталог repo/data/, затем выполните make run в каталоге repo/ — можно развернуть окно на весь экран, чтобы играть. Однако мы изменили частоту дискретизации аудио SampleRate в конфигурационном файле sdlpal.cfg на 11025, чтобы игра работала достаточно плавно в Navy; если у вас повышенные требования к качеству звука, при работе в Linux native можно временно вернуть значение 44100. Подробнее см. README.

Я не студент Nanjing University, как мне получить файлы данных Chinese Paladin?

Поскольку авторские права на файлы данных принадлежат игровой компании, нам неудобно публиковать их открыто. Однако как классическая игра с 25-летней историей, она всё же, скорее всего, найдётся через STFW.

Кроме того, вам нужно создать конфигурационный файл sdlpal.cfg и добавить в него следующее содержимое:

OPLSampleRate=11025
SampleRate=11025
WindowHeight=200
WindowWidth=320

Подробнее см. repo/docs/README.md и repo/docs/sdlpal.cfg.example.

Чтобы запустить Chinese Paladin в Navy, вам также нужно расширить функциональность API отрисовки в miniSDL. А именно: поскольку это игра из 90-х годов прошлого века, при отрисовке каждый пиксель представлен 8 битами, а не широко используемым сегодня 32-битным форматом 00RRGGBB. При этом эти 8 бит — не настоящий цвет, а индекс в массиве, называемом «палитрой» (palette), и уже в самой палитре хранятся 32-битные цвета. В коде это можно выразить так:

// Теперь в массиве пикселей напрямую хранится 32-битная информация о цвете
uint32_t color_xy = pixels[x][y];

// В массиве пикселей Chinese Paladin хранятся 8-битные индексы палитры,
// и только используя этот индекс для поиска в палитре, можно получить 32-битную информацию о цвете
uint32_t pal_color_xy = palette[pixels[x][y]];

Код Chinese Paladin создаёт структуры Surface в 8-битном пиксельном формате и обрабатывает их через соответствующие API. Поэтому вам также нужно добавить в соответствующие API miniSDL поддержку таких структур Surface в 8-битном пиксельном формате.

Запустите Chinese Paladin

Добавьте в API отрисовки miniSDL поддержку 8-битного пиксельного формата. После правильной реализации вы увидите игровой экран. Для управления вам также нужно реализовать другие API — какие именно, определите самостоятельно. После правильной реализации вы сможете запустить Chinese Paladin на созданном вами самими NEMU! Об управлении в игре читайте здесьоткрыть в новом окне.

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

  1. Механический лабиринт без врагов
  2. Сюжет без анимации
  3. Сюжет с анимацией
  4. Лабиринт, где герой уже попал в поле зрения врага
  5. Лабиринт, где герой ещё не попал в поле зрения врага

pal

Как работает каркас Chinese Paladin?

В PA2 мы обсуждали базовый каркас игры. Попробуйте прочитать код Chinese Paladin и выяснить, какими функциями реализован этот базовый каркас. Найдя их, вы, возможно, получите некоторую помощь при отладке Chinese Paladin. Хотя кода в Chinese Paladin довольно много, чтобы ответить на этот вопрос, вам не нужно читать большой объём кода.

Скриптовый движок Chinese Paladin

В navy-apps/apps/pal/repo/src/game/script.c есть функция PAL_InterpretInstruction() — попробуйте примерно разобраться в назначении и поведении этой функции. А затем смело предположите, как разработчики Chinese Paladin создавали эту игру? Появилось ли у вас новое понимание «игрового движка»?

Больше не загадочные читы

В интернете ходят некоторые читы для Chinese Paladin, вот несколько из них:

  1. Многие, придя к тётушке Юнь, забирают деньги три раза, хотя на самом деле достаточно забрать один раз, чтобы копилка переполнилась! Заберите деньги один раз, потратьте их на покупку меча, пока не останется чуть больше тысячи, затем идите к даосу — но не поднимайтесь наверх, а зайдите к хозяину лавки купить вина; купив вино несколько раз, вы обнаружите, что деньги больше никогда не заканчиваются.
  2. Постоянно используйте приём «Цянь Кунь И Чжи» (Qian Kun Yi Zhi, требуется более 5000 вэней) до тех пор, пока состояние не опустится ниже 5000 вэней — деньги резко вырастут до максимума, и так у вас появятся неисчерпаемые средства.
  3. Когда Ли Сяояо достигнет 99 уровня, используйте 5–10 Королей золотого шелкопряда — очки опыта снова «выбегут», а опыт, необходимый для повышения уровня, вернётся к значениям начальных 5–10 уровней; после этого можно бить врагов или использовать Королей золотого шелкопряда для прокачки и выучить заклинания Лин Эр (начиная с «Пяти стихий, обращённых к первоначалу»); поднявшись до 199 уровня, снова используйте 5–10 Королей золотого шелкопряда — очки опыта снова «выбегут», требуемый опыт также будет очень низким, можно выучить заклинания Юэ Жу (начиная с «Пальца одного ян»); достигнув 299 уровня, используйте 10–30 Королей золотого шелкопряда — после того как очки опыта появятся, продолжайте прокачку, можно выучить заклинания А Ну (начиная с «Десяти тысяч муравьёв, пожирающих слона»).

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

am-kernels

В PA2 вы уже запускали некоторые приложения на AM, и мы также можем без труда запустить их в Navy. На самом деле AM может работать в любом окружении, лишь бы оно поддерживало реализацию API AM. В Navy есть библиотека libam, которая как раз реализует API AM. navy-apps/apps/am-kernels/Makefile добавит libam в список для компоновки, и после этого AM API, вызываемые в AM-приложении, будут линковаться с libam, а эти API, в свою очередь, реализуются через среду выполнения Navy — так мы сможем запускать в Navy различные AM-приложения.

Реализуйте AM в Navy

Реализуйте TRM и IOE в libam, а затем запустите в Navy несколько AM-приложений. Указанный выше Makefile может скомпилировать в Navy coremark, dhrystone и мини-игру на набор текста, но сначала нужно проверить, правильно ли задана переменная AM_KERNELS_PATH. Как и раньше при запуске cpu-tests, вы можете указывать объект компиляции через ALL, например make ISA=native ALL=coremark run или make ISA=x86 ALL=typing-game install.

Запуск microbench в Navy

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

FCEUX

После реализации libam FCEUX тоже сможет работать в Navy.

Запустите FCEUX

Для успешной компиляции вам, возможно, придётся изменить переменную FCEUX_PATH в Makefile, чтобы она указывала на правильный путь. Кроме того, при компиляции FCEUX через Navy мы отключили звуковые эффекты, поэтому вам не нужно реализовывать в libam абстракции, связанные со звуковой картой.

Как запустить Nanos-lite в Navy?

Раз уж мы можем запускать в Navy основанный на AM FCEUX, то ради похвастаться запустить Nanos-lite в Navy тоже не является невозможным. Подумайте: что ещё нам понадобится, если мы захотим реализовать CTE в Navy?

oslab0

Прелесть AM не только в том, что она удобно поддерживает архитектуры, но и в том, что добавление новых приложений даётся легко. Ваши старшекурсники на курсе ОС написали несколько небольших игр на основе AM, и поскольку их API не менялись, мы можем без труда перенести эти игры в PA. Конечно, в следующем семестре на курсе ОС вы тоже сможете сделать так же.

Мы собрали часть игр в

https://github.com/NJU-ProjectN/oslab0-collection

Код игр можно получить, выполнив make init в каталоге navy-apps/apps/oslab0/. Вы можете скомпилировать их под AM и запустить — подробности см. в соответствующем README. Также их можно скомпилировать под Navy, например, выполнив make ISA=native ALL=161220016 в каталоге navy-apps/apps/oslab0/.

Игры, «рождённые в будущем»

Попробуйте запустить в Navy игры, написанные вашими старшекурсниками; описание игр и управление можно найти в соответствующем README.

RTFSC???

Сообразительный вы, возможно, подумаете: ух ты, значит, в следующем семестре на oslab0 у меня уже будет отличный код для справки? Однако мы уже подвергли опубликованный код особой обработке. Отбросив досаду, подумайте: как бы вы реализовали эту особую обработку, если бы это было нужно сделать вам самим? Есть ли в этом что-то похожее на вычисление выражений из PA1?

NPlayer (NJU Player)

Этот раздел необязателен

Предварительное условие: реализовать звуковую карту в PA2.

NPlayer — это музыкальный плеер (возможно, в будущем появится поддержка видео), который можно считать урезанной версией MPlayer для Linux; он поддерживает регулировку громкости и визуализацию аудио. Вы уже реализовали устройство звуковой карты в PA2 и предоставили соответствующую абстракцию IOE в AM. Чтобы программы в Navy могли использовать звуковую карту, нам нужно предоставить в среде выполнения Navy соответствующую функциональность — этот процесс очень похож на реализацию функций, связанных с отрисовкой.

Среда выполнения, связанная с аудио, включает следующее:

  • Файлы устройств. Nanos-lite и Navy договариваются предоставлять следующие файлы устройств:
    • /dev/sb: этот файл устройства должен поддерживать операцию записи, позволяя приложению записывать декодированные аудиоданные в потоковый буфер звуковой карты и воспроизводить их, но не поддерживает lseek, поскольку поток аудиоданных перестаёт существовать после воспроизведения, а значит, понятия «позиции» здесь нет. Кроме того, запись в это устройство блокирующая: если в потоковом буфере звуковой карты недостаточно свободного места, операция записи будет ждать и вернёт управление только после того, как аудиоданные будут полностью записаны в потоковый буфер.
    • /dev/sbctl: этот файл устройства используется для управления звуковой картой и запроса её состояния. При записи он используется для инициализации устройства звуковой карты: приложению нужно за один раз записать 3 целых числа типа int, всего 12 байт, эти 3 числа будут интерпретированы по порядку как freq, channels, samples для инициализации устройства звуковой карты; при чтении он используется для запроса состояния устройства звуковой карты: приложение может прочитать одно целое число типа int, обозначающее текущее число свободных байт в потоковом буфере устройства звуковой карты. Это устройство не поддерживает lseek.
  • NDL API. NDL инкапсулирует вышеописанные файлы устройств, связанные с аудио, и предоставляет следующие API:
// Открыть аудио-функциональность, инициализировать устройство звуковой карты
void NDL_OpenAudio(int freq, int channels, int samples);

// Закрыть аудио-функциональность
void NDL_CloseAudio();

// Воспроизвести аудиоданные длиной `len` байт из буфера `buf`, вернуть количество успешно воспроизведённых байт аудиоданных
int NDL_PlayAudio(void *buf, int len);

// Вернуть текущее количество свободных байт в потоковом буфере устройства звуковой карты
int NDL_QueryAudio();
  • miniSDL API. miniSDL дополнительно инкапсулирует вышеописанные API NDL, предоставляя следующую функциональность:
// Открыть аудио-функциональность и инициализировать устройство звуковой карты согласно полям `*desired`
// После успешной инициализации воспроизведение аудио находится в приостановленном состоянии
int SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained);

// Закрыть аудио-функциональность
void SDL_CloseAudio();

// Приостановить/возобновить воспроизведение аудио
void SDL_PauseAudio(int pause_on)

Эти API miniSDL совпадают с теми, что вы использовали при реализации устройства звуковой карты в NEMU в PA2 — их точное поведение можно узнать через RTFM.

Одна из проблем, которую нужно решить, — как реализовать callback-функцию для заполнения аудиоданных. Эту callback-функцию предоставляет приложение, вызвавшее SDL_OpenAudio(), а miniSDL должна периодически её вызывать, чтобы получать новые аудиоданные для записи в потоковый буфер. Чтобы реализовать указанную функциональность callback-функции, нам нужно решить следующие проблемы:

  1. Как часто вызывать callback-функцию? Это можно вычислить на основе параметров, предоставленных приложением в структуре SDL_AudioSpec. А именно: freq — это частота дискретизации в секунду, samples — это количество сэмплов, которые callback-функция запрашивает у приложения за один раз, — так можно вычислить интервал, с которым miniSDL вызывает callback-функцию.

  2. Как заставить miniSDL периодически вызывать callback-функцию? В Linux есть механизм уведомлений под названием «сигнал (signal)открыть в новом окне», на основе которого можно реализовать функциональность таймера (наподобие будильника), уведомляющего приложение по истечении некоторого времени. Но реализовать механизм сигналов в Nanos-lite и Navy — крайне сложная задача, поэтому Nanos-lite не предоставляет механизма уведомлений, аналогичного сигналам. Чтобы добиться эффекта «периодического вызова callback-функции» в отсутствие механизма уведомлений, miniSDL остаётся только самостоятельно опрашивать, «не настало ли уже время для очередного вызова callback-функции». Поэтому мы можем реализовать вспомогательную функцию под названием CallbackHelper(), поведение которой таково:

    • Запросить текущее время
    • Если текущее время превышает время последнего вызова callback-функции плюс интервал вызова, вызвать callback-функцию, иначе просто вернуть управление
    • Если callback-функция была вызвана, обновить «время последнего вызова»

    После этого, если мы будем вызывать CallbackHelper() как можно чаще, мы сможем своевременно вызывать callback-функцию. Для этого можно вставить CallbackHelper() в некоторые API miniSDL, которые приложения вызывают часто. Хотя такой подход не идеален, он всё же является рабочим способом.

После того как miniSDL вызвала callback-функцию и получила новые аудиоданные, их можно воспроизвести через API NDL. Однако, согласно договорённости, запись в /dev/sb блокирующая, поэтому лучше избегать записи в потоковый буфер слишком большого объёма аудиоданных, приводящей к ожиданию, — время ожидания выгоднее потратить на выполнение программы. Поэтому мы можем сначала запросить текущий объём свободного места в потоковом буфере и следить за тем, чтобы длина аудиоданных, получаемых от callback-функции за раз, не превышала это свободное место, — тогда ожидания можно избежать.

После реализации этих функций мы сможем запустить NPlayer. Помимо вызовов miniSDL, NPlayer также использует библиотеку под названием vorbis, реализованную на основе библиотеки декодирования аудио OGG из проекта stbоткрыть в новом окне, которая может декодировать аудиофайл OGG в аудиоданные формата PCM.

Запустите NPlayer

После реализации вышеописанной аудио-функциональности попробуйте запустить NPlayer в Navy. По умолчанию NPlayer проигрывает целиком песню «Twinkle Twinkle Little Star» («Мерцай, звёздочка»). Во время воспроизведения также можно регулировать громкость — подробности управления уточните через RTFSC.

Мы также советуем прочитать код NPlayer: он реализует очень простой аудиоплеер менее чем в 150 строках кода. О функциях API библиотеки vorbis можно прочитать в документации navy-apps/libs/libvorbis/include/vorbis.h.

Проигрываем свою любимую музыку

Поскольку в библиотеках Navy не предоставлены декодеры для других аудиоформатов, на данный момент NPlayer умеет воспроизводить музыку только в формате OGG. Однако вы можете с помощью ffmpeg конвертировать понравившуюся вам музыку в формат OGG, поместить её в каталог navy-apps/fsimg/, и тогда NPlayer сможет её воспроизвести.

PAL (с музыкой и звуковыми эффектами)

Музыка в Chinese Paladin использует собственный формат RIX, разработанный компанией, а декодер аудио формата RIX уже интегрирован в SDLPAL. Однако, чтобы Chinese Paladin мог успешно воспроизводить музыку в Navy, вам нужно решить ещё две следующие проблемы.

Первая проблема связана с инициализацией декодера RIX. Декодер использует библиотеку под названием Adplug (см. navy-apps/apps/pal/repo/src/sound/adplug/), написанную на C++, в которой определены некоторые глобальные объекты. Для глобальных объектов вызов конструктора требует поддержки со стороны среды выполнения, но среда выполнения Navy по умолчанию такой поддержки не предоставляет.

Чтобы помочь вам глубже разобраться в этой проблеме, в Navy подготовлен тест cpp-test. Эта тестовая программа делает очень простую вещь: в коде определён класс с выводом в конструкторе и деструкторе, и через этот класс определён глобальный объект. При прямом запуске на Navy native вы увидите, что программа выполняется в порядке конструктор -> main() -> деструктор — это потому, что Navy native линкуется с glibc Linux, а предоставляемая ею среда выполнения уже поддерживает конструирование и уничтожение глобальных объектов. Но если запустить её через Nanos-lite, вы обнаружите, что программа не вызывает ни конструктор, ни деструктор — это оставляет поля глобального объекта в неинициализированном состоянии, и обращение программы к этому глобальному объекту приведёт к непредсказуемым результатам.

На самом деле стандарт C++ определяет, что вопрос «вызывается ли конструктор глобального объекта до выполнения функции main()» зависит от реализации компилятора (implementation-defined behavior). g++ оборачивает инициализацию конструктора глобального объекта во вспомогательную функцию типа void (*)(void), а затем записывает адрес этой вспомогательной функции в секцию под названием .init_array. Эту особую секцию можно рассматривать как массив указателей на функции типа void (*)(void), специально предназначенный для сбора функций, которые нужно выполнить до выполнения функции main(). После этого CRT может обойти этот массив и вызвать эти функции одну за другой.

Добавьте в среду выполнения поддержку инициализации глобальных объектов C++

В Newlib уже есть функция __libc_init_array(), обходящая указанный выше массив (определена в navy-apps/libs/libc/src/misc/init.c), но среда выполнения в каркасном коде её не вызывает — вам нужно лишь вызвать эту функцию перед вызовом main(). Запустите cpp-test через Nanos-lite: если ваша реализация верна, вы увидите, что конструктор выполняется раньше функции main().

Разберитесь в процессе вызова конструкторов глобальных объектов

Попробуйте прочитать код указанной выше функции __libc_init_array() и, сопоставив его с результатами objdump и readelf, разберитесь, как компилятор, компоновщик и среда выполнения совместно реализуют функциональность «вызова конструкторов глобальных объектов». Чтобы увидеть содержимое секции .init_array, добавьте к objdump параметр -D.

Чтобы Chinese Paladin мог успешно воспроизводить музыку в Navy, вам также нужно решить вторую проблему — повторный вход (reentrancy) в callback-функцию. Чтобы miniSDL вызывала callback-функцию как можно своевременнее, мы вызываем CallbackHelper() в некоторых часто используемых API miniSDL. Но если callback-функция сама снова вызовет эти API, это приведёт к бесконечной рекурсии. Один из способов решить эту проблему — использовать флаг, указывающий, является ли текущий вызов функции повторным входом, и если да, сразу возвращать управление.

Запустите Chinese Paladin с музыкой и звуковыми эффектами

Решив описанную выше проблему повторного входа, вы сможете воспроизводить музыку в Chinese Paladin.

Flappy Bird (со звуковыми эффектами)

Для воспроизведения звуковых эффектов в Flappy Bird нужно реализовать в miniSDL ещё 3 API, связанных с аудио:

// Открыть WAV-файл, на который указывает `file`, и разобрать его, заполнив соответствующий формат в spec,
// выделить память объёмом, равным общей длине аудиоданных, прочитать аудиоданные из WAV-файла в выделенную память,
// вернуть начальный адрес памяти через audio_buf и количество байт аудиоданных через audio_len
SDL_AudioSpec *SDL_LoadWAV(const char *file, SDL_AudioSpec *spec, uint8_t **audio_buf, uint32_t *audio_len);

// Освободить память, выделенную через SDL_LoadWAV()
void SDL_FreeWAV(uint8_t *audio_buf);

// Смешать `len` байт аудиоданных из буфера `src` с громкостью `volume` в другой буфер `dst`
void SDL_MixAudio(uint8_t *dst, uint8_t *src, uint32_t len, int volume);

Чтобы реализовать SDL_LoadWAV(), вам нужно разобраться в формате файла WAVоткрыть в новом окне. Отношение «PCM к WAV» очень похоже на отношение «BIN к ELF»: в PA2 мы напрямую воспроизводили аудиоданные в формате PCM, а WAV-файл можно рассматривать как комбинацию аудиоданных PCM и некоторой организационной информации — процесс разбора WAV заключается именно в чтении этой информации из заголовка WAV-файла. Этот процесс очень похож на то, как вы ранее реализовывали ELF loader. Кроме того, WAV-файлы также поддерживают сжатие аудиоданных, но WAV-файлы, используемые в PA, все в несжатом формате PCM, поэтому вам не нужно распознавать и обрабатывать случаи сжатия.

Наконец рассмотрим SDL_MixAudio() — она используется для смешивания двух фрагментов аудиоданных с целью их одновременного воспроизведения. Перед смешиванием можно также скорректировать громкость одного из фрагментов аудиоданных. Как известно, звук — это суперпозиция нескольких синусоидальных волн, а кодирование PCM получается путём выборки и квантования результирующей кривой. Поскольку громкость пропорциональна амплитуде кривой, регулировка громкости — это пропорциональное изменение значения каждой отсчётной точки. В navy-apps/libs/libminiSDL/include/sdl-audio.h мы определили максимальную громкость SDL_MIX_MAXVOLUME: если параметр volume равен 1/4 от SDL_MIX_MAXVOLUME, это означает регулировку громкости аудио до 1/4 от исходной. А смешивание двух фрагментов аудио — это прямое сложение их кривых. Однако после сложения требуется ещё и обрезка: для формата 16-битных знаковых чисел максимальное значение результата после сложения — 32767, минимальное — -32768; это нужно, чтобы предотвратить переполнение данных после сложения, приводящее к искажению звука (например, отсчёт, расположенный на кривой выше оси x, может из-за переполнения оказаться ниже оси x). Разобравшись во всём этом, реализовать SDL_MixAudio() будет несложно.

Запустите Flappy Bird со звуковыми эффектами

Реализуйте указанные выше API и запустите в Navy Flappy Bird со звуковыми эффектами.

Инфраструктура (3)

Если ваш Chinese Paladin не работает корректно, с помощью разных уровней native вы должны быстро найти уровень, на котором находится баг. Если баг аппаратный, вы можете впасть в отчаяние: DiffTest работает слишком медленно, особенно DiffTest на основе QEMU! Есть ли способ ускорить работу DiffTest?

Свободное включение и выключение режима DiffTest

На данный момент DiffTest каждый раз выполняется с самого начала, но если этот баг проявляется лишь спустя долгое время, то каждый раз запускать DiffTest с начала не нужно. Если мы подозреваем, что баг возникает в какой-то определённой функции, нам хотелось бы, чтобы DUT сначала выполнялся в обычном режиме вплоть до этой функции, а затем включался режим DiffTest перед входом в эту функцию. Так мы сэкономим огромные затраты на ненужное сравнение на начальном этапе.

Чтобы реализовать эту функцию, ключевым является вход в режим DiffTest в определённый момент выполнения DUT. А важным условием для входа в режим DiffTest является приведение состояний DUT и REF в соответствие друг другу, иначе результат сравнения теряет смысл. Мы снова обращаемся к понятию состояния, которое вам уже давно знакомо: состояние компьютера — это состояние последовательностных логических компонентов в компьютере. Таким образом, достаточно перед входом в режим DiffTest установить регистры и память REF такими же, как у DUT, и они смогут начать сравнение с одинакового состояния.

Чтобы управлять тем, включён ли у DUT режим DiffTest, нам также нужно добавить в простой отладчик следующие две команды:

  • Команда detach используется для выхода из режима DiffTest, после чего все инструкции, выполняемые DUT, больше не будут сравниваться с REF. Реализация очень простая — достаточно заставить difftest_step(), difftest_skip_dut() и difftest_skip_ref() немедленно возвращать управление.
  • Команда attach используется для входа в режим DiffTest, после чего все инструкции, выполняемые DUT, будут по одной сравниваться с REF. Для этого вам также нужно синхронизировать содержимое физической памяти DUT с соответствующей областью памяти REF, а также синхронизировать состояние регистров DUT с REF. В частности, если вы выбрали x86, вам нужно обойти область памяти в REF около 0x7c00, поскольку у REF рядом с 0x7c00 находится код, связанный с GDT, — перезапись этого кода приведёт к тому, что REF не сможет работать в защищённом режиме, и дальнейший DiffTest станет невозможным. На самом деле достаточно синхронизировать память [0x100000, PMEM_SIZE), поскольку программа, выполняемая в NEMU, не использует область памяти [0, 0x100000).

После этого вы сможете включать DiffTest в момент, когда клиентская программа достигает нужной целевой позиции, следующим образом:

  1. Уберите параметр -b при запуске NEMU, чтобы можно было вводить команды до начала выполнения клиентской программы
  2. Введите команду detach, чтобы выйти из режима DiffTest
  3. С помощью пошагового выполнения, точек наблюдения, точек останова и т.п. дайте клиентской программе в обычном режиме дойти до целевой позиции
  4. Введите команду attach, чтобы войти в режим DiffTest — учтите, что настройка памяти REF занимает около десятков секунд
  5. После этого можно продолжать выполнение клиентской программы в режиме DiffTest

Впрочем, у описанного выше метода всё ещё есть упущения. А именно: нам также нужно обработать некоторые специальные регистры, поскольку они тоже являются частью состояния машины. На примере x86 нам нужно также обработать регистры EFLAGS и IDTR, иначе несогласованный EFLAGS приведёт к непредсказуемым результатам при выполнении последующих инструкций jcc или setcc в REF, а несогласованный IDTR приведёт к падению системных вызовов, выполняемых в REF, из-за невозможности найти правильную целевую позицию. Одна из трудностей здесь в том, что некоторые регистры в REF сложно установить напрямую — например, в GDB-протоколе для связи с QEMU не определён способ доступа к IDTR. Однако API, предоставляемые DiffTest, уже позволяют решить эти проблемы: с помощью difftest_memcpy_from_dut() можно скопировать последовательность инструкций в свободную память REF, затем с помощью difftest_setregs() заставить pc REF указывать на эту последовательность инструкций, а затем с помощью difftest_exec() заставить REF выполнить эту последовательность инструкций. Таким образом мы можем заставить REF выполнить любую программу — например, заставить REF выполнить инструкцию lidt, что позволит косвенно установить IDTR. Чтобы установить регистр EFLAGS, можно выполнить инструкцию popf.

Реализуйте свободно переключаемый DiffTest

Основываясь на изложенном выше, добавьте в простой отладчик команды detach и attach, реализовав свободное переключение между обычным режимом и режимом DiffTest.

Приведённый выше текст в целом ясно описывает идею реализации. Если вы столкнётесь с конкретными проблемами, попробуйте проанализировать и решить их самостоятельно.

Снимки состояния

Идём дальше: на самом деле даже NEMU не обязательно каждый раз выполняться с самого начала. Подобно системе сохранений в Chinese Paladin, мы можем сохранять состояние NEMU в файл, а затем сразу восстанавливать это состояние из файла и продолжать выполнение. В области виртуализации такой механизм имеет специальное название — снимок состояния (snapshot)открыть в новом окне. Если вы выполняете PA с помощью виртуальной машины, этот термин наверняка вам знаком. Реализовать снимки состояния в NEMU очень просто — достаточно добавить в простой отладчик следующие команды:

  • save [path] — сохранить текущее состояние NEMU в файл, указанный path
  • load [path] — восстановить состояние NEMU из файла, указанного path

Реализуйте снимки состояния в NEMU

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

Продемонстрируйте свою систему пакетной обработки

В конце PA3 вы добавите в Nanos-lite несколько простых функций, чтобы продемонстрировать свою систему пакетной обработки.

Ранее вы уже запускали в Navy загрузочное меню и NTerm, но ни то, ни другое не поддерживает запуск других программ. Это потому, что «запуск других программ» требует поддержки нового системного вызова — это SYS_execve, назначение которого — завершить выполнение текущей программы и запустить указанную программу. Этот системный вызов несколько особенный: при успешном выполнении он не возвращает управление в текущую программу — подробности см. man execve. Чтобы реализовать этот системный вызов, вам достаточно вызвать naive_uload() в соответствующей функции-обработчике системного вызова. Пока нам нужно заботиться только о filename, а параметры argv и envp можно временно проигнорировать.

Загрузочное меню, способное запускать другие программы

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

Продемонстрируйте свою систему пакетной обработки

Имея программу загрузочного меню, можно легко реализовать хоть сколько-то похожую на настоящую систему пакетной обработки. Вам нужно лишь изменить реализацию SYS_exit, чтобы она вызывала SYS_execve для повторного запуска /bin/menu, а не напрямую вызывала halt() для завершения работы всей системы. После этого при завершении пользовательской программы операционная система будет автоматически снова запускать программу загрузочного меню, позволяя пользователю выбрать новую программу для запуска.

По мере роста числа приложений запускать программы через загрузочное меню становится не так удобно: вам приходится постоянно добавлять в загрузочное меню новые приложения. Более удобный способ — запускать эти программы через NTerm: достаточно просто ввести путь к программе, например /bin/pal.

Продемонстрируйте свою систему пакетной обработки (2)

Реализуйте разбор команд во встроенном Shell NTerm: введённую команду используйте как аргумент при вызове execve(). Затем сделайте NTerm первой программой, которую запускает Nanos-lite, и измените реализацию SYS_exit, чтобы она снова запускала /bin/nterm. Пока мы не поддерживаем передачу аргументов, так что можете пока игнорировать аргументы команды.

Вводить полный путь к команде — дело довольно утомительное. Вспомните, что при использовании ls нам не нужно вводить /bin/ls. Это потому, что в системе определена переменная окружения PATH — о соответствующем поведении можно прочитать через man execvp. Мы также можем добавить эту функциональность во встроенный Shell NTerm: вам достаточно установить PATH=/bin через функцию setenv(), а затем вызвать execvp() для выполнения новой программы. При вызове setenv() параметр overwrite нужно установить в 0 — это нужно, чтобы добиться того же эффекта на Navy native.

Добавьте поддержку переменных окружения во встроенный Shell NTerm

Это очень простая задача: вам достаточно узнать через RTFM поведение setenv() и execvp() и внести небольшие изменения в код встроенного Shell, чтобы получить Shell, очень похожий по ощущениям на тот, которым вы пользуетесь ежедневно.

Главный вопрос

С незапамятных времён в курсах по компьютерным системам существует один главный, «коронный» вопрос:

Когда вы вводите в терминале ./hello, чтобы запустить программу Hello World, что именно делает компьютер?

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

Добавьте музыку при загрузке

Вы можете подготовить музыкальный фрагмент длительностью в несколько секунд (например, знаменитую мелодию загрузки Windows XP) и воспроизводить его в NTerm — тогда при запуске системы и переходе в NTerm эта музыка будет автоматически проигрываться. Для реализации воспроизведения музыки можно обратиться к коду NPlayer.

На этом мы, по сути, реализовали «современную» систему пакетной обработки: только что запущенное нами загрузочное меню MENU похоже на меню выбора игр вроде «100-в-1» на приставке NES, а поведение NTerm очень близко к тем терминалам и Shell, которыми мы пользуемся каждый день. Важно то, что всё это построено вашими собственными руками: NEMU, AM, Nanos-lite, среда выполнения Navy и, наконец, приложения — макроскопический взгляд на «компьютер как абстрактный слой» теперь полностью открылся перед вами. Вы наконец понимаете, как такая сложная программа, как Chinese Paladin, проходит через слои абстракции компьютерной системы и в итоге раскладывается на простейшие аппаратные операции, выполняемые на железе в виде конечного автомата. Когда вы осознаёте эту истину и она вас поражает, главная цель PA — дать всем понять, «как программа работает на компьютере», — можно считать в значительной мере достигнутой.

Обязательный вопрос — понимание компьютерных систем

  • Разберитесь в прошлом и настоящем структуры контекста (см. этап PA3.1)

  • Разберитесь в путешествии сквозь пространство и время (см. этап PA3.1)

  • Что такое программа hello, откуда она берётся и куда направляется (см. этап PA3.2)

  • Как на самом деле работает Chinese Paladin При запуске Chinese Paladin проигрывается заставочная анимация: на ней журавль пролетает над горами. Эта анимация воспроизводится через функцию PAL_SplashScreen() в navy-apps/apps/pal/repo/src/main.c. Прочитав эту функцию, можно узнать, что пиксельная информация журавля хранится в файле данных mgo.mkf. Пожалуйста, ответьте на следующий вопрос: как библиотечные функции, libos, Nanos-lite, AM и NEMU совместно помогают коду Chinese Paladin прочитать пиксельную информацию журавля из файла mgo.mkf и обновить ею экран? Другими словами, в классической формулировке PA: что именно происходит в этом процессе? (Подсказка: разумное использование различных инструментов трассировки поможет вам легче понять поведение Chinese Paladin)

Дружеское напоминание

На этом PA3 завершается. Пожалуйста, напишите отчёт по лабораторной работе (не забудьте ответить в нём на обязательные вопросы), затем поместите файл отчёта с именем student_id.pdf в каталог проекта и выполните make submit, чтобы отправить проект на указанный сайт.