Что такое fd0 linux

Обновлено: 03.07.2024

select, pselect, FD_CLR, FD_ISSET, FD_SET

ОБЗОР

Требования макроса тестирования свойств для glibc (см. feature_test_macros(7)):

pselect(): _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600

ОПИСАНИЕ

Вызов select() (или pselect()) используется для эффективного слежения за несколькими файловыми дескрипторами — для ожидания, когда какой-то из них не станет «готов», то есть появится возможность чтения-записи данных, или с файловым дескриптором не возникнет «исключительная ситуация».

Основные параметры задаются в виде «набора» файловых дескрипторов: readfds, writefds и exceptfds. Каждый набор имеет тип fd_set и его содержимое можно изменять с помощью макросов FD_CLR(), FD_ISSET(), FD_SET() и FD_ZERO(). При создании нового набора сначала его нужно очистить с помощью FD_ZERO(). Вызов select() изменяет содержимое наборов в соответствии с правилами, описанными далее; после вызова select() вы можете проверить существует ли ещё файловый дескриптор в наборе с помощью макроса FD_ISSET(). Макрос FD_ISSET() возвращает ненулевое значение, если указанный файловый дескриптор присутствует в наборе и ноль, если отсутствует. Макрос FD_CLR() удаляет файловый дескриптор из набора.

Аргументы

readfds Этот набор служит для слежения за появлением данных, доступных для чтения из любого файлового дескриптора. После возврата из select() в readfds остаются только те дескрипторы файлов, из которых возможно немедленное чтение. writefds Этот набор служит для слежения за появлением места для записи данных в любой из файловых дескрипторов набора. После возврата из select() в writefds остаются только те файловые дескрипторы, в которые возможна немедленная запись. exceptfds Этот набор служит для слежения за «исключительными ситуациями». На самом деле, отслеживается только одна распространённая исключительная ситуация: доступность внепоточных (out-of-band — OOB) данных для чтения из сокета TCP. Более подробно о данных OOB смотрите в recv(2), send(2) и tcp(7). Другая менее распространённая ситуация: select(2) указывает на исключительную ситуацию с псевдотерминалом в пакетном режиме; см. tty_ioctl(4). После возврата из select() в exceptfds остаются только те файловые дескрипторы, в которых произошла исключительная ситуация. nfds Представляет собой целое число, на единицу большее максимального файлового дескриптора в любом из наборов. Другими словами, при добавлении файловых дескрипторов в наборы вам необходимо вычислять максимальное целое значение любого из них, а затем увеличивать это значение на единицу и передавать в nfds. utimeout Этот аргумент задаёт наибольшее время, которое вызов select() будет ожидать событий, по прошествии которого завершит работу, даже если ничего не произойдёт. Если значение этого аргумента равно NULL, то select() будет ожидать бесконечно. Значение utimeout может быть установлено в ноль секунд; в этом случае select() возвратит управление немедленно, с информацией о готовности файловых дескрипторов на момент вызова. Структура struct timeval определена следующим образом: ntimeout Этот аргумент pselect() имеет то же значение, что и utimeout, но структура struct timespec позволяет указывать время с точностью до наносекунд: sigmask Этот аргумент содержит набор сигналов, которые ядро должно разблокировать (то есть удалить из маски сигналов вызывающей нити) на время, пока вызывающий заблокирован в вызове pselect() (см. sigaddset(3) и sigprocmask(2)). В качестве аргумента может быть передано значение NULL — вызов не изменяет маску сигналов при входе и выходе из функции. То есть pselect() ведёт себя как select().

Комбинирование событий сигналов и данных

