The design of the unix operating system by Maurice J

Вид материалаРеферат
11.2 Взаимодействие процессов в версии v системы
Подобный материал:
1   ...   42   43   44   45   46   47   48   49   ...   55

(сигнал прерывания), процесс trace переходит в состояние трасси-

ровки, ожидая поступления команды от программы debug. Если про-

цесс, реализующий программу debug, находился в состоянии приоста-

нова, связанного с выполнением функции wait, он "пробуждается",

обнаруживает наличие порожденного трассируемого процесса и выхо-

дит из функции wait. Затем процесс debug вызывает функцию ptrace,

записывает значение переменной цикла i в пространство данных про-

цесса trace по адресу, содержащемуся в переменной addr, и увели-

чивает значение переменной addr; в программе trace переменная

addr хранит адрес точки входа в массив data. Последнее обращение

процесса debug к функции ptrace вызывает запуск программы trace,

и в этот момент массив data содержит значения от 0 до 31. Отлад-


-------------------------------------------------------------┐

│ #define TR_SETUP 0 │

│ #define TR_WRITE 5 │

│ #define TR_RESUME 7 │

│ int addr; │

│ │

│ main(argc,argv) │

│ int argc; │

│ char *argv[]; │

│ { │

│ int i,pid; │

│ │

│ sscanf(argv[1],"%x",&addr); │

│ │

│ if ((pid = fork() == 0) │

│ { │

│ ptrace(TR_SETUP,0,0,0); │

│ execl("trace","trace",0); │

│ exit(); │

│ } │

│ for (i = 0; i < 32, i++) │

│ { │

│ wait((int *) 0); │

│ /* записать значение i в пространство процесса с │

│ * идентификатором pid по адресу, содержащемуся в │

│ * переменной addr */ │

│ if (ptrace(TR_WRITE,pid,addr,i) == -1) │

│ exit(); │

│ addr += sizeof(int); │

│ } │

│ /* трассируемый процесс возобновляет выполнение */ │

│ ptrace(TR_RESUME,pid,1,0); │

│ } │

L-------------------------------------------------------------


Рисунок 11.3. Программа debug (трассирующий процесс)


чики, подобные sdb, имеют доступ к таблице идентификаторов трас-

сируемого процесса, из которой они получают информацию об адресах

данных, используемых в качестве параметров функции ptrace.

Использование функции ptrace для трассировки процессов явля-

ется обычным делом, но оно имеет ряд недостатков.

* Для того, чтобы произвести передачу порции данных длиною в

слово между процессом-отладчиком и трассируемым процессом,

ядро должно выполнить четыре переключения контекста: оно пе-

реключает контекст во время вызова отладчиком функции ptrace,

загружает и выгружает контекст трассируемого процесса и пе-

реключает контекст вновь на процесс-отладчик по получении от-

вета от трассируемого процесса. Все вышеуказанное необходимо,

поскольку у отладчика нет иного способа получить доступ к

виртуальному адресному пространству трассируемого процесса,

отсюда замедленность протекания процедуры трассировки.

* Процесс-отладчик может вести одновременную трассировку нес-

кольких процессов-потомков, хотя на практике эта возможность

используется редко. Если быть более критичным, следует отме-

тить, что отладчик может трассировать только своих ближайших

потомков: если трассируемый процесс-потомок вызовет функцию

fork, отладчик не будет иметь контроля над порождаемым, вну-

чатым для него, процессом, что является серьезным препятстви-

ем в отладке многоуровневых программ. Если трассируемый про-

цесс вызывает функцию exec, запускаемые образы задач тоже

подвергаются трассировке под управлением ранее вызванной

функции ptrace, однако отладчик может не знать имени исполня-

емого образа, что затрудняет проведение символьной отладки.

* Отладчик не может вести трассировку уже выполняющегося про-

цесса, если отлаживаемый процесс не вызвал предварительно

функцию ptrace, дав тем самым ядру свое согласие на трасси-

ровку. Это неудобно, так как в указанном случае выполняющийся

процесс придется удалить из системы и перезапустить в режиме

трассировки.

* Не разрешается трассировать setuid-программы, поскольку это

может привести к нарушению защиты данных (ибо в результате

выполнения функции ptrace в их адресное пространство произво-

дилась бы запись данных) и к выполнению недопустимых дейс-

твий. Предположим, например, что setuid-программа запускает

файл с именем "privatefile". Умелый пользователь с помощью

функции ptrace мог бы заменить имя файла на "/bin/sh", запус-

тив на выполнение командный процессор shell (и все программы,

исполняемые shell'ом), не имея на то соответствующих полномо-

чий. Функция exec игнорирует бит setuid, если процесс подвер-

гается трассировке, тем самым адресное пространство

setuid-программ защищается от пользовательской записи.

Киллиан [Killian 84] описывает другую схему трассировки про-

цессов, основанную на переключении файловых систем (см. главу 5).

Администратор монтирует файловую систему под именем "/proc";

пользователи идентифицируют процессы с помощью кодов идентифика-

ции и трактуют их как файлы, принадлежащие каталогу "/proc". Ядро

дает разрешение на открытие файлов, исходя из кода идентификации

пользователя процесса и кода идентификации группы. Пользователи

могут обращаться к адресному пространству процесса путем чтения

(read) файла и устанавливать точки прерываний путем записи

(write) в файл. Функция stat сообщает различную статистическую

информацию, касающуюся процесса. В данном подходе устранены три

недостатка, присущие функции ptrace. Во-первых, эта схема работа-

ет быстрее, поскольку процесс-отладчик за одно обращение к ука-

занным системным функциям может передавать больше информации, чем

при работе с ptrace. Во-вторых, отладчик здесь может вести трас-

сировку совершенно произвольных процессов, а не только своих по-

томков. Наконец, трассируемый процесс не должен предпринимать

предварительно никаких действий по подготовке к трассировке; от-

ладчик может трассировать и существующие процессы. Возможность


вести отладку setuid-программ, предоставляемая только суперполь-

зователю, реализуется как составная часть традиционного механизма

защиты файлов.


11.2 ВЗАИМОДЕЙСТВИЕ ПРОЦЕССОВ В ВЕРСИИ V СИСТЕМЫ


Пакет IPC (interprocess communication) в версии V системы

UNIX включает в себя три механизма. Механизм сообщений дает про-

цессам возможность посылать другим процессам потоки сформатиро-

ванных данных, механизм разделения памяти позволяет процессам

совместно использовать отдельные части виртуального адресного

пространства, а семафоры - синхронизировать свое выполнение с вы-

полнением параллельных процессов. Несмотря на то, что они реали-

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

* С каждым механизмом связана таблица, в записях которой описы-

ваются все его детали.

* В каждой записи содержится числовой ключ (key), который

представляет собой идентификатор записи, выбранный пользова-

телем.

* В каждом механизме имеется системная функция типа "get", ис-

пользуемая для создания новой или поиска существующей записи;

параметрами функции являются идентификатор записи и различные

флаги (flag). Ядро ведет поиск записи по ее идентификатору в

соответствующей таблице. Процессы могут с помощью флага

IPC_PRIVATE гарантировать получение еще неиспользуемой запи-

си. С помощью флага IPC_CREAT они могут создать новую запись,

если записи с указанным идентификатором нет, а если еще к то-

му же установить флаг IPC_EXCL, можно получить уведомление об

ошибке в том случае, если запись с таким идентификатором су-

ществует. Функция возвращает некий выбранный ядром дескрип-

тор, предназначенный для последующего использования в других

системных функциях, таким образом, она работает аналогично

системным функциям creat и open.

* В каждом механизме ядро использует следующую формулу для по-

иска по дескриптору указателя на запись в таблице структур

данных:

указатель = значение дескриптора по модулю от числа записей в

таблице

Если, например, таблица структур сообщений состоит из 100 за-

писей, дескрипторы, связанные с записью номер 1, имеют значе-

ния, равные 1, 101, 201 и т.д. Когда процесс удаляет запись,

ядро увеличивает значение связанного с ней дескриптора на

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

дескриптором этой записи, когда к ней вновь будет произведено

обращение при помощи функции типа "get". Процессы, которые

будут пытаться обратиться к записи по ее старому дескриптору,

потерпят неудачу. Обратимся вновь к предыдущему примеру. Если

с записью 1 связан дескриптор, имеющий значение 201, при его

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

чение 301. Процессы, пытающиеся обратиться к дескриптору 201,

получат ошибку, поскольку этого дескриптора больше нет. В ко-

нечном итоге ядро произведет перенумерацию дескрипторов, но

пока это произойдет, может пройти значительный промежуток

времени.

* Каждая запись имеет некую структуру данных, описывающую права

доступа к ней и включающую в себя пользовательский и группо-

вой коды идентификации, которые имеет процесс, создавший за-

пись, а также пользовательский и групповой коды идентифика-

ции, установленные системной функцией типа "control" (об этом

ниже), и двоичные коды разрешений чтения-записи-исполнения

для владельца, группы и прочих пользователей, по аналогии с

установкой прав доступа к файлам.

* В каждой записи имеется другая информация, описывающая состо-

яние записи, в частности, идентификатор последнего из процес-

сов, внесших изменения в запись (посылка сообщения, прием со-

общения, подключение разделяемой памяти и т.д.), и время

последнего обращения или корректировки.

* В каждом механизме имеется системная функция типа "control",

запрашивающая информацию о состоянии записи, изменяющая эту

информацию или удаляющая запись из системы. Когда процесс за-

прашивает информацию о состоянии записи, ядро проверяет, име-

ет ли процесс разрешение на чтение записи, после чего копиру-

ет данные из записи таблицы по адресу, указанному

пользователем. При установке значений принадлежащих записи

параметров ядро проверяет, совпадают ли между собой пользова-

тельский код идентификации процесса и идентификатор пользова-

теля (или создателя), указанный в записи, не запущен ли про-

цесс под управлением суперпользователя; одного разрешения на

запись недостаточно для установки параметров. Ядро копирует

сообщенную пользователем информацию в запись таблицы, уста-

навливая значения пользовательского и группового кодов иден-

тификации, режимы доступа и другие параметры (в зависимости

от типа механизма). Ядро не изменяет значения полей, описыва-

ющих пользовательский и групповой коды идентификации создате-

ля записи, поэтому пользователь, создавший запись, сохраняет

управляющие права на нее. Пользователь может удалить запись,

либо если он является суперпользователем, либо если идентифи-

катор процесса совпадает с любым из идентификаторов, указан-

ных в структуре записи. Ядро увеличивает номер дескриптора,

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

дескриптор. Следовательно, как уже ранее говорилось, если

процесс попытается обратиться к записи по старому дескрипто-


ру, вызванная им функция получит отказ.


11.2.1 Сообщения


С сообщениями работают четыре системных функции: msgget, ко-

торая возвращает (и в некоторых случаях создает) дескриптор сооб-

щения, определяющий очередь сообщений и используемый другими сис-

темными функциями, msgctl, которая устанавливает и возвращает

связанные с дескриптором сообщений параметры или удаляет дескрип-

торы, msgsnd, которая посылает сообщение, и msgrcv, которая полу-

чает сообщение.

Синтаксис вызова системной функции msgget:

msgqid = msgget(key,flag);

где msgqid - возвращаемый функцией дескриптор, а key и flag имеют

ту же семантику, что и в системной функции типа "get". Ядро хра-

нит сообщения в связном списке (очереди), определяемом значением

дескриптора, и использует значение msgqid в качестве указателя на

массив заголовков очередей. Кроме вышеуказанных полей, описываю-

щих общие для всего механизма права доступа, заголовок очереди

содержит следующие поля:

* Указатели на первое и последнее сообщение в списке;

* Количество сообщений и общий объем информации в списке в бай-

тах;

* Максимальная емкость списка в байтах;

* Идентификаторы процессов, пославших и принявших сообщения

последними;

* Поля, указывающие время последнего выполнения функций msgsnd,

msgrcv и msgctl.

Когда пользователь вызывает функцию msgget для того, чтобы

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

общений в поисках существующей очереди с указанным идентификато-

ром. Если такой очереди нет, ядро выделяет новую очередь, инициа-

лизирует ее и возвращает идентификатор пользователю. В противном

случае ядро проверяет наличие необходимых прав доступа и заверша-

ет выполнение функции.

Для посылки сообщения процесс использует системную функцию

msgsnd:

msgsnd(msgqid,msg,count,flag);

где msgqid - дескриптор очереди сообщений, обычно возвращаемый

функцией msgget, msg - указатель на структуру, состоящую из типа

в виде назначаемого пользователем целого числа и массива симво-

лов, count - размер информационного массива, flag - действие,

предпринимаемое ядром в случае переполнения внутреннего буферного

пространства.

Ядро проверяет (Рисунок 11.4), имеется ли у посылающего сооб-

щение процесса разрешения на запись по указанному дескриптору, не

выходит ли размер сообщения за установленную системой границу, не

содержится ли в очереди слишком большой объем информации, а также

является ли тип сообщения положительным целым числом. Если все

условия соблюдены, ядро выделяет сообщению место, используя карту

сообщений (см. раздел 9.1), и копирует в это место данные из

пространства пользователя. К сообщению присоединяется заголовок,

после чего оно помещается в конец связного списка заголовков со-

общений. В заголовке сообщения записывается тип и размер сообще-


-------------------------------------------------------------┐

│ алгоритм msgsnd /* послать сообщение */ │

│ входная информация: (1) дескриптор очереди сообщений │

│ (2) адрес структуры сообщения │

│ (3) размер сообщения │

│ (4) флаги │

│ выходная информация: количество посланных байт │

│ { │

│ проверить правильность указания дескриптора и наличие │

│ соответствующих прав доступа; │

│ выполнить пока (для хранения сообщения не будет выделено│

│ место) │

│ { │

│ если (флаги не разрешают ждать) │

│ вернуться; │

│ приостановиться (до тех пор, пока место не освобо- │

│ дится); │

│ } │

│ получить заголовок сообщения; │

│ считать текст сообщения из пространства задачи в прост- │

│ ранство ядра; │

│ настроить структуры данных: выстроить очередь заголовков│

│ сообщений, установить в заголовке указатель на текст │

│ сообщения, заполнить поля, содержащие счетчики, время │

│ последнего выполнения операций и идентификатор процес- │

│ са; │

│ вывести из состояния приостанова все процессы, ожидающие│

│ разрешения считать сообщение из очереди; │

│ } │

L-------------------------------------------------------------


Рисунок 11.4. Алгоритм посылки сообщения


ния, устанавливается указатель на текст сообщения и производится

корректировка содержимого различных полей заголовка очереди, со-

держащих статистическую информацию (количество сообщений в очере-

ди и их суммарный объем в байтах, время последнего выполнения

операций и идентификатор процесса, пославшего сообщение). Затем

ядро выводит из состояния приостанова все процессы, ожидающие по-

полнения очереди сообщений. Если размер очереди в байтах превыша-

ет границу допустимости, процесс приостанавливается до тех пор,

пока другие сообщения не уйдут из очереди. Однако, если процессу

было дано указание не ждать (флаг IPC_NOWAIT), он немедленно

возвращает управление с уведомлением об ошибке. На Рисунке 11.5

показана очередь сообщений, состоящая из заголовков сообщений,

организованных в связные списки, с указателями на область текста.

Рассмотрим программу, представленную на Рисунке 11.6. Процесс

вызывает функцию msgget для того, чтобы получить дескриптор для

записи с идентификатором MSGKEY. Длина сообщения принимается рав-

ной 256 байт, хотя используется только первое поле целого типа, в

область текста сообщения копируется идентификатор процесса, типу

сообщения присваивается значение 1, после чего вызывается функция

msgsnd для посылки сообщения. Мы вернемся к этому примеру позже.

Процесс получает сообщения, вызывая функцию msgrcv по следую-

щему формату:

count = msgrcv(id,msg,maxcount,type,flag);

где id - дескриптор сообщения, msg - адрес пользовательской

структуры, которая будет содержать полученное сообщение, maxcount

- размер структуры msg, type - тип считываемого сообщения, flag -

действие, предпринимаемое ядром в том случае, если в очереди со-


Заголовки Область

очередей текста

-------┐ Заголовки сообщений -->-------┐

│ │ -------┐ -------┐ -------┐ │ │ │

│ --+---->│ +--->│ +--->│ │ │ │ │

│ │ L---+--- L---+--- L---+--- │ │ │

+------+ │ │ L----- │ │

│ │ L-----------│------------------>+------+

│ │ │ │ │

│ │ │ │ │

+------+ │ │ │

│ │ -------┐ │ │ │

│ --+---->│ │ │ │ │

│ │ L---+--- │ │ │

+------+ │ │ │ │

│ │ │ │ +------+

│ │ L-----------│------------------>+------+

│ │ │ │ │

│ │ │ │ │

│ │ L------------------>+------+

│ │ │ │

│ │ +------+

│ │ │ │

│ │ │ │

│ │ │ │

L------- L-------


Рисунок 11.5. Структуры данных, используемые в организации

сообщений


общений нет. В переменной count пользователю возвращается число

прочитанных байт сообщения.

Ядро проверяет (Рисунок 11.7), имеет ли пользователь необхо-

димые права доступа к очереди сообщений. Если тип считываемого

сообщения имеет нулевое значение, ядро ищет первое по счету сооб-

щение в связном списке. Если его размер меньше или равен размеру,

указанному пользователем, ядро копирует текст сообщения в пользо-

вательскую структуру и соответствующим образом настраивает свои

внутренние структуры: уменьшает счетчик сообщений в очереди и

суммарный объем информации в байтах, запоминает время получения

сообщения и идентификатор процесса-получателя, перестраивает

связный список и освобождает место в системном пространстве, где

хранился текст сообщения. Если какие-либо процессы, ожидавшие по-

лучения сообщения, находились в состоянии приостанова из-за от-

сутствия свободного места в списке, ядро выводит их из этого сос-

тояния. Если размер сообщения превышает значение maxcount, ука-

занное пользователем, ядро посылает системной функции уведомление

об ошибке и оставляет сообщение в очереди. Если, тем не менее,

процесс игнорирует ограничения на размер (в поле flag установлен