Что такое дескриптор c
Перейти к содержимому

Что такое дескриптор c

  • автор:

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

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

94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:

Что такое файловый буфер? Что такое режим (модификатор) доступа, при работе с файлами?
Что такое файловый буфер? Что такое режим (модификатор) доступа, при работе с файлами?

Что такое рекурсивный тип данных? Что такое конструкция рекурсивного типа?
Что такое рекурсивный тип данных? Что такое конструкция рекурсивного типа?

Что такое хэндлер файла? Что такое файловый указатель?
Что такое хэндлер файла? Что такое файловый указатель?

Что такое заголовочный файл? Что такое файл исходного кода? Рассмотрите назначение каждого из них
Что такое заголовочный файл? Что такое файл исходного кода? Рассмотрите назначение каждого из.

Эксперт по пяченькам
67 / 67 / 12
Регистрация: 16.08.2011
Сообщений: 249

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

Добавлено через 3 минуты
Если да, то лично я понимаю дескриптор, как своего рода указатель на файл (не в смысле сишного указателя), с помощью которого, можно проводить операции над конкретным файлом или в Linux-е с устройствами.

Регистрация: 22.02.2013
Сообщений: 33
И все же немного непонятно ! его можно как то увидить, управлять им ?
4226 / 1795 / 211
Регистрация: 24.11.2009
Сообщений: 27,562

ЦитатаСообщение от roshax Посмотреть сообщение

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

Дескриптор есть некое данное, описывающее и однозначно идентифицирующее объект. Как именно дескриптор описывает объект и как идентифицирует, мелкомягкие определяют сами, а остальным достаточно уметь работать с ним, как с «чёрным ящиком».

Дескриптор

Дескриптор (лат. descriptor — описывающий, идентификатор, описание) – лексическая единица в компьютерных информационных системах, предназначенная для описания свойств, параметров, идентификации тех или иных объектных элементов языка. При помощи дескрипторов программисты задают характер работы вычислительной машины – что это за объект, что с ним делать и как отрабатывать. Дескриптор помогает компьютерному процессору лучше и точнее понять – какого рода информацию ищет человек, как ее обрабатывать и в каком формате представить на экран либо куда ее перенаправить. В качестве примера можно привести язык HTML , предназначенный для верстки веб-страниц. HTML-теги – это и есть дескрипторы на сайте, при помощи которых разработчик сайта задает параметры обработки содержащейся в внутри тегов информации браузерами и удаленными серверами.

Услуги, связанные с термином:

  • SEO-продвижение
  • В ТОП
  • Базовая оптимизация
  • Сбор и кластеризация запросов

Сертифицированное агентство Яндекс

Сертифицированное агентство Яндекс

Сертифицированное агентство Google

Сертифицированное агентство Google

Сертифицированный партнёр ВКонтакте

Сертифицированный партнёр ВКонтакте

Сертифицированное агентство MyTarget

Сертифицированное агентство MyTarget

Разработка классов-дескрипторов на C++/CLI

В C++/CLI достаточно часто используются так называемые классы-дескрипторы — управляемые классы, имеющие указатель на родной класс в качестве члена. В статье рассматривается удобная и компактная схема управления временем жизни соответствующего родного объекта, основанная на использовании управляемых шаблонов. Рассмотрены сложные случаи финализации.

Оглавление

Введение

C++/CLI — один из языков платформы .NET Framework — редко используется для разработки больших самостоятельных проектов. Его главное назначение — создание сборок для взаимодействия .NET с родным (неуправляемым) кодом. Соответственно, весьма широко используются классы, называемые классами-дескрипторами, управляемые классы, имеющие указатель на родной класс в качестве члена. Обычно такой класс-дескриптор владеет соответствующим родным объектом, то есть он должен его удалить в надлежащий момент. Вполне естественно сделать такой класс освобождаемым, то есть реализующим интерфейс System::IDisposable . Реализация этого интерфейса в .NET должна следовать специальному шаблону, называемому Basic Dispose [Cwalina]. Замечательной особенностью C++/CLI является то, что компилятор берет на себя практически всю рутинную работу по реализации этого шаблона, тогда как в C# почти все приходится делать руками.