Вызов pselect() полезен как для ожидания сигнала, так и для ожидания готовности файлового дескриптора для ввода-вывода. Программы, принимающие сигналы, как правило, лишь выставляют в обработчике сигнала глобальный флаг, который означает, что требуется обработка события в главном цикле программы. Появление сигнала заставит вызов select() (или pselect()) вернуть управление вызвавшей программе; при этом errno будет присвоено EINTR. Это поведение продиктовано необходимостью обработки сигналов в главном цикле программы во избежание бесконечной блокировки select(). В главном цикле программы должно быть условие, проверяющее глобальный флаг. Возникает вопрос: а что если сигнал придёт после проверки этого условия, но до вызова select()? В этом случае программа навсегда останется в select(), хотя и есть ожидающее событие. Для разрешения этой проблемы существует вызов pselect(). Этот вызов можно использовать для установки в сигнальной маске сигналов, которые принимаются только внутри вызова pselect(). Например, предположим что интересующее нас событие — это завершение дочернего процесса. Перед запуском главного цикла заблокируем SIGCHLD с помощью sigprocmask(2). Наш вызов pselect() разблокирует SIGCHLD, указав пустую маску сигналов. Программа будет выглядеть так:

Практика

Правила использования

Многие из тех, кто пытался использовать select(), сталкивались с поведением, которое трудно понять, и которое приводило к непереносимым или просто плохим результатам. Например, вышеприведенная программа тщательно спланирована так, чтобы ни в каком случае не блокироваться, хотя для её файловых дескрипторов не установлен неблокирующий режим. Несложно перечислить не очевидные ошибки, которые лишат всех преимуществ использования select(), поэтому вот список основных моментов, на которые нужно обращать внимание при использовании select(). 1. Всегда старайтесь использовать select() без указания времени ожидания. Ваша программа не должна ничего делать, если нет данных. Код, зависимый от времени ожидания, обычно плохо переносим и сложен для отладки. 2. Для повышения эффективности значение nfds должно правильно вычисляться, как это объяснялось выше. 3. Файловые дескрипторы не должны добавляться в наборы, если вы не планируете после вызова select() проверять результат и соответствующим образом реагировать. Смотрите следующее правило. 4. После возврата из select() должны быть проверены все файловые дескрипторы во всех наборах. 5. Вызовы read(2), recv(2), write(2) и send(2) не обязательно считывают/записывают данные в полном объёме. Такое, конечно, возможно при низком трафике или быстром потоке, однако происходит далеко не всегда. Вы должны рассчитывать, что ваши функции получают/отправляют только один байт за раз. 6. Никогда не считывайте/записывайте побайтно, если только вы не абсолютно уверены в том, что нужно обработать небольшой объём данных. Крайне неэффективно считывать/записывать меньшее количество байт, чем вы можете поместить в буфер за один раз. Буферы в вышеприведённом примере имеют размер 1024 байта, однако могут быть легко увеличены до максимального размера пакета в вашей локальной сети. 7. Вызовы read(2), recv(2), write(2) и send(2) также как и select() могут возвратить -1 с errno равным EINTR, или errno равным EAGAIN (EWOULDBLOCK). Такие ситуации должны быть правильно обработаны (в вышеприведенной программе этого не сделано). Если ваша программа не собирается принимать сигналы, то маловероятно, что вы получите EINTR. Если ваша программа не использует неблокирующий ввод-вывод, то вы не получите EAGAIN. 8. Никогда не вызывайте read(2), recv(2), write(2) или send(2) с буфером нулевой длины. 9. Если вызовы read(2), recv(2), write(2) и send(2) завершаются с ошибками, отличными от перечисленных в пункте 7. или один из вызовов ввода вернул 0, что указывает на конец файла, то вы не должны передавать этот файловый дескриптор в select() снова. В примере выше я немедленно закрываю файловый дескриптор и устанавливаю его в -1 для предотвращения его включения в набор. 10. Значение времени ожидания должно быть инициализировано при каждом новом вызове select(), так как некоторые операционные системы изменяют значение структуры. Однако pselect() не изменяет структуру времени ожидания. 11. Так как select() изменяет переданные наборы файловых дескрипторов, то при использовании его в цикле наборы должны повторно инициализироваться перед каждым вызовом.

Эмуляция usleep

В системах, не имеющих функции usleep(3), вы можете использовать select() с конечной задержкой и без файловых дескрипторов следующим образом:

Однако работа гарантируется только в системах UNIX.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

