Inferno OS Wiki
Advertisement


Роб ПайкBell LabsLucent Technologiesrob@plan9.bell-labs.com4 февраля 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 является файловым сервером или как минимум представителем одной из категорий:

  • процессы (для отладки);
  • переменные окружения;
  • консоль;
  • графика;
  • сеть;

Клиент подключается к серверу.
Клиент запрашивает — сервер отвечает.

Запросы:


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): распределение процессов[]

Итак, теперь давайте подойдем поближе к вопросу «процессы против потоков». Давайте дадим каждому отдельному компоненту процесс, управляющий им:

  1. Окна. (Каждый запустит пример одинаковой winctl-функции с разными аргументами.)
  2. Мышь
  3. Клавиатура.
  4. Интерфейс пользователя для оконного управления.
  5. Соединение с файловым сервером, который будет получать запросы 9P.
  6. Каждый входящий клиентский запрос ввода-вывода.

Все эти процессы будут общаться лишь посредством сообщений в каналах.

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

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)
  {
      /* управление чтением как потоком главного процесса */
  }

Оригинал: http://cm.bell-labs.com/who/rob/lec5.pdf  © Перевод на русский язык, Андрей С. Кухар, 2004

Advertisement