1. Шаблон Basic Dispose в C++/CLI

Существуют два основных способа реализовать этот шаблон.

1.1. Определение деструктора и финализатора

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

public ref class X < ~X() // деструктор !X() // финализатор // . >;

В частности компилятор делает следующее:

  1. Для класса X реализует интерфейс System::IDisposable .
  2. В X::Dispose() обеспечивает вызов деструктора, вызов деструктора базового класса (если он есть) и вызов GC::SupressFinalize() .
  3. Переопределяет System::Object::Finalize() , где обеспечивает вызов финализатора и финализаторов базовых классов (если они есть).

Наследование от System::IDisposable можно указать явно, а вот самостоятельно определить X::Dispose() нельзя.

1.2. Использование семантики стека

Шаблон Basic Dispose также реализуется компилятором, если в классе имеется член освобождаемого типа и он объявлен с использованием семантики стека. Это означает, что для объявления используется имя типа без крышки (‘ ^ ‘), а инициализация происходит в списке инициализации конструктора, а не с помощью gcnew . Семантика стека описана в [Hogenson].

public ref class R : System::IDisposable < public: R(/* параметры */); // конструктор // . >; public ref class X < R m_R; // а не R^ m_R public: X(/* параметры */) // конструктор : m_R(/* аргументы */) // а не m_R = gcnew R(/* аргументы */) // . >;

Компилятор в этом случае делает следующее:

  1. Для класса X реализует интерфейс System::IDisposable .
  2. В X::Dispose() обеспечивает вызов R::Dispose() для m_R .

Финализация определяется соответствующей функциональностью класса R . Как и в предыдущем случае, наследование от System::IDisposable можно указать явно, а самостоятельно определить X::Dispose() нельзя. Естественно, класс может иметь еще другие члены, объявленные с использованием семантики стека, и для них также обеспечивается вызов их Dispose() .

2. Управляемые шаблоны

И наконец, еще одна замечательная особенность C++/CLI позволяет максимально упростить создание классов-дескрипторов. Речь идет об управляемых шаблонах (managed templates). Это не обобщения (generics), а настоящие шаблоны, как в классическом C++, но шаблоны не родных, а управляемых классов. Инстанцирование таких шаблонов приводит к созданию управляемых классов, которые можно использовать в качестве базовых классов или членов других классов внутри сборки. Управляемые шаблоны описаны в [Hogenson].

2.1. Интеллектуальные указатели

Управляемые шаблоны позволяют создавать классы типа интеллектуальных указателей, которые содержат указатель на родной объект в качестве члена и обеспечивают его удаление в деструкторе и финализаторе. Такие интеллектуальные указатели можно использовать в качестве базовых классов или членов (естественно, с использованием семантики стека) при разработке классов-дескрипторов, которые автоматически становятся освобождаемыми.

Приведем пример таких шаблонов. Первый шаблон является базовым, второй предназначен для использования в качестве базового класса и третий — в качестве члена класса. Эти шаблоны имеют шаблонный параметр (родной), предназначенный для удаления объекта. Класс-удалитель по умолчанию удаляет объект оператором delete .

// родной шаблон, класс-удалитель по умолчанию, T — родной класс template struct DefDeleter < void operator()(T* p) const < delete p; >>; // управляемые шаблоны, // интеллектуальные указатели на родной объект // базовый шаблон, T — родной класс, D — класс-удалитель template public ref class ImplPtrBase : System::IDisposable < T* m_Ptr; void Delete() < if (m_Ptr != nullptr) < D del; del(m_Ptr); m_Ptr = nullptr; >> ~ImplPtrBase() < Delete(); >!ImplPtrBase() < Delete(); >protected: ImplPtrBase(T* p) : m_Ptr(p) <> T* Ptr() < return m_Ptr; >>; // шаблон для использования в качестве базового класса template > public ref class ImplPtr : ImplPtrBase  < protected: ImplPtr(T* p) : ImplPtrBase(p) <>public: property bool IsValid < bool get() < return (ImplPtrBase::Ptr() != nullptr); >> >; // шаблон для использования в качестве члена класса template > public ref class ImplPtrM sealed : ImplPtrBase  < public: ImplPtrM(T* p) : ImplPtrBase(p) <>operator bool() < return ( ImplPtrBase::Ptr() != nullptr); >T* operator->() < return ImplPtrBase::Ptr(); >T* Get() < return ImplPtrBase::Ptr(); >>;