При удачно завершении select() возвращает общее число файловых дескрипторов, которые остались в наборах.

При выходе из select() по окончании времени ожидания возвращается ноль. Все наборы файловых дескрипторов будут пусты (но могут быть не пусты на некоторых системах).

Значение -1 указывает на ошибку, при этом errno устанавливается соответствующим образом. В случае ошибки содержимое наборов и структуры struct timeout не определено и не должно быть использовано. Однако вызов pselect() никогда не изменяет ntimeout.

ЗАМЕЧАНИЯ

В общем случае, все операционные системы, поддерживающие сокеты, поддерживают также и select(). Вызов select() можно применять для переносимого и эффективного решения многих задач, вместо которого многие программисты пытаются использовать нити, ветвление процессов, IPC, сигналы, разделение памяти и другие методы.

Системный вызов poll(2) имеет такую же функциональность, как и select() и иногда более эффективен для слежения за разреженным набором файловых дескрипторов. В настоящее время он стал широко распространён, но исторически является менее переносимым чем select().

Программный интерфейс Linux epoll(7) предоставляет более эффективный метод для слежения за большим количеством файловых дескрипторов чем select(2) и poll(2).

ПРИМЕР

Вот пример, который лучше демонстрирует возможности select(). Программа осуществляет перенаправление одного порта TCP в другой.

Вышеприведенная программа правильно перенаправляет большинство данных задач, использующих соединения TCP, включая внепоточные (OOB) данные, передаваемые серверами telnet. Она справляется со сложной проблемой поддержания одновременного двустороннего обмена данными. Возможно, вы решите, что эффективнее использовать fork(2) и выделить отдельную нить для каждого потока. На самом деле это сложнее, чем кажется. Другой идеей может быть использование неблокирующего ввода-вывода с помощью fcntl(2). Это также может вызвать проблемы из-за того, что придётся использовать неэффективные таймауты.

Программа не обрабатывает более одного соединения, однако она может быть легко доработана путем добавления связанного списка буферов — по одному на каждое соединение. В данный момент новые соединения приводят к закрытию текущего.


В этом руководстве мы обсудим, что такое перенаправление Bash и как работать с перенаправлением в Bash с помощью примеров команд.

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

Что такое дескриптор файла?

Когда вы открываете файл в Linux, каждому файлу присваивается целое число, и эта информация хранится в ядре.

Таким образом, ядро знает, какие файлы открыты и какой процесс открыл эти файлы.

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

  • FD 0 -> Стандартный вход(stdin) -> клавиатура
  • FD 1 -> Стандартный вывод(Stdout) -> дисплей(терминал)
  • FD 2 -> Стандартная ошибка(Stderr) -> Дисплей(терминал)

Вы можете увидеть дескрипторы файлов в каталоге /dev:

Stdin (FD0) получает ввод с клавиатуры. stdout (FD1) и stderr (FD2) отправляются на терминал.

При использовании пайпов и перенаправлений вы можете изменить способ передачи входных данных, а также выходных данных и ошибок.

Перенаправление вывода в файл

Как было сказано ранее, вывод (stdout) и ошибки (stderr) любой программы отправляются на терминал.

Посмотрите на приведенный ниже пример.

Я выполняю команду uname -mrs и перенаправляю вывод в файл с именем uname.log.

Как я уже упоминал, одинарный оператор перенаправления (>) будет заменять содержимое файла, только если файл уже существует.

Однако, если вы хотите добавить содержимое вместо перезаписи в тот же файл, используйте двойной оператор перенаправления (т.е. >>).

Вы также можете использовать здесь дескриптор файла stdout(1).

Как работать с Stderr

Каждая программа генерирует как вывод, так и ошибку, и оба вывода отправляются на терминал.

Давайте рассмотрим простой пример выполнения команды ls, чтобы увидеть, как это работает.

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

Я получаю и ошибку, и вывод в терминале, как я уже говорил.

Я снова выполняю ту же команду ls, но на этот раз перенаправляю вывод в файл.

