Что такое файловый дескриптор
Файловый дескриптор — это неотрицательное число, которое является идентификатором потока ввода-вывода, это число, которое однозначно идентифицирует открытый файл в операционной системе. Дескриптор описывает ресурс данных и способ доступа к этому ресурсу. Дескриптор может быть связан с файлом, каталогом, сокетом.
Например, когда вы открываете или создаете новый файл или сокет, что делает ядро: предоставляет доступ, создает запись в глобальной файловой таблице и отдает приложению местоположение этой записи. Операционная система формирует для себя запись для представления этого файла и хранения информации о нем. Запись в файловой таблице содержит такую информацию, как inode файла, смещение байта и ограничения доступа для этого потока данных (только для чтения, только для записи и т.д.). У каждого файла существует как минимум один индивидуальный файловый дескриптор Linux. Открыли 100 файлов — где-то в ядре появились 100 записей, представленных целыми числами.
Дескриптор для каждого процесса является уникальным. Но есть три жестко закрепленных индекса — это первые три номера (0, 1, 2).
- 0 — стандартный ввод (stdin), место, из которого программа получает интерактивный ввод.
- 1 — стандартный вывод (stdout), на который направлена большая часть вывода программы.
- 2 — стандартный поток ошибок (stderror), в который направляются сообщения об ошибках.
Когда работа с файлом завершена, присвоенный ему дескриптор освобождается и возвращается в пул свободных номеров. Он снова доступен для выделения под новый файл.
В Unix-подобных системах файловые дескрипторы могут относиться к любому типу файлов Unix: обычным файлам, каталогам, блочным и символьным устройствам, сокетам домена, именованным каналам. Дескрипторы также могут относиться к объектам, которые не существуют в файловой системе: анонимным каналам и сетевым сокетам.
Понятием «файловый дескриптор» оперируют и в языках программирования. Например, в Python функция os.open(path, flags, mode=0o777, *, dir_fd=None) открывает путь к файлу path, добавляет флаги и режим, а также возвращает дескриптор для вновь открытого файла. Начиная с версии 3.4 файловые дескрипторы в дочернем процессе Python не наследуются. В Unix они закрываются в дочерних процессах при выполнении новой программы.
Для чего нужны файловые дескрипторы
Чтобы оценить важность файловых дескрипторов, нужно разобраться, как работает файловая система.
- В традиционной реализации Unix дескрипторы индексируются в таблицу дескрипторов для каждого процесса, поддерживаемого ядром.
- Таблица файловых дескрипторов индексирует общесистемную таблицу файлов, открытых всеми процессами.
- В таблице файлов записывается режим, в котором открыт файл или другой ресурс — например, для чтения, записи, чтения и записи.
- Режим индексируется в таблицу индексных дескрипторов, описывающих фактические базовые файлы. В каждом индексном дескрипторе хранятся атрибуты и расположение дисковых блоков переданного объекта.
Когда нужно выполнить ввод или вывод, процесс через системный вызов передает ядру дескриптор нужного файла. Ядро обращается к файлу от имени процесса. При этом у самого процесса нет доступа к файлу или таблице индексных дескрипторов.
Unix2019b/Ввод-вывод
Мы уже ранее при работе с командной оболочкой сталкивались с понятием файлового дескриптора. Файловый дескриптор (иногда переводят как описатель файла) — неотрицательное целое число (int). Как правило, небольшое.
Дескрипторы 0, 1 и 2 по умолчанию открыты и связаны с устройствами stdin, stdout и stderr соответственно. Стандарт POSIX определяет константы для этих потоков ввода-вывода, рекомендуется использовать их в коде вместо магических чисел.
/* Standard file descriptors. */ #define STDIN_FILENO 0 /* Standard input. */ #define STDOUT_FILENO 1 /* Standard output. */ #define STDERR_FILENO 2 /* Standard error output. */
Файловые дескрипторы передаются в функции read, write и др. для выполнения ввода-вывода.
Величина численного значения дескриптора ограничена параметром RLIMIT_NOFILE:
sobols@sobols-w10:~$ ulimit -n 1024
В старых версиях UNIX ограничение было очень маленькое (в UNIX версии 7, например, — 20 дескрипторов).
Функции ввода/вывода
Для большинства задач ввода-вывода достаточно всего пяти функций: open, read, write, lseek, close.
Ввод-вывод является небуферизованным в том смысле, что каждый вызов read, write делает системный вызов в ядро ОС. Это важное отличие от стандартных C-функций printf, fwrite, .
Дёргать напрямую read/write для чтения/записи нескольких байт получается медленно. В этом случае обычно приходится реализовывать свою буферизацию.
open
#include #include #include int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); int creat(const char *pathname, mode_t mode); int openat(int dirfd, const char *pathname, int flags); int openat(int dirfd, const char *pathname, int flags, mode_t mode);
Функция открывает файл по указанному пути (путь задаётся в виде строки).
- Возвращает новый файловый дескриптор при успехе. В силу исторических причин всегда возвращается минимальный дескриптор из тех, которые сейчас свободны.
- Возвращает −1 при ошибке, при этом код ошибки заносится в переменную errno. Причин ошибки может быть множество: несуществующий путь, нет прав доступа к файлу, закончились свободные дескрипторы, .
Флаги определяют режим открытия файла, могут комбинироваться битовым или. Флагов много, можно найти в документации.
- O_RDONLY — открыть только для чтения
- O_WRONLY — открыть только для записи
- O_RDWR — открыть для чтения и записи
- O_APPEND — перед каждой записью позиция в файле ставится на конец и производится дописывание в конец
- O_CREAT — если файл не существует, создать его (параметр mode обязателен)
- O_TRUNC — если файл обычный, его длина будет вначале установлена в 0 (перезапись)
Функции openat принимают файловый дескриптор каталога и позволяют использовать путь относительно этого каталога (а не относительно текущего рабочего каталога программы).
Типичные примеры использования:
int fd1 = open("input.txt", O_RDONLY); int fd2 = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
creat
int creat(const char *pathname, mode_t mode);
Создание нового файла. Эквивалентно
open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);
close
#include int close(int fd);
Возвращает 0 при успехе, −1 при ошибке.
Если файл забыть закрыть, то его закроет ядро операционной системы при завершении работы вашего процесса. Однако обычно рекомендуется не полагаться на это, а закрывать файлы явно после окончания работы с файлом (например потому, что число одновременно открытых файлов ограничено, каждый открытый файл потребляет некоторый объём памяти в ядре).
Заметьте, что функция close() возвращает целое число. Отсутствие проверки возвращаемого значения close — частая ошибка. Может быть так, что об ошибке во время последнего write система сообщит пользователю только при close, в итоге данные могут быть молча потеряны.
Успешный вызов close не гарантирует, что данные физически записались на диск. Файловые системы обычно не занимаются сбросом своих буферов при close. Чтобы быть уверенным, нужно вызывать fsync.
read
ssize_t read(int fd, void *buf, size_t count);
read() пытается считывать count байтов из файлового дескриптора fd в буфер, начинающийся с buf.
- В файлах, которые поддерживают произвольный доступ, операция чтения начинается с текущего смещения, а смещение файла увеличивается на количество
- При успехе возвращается количество прочитанных байтов (0 указывает на конец файл).
- Не является ошибкой ситуация, если это число меньше, чем количество запрошенных байтов:
- мы были близки к концу файла;
- чтение выполняется с терминала (часто в этом случае идёт буферизация по строкам);
- чтение выполняется из сети;
- системный вызов был прерван сигналом.
write
#include ssize_t write(int fd, const void *buf, size_t count);
Как правильно использовать
Допустим, мы хотим записать в файл какие-то данные.
char buf[512]; // . write(fd, buf, 512);
Плохо, потому что мы не проверяем код выхода (успешно ли записалось и сколько записалось, могло не всё записаться за раз).
char buf[512]; // . for (size_t i = 0; i 512;) { i += write(fd, buf + i, 512 - i); }
Уже лучше: учитываем, что не всё может записаться за раз. Но не учитываем, что в процессе записи функция может вернуть −1 в случае ошибки.
char buf[512]; // . for (size_t i = 0; i 512;) { ssize_t r = write(fd, buf + i, 512 - i); if (r 0) { goto write_failed; } else { i += r; } } write_failed: // .
Ещё лучше: учитываем, что запись может быть неудачной (например, закончилось место на диске). Но есть нюанс: не все ошибки фатальны, в некоторых случаях при ошибке лучший вариант — это попробовать снова (и с большой вероятностью будет успех). Например, если работа прервётся получением сигнала, функция вернёт EINTR (от interrupted).
char buf[512]; // . for (size_t i = 0; i 512;) ssize_t r = write(fd, buf + i, 512 - i); if (r 0) { if (errno == EINTR goto write_failed; } else { i += r; } } write_failed: // .
lseek
Для каждого открытого файла в ядре хранится file offset, на русский это переводят как «текущее смещение в файле», «позиция курсора/каретки».
Функция lseek позволяет эту позицию менять. Название lseek расшифровывается как «long seek» (исторически первой была функция seek, она принимала значение в виде числа меньшей разрядности).
#include #include off_t lseek(int fd, off_t offset, int whence);
Третий параметр определяет тип операции:
Узнать текущую позицию:
off_t curpos; curpos = lseek(fd, 0, SEEK_CUR);
Не все файловые дескрипторы являются seekable. Именованный или анонимный канал, сокет нельзя перематывать.
#include #include int main() { if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1) { printf("cannot seek\n"); } else { printf("seek OK\n"); } return 0; }
$ ./a.out cannot seek $ ./a.out < ./a.out seek OK
Если позиция устанавливается за границей файла, то файл расширяется до нужного размера, в нём образуется «дыра». Байты из неё читаются так, как будто там нули. В зависимости от файловой системы, эти нули могут физически писаться на диск, а могут не писаться (sparse file).
ftruncate
#include #include int truncate(const char *path, off_t length); int ftruncate(int fd, off_t length);
Устанавливает размер файла равным заданному. Если файл был больше, он обрезается. Если был меньше, то дополняется нулями (опять же, если файловая система поддерживает, то будет создан sparse-файл и нули не будут фактически писаться на диск). Позиция не изменяется.
Внутренняя реализация
Вплоть до настоящего времени могло показаться, что существует однозначное соответствие между файловым дескриптором и открытым файлом. Однако это не так. Возможно иметь несколько дескрипторов, ссылающихся на один и тот же открытый файл. Эти файловые дескрипторы могут быть открыты в одном и том же процессе или в разных процессах.
Чтобы понять, что происходит, нам нужно изучить три структуры данных, поддерживается ядром:
- таблица файловых дескрипторов для каждого процесса;
- общесистемная таблица открытых файлов;
- таблица i-node'ов файловой системы.
Можно считать, что файловый дескриптор, будучи небольшим целым числом, является индексом в первой таблице — таблице открытых файловых дескрипторов (file descriptor table). Эта таблица своя для каждого процесса. В ней хранятся некоторые флаги (конкретно, close-on-exec, который мы рассмотрим позже) и ссылка на описание в общесистемной таблице.
Вторая таблица описывает все файлы, открытые всеми процессами (её называют open file table). В ней хранятся:
- смещение (offset), которое обновляется при выполнении read/write/lseek;
- флаги статуса, которые передавались в open;
- режим доступа (только чтение, запись и пр.);
- ссылка на объект inode для файла.
Третья таблица — i-node table — содержит представление в памяти тех i-узлов, которые хранятся на диске в файловой системе.
Дублирование файловых дескрипторов
#include int dup(int oldfd); int dup2(int oldfd, int newfd);
Функция dup принимает файловый дескриптор oldfd и создаёт его копию, используя наименьший неиспользуемый номер в качестве нового файлового дескриптора. Функция возвращает этот дескриптор.
После dup два дескриптора (старый и новый) можно использовать равнозначно. Они связаны с тем же самым открытым файлом, поэтому у них общее смещение (offset) и флаги статуса. Например, если использовать lseek с одним дескриптором для «перемотки», то изменения будут применены ко второму тоже.
Функция dup2 делает то же самое, но вместо взятия нового дескриптора с наименьшим номером берёт дескриптор, указанный в качестве newfd. Если он уже ранее был открыт, то он будет молча закрыт перед повторным использованием. Причём закрывание и переоткрывание будут выполнены атомарно.
Функции используются для организации перенаправления ввода-вывода. На следующих занятиях мы посмотрим, как можно с их помощью соединять процессы в конвейер (как делает | в командной оболочке).
Пример: перенаправление stdout в stderr:
#include . dup2(2, 1); // 2-stderr; 1-stdout .
Пример
Буферизация
Оптимальный размер буфера
Чтение и запись мелкими порциями неэффективна. Возникает вопрос, какой размер буфера стоит выставлять.
Рассмотрим пример из книги Advanced Programming in the UNIX Environment.
Программа выполняет копирование данных:
#include #include #define BUFFSIZE 4096 int main(void) { ssize_t n; char buf[BUFFSIZE]; while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0) { if (write(STDOUT_FILENO, buf, n) != n) { perror("write error"); } } if (n 0) { perror("read error"); } return 0; }
Тестирование производилось на файле размером 516,581,760. С двадцатью разными размерами буфера.
BUFFSIZE User CPU (s) System CPU (s) Clock time (s) Number of loops 1 20.03 117.50 138.73 516,581,760 2 9.69 58.76 68.60 258,290,880 4 4.60 36.47 41.27 129,145,440 8 2.47 15.44 18.38 64,572,720 16 1.07 7.93 9.38 32,286,360 32 0.56 4.51 8.82 16,143,180 64 0.34 2.72 8.66 8,071,590 128 0.34 1.84 8.69 4,035,795 256 0.15 1.30 8.69 2,017,898 512 0.09 0.95 8.63 1,008,949 1,024 0.02 0.78 8.58 504,475 2,048 0.04 0.66 8.68 252,238 4,096 0.03 0.58 8.62 126,119 8,192 0.00 0.54 8.52 63,060 16,384 0.01 0.56 8.69 31,530 32,768 0.00 0.56 8.51 15,765 65,536 0.01 0.56 9.12 7,883 131,072 0.00 0.58 9.08 3,942 262,144 0.00 0.60 8.70 1,971 524,288 0.01 0.58 8.58 986 Из-за того что на файловой системе ext4 использовались блоки размера 4096, дальнейшее увеличение буфера не даёт положительного эффекта.
Многие системы поддерживают read-ahead, то есть читают наперёд данные до того, как их запросило приложение, в надежде, что затем они понадобятся. Это видно на блоках размера от 32.
Принудительный сброс изменений на диск
Функции типа write копируют данные в буфер внутри ядра. Данные могут быть записаны физически на диск когда-нибудь (delayed write). Бывает иногда необходимо принудительно вызвать запись на диск, чтобы иметь гарантию, что данные не будут потеряны.
#include int fsync(int fd); int fdatasync(int fd); void sync(void);
Функция sync просто добавляет в очередь на запись все модифицированные блоки и не ожидает конца их записи. Есть демон, который раз в 30 секунд вызывает sync. Также есть команда sync.
Функция fsync работает с одним файлом. Ждёт, пока данные не запишутся на диск. Может использоваться разными СУБД и подобными программами.
Функция fdatasync такая же по сути, но вызывает только запись данных, не атрибутов файла.
К сожалению, про fsync() в стандарте написано:
If _POSIX_SYNCHRONIZED_IO is not defined, the wording relies heavily on the conformance document to tell the user what can be expected from the system. It is explicitly intended that a null implementation is permitted.
Атомарные операции
Дозапись в файл
Допустим, есть два процесса, которые пишу данные в один и тот же файл (например, глобальный лог). Пусть каждый процесс независимо открывает файл по имени у себя и выполняет запись сообщения в него.
Старые программы могли делать запись следующим образом:
if (lseek(fd, 0L, 2) 0) /* position to EOF */ err_sys("lseek error"); if (write(fd, buf, 100) != 100) /* and write */ err_sys("write error");
Есть проблема гонки между двумя системными вызовами. Если между lseek и write первого процесса влезет второй процесс и успеет сделать то же, то первый процесс в итоге запишет свои данные поверх тех, что только что записал второй.
На сегодняшний день для решения этой задачи следует открывать файл в режиме O_APPEND. Тогда перемотка позиции на конец файла в соответствии с его текущим размером и сама запись выполняются атомарно.
pread и pwrite
#include ssize_t pread(int fd, void *buf, size_t count, off_t offset); ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
Выполняют чтение и запись по заданному смещению. Текущее смещение они вообще не трогают.
Эти функции могу быть полезны в многопоточных программах и в случае использования одного дескриптора из разных процессов. Позволяют избежать гонки.
Создание файла
Допустим, стоит задача создать файл, если он не существует, или вывести ошибку, если он уже существует.
Наивный подход такой:
if ((fd = open(path, O_WRONLY)) 0) { if (errno == ENOENT) { if ((fd = creat(path, mode)) 0) { err_sys("creat error"); } else { err_sys("open error"); } } }
Но тут возможна гонка.
Следует использовать комбинацию флагов O_CREAT и O_EXCL.
Операции с файловой системой
Операции rename, link, symlink, mkdir атомарны.
Если надо подменить файл:
- Записать данные во временный файл.
- Сделать fsync.
- Закрыть временный файл.
- Переименовать временный файл в требуемое имя.
Файловый дескриптор
Ко всем потокам ввода-вывода (которые могут быть связаны как с файлами, так и с папками, сокетами и FIFO) можно получить доступ через так называемые файловые дескрипторы. Файловый дескриптор — это неотрицательное целое число. Когда создается новый поток ввода-вывода, ядро возвращает процессу, создавшему поток ввода-вывода, его файловый дескриптор.
По умолчанию Unix-оболочки связывают файловый дескриптор 0 с потоком стандартного ввода процесса (терминал), файловый дескриптор 1 — с потоком стандартного вывода (терминал), и файловый дескриптор 2 — с потоком диагностики (куда обычно выводятся сообщения об ошибках). Это соглашение соблюдается многими Unix-оболочками и многими приложениями — и ни в коем случае не является составной частью ядра.
Стандарт POSIX.1 заменил «магические числа» 0, 1, 2 символическими константами STDIN_FILENO, STDOUT_FILENO и STDERR_FILENO соответственно.
Файловые дескрипторы могут принимать значения от 0 до OPEN_MAX. Согласно POSIX.1, значение OPEN_MAX равно 19. В реальных ОС это значение может быть больше.
См. также
- Файл
- Сокет (программный интерфейс)
- Дескриптор
- Перенаправление (UNIX)
- Поток данных
- UNIX
Wikimedia Foundation . 2010 .
Полезное
Смотреть что такое "Файловый дескриптор" в других словарях:
- Дескриптор — (англ. Descriptor): Дескриптор HTML элемент языка разметки гипертекста HTML. В разговорной речи дескрипторы называют тегами. Дескриптор развертывания XML файлы, которые описывают, как развернуть модули. Файловый дескриптор число… … Википедия
- Epoll — (extended poll) API асинхронного ввода вывода, предоставляемого Linux для приложений. API позволяет приложениям осуществлять мониторинг нескольких открытых файловых дескрипторов (которые могут быть файлами, устройствами или сокетами, в том… … Википедия
- Perl — Семантика: мультипарадигменный: императивный, объектно ориентированный, функциональный Тип исполнения: интерпретатор Появился в: 1987 Автор(ы) … Википедия
- Перл (язык программирования) — Perl Семантика: мультипарадигменный: императивный, объектно ориентированный, функциональный Тип исполнения: интерпретатор Появился в: 1987 г. Автор(ы): Ларри Уолл … Википедия
- Inotify — это подсистема ядра Linux, которая позволяет получать уведомления об изменениях в файловой системе. В основное ядро была включена начиная с 2.6.13, но может использоваться и с более ранними ядрами с помощью наложения патча. Основная сфера… … Википедия
- inotify — это подсистема ядра Linux, которая позволяет получать уведомления об изменениях в файловой системе. В основное ядро была включена начиная с 2.6.13, но может использоваться и с более ранними ядрами с помощью наложения патча. Основная сфера… … Википедия
- Перенаправление (UNIX) — У этого термина существуют и другие значения, см. Перенаправление. В информатике перенаправление это возможность присущая многим командным оболочкам, таким как командные оболочки UNIX, позволяющая перенаправлять стандартные потоки в… … Википедия
- Перенаправление (программное обеспечение) — У этого термина существуют и другие значения, см. Перенаправление. В информатике перенаправление это возможность присущая многим командным оболочкам, таким как командные оболочки UNIX, позволяющая перенаправлять стандартные потоки в… … Википедия
- Сокет (программный интерфейс) — У этого термина существуют и другие значения, см. Сокет. Сокеты (англ. socket углубление, гнездо, разъём) название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на… … Википедия
- Stdio.h — Стандартная библиотека языка программирования С assert.h complex.h ctype.h errno.h fenv.h float.h inttypes.h iso646.h limits.h locale.h math.h setjmp.h signal.h stdarg.h stdbool.h stddef.h stdint.h stdio.h stdlib.h … Википедия
- Обратная связь: Техподдержка, Реклама на сайте
- Путешествия
Экспорт словарей на сайты, сделанные на PHP,
WordPress, MODx.- Пометить текст и поделитьсяИскать в этом же словареИскать синонимы
- Искать во всех словарях
- Искать в переводах
- Искать в ИнтернетеИскать в этой же категории
Файл дескриптор в Linux с примерами
Однажды, на одном интервью меня спросили, что ты будешь делать, если обнаружишь неработающий сервис из-за того, что на диске закончилось место?
Конечно же я ответил, что посмотрю, чем занято это место и если возможно, то почищу место.
Тогда интервьюер спросил, а что если на разделе нет свободного места, но и файлов, которые бы занимали все место, ты тоже не видишь?На это я сказал, что всегда можно посмотреть открытые файл дескрипторы, например командой 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 нашего процесса
[user@localhost ]$ echo $$ 15771
Во второй консоли запустим
[user@localhost ]$ ls -lah /proc/15771/fd/ total 0 dr-x------ 2 user user 0 Oct 7 15:42 . dr-xr-xr-x 9 user user 0 Oct 7 15:42 .. lrwx------ 1 user user 64 Oct 7 15:42 0 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:42 1 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:42 2 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:42 255 -> /dev/pts/21
Файл дескриптор с номером 255 можете смело игнорировать в рамках данной статьи, он был открыт для своих нужд уже самим bash, а не прилинкованной библиотекой.
Сейчас все 3 файл дескриптора связаны с устройством псевдотерминала /dev/pts, но мы все равно можем ими манипулировать, например запустим во второй консоли
[user@localhost ]$ echo "hello world" > /proc/15771/fd/0
И в первой консоли мы увидим
[user@localhost ]$ hello world
Redirect и Pipe
Вы можете легко переопределить эти 3 файл дескриптора в любом процессе, в том числе и в bash, например через трубу(pipe), соединяющую два процесса, смотрим
[user@localhost ]$ cat /dev/zero | sleep 10000
Вы можете сами запустить эту команду с strace -f и увидеть, что происходит внутри, но я вкратце расскажу.
Наш родительский процесс bash с PID 15771 парсит нашу команду и понимает сколько именно команд мы хотим запустить, в нашем случае их две: cat и sleep. Bash знает что ему нужно создать два дочерних процесса, и объединить их одной трубой. Итого bash потребуется 2 дочерних процесса и один pipe.
Перед созданием дочерних процессов bash запускает системный вызов pipe и получает новые файл дескрипторы на временный буфер pipe, но этот буфер никак пока не связывает наши два дочерних процесса.
Для родительского процесса это выглядит так будто pipe уже есть, а дочерних процессов еще нет:
PID command 15771 bash lrwx------ 1 user user 64 Oct 7 15:42 0 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:42 1 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:42 2 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:42 3 -> pipe:[253543032] lrwx------ 1 user user 64 Oct 7 15:42 4 -> pipe:[253543032] lrwx------ 1 user user 64 Oct 7 15:42 255 -> /dev/pts/21
Затем с помощью системного вызова clone bash создает два дочерних процесса, и наши три процесса будут выглядеть так:
PID command 15771 bash lrwx------ 1 user user 64 Oct 7 15:42 0 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:42 1 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:42 2 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:42 3 -> pipe:[253543032] lrwx------ 1 user user 64 Oct 7 15:42 4 -> pipe:[253543032] lrwx------ 1 user user 64 Oct 7 15:42 255 -> /dev/pts/21 PID command 9004 bash lrwx------ 1 user user 64 Oct 7 15:57 0 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:57 1 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:57 2 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:57 3 -> pipe:[253543032] lrwx------ 1 user user 64 Oct 7 15:57 4 -> pipe:[253543032] lrwx------ 1 user user 64 Oct 7 15:57 255 -> /dev/pts/21 PID command 9005 bash lrwx------ 1 user user 64 Oct 7 15:57 0 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:57 1 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:57 2 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:57 3 -> pipe:[253543032] lrwx------ 1 user user 64 Oct 7 15:57 4 -> pipe:[253543032] lrwx------ 1 user user 64 Oct 7 15:57 255 -> /dev/pts/21
Не забываем, что 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. В нашем случае после запуска исполняемых файлов все текущие файл дескрипторы сохранятся.
Проверяем в консоли:
[user@localhost ]$ pgrep -P 15771 9004 9005 [user@localhost ]$ ls -lah /proc/15771/fd/ total 0 dr-x------ 2 user user 0 Oct 7 15:42 . dr-xr-xr-x 9 user user 0 Oct 7 15:42 .. lrwx------ 1 user user 64 Oct 7 15:42 0 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:42 1 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:42 2 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:42 255 -> /dev/pts/21 [user@localhost ]$ ls -lah /proc/9004/fd total 0 dr-x------ 2 user user 0 Oct 7 15:57 . dr-xr-xr-x 9 user user 0 Oct 7 15:57 .. lrwx------ 1 user user 64 Oct 7 15:57 0 -> /dev/pts/21 l-wx------ 1 user user 64 Oct 7 15:57 1 -> pipe:[253543032] lrwx------ 1 user user 64 Oct 7 15:57 2 -> /dev/pts/21 lr-x------ 1 user user 64 Oct 7 15:57 3 -> /dev/zero [user@localhost ]$ ls -lah /proc/9005/fd total 0 dr-x------ 2 user user 0 Oct 7 15:57 . dr-xr-xr-x 9 user user 0 Oct 7 15:57 .. lr-x------ 1 user user 64 Oct 7 15:57 0 -> pipe:[253543032] lrwx------ 1 user user 64 Oct 7 15:57 1 -> /dev/pts/21 lrwx------ 1 user user 64 Oct 7 15:57 2 -> /dev/pts/21 [user@localhost ]$ ps -up 9004 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND user 9004 0.0 0.0 107972 620 pts/21 S+ 15:57 0:00 cat /dev/zero [user@localhost ]$ ps -up 9005 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND user 9005 0.0 0.0 107952 360 pts/21 S+ 15:57 0:00 sleep 10000
Как видите уникальный номер нашего pipe у нас в обоих процессах совпадает. Таким образом у нас есть связь между двумя разными процессами с одним родителем.
Для тех, кто не знаком с системными вызовами, которые использует bash, крайне рекомендую запустить команды через strace и посмотреть, что происходит внутри, например, так:
strace -s 1024 -f bash -c "ls | grep hello"
Вернемся к нашей проблеме с нехваткой места на диске и попыткой сохранить данные без перезапуска процесса. Напишем небольшую программу, которая будет записывать на диск примерно 1 мегабайт в секунду. При этом если по какой-либо причине мы не смогли записать данные на диск, мы будем просто игнорировать это и пытаться записать данные вновь через секунду. В примере я использую Python, вы можете использовать любой другой язык программирования.
[user@localhost ]$ cat openforwrite.py import datetime import time mystr="a"*1024*1024+"\n" with open("123.txt", "w") as f: while True: try: f.write(str(datetime.datetime.now())) f.write(mystr) f.flush() time.sleep(1) except: pass
Запустим программу и посмотрим на файл дескрипторы
[user@localhost ]$ python openforwrite.py & [1] 3762 [user@localhost ]$ ps axuf | grep [o]penforwrite user 3762 0.0 0.0 128600 5744 pts/22 S+ 16:28 0:00 | \_ python openforwrite.py [user@localhost ]$ ls -la /proc/3762/fd total 0 dr-x------ 2 user user 0 Oct 7 16:29 . dr-xr-xr-x 9 user user 0 Oct 7 16:29 .. lrwx------ 1 user user 64 Oct 7 16:29 0 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 7 16:29 1 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 7 16:29 2 -> /dev/pts/22 l-wx------ 1 user user 64 Oct 7 16:29 3 -> /home/user/123.txt
Как видим у нас есть наши 3 стандартные файл дескрипторы и еще один, который мы открыли. Проверим размер файла:
[user@localhost ]$ ls -lah 123.txt -rw-rw-r-- 1 user user 117M Oct 7 16:30 123.txt
данные пишутся, пробуем поменять права на файл:
[user@localhost ]$ sudo chown root: 123.txt [user@localhost ]$ ls -lah 123.txt -rw-rw-r-- 1 root root 168M Oct 7 16:31 123.txt [user@localhost ]$ ls -lah 123.txt -rw-rw-r-- 1 root root 172M Oct 7 16:31 123.txt
Видим, что данные все еще пишутся, хотя наш пользователь не имеет права писать в файл. Попробуем его удалить:
[user@localhost ]$ sudo rm 123.txt [user@localhost ]$ ls 123.txt ls: cannot access 123.txt: No such file or directory
Куда пишутся данные? И пишутся ли вообще? Проверяем:
[user@localhost ]$ ls -la /proc/3762/fd total 0 dr-x------ 2 user user 0 Oct 7 16:29 . dr-xr-xr-x 9 user user 0 Oct 7 16:29 .. lrwx------ 1 user user 64 Oct 7 16:29 0 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 7 16:29 1 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 7 16:29 2 -> /dev/pts/22 l-wx------ 1 user user 64 Oct 7 16:29 3 -> /home/user/123.txt (deleted)
Да, наш файл дескриптор все еще существует, и мы можем работать с этим файл дескриптором как с нашим старым файлом, мы можем его читать, очищать и копировать.
Смотрим на размер файла:
[user@localhost ]$ lsof | grep 123.txt python 31083 user 3w REG 8,5 19923457 2621522 /home/user/123.txt
Размер файла 19923457. Пробуем очистить файл:
[user@localhost ]$ truncate -s 0 /proc/31083/fd/3 [user@localhost ]$ lsof | grep 123.txt python 31083 user 3w REG 8,5 136318390 2621522 /home/user/123.txt
Как видим размер файла только увеличивается и наш транкейт не сработал. Обратимся к документации по системному вызову open. Если при открытии файла мы используем флаг O_APPEND, то при каждой записи операционная система проверяет размер файла и пишет данные в самый конец файла, причем делает это атомарно. Это позволяет нескольким тредам или процессам писать в один и тот же файл. Но в нашем коде мы не используем этот флаг. Мы можем увидеть другой размер файла в lsof после транкейт только если откроем файл для дозаписи, а значит в нашем коде вместо
with open("123.txt", "w") as f:
мы должны поставить
with open("123.txt", "a") as f:
Проверяем с «w» флагом
[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt open("123.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt open("123.txt", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
Программируем уже запущенный процесс
Часто программисты при создании и тестировании программы используют дебагеры (например GDB) или различные уровни логирования в приложении. Linux предоставляет возможность фактически писать и менять уже запущенную программу, например менять значения переменных, устанавливать breakpoint и тд и тп.
Возвращаясь к оригинальному вопросу с нехваткой места на диске для записи файла, попробуем сэмулировать проблему.
Создадим файл для нашего раздела, который мы подмонтируем как отдельный диск:
[user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10 10+0 records in 10+0 records out 10485760 bytes (10 MB) copied, 0.00525929 s, 2.0 GB/s [user@localhost ~]$
Создадим файловую систему:
[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd mke2fs 1.42.9 (28-Dec-2013) /home/user/tempfile_for_article.dd is not a block special device. Proceed anyway? (y,n) y . Writing superblocks and filesystem accounting information: done [user@localhost ~]$
Подмонтируем файловую систему:
[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/ [sudo] password for user: [user@localhost ~]$ df -h | grep mnt /dev/loop0 8.7M 172K 7.9M 3% /mnt
Создаем директорию с нашим владельцем:
[user@localhost ~]$ sudo mkdir /mnt/logs [user@localhost ~]$ sudo chown user: /mnt/logs
Откроем файл только на запись в нашей программе:
with open("/mnt/logs/123.txt", "w") as f:
[user@localhost ]$ python openforwrite.py
Ждем несколько секунд
[user@localhost ~]$ df -h | grep mnt /dev/loop0 8.7M 8.0M 0 100% /mnt
Итак, мы получили проблему, описанную в начале этой статьи. Свободного места 0, занятого 100%.
Мы помним, что по условиям задачи мы пытаемся записать очень важные данные, которые нельзя потерять. И при этом нам нужно починить сервис без перезапуска процесса.
Допустим, у нас все же есть место на диске, но в другом разделе, например в /home.
Попробуем «перепрограммировать на лету» наш код.
Смотрим PID нашего процесса, который съел все место на диске:
[user@localhost ~]$ ps axuf | grep [o]penfor user 10078 27.2 0.0 128600 5744 pts/22 R+ 11:06 0:02 | \_ python openforwrite.py
Подключаемся к процессу через gdb
[user@localhost ~]$ gdb -p 10078 . (gdb)
Смотрим открытые файл дескрипторы:
(gdb) shell ls -lah /proc/10078/fd/ total 0 dr-x------ 2 user user 0 Oct 8 11:06 . dr-xr-xr-x 9 user user 0 Oct 8 11:06 .. lrwx------ 1 user user 64 Oct 8 11:09 0 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:09 1 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:06 2 -> /dev/pts/22 l-wx------ 1 user user 64 Oct 8 11:09 3 -> /mnt/logs/123.txt
Смотрим информацию о файл дескрипторе с номером 3, который нас интересует
(gdb) shell cat /proc/10078/fdinfo/3 pos: 8189952 flags: 0100001 mnt_id: 482
Помня о том, какой системный вызов делает Python (смотрите выше, где мы запускали strace и находили вызов open), обрабатывая наш код для открытия файла, мы делаем то же самое самостоятельно от имени нашего процесса, но биты O_WRONLY|O_CREAT|O_TRUNC нам нужно заменить на числовое значение. Для этого открываем исходники ядра, например тут и смотрим какие флаги за что отвечают
#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000Объединяем все значения в одно, получаем 00001101
Запускаем наш вызов из gdb
(gdb) call open("/home/user/123.txt", 00001101,0666) $1 = 4
Итак мы получили новый файл дескриптор с номером 4 и новый открытый файл на другом разделе, проверяем:
(gdb) shell ls -lah /proc/10078/fd/ total 0 dr-x------ 2 user user 0 Oct 8 11:06 . dr-xr-xr-x 9 user user 0 Oct 8 11:06 .. lrwx------ 1 user user 64 Oct 8 11:09 0 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:09 1 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:06 2 -> /dev/pts/22 l-wx------ 1 user user 64 Oct 8 11:09 3 -> /mnt/logs/123.txt l-wx------ 1 user user 64 Oct 8 11:15 4 -> /home/user/123.txt
Мы помним пример с pipe — как bash меняет файл дескрипторы, и уже выучили системный вызов dup2.
Пробуем подменить один файл дескриптор другим
(gdb) call dup2(4,3) $2 = 3
(gdb) shell ls -lah /proc/10078/fd/ total 0 dr-x------ 2 user user 0 Oct 8 11:06 . dr-xr-xr-x 9 user user 0 Oct 8 11:06 .. lrwx------ 1 user user 64 Oct 8 11:09 0 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:09 1 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:06 2 -> /dev/pts/22 l-wx------ 1 user user 64 Oct 8 11:09 3 -> /home/user/123.txt l-wx------ 1 user user 64 Oct 8 11:15 4 -> /home/user/123.txt
Закрываем файл дескриптор 4, так как нам он не нужен:
(gdb) call close (4) $1 = 0
И выходим из gdb
(gdb) quit A debugging session is active. Inferior 1 [process 10078] will be detached. Quit anyway? (y or n) y Detaching from program: /usr/bin/python2.7, process 10078
Проверяем новый файл:
[user@localhost ~]$ ls -lah /home/user/123.txt -rw-rw-r-- 1 user user 5.1M Oct 8 11:18 /home/user/123.txt [user@localhost ~]$ ls -lah /home/user/123.txt -rw-rw-r-- 1 user user 7.1M Oct 8 11:18 /home/user/123.txt
Как видим, данные пишутся в новый файл, проверяем старый:
[user@localhost ~]$ ls -lah /mnt/logs/123.txt -rw-rw-r-- 1 user user 7.9M Oct 8 11:08 /mnt/logs/123.txt
Данные не потеряны, приложение работает, логи пишутся в новое место.
Немного усложним задачу
Представим, что данные нам важны, но места на диске у нас нет ни в одном из разделов и подключить диск мы не можем.
Что мы можем сделать, так это перенаправить куда-то наши данные, например в pipe, а данные из pipe в свою очередь перенаправить в сеть через какую-либо программу, например netcat.
Мы можем создать именованный pipe командой mkfifo. Она создаст псевдофайл на файловой системе, даже если на ней нет свободного места.Перезапускаем приложение, и проверяем:
[user@localhost ]$ python openforwrite.py [user@localhost ~]$ ps axuf | grep [o]pen user 5946 72.9 0.0 128600 5744 pts/22 R+ 11:27 0:20 | \_ python openforwrite.py [user@localhost ~]$ ls -lah /proc/5946/fd total 0 dr-x------ 2 user user 0 Oct 8 11:27 . dr-xr-xr-x 9 user user 0 Oct 8 11:27 .. lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22 l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/123.txt [user@localhost ~]$ df -h | grep mnt /dev/loop0 8.7M 8.0M 0 100% /mnt
Места на диске нет, но мы успешно создаем там именованный pipe:
[user@localhost ~]$ mkfifo /mnt/logs/megapipe [user@localhost ~]$ ls -lah /mnt/logs/megapipe prw-rw-r-- 1 user user 0 Oct 8 11:28 /mnt/logs/megapipe
Теперь нам надо как-то завернуть все данные, что попадают в этот pipe на другой сервер через сеть, для этого подойдет все тот же netcat.
На сервере remote-server.example.com запускаем
[user@localhost ~]$ nc -l 7777 > 123.txt
На нашем проблемном сервере запускаем в отдельном терминале
[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe
Теперь все данные, которые попадут в pipe автоматически попадут на stdin в netcat, который их отправит в сеть на порт 7777.
Все что нам осталось сделать это начать писать наши данные в этот именованный pipe.
У нас уже есть запущенное приложение:
[user@localhost ~]$ ps axuf | grep [o]pen user 5946 99.8 0.0 128600 5744 pts/22 R+ 11:27 169:27 | \_ python openforwrite.py [user@localhost ~]$ ls -lah /proc/5946/fd total 0 dr-x------ 2 user user 0 Oct 8 11:27 . dr-xr-xr-x 9 user user 0 Oct 8 11:27 .. lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22 l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/123.txt
Из всех флагов нам нужен только O_WRONLY так как файл уже существует и очищать нам его не нужно
[user@localhost ~]$ gdb -p 5946 . (gdb) call open("/mnt/logs/megapipe", 00000001,0666) $1 = 4 (gdb) shell ls -lah /proc/5946/fd total 0 dr-x------ 2 user user 0 Oct 8 11:27 . dr-xr-xr-x 9 user user 0 Oct 8 11:27 .. lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22 l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/123.txt l-wx------ 1 user user 64 Oct 8 14:20 4 -> /mnt/logs/megapipe (gdb) call dup2(4,3) $2 = 3 (gdb) shell ls -lah /proc/5946/fd total 0 dr-x------ 2 user user 0 Oct 8 11:27 . dr-xr-xr-x 9 user user 0 Oct 8 11:27 .. lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22 l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/megapipe l-wx------ 1 user user 64 Oct 8 14:20 4 -> /mnt/logs/megapipe (gdb) call close(4) $3 = 0 (gdb) shell ls -lah /proc/5946/fd total 0 dr-x------ 2 user user 0 Oct 8 11:27 . dr-xr-xr-x 9 user user 0 Oct 8 11:27 .. lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22 lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22 l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/megapipe (gdb) quit A debugging session is active. Inferior 1 [process 5946] will be detached. Quit anyway? (y or n) y Detaching from program: /usr/bin/python2.7, process 5946
Проверяем удаленный сервер remote-server.example.com
[user@localhost ~]$ ls -lah 123.txt -rw-rw-r-- 1 user user 38M Oct 8 14:21 123.txt
Данные идут, проверяем проблемный сервер
[user@localhost ~]$ ls -lah /mnt/logs/ total 7.9M drwxr-xr-x 2 user user 1.0K Oct 8 11:28 . drwxr-xr-x 4 root root 1.0K Oct 8 10:55 .. -rw-rw-r-- 1 user user 7.9M Oct 8 14:17 123.txt prw-rw-r-- 1 user user 0 Oct 8 14:22 megapipe
Данные сохранились, проблема решена.
Пользуясь случаем, передаю привет коллегам из компании Degiro.
Слушайте подкасты Радио-Т.В качестве домашнего задания предлагаю подумать, что будет в файл дескрипторах процесса cat и sleep если запустить такую команду:
[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000