Rio: разработка параллельной оконной системы
-
Роб Пайк
Bell Labs
Lucent Technologies
rob@plan9.bell-labs.com
4 февраля 2000 года
1. История
Тема: внешняя и, особенно, внутренняя конструкция Rio, (новой) оконной системы ОС Plan 9.
Потомок 8½:
Pike, Rob, ‘‘8½, the Plan 9 Window System’’, Proceedings of the Summer 1991 USENIX Conference, Nashville, 1991, pp. 257-265.
Оконная система 8½ первоначально была написана на языке C с использованием процессов и основанных на setjmp и longjmp дополнительных сопрограмм (интересное упражнение).
В 1995 году для проекта Brazil система была переписана на языке Alef и конвертирована в настоящие потоки (threads) и процессы.
В 1999 году конвертирована для использования потоковой библиотеки.
Историческая заметка: хотя Alef был и многообещающим языком, но задача по поддержке еще одного варианта языка программирования для сонм архитектур оказалась слишком трудной, так что мы собрали во едино все наши знания и создали потоковую библиотеку для С.
2. Общая конструкция
По сути, не считая очевидных, оконная система состоит из трех составляющих:
- системного интерфейса (для доступа к оборудованию);
- программного интерфейса (для манипулирования окнами);
- пользовательского интерфейса (мышь, клавиатура, меню…).
Она должна параллельно мультиплексировать все эти части для многочисленных клиентов.
Rio выглядит довольно необычно по сравнению с другими оконными системами (но не по сравнению с сервисами Plan 9), так как она реализована как файловый сервер.
3. 9P: файл-серверный протокол Plan 9
(Почти) все «население» мира Plan 9 является файловым сервером или как минимум представителем одной из категорий:
- процессы (для отладки);
- переменные окружения;
- консоль;
- графика;
- сеть;
- …
Клиент подключается к серверу.
Клиент запрашивает — сервер отвечает.
Запросы:
-
Attach
Walk, Open, Read, Write, Close, Create
Remove, Stat
Могут работать через любой надежный канал: TCP/IP, pipe,…
4. Пространство имен
Пространство имен представляет собой доступный процессу набор файлов.
В Unix-системах пространство имен одинаково для всех процессов. В Plan 9 оно управляемо, при чем у каждого процесса может быть свое частное пространство имен. На практике, пространства имен разделены между набором связанных процессов под названием группа.
Пользовательский файл профиля ($home/lib/profile) формирует пространство имен начиная с корневого каталога / и монтирует, подключая, сервисы.
«Превращает» все сервисы, включая основные устройства, в файловые серверы.
Таковы основы Plan 9.
5. Примеры устройств
Процессы:
/proc/1/… … /proc/27/ctl /proc/27/mem /proc/27/note /proc/27/notepg /proc/27/proc /proc/27/status /proc/27/text …
Консоль:
/dev/cons /dev/time /dev/cputime /dev/pid(Все файлы текстовые)
6. Примеры устройств (управляемые)
Сетевые:
/net/il/clone /net/il/stats /net/il/29/ctl /net/il/29/data /net/il/29/err /net/il/29/listen /net/il/29/local /net/il/29/remote /net/il/29/status … /net/tcp/… /net/udp/… /net/cs …При чем все это без использования каких-либо специальных системных вызовов. Просто открывайте, читайте и записывайте файлы.
7. Примеры устройств (управляемые)
Мышь:
/dev/mouse /dev/cursor
Растровая графика:
/dev/screen /dev/window /dev/drawФормат файла /dev/draw — RPC (уделенный вызов процедур), заканчивающийся еще одним файлом. (Так называемый, вложенный RPC.)
void draw(Image *dst, Rectangle dr, Image *src, Point sp, Image *mask, Point mp); /dev/draw: ’d’ 2 байта для идентификатора назначения 4x4 байтов назначения 2 байта исходного идентификатора …
8. Структура оконной системы (I)
Для записи оконной системы,
Для получения символов ввода — чтение /dev/cons.
Для получения позиции мыши и нажатых кнопок — чтение /dev/mouse.
Для обновления экрана — запись /dev/draw.
Что же делает клиент?
Для получения символов ввода — чтение /dev/cons.
Для получения позиции мыши и нажатых кнопок — чтение /dev/mouse.
Для обновления экрана — запись /dev/draw.
Как вы видите, действия оконной системы и ее клиента идентичны!
9. Структура оконной системы (II)
Таким образом, Rio — это все лишь мультиплексор:
Обеспечивает одинаковый (выглядящий таковым) вид окружения для приложений.
Всем клиентам представляет эти имена файлов, но каждый получает собственный четкий набор.
Файлы /dev/cons и /dev/mouse для каждого окна отличаются. (Фактически, 8½ обеспечивала также разные файлы /dev/draw, но Rio не делает этого, что увеличивает ее эффективность.)
В отличие от Unix, где /dev/tty — это одни и те же файлы, но с различными содержимыми, в Rio /dev/cons действительно являются другими файлами, но с одинаковыми именами и различными содержимыми. (Я могу доказать, что этот вариант гораздо лучше.)
Выводы: в каждом окне монтируются разные корневые каталоги четкой файловой системы, реализованной Rio, содержащие идентичную имитацию стандартного набора файлов для экрана, мыши и клавиатуры.
10. Останов: хорошие побочные эффекты
Удаление окна: контрольный счет открытых файлов.
Del (прерывание)
/dev/cons
Удаленная прозрачность: если есть возможность экспортировать файл (напр., NFS), то есть и возможность экспортировать целую оконную систему.
Рекурсия:
- Легкая отладка: запуск оконной системы в окне.
- Легкая загрузка: запуск оконной системы на CPU-сервере.
- Легкая имитация: возможность запуска X в окне.
11. Проблема
Rio должна управлять всеми следующими операциями:
- ввод с помощью мыши;
- клавиатурный ввод;
- оконное управление (в отличие от X, пользовательский интерфейс для оконного управления является частью Rio);
- вывод на экран;
- запросы ввода-вывода в 9P от клиента для чтения и записи /dev/cons, /dev/mouse, /dev/window, и т.п.
Как же управлять всем этим параллельно, одновременные требования?
Также, как и в каждом мультиплексоре, существует проблема блокировок критических ресурсов (внутренние структуры данных). Блокировки дороги и имеют склонность приводить к тупиковым ситуациям.
12. Параллельный дизайн (I): распределение процессов
Итак, теперь давайте подойдем поближе к вопросу «процессы против потоков». Давайте дадим каждому отдельному компоненту процесс, управляющий им:
- Окна. (Каждый запустит пример одинаковой winctl-функции с разными аргументами.)
- Мышь
- Клавиатура.
- Интерфейс пользователя для оконного управления.
- Соединение с файловым сервером, который будет получать запросы 9P.
- Каждый входящий клиентский запрос ввода-вывода.
Все эти процессы будут общаться лишь посредством сообщений в каналах.
Следует заметить, что большую часть времени все эти процессы будут блокированы ожиданием каких-нибудь событий. Мощь этой модели заключается в том, что мы можем разлагать сложное состояние системы на независимые исполнения — небольшие последовательности операций. Внешние события будут естественно следовать за упорядочением процессов по мере их выполнения, планируя ассоциированные связи.
13. Процессы против потоков
Выбор любого из них зависит от двух факторов: (1) системные вызовы блокировки и (2) протоколы с блокировкой.
1. При блокировке процесса с помощью системного вызова (read, write, и т.п.) будут заблокированы все потоки данного процесса. Следовательно, нам необходимо выполнять системные вызовы блокировки (прочитать мышь, прочитать 9P pipe, и т.п.) в отдельных процессах, и передавать их данные через каналы после того как они их получат.
2. Параллельное выполнение процессов не дает гарантии об одновременном доступе к памяти, так что мы должны использовать блокировки для защиты структур данных. Тем не менее, потоки в пределах одного процесса не выполняются одновременно, и планирование происходит только в местах связи. Поэтому между связями поток может получать доступ к глобальным данным без создания помех другим потокам данного процесса, и без надобности в блокировках.
Наша общая конструкция такова:
- Единственный процесс ответственен за все разделения данных и хранение всех потоков, которые могут получить доступ к данным.
- Отдельный процесс для каждого вызова блокировки, передача запросов для проведения действий потокам главного процесса.
14. Картина
Прямоугольники — процессы, эллипсы — потоки.
Наличествуют и другие связи, напр., поток интерфейса пользователя также общается с потоками управления окном (window control threads — Wctl) для выполнения команды resize, и т.д.
15. Комментарии
Rio может быть представлена как основная программа (большой прямоугольник) с большим количеством потоков внутри, и несколькими процессами-серверами, соединяющими сервисы операционной системы с ней.
Никакие разделяемые данные не трогаются вне прямоугольника, так что блокировки
необязательны.
Связь синхронизирует, планирует и защищает данные от модификаций.
Параллельное соединение процессов/потоков означает, что система не блокируется пока происходит ожидание чего-либо; напр., действия по управлению окна могут происходить пока клиент занят записью в окне.
Этот метод позволяет писать ПО относительно легко.
16. Xfids (1)
Xfids отвечает за состояние единого сообщения 9P. Они управляются как пул анонимных потоков, которые размещаются и передаются сообщению 9P динамически.
Входящий запрос может быть ответственным за непосредственный процесс файлового сервера, напр., запрос на чтение каталога. Все же, если ему требуется доступ к совместно используемым данным, он передает запрос следующим образом:
17. Xfids (2): процесс 9P
Xfid *x; for(;;){ read(fd, message); if(x == 0){ sendp(cxfidalloc, nil); x = recvp(cxfidalloc); } преобразование message в x; x = (*fcall[x->type])(x); } extern void xfidread(Xfid*); static Xfid* fsysread(Xfid *x) { if(read of directory){ оперирование message; return x; } sendp(x->c, xfidread); return nil; }
18. Xfids (3): потоки в основном процессе
Процесс-распределитель тривиален; большая часть кода в данном примере опущена. Он запускает новый Xfid схожий с этим:x=emalloc(sizeof(Xfid)); x->c=chancreate(sizeof(void(*)(Xfid*)), 0); threadcreate(xfidctl, x, 16384);Контроллер Xfid во многом схож с этим:
void xfidctl(void *arg) { Xfid *x; void (*f)(Xfid*); x = arg; for(;;){ f = recvp(x->c); (*f)(x); if(decref(x) == 0) sendp(cxfidfree, x); } } void xfidread(Xfid *x) { /* управление чтением как потоком главного процесса */ }
Оригинал: /plan_9/3rd_edition/rio/
© Перевод на русский язык, Андрей С. Кухар, 2004