2.2. Пример использования

class N // родной класс < public: N(); ~N(); void DoSomething(); // . >; using NPtr = ImplPtr; // базовый класс public ref class U : NPtr // управляемый класс-дескриптор < public: U() : NPtr(new N()) <>void DoSomething() < if (IsValid) Ptr()->DoSomething(); > // . >; public ref class V // управляемый класс-дескриптор, второй вариант < ImplPtrMm_NPtr; // семантика стека public: V() : m_NPtr(new N()) <> void DoSomething() < if (m_NPtr) m_NPtr->DoSomething(); > // . >;

В этих примерах классы U и V становятся освобождаемыми без всяких дополнительных усилий, их Dispose() обеспечивает вызов оператора delete для указателя на N . Второй вариант, с использованием ImplPtrM<> , позволяет в одном классе-дескрипторе управлять несколькими родными классами.

2.3. Более сложные варианты финализации

Финализация является достаточно проблемным аспектом работы .NET. В нормальных сценариях работы приложения финализаторы вызываться не должны, освобождение ресурсов происходить в Dispose() . Но в аварийных сценариях это может произойти и финализаторы должны работать корректно.

2.3.1. Блокировка финализаторов

Если родной класс находится в DLL, которая загружается и выгружается динамически — с использованием LoadLibrary()/FreeLibrary() , — то может возникнуть ситуация, когда после выгрузки DLL остались неосвобожденные объекты, имеющие ссылки на экземпляры этого класса. В этом случае через некоторое время сборщик мусора попытается их финализировать, а так как DLL выгружена, то скорее всего произойдет аварийное завершение программы. (Характерный признак — аварийное завершение через несколько секунд после видимого закрытия приложения.) Поэтому после выгрузки DLL финализаторы должны быть блокированы. Этого можно достичь небольшой модификацией базового шаблона ImplPtrBase .

public ref class DllFlag < protected: static bool s_Loaded = false; public: static void SetLoaded(bool loaded) < s_Loaded = loaded; >>; template public ref class ImplPtrBase : DllFlag, System::IDisposable < // . !ImplPtrBase() < if (s_Loaded) Delete(); >// . >;

После загрузки DLL надо вызвать DllFlag::SetLoaded(true) , а перед выгрузкой DllFlag::SetLoaded(false) .

2.3.2. Использование SafeHandle

Класс SafeHandle реализует достаточно сложный и максимально надежный алгоритм финализации, см. [Richter]. Шаблон ImplPtrBase<> можно переработать так, чтобы он использовал SafeHandle . Остальные шаблоны менять не нужно.

using SH = System::Runtime::InteropServices::SafeHandle; using PtrType = System::IntPtr; template public ref class ImplPtrBase : SH < protected: ImplPtrBase(T* p) : SH(PtrType::Zero, true) < handle = PtrType(p); >T* Ptr() < return static_cast(handle.ToPointer()); > bool ReleaseHandle() override < if (!IsInvalid) < D del; del(Ptr()); handle = PtrType::Zero; >return true; > public: property bool IsInvalid < bool get() override < return (handle == PtrType::Zero); >> >;

Список литературы

[Richter]
Рихтер, Джеффри. Программирование на платформме Microsoft .NET Framework 4.5 на языке C#. 4-е изд.: Пер. с англ. — СПб.: Питер, 2016.

[Cwalina]
Цвалина, Кржиштов. Абрамс, Бред. Инфраструктура программных проектов: соглашения, идиомы и шаблоны для многократно используемых библиотек .NET.: Пер. с англ. — М.: ООО «И.Д. Вильямс», 2011.

[Hogenson]
Хогенсон, Гордон. С++/CLI: язык Visual C++ для среды .NET.: Пер. с англ. — М.: ООО «И.Д. Вильямс», 2007.

  • c++/cli
  • управление родными ресурсами

Файл дескриптор в 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 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *