Intereting Posts
Как определить демон уведомления? Настройте 3 экрана / двойные графические / повернутые экраны Ubuntu 15.10 Где получить файл «/etc/ld.so.nohwcap»? Как вызвать процесс с определенным двоичным кодом java и всеми процессами, вызванными этим процессом? gnuwin bash Если файл .txt содержит строковый файл-копию Virtualbox не работает после обновления arch linux? Управление документами Linux Как сохранить время на сервере (виртуальной машине) с течением времени? Использование printf для раунда 49.765 до 49.77? Создание пакета Debian, который не удаляет каталоги после его очистки Как извлечь только файлы, не создавая пути каталога из файла tar.gz? Команда Whatis (встроенная оболочка для исполняемых программ) Может ли w3m автоматически искать введенный текст? Отображение сеанса экрана Bash для удаленного экрана для пользователя Загрузка ядра Linux: uninitialized urandom read (прочитано 16 байт)

Прерывание системных вызовов при попадании сигнала

Из чтения man-страниц при вызовах read() и write() кажется, что эти вызовы прерываются сигналами независимо от того, нужно ли их блокировать или нет.

В частности, предположим

  • процесс устанавливает обработчик для некоторого сигнала.
  • устройство открыто (скажем, терминал), если O_NONBLOCK не установлен (т.е. работает в режиме блокировки)
  • процесс затем делает системный вызов read() для чтения с устройства и в результате выполняет путь управления ядром в пространстве ядра.
  • в то время как прецессия выполняет свое read() в пространстве ядра, сигнал, для которого ранее был установлен обработчик, доставляется этому процессу, и его обработчик сигнала вызывается.

Читая страницы руководства и соответствующие разделы в SUSv3 «Объем системных интерфейсов (XSH)» , можно обнаружить:

я. Если read() прерывается сигналом, прежде чем он считывает любые данные (т. Е. Должен был блокироваться, потому что не было данных), он возвращает -1, а errno в [EINTR].

II. Если read() прерывается сигналом после того, как он успешно прочитал некоторые данные (т. Е. Было возможно немедленно начать обслуживание запроса), он возвращает количество прочитанных байтов.

Вопрос А): Правильно ли я предполагаю, что в любом случае (блок / нет блока) доставка и обработка сигнала не полностью прозрачны для read() ?

Случай i. представляется понятным, поскольку блокировка read() обычно помещает процесс в состояние TASK_INTERRUPTIBLE так что, когда сигнал доставляется, ядро TASK_RUNNING процесс в состояние TASK_RUNNING .

Однако, когда read() не нужно блокировать (случай ii.) И обрабатывает запрос в пространстве ядра, я бы подумал, что приход сигнала и его обработка будут прозрачными, как приход и правильная обработка прерывания HW. В частности, я предположил бы, что после доставки сигнала процесс будет временно помещен в пользовательский режим для выполнения своего обработчика сигнала, из которого он в конечном итоге вернется, чтобы завершить обработку прерванного read() (в пространстве ядра), так что read() завершает свой курс, после чего процесс возвращается обратно к точке сразу после вызова read() (в пользовательском пространстве), при этом все доступные байты считываются в результате.

Но ii. кажется, подразумевает, что read() прерван, поскольку данные доступны немедленно, но он возвращает только некоторые данные (вместо всех).

Это подводит меня ко второму (и окончательному) вопросу:

Вопрос B): Если мое предположение в соответствии с A) является правильным, то почему read() прерывается, хотя его не нужно блокировать, потому что есть данные, доступные для немедленного удовлетворения запроса? Другими словами, почему read() не возобновляется после выполнения обработчика сигнала, что в итоге приводит к возврату всех доступных данных (которые были доступны после всех)?

Сводка: вы правы, что получение сигнала не является прозрачным, ни в случае i (прерывается, не прочитав ничего), ни в случае ii (прерывается после частичного чтения). В противном случае я бы потребовал внесения фундаментальных изменений как в архитектуру операционной системы, так и в архитектуру приложений.

Представление реализации ОС

Подумайте, что произойдет, если системный вызов прерывается сигналом. Обработчик сигнала выполнит код режима пользователя. Но обработчик syscall – это код ядра и не доверяет коду пользовательского режима. Итак, давайте рассмотрим выбор для обработчика syscall:

  • Завершить системный вызов; сообщите, сколько было сделано для кода пользователя. При необходимости код приложения должен перезапустить системный вызов. Вот как работает Unix.
  • Сохраните состояние системного вызова и позвольте коду пользователя возобновить вызов. Это проблематично по нескольким причинам:
    • Пока код пользователя запущен, что-то может привести к аннулированию сохраненного состояния. Например, при чтении из файла файл может быть усечен. Поэтому для обработки этих случаев для кода ядра потребуется много логики.
    • Сохраняемому состоянию нельзя запретить блокировку, потому что нет никакой гарантии, что код пользователя когда-либо возобновит работу в режиме syscall, а затем блокировка будет сохранена навсегда.
    • Ядро должно выставить новые интерфейсы для возобновления или отмены текущих системных вызовов, в дополнение к нормальному интерфейсу для запуска системного вызова. Это очень сложно для редкого случая.
    • Сохраненное состояние должно будет использовать ресурсы (по крайней мере, память); эти ресурсы должны быть выделены и сохранены ядром, но будут учтены против выделения процесса. Это не является непреодолимым, но это сложность.
      • Обратите внимание, что обработчик сигнала может сделать системные вызовы, которые сами прерываются; поэтому вы не можете просто иметь статический ресурс, который охватывает все возможные системные вызовы.
      • А что, если ресурсы не могут быть выделены? Тогда syscall пришлось бы терпеть неудачу в любом случае. Это означает, что приложение должно иметь код для обработки этого случая, поэтому этот проект не упростит код приложения.
  • Оставайтесь в процессе (но приостановлено), создайте новый поток для обработчика сигнала. Это опять же проблематично:
    • Ранние реализации unix имели один поток для каждого процесса.
    • Обработчик сигнала будет рисковать переступать на башмаки syscall. В любом случае это проблема, но в текущем дизайне unix она содержится.
    • Ресурсы должны быть выделены для нового потока; см. выше.

Основное отличие от прерывания состоит в том, что код прерывания является надежным и сильно ограниченным. Обычно не разрешается выделять ресурсы или запускаться вечно, или принимать блокировки, а не выпускать их, или делать какие-либо другие неприятные вещи; так как обработчик прерываний написан самим разработчиком ОС, он знает, что ничего плохого не сделает. С другой стороны, код приложения может делать все.

Конструкция приложения

Когда приложение прерывается в середине системного вызова, должен ли syscall продолжить выполнение? Не всегда. Например, рассмотрите программу, такую ​​как оболочка, которая считывает строку с терминала, и пользователь нажимает Ctrl+C , вызывая SIGINT. Чтение не должно завершаться, вот что такое сигнал. Обратите внимание, что этот пример показывает, что syscall read должен прерываться, даже если байт не был прочитан.

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

Системный вызов read так, как это происходит, потому что это примитив, который имеет смысл, учитывая общий дизайн операционной системы. То, что это означает, грубо говоря, «читайте как можно больше, до предела (размер буфера), но остановитесь, если что-то еще произойдет». Чтобы действительно прочитать полный буфер, нужно запустить read в цикле до тех пор, пока не будет прочитано столько байтов, сколько возможно; это функция более высокого уровня, fread(3) . В отличие от read(2) который является системным вызовом, fread – это библиотечная функция, реализованная в пользовательском пространстве поверх read . Он подходит для приложения, которое читает файл или пытается пробовать; он не подходит для интерпретатора командной строки или для сетевой программы, которая должна дросселировать соединения, а также для сетевой программы, которая имеет параллельные соединения и не использует потоки.

Пример чтения в цикле представлен в программном обеспечении Linux Linux Linux:

 ssize_t ret; while (len != 0 && (ret = read (fd, buf, len)) != 0) { if (ret == -1) { if (errno == EINTR) continue; perror ("read"); break; } len -= ret; buf += ret; } 

Он заботится о case i и case ii и немногих других.

Чтобы ответить на вопрос A :

Да, доставка и обработка сигнала не полностью прозрачны для read() .

Попытка read() на полпути может занимать некоторые ресурсы, пока сигнал прерывается. И обработчик сигнала может вызывать еще одно read() (или любые другие безопасные системные вызовы с асинхронным сигналом ). Таким образом, read() прерванный сигналом, должен быть остановлен первым, чтобы освободить ресурсы, которые он использует, в противном случае read() вызванный обработчиком сигнала, будет обращаться к тем же ресурсам и вызвать повторные проблемы.

Поскольку системные вызовы, отличные от read() могут быть вызваны из обработчика сигналов, и они могут также занимать идентичный набор ресурсов, как это делает read() . Чтобы избежать повторных проблем выше, простейшая, безопасная конструкция заключается в том, чтобы остановить прерывание read() каждый раз, когда во время его запуска происходит сигнал.