Как видно из вывода, оператор > перенаправляет stdout(1) в файл, но stderr(2) отправляется на терминал.

Обязательно использовать дескриптор файла для stderr(2) перед оператором перенаправления >, который будет отправлять ошибку только в файл.

Теперь stdout и stderr записываются в отдельные файлы.

Начиная с bash 4.4, вы также можете использовать знак &> для перенаправления и stdout, и stderr в один файл.

Что такое /dev/null?

Проще говоря, null отбрасывает все, что вы перенаправляете в него.

Почему нуль имеет значение при перенаправлении?

Вы можете задаться этим вопросом

. В некоторых случаях вы можете не выводить и не сохранять stdout или stderr.

Перенаправление ввода в Bash

Подобно тому, как вы перенаправляете вывод и ошибку в файл, вы также можете передавать входные данные команде с помощью оператора перенаправления ввода (<).

Давайте начнем с простой программы подсчета слов.

Здесь я перенаправляю содержимое файла itisgood.txt в программу подсчета слов, чтобы найти количество строк.

Вы также можете передать дескриптор файла stdin (0) при перенаправлении ввода.

Вы можете комбинировать операторы перенаправления ввода и вывода, как показано ниже.

Перенаправление ввода будет использоваться вместе с циклом while для чтения содержимого файла построчно.

Взгляните на приведенный ниже пример.

Я передаю файл /etc/passwd в качестве входных данных команде while.

Здесь команда read прочитает строку за строкой и сохранит ее в переменной VAL, а далее в цикле будет записано условие для проверки, доступен ли пользователь.

Сохраните файл и закройте его.

Это не самый оптимальный способ решения задачи поиска пользователя, но для демонстрационных целей сойдет.

Теперь сделайте скрипт исполняемым и запустите его:

Заключение

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

Конечно же я ответил, что посмотрю, чем занято это место и если возможно, то почищу место.
Тогда интервьюер спросил, а что если на разделе нет свободного места, но и файлов, которые бы занимали все место, ты тоже не видишь?

На это я сказал, что всегда можно посмотреть открытые файл дескрипторы, например командой lsof и понять какое приложение заняло все доступное место, а дальше можно действовать по обстоятельствам, в зависимости от того, нужны ли данные.

Интервьюер прервал меня на последнем слове, дополнив свой вопрос: «Предположим, что данные нам не нужны, это просто дебаг лог, но приложение не работает из-за того, что не может записать дебаг»?

«окей», — ответил я, «мы можем выключить дебаг в конфиге приложения и перезапустить его».
Интервьюер возразил: «Нет, приложение мы перезапустить не можем, у нас в памяти все еще хранятся важные данные, а к самому сервису подключены важные клиенты, которых мы не можем заставлять переподключаться заново».

«ну хорошо», сказал я, «если мы не можем перезапускать приложение и данные нам не важны, то мы можем просто очистить этот открытый файл через файл дескриптор, даже если мы его не видим в команде ls на файловой системе».

Интервьюер остался доволен, а я нет.

Тогда я подумал, почему человек, проверяющий мои знания, не копает глубже? А что, если данные все-таки важны? Что если мы не можем перезапускать процесс, и при этом этот процесс пишет на файловую систему в раздел, на котором нет свободного места? Что если мы не можем потерять не только уже записанные данные, но и те данные, что этот процесс пишет или пытается записать?

Тузик

В начале моей карьеры я пытался создать небольшое приложение, в котором нужно было хранить информацию о пользователях. И тогда я думал, а как мне сопоставить пользователя к его данным. Есть, например, у меня Иванов Иван Иваныч, и есть у него какие-то данные, но как их подружить? Я могу указать напрямую, что собака по имени «Тузик» принадлежит этому самому Ивану. Но что, если он сменит имя и вместо Ивана станет, например, Олей? Тогда получится, что наша Оля Ивановна Иванова больше не будет иметь собаки, а наш Тузик все еще будет принадлежать несуществующему Ивану. Решить эту проблему помогла база данных, которая каждому пользователю давала уникальный идентификатор (ID), и мой Тузик привязывался к этому ID, который, по сути, был просто порядковым номером. Таким образом хозяин у тузика был с ID под номером 2, и на какой-то момент времени под этим ID был Иван, а потом под этим же ID стала Оля. Проблема человечества и животноводства была практически решена.

