Главная страница
Навигация по странице:

  • Доступ к системным вызовам из пространства пользователя

  • Почему не нужно создавать системные вызовы

  • В заключение о системных вызовах

  • Исключительные ситуации

  • 1 1 0 Глава 6 Обработчики прерываний

  • Верхняя и нижняя половины

  • Второе издание


    Скачать 3.09 Mb.
    НазваниеВторое издание
    Дата08.09.2019
    Размер3.09 Mb.
    Формат файлаpdf
    Имя файлаLav_Robert_Razrabotka_yadra_Linux_Litmir.net_264560_original_254.pdf
    ТипДокументы
    #86226
    страница14 из 53
    1   ...   10   11   12   13   14   15   16   17   ...   53
    104 Глава 5

    Давайте более детально рассмотрим эти шаги на примере функции системного вызова, fоо (). Вначале функция sys_fоо () должна быть добавлена в таблицу си- стемных вызовов. Для большинства аппаратных платформ таблица системных вызо- вов размещается в файле e n t r y . S и выглядит примерно следующим образом.
    ENTRY(sys_call_table)
    .long sys_restart_syscall /* 0 */
    .long sys_exit
    .long sys_fork
    .long sys_read
    .long sys_write
    .long sys_open /* 5 */
    .long sys_timer_delete
    .long sys_clock_settime
    .long sys_clock_gettime /* 280 */
    .long sys_clock_getres
    .long sys_clock_nanosleep
    Необходимо добавить новый системный вызов в конец этого списка:
    .long sys_foo
    Нашему системному вызову будет назначен следующий свободный номер, 283,
    котя мы этого явно и не указывали. Для каждой аппаратной платформы, которую мы будем поддерживать, системный вызов должен быть добавлен в таблицу систем- ных вызовов соответствующей аппаратной платформы (нет необходимости получать номер системного вызова для каждой платформы). Обычно необходимо сделать си- стемный вызов доступным для всех аппаратных платформ. Следует обратить внима- ние на договоренность указывать комментарии с номером системного вызова через каждые пять записей, что позволяет быстро найти, какой номер какому системному вызову соответствует.
    Далее необходимо добавить номер системного вызова в заголовочный файл include/asm/unistd.h, который сейчас выглядит примерно так.
    /*
    * This file contains the system call numbers.
    */
    #define __NR_restart_syscall 0
    #define __NR_exit 1
    #define __NR_fork 2
    #define __NR_read 3
    #define __HR_write 4
    #define __NR_open 5
    #define __NR_mq_unlink 278
    #define __NR_mq_timedsend 279
    #define __NR_mq_timedreceive 280
    #define __NR_mq_notify 281
    #define __NR_mq_getsetattr 282
    Системные вызовы 105

    В конец файла добавляется следующая строка.
    #define __NR_foo 283
    В конце концов необходимо реализовать сам системный вызов fоо (). Так как си- стемный вызов должен быть вкомпилорован в образ ядра во всех конфигурациях,
    мы его поместим в файл k e r n e l / s y s . с . Код необходимо размещать в наиболее под- ходящем файле. Например, если функция относится к планированию выполнения процессов, то ее необходимо помещать в файл s c h e d . с .
    /*
    * sys_foo - всеми любимый системный вызов.
    *
    * Возвращает размер стека ядра процесса
    */
    asmlinkage long sys_foo(void)
    {
    return THREAD_SIZE;
    }
    Это все! Загрузите новое ядро. Теперь из пространства пользователя можно вы- звать системную функцию foo ().
    Доступ к системным вызовам из
    пространства пользователя
    В большинстве случаев системные вызовы поддерживаются библиотекой функ- ций языка С. Пользовательские приложения могут получать прототипы функций из стандартных заголовочных файлов и компоновать программы с библиотекой С
    для использования вашего системного вызова (или библиотечной функции, которая вызывает ваш системный вызов). Однако если вы только что написали системный вызов, то маловероятно, что библиотека g l i b c уже его поддерживает!
    К счастью, ОС Linux предоставляет набор макросов-оболочек для доступа к си- стемным вызовам. Они позволяют установить содержимое регистров и выполнить машинную инструкцию i n t 50x80. Эти макросы имеют имя s y s c a l l n ( ) , где п —
    число от нуля до шести. Это число соответствует числу параметров, которые долж- ны передаваться в системный вызов, так как макросу необходима информация о том,
    сколько ожидается параметров, и соответственно, нужно записать эти параметры в регистры процессора. Например, рассмотрим системный вызов open (), который определен следующим образом.
    long open(const char "filename, int flags, int model
    Макрос для вызова этой системной функции будет выглядеть так.
    #define NR_open 5
    _syscall3(long, NR_open, const char *, filename, int, flags, int, mode)
    После этого приложение может просто вызывать функцию open ().
    Каждый макрос принимает 2 + 2*n параметров. Первый параметр соответствует типу возвращаемого значения системного вызова. Второй параметр — имя систем- ного вызова. После этого следуют тип и имя каждого параметра в том же поряд-
    106 Глава 5
    ке, что и у системного вызова. Постоянная NR_open, которая определена в файле
    , — это номер системного вызова. В функцию на языке программи- рования С такой вызов превращается с помощью вставок на языке ассемблера, ко- торые выполняют рассмотренные в предыдущем разделе шаги. Значения аргументов помещаются в соответствующие регистры, и выполняется программное прерывание,
    которое перехватывается в режиме ядра. Вставка данного макроса в приложение —
    это все, что необходимо для выполнения системного вызова open ().
    Напишем макрос, который позволяет вызвать нашу замечательную системную функцию, и соответствующий код, который позволяет этот вызов протестировать.
    #define NR_foo 283
    __syscallO(long, foo)
    int main ()
    {
    long stack_size;
    stack_size = foo () ;
    printf ("Размер стека ядра равен %ld\n", stack_size);
    return 0;
    }
    Почему не нужно создавать системные вызовы
    Новый системный вызов легко реализовать, тем не менее это необходимо делать только тогда, когда ничего другого не остается. Часто, для того чтобы обеспечить новый системный вызов, существуют более подходящие варианты. Давайте рассмо- трим некоторые "за" и "против" и возможные варианты.
    Для создания нового интерфейса в виде системного вызова могут быть следую- щие "за".
    • Системные вызовы просто реализовать и легко использовать.
    • Производительность системных вызовов в операционной системе Linux очень высока.
    Возможные "против".
    • Необходимо получить номер системного вызова, который должен быть офици- ально назначен в период работы над разрабатываемыми сериями ядер.
    • После того как системный вызов включен в стабильную серию ядра, он стано- вится "высеченным в камне". Интерфейс не должен меняться, чтобы не нару- шить совместимости с прикладными пользовательскими программами.
    • Для каждой аппаратной платформы необходимо регистрировать отдельный си- стемный вызов и осуществлять его поддержку.
    • Для простого обмена информацией системный вызов — это "стрельба из пушки по воробьям".
    Системные вызовы 107

    Возможные варианты.
    • Реализовать файл устройства и использовать функции r e a d () и w r i t e () для этого устройства, а также использовать функцию i o c t l () для манипуляции специфическими параметрами или для получения специфической информа- ции.
    • Некоторые интерфейсы, например семафоры, могут быть представлены через дескрипторы файлов. Управлять этими устройствами также можно по анало- гии с файлами.
    • Добавить информационный файл в соответствующем месте файловой системы s y s f s .
    Для большого числа интерфейсов, системные вызовы— это правильный выбор.
    В операционной системе Linux пытаются избегать простого добавления системного нызова для поддержки каждой повой, вдруг появляющейся абстракции. В результате получился удивительно четкий уровень системных вызовов, который принес очень мало разочарований и привел к малому числу не рекомендованных к использованию и устаревших (deprecated) интерфейсов (т.е. таких, которые больше не используют- ся или не поддерживаются).
    Малая частота добавления новых системных вызовов свидетельствует о том, что
    Linux— это стабильная операционная система с полным набором функций. Очень немного системных вызовов было добавлено во время разработки серий ядер 2.3 и
    2.5. Большая часть из новых системных вызовов предназначена для улучшения про- изводительности.
    В заключение о системных вызовах
    В этой главе было рассмотрено, что такое системные вызовы и как они соот- носятся с вызовами библиотечных функций и интерфейсом прикладных программ
    (API). После этого было описано, как системные вызовы реализованы в ядре Linux,
    а также была представлена последовательность событий для выполнения системного вызова: программное прерывание ядра, передача номера системного вызова и аргу- ментов системного вызова, выполнение соответствующей функции системного вы- зова и возврат результатов работы в пространстно пользователя.
    Далее было рассказано, как добавить новый системный вызов, и был приведен простой пример использования системного вызова из пространства пользователя.
    Весь процесс является достаточно простым! Из простоты создания системного вызо- ва следует, что основная работа по добавлению нового системного вызова сводится к реализации функции системного вызова. В оставшейся части книги рассмотрены основные принципы, а также интерфейсы, которые необходимо использовать при создании хорошо работающих, оптимальных и безопасных системных вызовов.
    В конце главы были рассмотрены "за" и "против" относительно реализации си- стемных вызовов и представлен краткий список возможных вариантов добавления новых системных вызовов.
    108 Глава 5

    6
    Прерывания и обработка
    прерываний
    У
    правление аппаратными устройствами, которые подключены к вычислитель- ной машине, — это одна из самых ответственных функций ядра. Частью этой работы является необходимость взаимодействия с отдельными устройствами маши- ны. Поскольку процессоры обычно работают во много раз быстрее, чем аппаратура,
    с которой они должны взаимодействовать, то для ядра получается неэффективным отправлять запросы и тратить время, ожидая ответы от потенциально более мед- ленного оборудования. Учитывая небольшую скорость отклика оборудования, ядро должно иметь возможность оставлять на время работу с оборудованием и выпол- нять другие действия, пока аппаратное устройство не закончит обработку запроса.
    Одно из возможных решений этой проблемы — периодический опрос оборудования
    (polling). Ядро периодически может проверять состояние аппаратного устройства системы и соответственным образом реагировать. Однако такой подход вносит до- полнительные накладные расходы, потому что, независимо от того, готов ответ от аппаратного устройства или оно еще выполняет запрос, все равно осуществляется постоянный систематический опрос состояния устройства через постоянные интер- валы времени. Лучшим решением является обеспечение механизма, который позво- ляет подавать ядру сигнал о необходимости уделить внимание оборудованию. Такой механизм называется прерыванием (interrupt).
    Прерывания
    Прерывания позволяют аппаратным устройствам взаимодействовать с процес- сором. Например, при наборе на клавиатуре контроллер клавиатуры (или другое устройство, которое обслуживает клавиатуру) генерирует прерывание, чтобы объ- явить операционной системе о том, что произошли нажатия клавиш. Прерывания —
    это специальные электрические сигналы, которые аппаратные устройства посылают процессору. Процессор получает прерывание и дает сигнал операционной системе о том, что ОС может обработать новые данные. Аппаратные устройства генерируют прерывания асинхронно по отношению к тактовому генератору процессора — пре- рывания могут возникать непредсказуемо, в любой момент времени. Следовательно,
    работа ядра может быть прервана в любой момент для того, чтобы обработать пре- рывания.

    Физически прерывания производятся электрическими сигналами, которые созда- ются устройствами и направляются на входные контакты микросхемы контроллера прерываний. Контроллер прерываний в свою очередь отправляет сигнал процессо- ру. Процессор выполняет детектирование сигнала и прерывает выполнение работы для того, чтобы обработать прерывание. После этого процессор извещает операци- онную систему о том, что произошло прерывание и операционная система может соответствующим образом это прерывание обработать.
    Различные устройства связаны со своими прерываниями с помощью уникальных числовых значений, соответствующих каждому прерыванию. Отсюда следует, что прерывания, поступившие от клавиатуры, отличаются от прерываний, поступивших от жесткого диска. Это позволяет операционной системе различать прерывания и иметь информацию о том, какое аппаратное устройство произвело данное преры- вание. Поэтому операционная система может обслуживать каждое прерывание с по- мощью своего уникального обработчика.
    Идентификаторы, соответствующие прерываниям, часто называются линиями запросов на прерывание (interrupt request lines, IRQ lines). Обычно это некоторые числа. Например, для платформы PC значение IRQ, равное 0, — это прерывание тай- мера, a IRQ, равное 1, — прерывание клавиатуры. Однако не все номера прерываний жестко определены. Прерывания, связанные с устройствами шины PCI, например,
    назначаются динамически. Другие платформы, которые не поддерживают стандарт
    PCI, имеют аналогичные функции динамического назначения номеров прерываний.
    Основная идея состоит в том, что определенные прерывания связаны с определен- ными устройствами, и у ядра есть вся эта информация. Аппаратное обеспечение,
    чтобы привлечь внимание ядра, генерирует прерывание вроде "Эй! Было новое нажа-
    тие клавиши! Его необходимо обработать!.
    Исключительные ситуации
    Исключительные ситуации (exceptions) часто рассматриваются вместе с прерываниями. В от- личие от прерываний, они возникают синхронно с тактовым генератором процессора. И дей- ствительно, их часто называют синхронными прерываниями. Исключительные ситуации генери- руются процессором при выполнении машинных инструкций как реакция на ошибку программы
    (например, деление на нуль) или как реакция на аварийную ситуацию, которая может быть об- работана ядром (например, прерывание из-за отсутствия страницы, page fault). Так как боль- шинство аппаратных платформ обрабатывают исключительные ситуации аналогично обработке прерываний, то инфраструктуры ядра, для обоих видов обработки, также аналогичны. Большая часть материала, посвященная обработке прерываний (асинхронных, которые генерируются ап- паратными устройствами), также относится и к исключительным ситуациям (синхронным, кото- рые генерируются самим процессором).
    С одним типом исключительной ситуации мы уже встречались в предыдущей главе. Там было рассказано, как для аппаратной платформы х86 реализованы системные вызовы на основе программных прерываний. При этом генерируется исключительная ситуация, которая застав- ляет переключиться в режим ядра и в конечном итоге приводит к выполнению определенного обработчика системного вызова. Прерывания работают аналогичным образом, за исключением того, что прерывания генерируются не программным, а аппаратным обеспечением.
    1 1 0 Глава 6

    Обработчики прерываний
    Функция, которую выполняет ядро в ответ на определенное прерывание, называ- ется обработчиком прерывания (interrupt handler) или подпрограммой обслуживания преры-
    вания (interrupt service routine). Каждому устройству, которое генерирует прерывания,
    соответствует свой обработчик прерывания. Например, одна функция обрабатывает прерывание от системного таймера, а другая — прерывания, сгенерированные клави- атурой. Обработчик прерывания для какого-либо устройства является частью драйве-
    ра этого устройства — кода ядра, который управляет устройством.
    В операционной системе Linux обработчики прерываний — это обычные функ- ции, написанные на языке программирования С. Они должны соответствовать определенному прототипу, чтобы ядро могло стандартным образом принимать ин- формацию об обработчике, а в остальном— это обычные функции. Единственное,
    что отличает обработчики прерываний от других функций ядра, — это то, что они вызываются ядром в ответ на прерывание и выполняются в специальном контексте,
    именуемом контекстом прерывания (interrupt context), который будет рассмотрен да- лее.
    Так как прерывание может возникнуть в любой момент времени, то, соответ- ственно, и обработчик прерывания может быть вызван в любой момент времени.
    Крайне важно, чтобы обработчик прерывания выполнялся очень быстро и возоб- новлял управление прерванного кода по возможности быстро. Поэтому, хотя для аппаратного обеспечения и важно, чтобы прерывание обслуживалось немедленно,
    для остальной системы важно, чтобы обработчик прерывания выполнялся в тече- ние максимально короткого промежутка времени. Минимально возможная работа,
    которую должен сделать обработчик прерывания, — это отправить подтверждение устройству, что прерывание получено. Однако обычно обработчики прерываний должны выполнить большее количество работы. Например, рассмотрим обработчик прерывания сетевого устройства. Вместе с отправкой подтверждения аппаратному обеспечению, обработчик прерывания должен скопировать сетевые пакеты из аппа- ратного устройства в память системы, обработать их, отправить соответствующему стеку протоколов или соответствующей программе. Очевидно, что для этого требу- ется много работы.
    Верхняя и нижняя половины
    Ясно, что два указанных требования о том, что обработчик прерывания должен выполняться быстро и, в дополнение к этому, выполнять много работы, являются противоречивыми. В связи с конфликтными требованиями, обработчик прерываний разбивается на две части, или половины. Обработчик прерывания является верхней
    половиной (top half)— он выполняется сразу после приема прерывания и выполняет работу, критичную к задержкам во времени, такую как отправка подтверждения о получении прерывания или сброс аппаратного устройства. Работа, которую можно выполнить позже, откладывается до выполнения нижней (или основной) половины
    (bottom half). Нижняя половина обрабатывается позже, в более удобное время, когда все прерывания разрешены. Достаточно часто нижняя половина выполняется сразу же после возврата из обработчика прерывания.
    Прерывания и обработка прерываний 111

    Операционная система предоставляет различные механизмы для реализации об- работки нижних половин, которые обсуждаются в главе 7, "Обработка нижних по- ловин и отложенные действия".
    Рассмотрим пример разделения обработчика прерывания на верхнюю и нижнюю половины на основе старой доброй сетевой платы. Когда сетевой интерфейсный адаптер получает входящие из сети пакеты, он должен уведомить ядро о том, что доступны новые данные. Это необходимо сделать немедленно, чтобы получить опти- мальную пропускную способность и время задержки при передаче информации по сети. Поэтому немедленно генерируется прерывание: "Эй, ядро! Есть свежие пакеты!.
    Ядро отвечает выполнением зарегистрированного обработчика прерывания от сете- вого адаптера.
    Обработчик прерывания выполняется, аппаратному обеспечению направляется подтверждение, пакеты копируются в основную память, и после этого сетевой адап- тер готов к получению новых пакетов. Эта задача является важной, критичной ко времени выполнения и специфической для каждого типа аппаратного обеспечения.
    Остальная часть обработки сетевых пакетов выполняется позже — нижней полови- ной обработчика прерывания. В этой главе мы рассмотрим обработку верхних по- ловин, а в следующей — нижних.
    1   ...   10   11   12   13   14   15   16   17   ...   53


    написать администратору сайта