Файл дескриптор

Проблема файла и программы, работающей с этим файлом, примерно такая же как нашей собаки и человека. Предположим я открыл файл под именем ivan.txt и начал в него записывать слово tuzik, но успел записать только первую букву «t» в файл, и этот файл был кем-то переименован, например в olya.txt. Но файл остался тем же самым, и я все еще хочу записать в него своего тузика. Каждый раз при открытии файла системным вызовом open в любом языке программирования я получаю уникальный ID, который указывает мне на файл, этот ID и есть файл дескриптор. И совершенно не важно, что и кто делает с этим файлом дальше, его могут удалить, его могут переименовать, ему могут поменять владельца или забрать права на чтение и запись, я все равно буду иметь к нему доступ, потому что на момент открытия файла у меня были права для его чтения и/или записи и я успел начать с ним работать, а значит должен продолжать это делать.

В Linux библиотека libc открывает для каждого запущенного приложения(процесса) 3 файл дескриптора, с номерами 0,1,2. Больше информации вы можете найти по ссылкам man stdio и man stdout

  • Файл дескриптор 0 называется STDIN и ассоциируется с вводом данных у приложения
  • Файл дескриптор 1 называется STDOUT и используется приложениями для вывода данных, например командами print
  • Файл дескриптор 2 называется STDERR и используется приложениями для вывода данных, сообщающих об ошибке

Список файл дескрипторов можно посмотреть у любого процесса, если вы знаете его PID.

Например, откроем консоль с bash и посмотрим PID нашего процесса


Во второй консоли запустим


Файл дескриптор с номером 255 можете смело игнорировать в рамках данной статьи, он был открыт для своих нужд уже самим bash, а не прилинкованной библиотекой.

Сейчас все 3 файл дескриптора связаны с устройством псевдотерминала /dev/pts, но мы все равно можем ими манипулировать, например запустим во второй консоли


И в первой консоли мы увидим

Redirect и Pipe

Вы можете легко переопределить эти 3 файл дескриптора в любом процессе, в том числе и в bash, например через трубу(pipe), соединяющую два процесса, смотрим


Вы можете сами запустить эту команду с strace -f и увидеть, что происходит внутри, но я вкратце расскажу.

Наш родительский процесс bash с PID 15771 парсит нашу команду и понимает сколько именно команд мы хотим запустить, в нашем случае их две: cat и sleep. Bash знает что ему нужно создать два дочерних процесса, и объединить их одной трубой. Итого bash потребуется 2 дочерних процесса и один pipe.

Перед созданием дочерних процессов bash запускает системный вызов pipe и получает новые файл дескрипторы на временный буфер pipe, но этот буфер никак пока не связывает наши два дочерних процесса.

Для родительского процесса это выглядит так будто pipe уже есть, а дочерних процессов еще нет:


Затем с помощью системного вызова clone bash создает два дочерних процесса, и наши три процесса будут выглядеть так:


Не забываем, что clone клонирует процесс вместе со всеми файл дескрипторами, поэтому в родительском процессе и в дочерних они будут одинаковые. Задача родительского процесса с PID 15771 следить за дочерними процессами, поэтому он просто ждет ответ от дочерних.

Следовательно pipe ему не нужен, и он закрывает файл дескрипторы с номерами 3 и 4.

В первом дочернем процессе bash с PID 9004, системным вызовом dup2, меняет наш STDOUT файл дескриптор с номером 1 на файл дескриптор указывающий на pipe, в нашем случае это номер 3. Таким образом все, что первый дочерний процесс с PID 9004 будет писать в STDOUT, будет автоматически попадать в буфер pipe.

Во втором дочернем процессе с PID 9005 bash меняет с помощью dup2 файл дескриптор STDIN с номером 0. Теперь все, что будет читать наш второй bash с PID 9005, будет читать из pipe.

После этого в дочерних процессах так же закрываются файл дескрипторы с номерами 3 и 4, так как они более не используются.

Файл дескриптор 255 я намеренно игнорирую, он использует для внутренних нужд самого bash и в дочерних процессах будет также закрыт.

Далее в первом дочернем процессе с PID 9004 bash запускает с помощью системного вызова exec исполняемый файл, который мы указали в командной строке, в нашем случае это /usr/bin/cat.

Во втором дочернем процессе с PID 9005 bash запускает второй исполняемый файл, который мы указали, в нашем случае это /usr/bin/sleep.

Системный вызов exec не закрывает файл дескрипторы, если они не были открыты с флагом O_CLOEXEC во время выполнения вызова open. В нашем случае после запуска исполняемых файлов все текущие файл дескрипторы сохранятся.

Проверяем в консоли:


Как видите уникальный номер нашего pipe у нас в обоих процессах совпадает. Таким образом у нас есть связь между двумя разными процессами с одним родителем.

Для тех, кто не знаком с системными вызовами, которые использует bash, крайне рекомендую запустить команды через strace и посмотреть, что происходит внутри, например, так:


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


Запустим программу и посмотрим на файл дескрипторы


Как видим у нас есть наши 3 стандартные файл дескрипторы и еще один, который мы открыли. Проверим размер файла:


данные пишутся, пробуем поменять права на файл:


Видим, что данные все еще пишутся, хотя наш пользователь не имеет права писать в файл. Попробуем его удалить:


Куда пишутся данные? И пишутся ли вообще? Проверяем:


Да, наш файл дескриптор все еще существует, и мы можем работать с этим файл дескриптором как с нашим старым файлом, мы можем его читать, очищать и копировать.

Смотрим на размер файла:


Размер файла 19923457. Пробуем очистить файл:


Как видим размер файла только увеличивается и наш транкейт не сработал. Обратимся к документации по системному вызову open. Если при открытии файла мы используем флаг O_APPEND, то при каждой записи операционная система проверяет размер файла и пишет данные в самый конец файла, причем делает это атомарно. Это позволяет нескольким тредам или процессам писать в один и тот же файл. Но в нашем коде мы не используем этот флаг. Мы можем увидеть другой размер файла в lsof после транкейт только если откроем файл для дозаписи, а значит в нашем коде вместо


мы должны поставить


Проверяем с «w» флагом

Программируем уже запущенный процесс

Часто программисты при создании и тестировании программы используют дебагеры (например GDB) или различные уровни логирования в приложении. Linux предоставляет возможность фактически писать и менять уже запущенную программу, например менять значения переменных, устанавливать breakpoint и тд и тп.

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

Создадим файл для нашего раздела, который мы подмонтируем как отдельный диск:


Создадим файловую систему:


Подмонтируем файловую систему:


Создаем директорию с нашим владельцем:


Откроем файл только на запись в нашей программе:


Ждем несколько секунд


Итак, мы получили проблему, описанную в начале этой статьи. Свободного места 0, занятого 100%.

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

Допустим, у нас все же есть место на диске, но в другом разделе, например в /home.

Попробуем «перепрограммировать на лету» наш код.

Смотрим PID нашего процесса, который съел все место на диске:


Подключаемся к процессу через gdb


Смотрим открытые файл дескрипторы:


Смотрим информацию о файл дескрипторе с номером 3, который нас интересует


Помня о том, какой системный вызов делает Python (смотрите выше, где мы запускали strace и находили вызов open), обрабатывая наш код для открытия файла, мы делаем то же самое самостоятельно от имени нашего процесса, но биты O_WRONLY|O_CREAT|O_TRUNC нам нужно заменить на числовое значение. Для этого открываем исходники ядра, например тут и смотрим какие флаги за что отвечают

Объединяем все значения в одно, получаем 00001101

Запускаем наш вызов из gdb


Итак мы получили новый файл дескриптор с номером 4 и новый открытый файл на другом разделе, проверяем:


Мы помним пример с pipe — как bash меняет файл дескрипторы, и уже выучили системный вызов dup2.

Пробуем подменить один файл дескриптор другим


Закрываем файл дескриптор 4, так как нам он не нужен:


И выходим из gdb


Проверяем новый файл:


Как видим, данные пишутся в новый файл, проверяем старый:


Данные не потеряны, приложение работает, логи пишутся в новое место.

Немного усложним задачу

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

Перезапускаем приложение, и проверяем:


Места на диске нет, но мы успешно создаем там именованный pipe:


Теперь нам надо как-то завернуть все данные, что попадают в этот pipe на другой сервер через сеть, для этого подойдет все тот же netcat.


На нашем проблемном сервере запускаем в отдельном терминале


Теперь все данные, которые попадут в pipe автоматически попадут на stdin в netcat, который их отправит в сеть на порт 7777.

Все что нам осталось сделать это начать писать наши данные в этот именованный pipe.

У нас уже есть запущенное приложение:


Из всех флагов нам нужен только O_WRONLY так как файл уже существует и очищать нам его не нужно


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


Данные сохранились, проблема решена.

Пользуясь случаем, передаю привет коллегам из компании Degiro.
Слушайте подкасты Радио-Т.

В качестве домашнего задания предлагаю подумать, что будет в файл дескрипторах процесса cat и sleep если запустить такую команду:

1,1 FD полное имяfile descriptor, Это индекс уникальной таблицы дескриптора файлов

Википедия описывает это:

File_table_and_inode_table

In the traditional implementation of Unix, file descriptors index into a per-process file descriptor table maintained by the kernel , that in turn indexes into a system-wide table of files opened by all processes, called the file table . This table records the mode with which the file (or other resource) has been opened: for reading, writing, appending, and possibly other modes. It also indexes into a third table called the inode table that describes the actual underlying files.[3]

Изображение оригинальное соединение

这里写图片描述

Проще говоря, ядро ​​поддерживает таблицу дескрипторов файлов для каждого процесса. Дескриптор файла - это индекс таблицы дескриптора файла, а таблица дескриптора файла повернута к индексированию в таблице файлов на уровне системы, а таблица файлов может индексировать. Система Таблица Inode, и эта таблица INODE действительно описывает базовый файл. Файловая таблица системы также записывает, как открыт каждый файл: читать, писать и добавить . Таблица дескриптора файлов имеет одну, поэтому вилка будет скопирована.

1.2. При выполнении тесной эксплуатации на файловом дескрипторе только процесс закрывается в файл, а другие процессы все еще доступны.

If fd is the last file descriptor referring to the underlying open file description (see open(2)), the resources associated with the open file description are freed; if the descriptor was the last reference to a file which has been removed using unlink(2) the file is deleted.

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

To perform input or output, the process passes the file descriptor to the kernel through a system call, and the kernel will access the file on behalf of the process. The process does not have direct access to the file or inode tables.

On Linux, the set of file descriptors open in a process can be accessed under the path /proc/PID/fd/, where PID is the process identifier.

In Unix-like systems, file descriptors can refer to any Unix file type named in a file system. As well as regular files, this includes directories, block and character devices (also called “special files”), Unix domain sockets, and named pipes. File descriptors can also refer to other objects that do not normally exist in the file system, such as anonymous pipes and network sockets.

Интеллектуальная рекомендация


[Makefile от более мелких к более глубоким полная запись обучения 4] Переменные и различные методы присвоения

Давайте сегодня узнаем о различных методах присваивания переменных в Makefile! Смысл тяжелой работы, чтобы бедность больше не ограничивать свое воображение! Добавьте QQ, чтобы вместе учиться и обменив.

[Luogu P3147] [BZOJ 4576] [USACO16OPEN]262144

Портал Луогу БЗОЙ Портал Описание заголовка Bessie likes downloading games to play on her cell phone, even though she doesfind the small touch screen rather cumbersome to use with her large hooves. Sh.

Читайте также: