www.samag.ru
Журнал «БИТ. Бизнес&Информационные технологии»      
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Подписка
Архив номеров
Где купить
Авторам
Рекламодателям
Контакты
   

  Опросы
  Статьи

Интеграция Open Source-решений  

Open Source в облачной среде

Облачные решения становятся всё более популярными в мире. Компании стремятся использовать их для

 Читать далее...

Автоматизация  

Нейросеть вам в руки! Как использовать ИИ для автоматизации задач

Использование ИИ для автоматизации задач помогает компании получить конкурентное преимущество, поскольку объединение

 Читать далее...

Рынок труда  

Специалист по этическому ИИ, инженер по квантовым вычислениям или аналитик по метавселенной?

Новые тенденции в развитии ИТ могут привести к возникновению новых специальностей в

 Читать далее...

Книжная полка  

Учитесь убеждать и побеждать

Издательство «БХВ», как всегда, порадовало своих читателей хорошими книжными новинками. Кроме популярных

 Читать далее...

Сетевая инфраструктура  

Как удаленная работа меняет подход к сетевой инфраструктуре?

С увеличением числа сотрудников, работающих из дома, организации сталкиваются с необходимостью создания

 Читать далее...

Мониторинг  

Какой мониторинг нужен сегодня?

По мнению экспертов ГК InfoWatch, действия сотрудников – самая распространенная причина инцидентов

 Читать далее...

Книжная полка  

Руководство для тех, кто увлечен ИИ, программированием. И дизайном

Накануне лета издательство «БХВ» выпустило книжные новинки, от которых любителям чтения будет

 Читать далее...

Мобильные приложения  

Искусственный интеллект в мобильных приложениях: возможности и перспективы

Обзор современных применений ИИ в мобильных приложениях, анализ перспектив развития этой технологии,

 Читать далее...

ИТ-образование  

Как сделать ИТ-образование эффективным?

Эксперты ИТ-отрасли отвечают на вопросы «СА». Обсуждаем ключевые аспекты для улучшения образовательных

 Читать далее...

Work-life balance  

Как айтишнику найти баланс между работой и личной жизнью?

Обсуждаем инструменты для эффективного управления временем, снижения уровня стресса и достижения гармонии. На

 Читать далее...

Книжная полка  

Всё самое нужное – под одной обложкой

Отличительная черта книжных новинок, выпущенных недавно издательством «БХВ» – это их универсальность. Не просто

 Читать далее...

ИТ-инфраструктура  

Системы мониторинга ИТ-инфраструктуры-2025

Без мониторинга ИТ-инфраструктуры не обходится ни одна компания, хотя бы потому, что

 Читать далее...

Открытое ПО  

Безопасность Open Source: рискуем или контролируем?

Компания «Кросс технолоджис» изучила, как используется ПО с открытым кодом в компаниях

 Читать далее...

Работа с нейросетью  

Скажи, есть ли у тебя AI, и я скажу, кто ты

Недавно сервис по поиску работы SuperJob выяснил, что каждый второй россиянин уже

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 9985
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 10104
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

12.03.2018г.
Просмотров: 7570
Комментарии: 0
Глубокое обучение с точки зрения практика

 Читать далее...

12.03.2018г.
Просмотров: 4698
Комментарии: 0
Изучаем pandas

 Читать далее...

12.03.2018г.
Просмотров: 5522
Комментарии: 0
Программирование на языке Rust (Цветное издание)

 Читать далее...

19.12.2017г.
Просмотров: 5495
Комментарии: 0
Глубокое обучение

 Читать далее...

19.12.2017г.
Просмотров: 8249
Комментарии: 0
Анализ социальных медиа на Python

 Читать далее...

19.12.2017г.
Просмотров: 4864
Комментарии: 0
Основы блокчейна

 Читать далее...

19.12.2017г.
Просмотров: 5127
Комментарии: 0
Java 9. Полный обзор нововведений

 Читать далее...

16.02.2017г.
Просмотров: 9208
Комментарии: 0
Опоздавших не бывает, или книга о стеке

 Читать далее...

17.05.2016г.
Просмотров: 12654
Комментарии: 0
Теория вычислений для программистов

 Читать далее...

30.03.2015г.
Просмотров: 14159
Комментарии: 0
От математики к обобщенному программированию

 Читать далее...

18.02.2014г.
Просмотров: 15892
Комментарии: 0
Рецензия на книгу «Читаем Тьюринга»

 Читать далее...

13.02.2014г.
Просмотров: 10779
Комментарии: 0
Читайте, размышляйте, действуйте

 Читать далее...

12.02.2014г.
Просмотров: 8772
Комментарии: 0
Рисуем наши мысли

 Читать далее...

10.02.2014г.
Просмотров: 7017
Комментарии: 4
Страна в цифрах

 Читать далее...

18.12.2013г.
Просмотров: 6128
Комментарии: 0
Большие данные меняют нашу жизнь

 Читать далее...

18.12.2013г.
Просмотров: 5058
Комментарии: 0
Компьютерные технологии – корень зла для точки роста

 Читать далее...

04.12.2013г.
Просмотров: 4693
Комментарии: 0
Паутина в облаках

 Читать далее...

03.12.2013г.
Просмотров: 4933
Комментарии: 1
Рецензия на книгу «MongoDB в действии»

 Читать далее...

Друзья сайта  

 Процессы и нити

Архив номеров / 2003 / Выпуск №5 (6) / Процессы и нити

Рубрика: Администрирование /  Архитектура

ВСЕВОЛОД СТАХОВ

Процессы и нити

В данной статье я расскажу о работе с процессами в многозадачных операционных системах, в частности о программировании процессов и нитей в Windows и POSIX-совместимых системах.

Как вы знаете, Windows NT – это многозадачная ОС, а это значит, что вы можете создавать программным путём другие процессы и нити внутри процесса. Для создания процесса могут использоваться две основные функции: WinExec и CreateProcess. Первая очень проста в применении, имеет только два параметра и может использоваться для создания оконных процессов (к ним можно отнести и консольные программы). Однако возможности WinExec сильно ограничены, и Microsoft громогласно объявила, что вместо этого следует использовать функцию CreateProcess. Но рассмотреть WinExec всё же надо, хотя бы для быстрого написания простых программ.

int WinExec(char *command_line, unsigned int show_mode);

Функция выполняет программу сommand_line в режиме отображения окна show_mode и ждёт, пока дочерний процесс вызовет функцию GetMessage или пока не прошло время ожидания (это может вызвать задержку выполнения). При успешном выполнении функция возвращает значение, большее 31; меньшее значение сигнализирует об ошибке:

  • 0 – не хватает ресурсов;
  • ERROR_BAD_FORMAT – указываемый файл неисполняемый;
  • ERROR_FILE_NOT_FOUND – указываемый файл не найден;
  • ERROR_PATH_NOT_FOUND – путь не существует.

Режимы отображения окна будут описаны далее.

Функция CreateProcess принимает большое количество параметров и может использоваться для указания множества атрибутов порождаемым процессам, например, можно сделать оболочку для DOS-программы с графическим интерфейсом ввода/вывода данных, переопределив STDIN и STDOUT и многое другое. Но использовать данную функцию достаточно непросто, так как приходится учитывать некоторые нюансы, а можно ещё и мучиться с атрибутами безопасности, но это уже на любителя... Итак, вот что написано в MSDN:

BOOL CreateProcess(

    // Имя исполняемого файла

    LPCTSTR lpApplicationName,

    // Командная строка

    LPTSTR lpCommandLine,

    // Атрибуты безопасности процесса

    LPSECURITY_ATTRIBUTES lpProcessAttributes,

    // Атрибуты безопасности потока

    LPSECURITY_ATTRIBUTES lpThreadAttributes,

    // Наследует ли дочерний процесс дескрипторы родителя

    BOOL bInheritHandles,

    // Флаги создания процесса

    DWORD dwCreationFlags,

    // Указатель на environment для дочернего процесса

    LPVOID lpEnvironment,

    // Текущая директория для процесса

    LPCTSTR lpCurrentDirectory,

    // Структура для запуска процесса

    LPSTARTUPINFO lpStartupInfo,

    // Указатель, получающий данные о дочернем процессе

    LPPROCESS_INFORMATION lpProcessInformation

);

Краткое объяснение параметров:

LPCTSTR lpApplicationName – путь (полный или относительный) к исполняемому файлу. Если путь длинный или содержит пробелы, то необходимо заключить его в кавычки: ""C:very long pathvery long filename.exe"". Учтите, что в WindowsNT для запуска 16-ти разрядных программ необходимо указывать путь к файлу в lpCommandLine, а данный параметр должен равняться NULL (в Win9x 16-ти разрядные файлы выполняются, как и все другие).

LPTSTR lpCommandLine – параметры, передаваемые порождаемому процессу в командной строке для 16-ти разрядных приложений в WinNT. При указании пути к 16-ти разрядному приложению надо путь к нему заключать в кавычки, чтобы отделить конец самого пути и начало передаваемых аргументов командной строки. Но такой метод срабатывает и для 32-х разрядных приложений. Поэтому параметр lpApplicationName оставляют в NULL, а в данном параметре прописывают полную командную строку, предварённую путём к файлу. При этом учтите, что если путь неполный и файл не найден относительно текущей директории, то происходит поиск в следующих местах:

  • в директории системных файлов (winntsystem32 или windowssystem);
  • в директории 16-ти разрядных системных файлов для WinNT winntsystem;
  • в директории windows (winnt windows);
  • в директориях, описанных в переменной окружения PATH.

LPSECURITY_ATTRIBUTES lpProcessAttributes – атрибуты безопасности процесса. Для большинства случаев следует писать NULL, т.е. атрибуты безопасности наследуются порождаемым процессом.

LPSECURITY_ATTRIBUTES lpThreadAttributes – атрибуты безопасности потока, тоже обычно NULL.

BOOL bInheritHandles – флаг, определяющий наследует ли дочерний процесс дескрипторы родителя. Данный флаг удобно применять для межпроцессовых коммуникаций через неименованные трубки (pipe) и для совместного использования файлов. Общее правило: если процесс собирается общаться с потомком, этот параметр должен быть TRUE.

DWORD dwCreationFlags – флаги создания процесса. Битовая маска, определяющая различные параметры создания процесса. Обычно используется для указания приоритета процесса:

  • HIGH_PRIORITY_CLASS – высокий приоритет процесса (нельзя создать процесс данного класса, не получив соответствующих привилегий).
  • IDLE_PRIORITY_CLASS – низкий приоритет процесса.
  • NORMAL_PRIORITY_CLASS – нормальный приоритет процесса.
  • REALTIME_PRIORITY_CLASS – режим реального времени для процесса (для запуска необходимы привилегии администратора, что обычно используется при создании сервисов реального времени, которые запускаются от пользователя SYSTEM) даёт порождаемому процессу 100% CPU, и если последний начнёт «пожирать» все ресурсы процессора, то ОС наглухо зависнет: мышь двигаться не сможет, буферы на диск не сбрасываются. В общем, хорошо, что всем нельзя такие процессы делать.

Есть ещё интересные параметры для отладки приложений, позволяющие применять функции непосредственного доступа к памяти процесса ReadProcessMemory и WriteProcessMemory, но описание этого выходит за рамки данной статьи.

LPVOID lpEnvironment – указатель на environment для дочернего процесса, представляет собой блок строк, заканчивающихся NULL, содержащих в себе описание переменных окружения для дочернего процесса в формате «имя=значение». Если данный параметр NULL, то по традиции он наследуется от родительского процесса.

LPCTSTR lpCurrentDirectory – текущая директория для процесса.

LPSTARTUPINFO lpStartupInfo – структура для запуска процесса – это самая интересная часть CreateProcess, будет описана далее.

LPPROCESS_INFORMATION lpProcessInformation – указатель, получающий данные о дочернем процессе в формате:

  struct PROCESS_INFORMATION{

    HANDLE hProcess;  – дескриптор порождённого процесса;

    HANDLE hThread;   – дескриптор главного потока дочернего процесса;

           DWORD dwProcessId; – идентификатор порождённого процесса;

           DWORD dwThreadId; – идентификатор порождённого потока.

   }

А теперь я приведу описание структуры STARTUPINFO, и многие поймут, почему системных программистов Windows возводят в ранг великомучеников.

struct STARTUPINFO {

    DWORD  cb;

    LPTSTR lpReserved;

    LPTSTR lpDesktop;

    LPTSTR lpTitle;

    DWORD  dwX;

    DWORD  dwY;

    DWORD  dwXSize;

    DWORD  dwYSize;

    DWORD  dwXCountChars;

    DWORD  dwYCountChars;

    DWORD  dwFillAttribute;

    DWORD  dwFlags;

    WORD   wShowWindow;

    WORD   cbReserved2;

    LPBYTE lpReserved2;

    HANDLE hStdInput;

    HANDLE hStdOutput;

    HANDLE hStdError;

};

Теперь «краткое» описание полей:

  • DWORD cb – размер структуры в байтах (sizeof(STARTUPINFO)).
  • LPTSTR lpReserved – очередная шутка Microsoft, должно быть NULL.
  • LPTSTR lpDesktop – указатель на рабочий стол для окна (полезно для сервисов, работающих с рабочим столом).
  • LPTSTR lpTitle – используется при создании окна консоли с заголовком, в остальных случаях должен быть NULL.
  • DWORD dwX – размеры окна по х.
  • DWORD dwY – размеры окна по у.
  • DWORD dwXSize – ширина.
  • DWORD dwYSize – высота.

Параметры размера игнорируются, если только в dwFlags не установлен атрибут STARTF_USEPOSITION и/или STARTF_USESIZE.

  • DWORD dwXCountChars – количество символов по ширине.
  • DWORD dwYCountChars – количество символов по высоте.
  • Данные параметры применяются для консольных приложений, если установлен флаг STARTF_USECOUNT-CHARS.
  • DWORD dwFillAttribute – устанавливают цвет (FORE-GROUND_BLUE, FOREGROUND_GREEN, FOREGRO-UND_RED, FOREGROUND_INTENSITY, BACKGRO-UND_BLUE, BACKGROUND_GREEN, BACKGRO-UND_RED, и BACKGROUND_INTENSITY) для консольных приложений, если установлен флаг STARTF_USEFILLATTRIBUTE.
  • DWORD dwFlags – а вот это те самые флаги; кроме тех, что были уже упомянуты выше, можно отметить флаг STARTF_USESTDHANDLES – переопределение стандартных потоков для потомка.
  • WORD wShowWindow – режим открытия окна (для его изменения необходимо установить флаг STARTF_USESHOWWINDOW), приведу несколько самых используемых:
    • SW_HIDE – спрятать окно и показать следующее;
    • SW_MAXIMIZE – развернуть и активизировать окно;
    • SW_MINIMIZE – свернуть окно и показать следующее;
    • SW_RESTORE – восстановить окно;
    • SW_SHOWNORMAL – восстановить и активизировать окно;
    • SW_SHOW – просто активизировать и показать окно.
  • WORD cbReserved2 – NULL.
  • LPBYTE lpReserved2 – NULL.
  • HANDLE hStdInput – дескриптор стандартного ввода.
  • HANDLE hStdOutput – дескриптор стандартного вывода.
  • HANDLE hStdError – дескриптор стандартных ошибок.

Тут могу посоветовать одно: обнуляйте структуру перед использованием, а затем заполняйте нужные поля, остальные установятся по умолчанию. Ну что же, пришло время для чего-нибудь интересного. Итак, примеры.

Для начала запустим какой-нибудь notepad и загрузим в него какой-нибудь файл, в данном примере test.txt:

// Информация о процессе

STARTUPINFO si;

// Обнуление структуры

memset(&si, 0, sizeof(STARTUPINFO));

// Заполнение полей

si.cb = sizeof(STARTUPINFO);

// Флаги

si.dwFlags = STARTF_USESHOWWINDOW;

// Показываем окошко

si.wShowWindow = SW_SHOWNORMAL;

// Информация о процессе

PROCESS_INFORMATION pi;

// Вот и всё! Теперь блокнот запущен

CreateProcess(NULL, "notepad.exe test.txt", NULL, NULL, FALSE, 0, 0, 0, &si, ?);

А теперь более интересный пример – создание оболочки для программы MS-DOS. Для общения с консольной программой можно использовать только неименованные каналы (pipe). Проблема в том, что в WinNT неименованные каналы не наследуются порождаемым процессом. Для того чтобы наследовать неименованные каналы, необходимо установить соответствующие параметры безопасности. Но я подробно на этом останавливаться не буду – просто примите это на веру, так как дескрипторы безопасности – это отдельная большая тема.

SECURITY_DESCRIPTOR sd; // Дескриптор безопасности

SECURITY_ATTRIBUTES sa; // и его атрибуты

LPSECURITY_ATTRIBUTES lpsa = NULL;

// Это Windows NT

if (GetVersion() < 0x80000000) {

    // Инициализация дескриптора

    InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);  

    SetSecurityDescriptorDacl(&sd, true, NULL, false);

    sa.nLength = sizeof(SECURITY_ATTRIBUTES);

    // Разрешаем наследование дескрипторов

    sa.bInheritHandle = true;

    sa.lpSecurityDescriptor = &sd;

    // А вот это уже нормальный дескриптор безопасности

    lpsa = &sa;

        }

// Создаём неименованный канал и получаем дескрипторы чтения/записи

HANDLE hReadPipe;

HANDLE hWritePipe;

// Создание канала

CreatePipe(&hReadPipe, &hWritePipe, lpsa,25000);

// Ну а это инициализация STARTUPINFO

STARTUPINFO si;

// Обнуление

memset(&si, 0, sizeof(STARTUPINFO));

si.cb = sizeof(STARTUPINFO);

// Флаг перенаправления дескрипторов

si.dwFlags = STARTF_USESHOWWINDOW |STARTF_USESTDHANDLES;

// Прячем окошко

si.wShowWindow = SW_HIDE;

// Указатели stdout и stderr перенаправляются в канал

si.hStdOutput = hWritePipe;

si.hStdError = hWritePipe;

PROCESS_INFORMATION pi;

if(CreateProcess(NULL, "bcc -otest test.c", NULL, NULL, TRUE, 0, 0, 0, &si, &pi)){

    // Закрываем дескриптор потока

    CloseHandle(pi.hThread);

    // Ждём завершения дочернего процесса 90 сек.

    WaitForSingleObject(pi.hProcess, 90000);

    // Читаем из канала данные

    DWORD BytesRead; // Количество считанных байт

    char dest[4000]; // Вот в этот буфер писать и будем

    int LoopDone = 0;

    int FBreak = 0;

    // Цикл чтения данных из канала, с защитой от тайм-аута, т.к. чтение неблокирующее

    while (!LoopDone) {

           memset(dest, 0, 4000);

           ReadFile(hReadPipe, &dest, sizeof(dest), &BytesRead, NULL);

           if (BytesRead < 4000 || FBreak > 150) LoopDone = -1;

           else LoopDone = 0;

           FBreak++;

    }

}

А теперь приведу пример взаимодействия сервиса с рабочим столом, например, сервис, вызывающий explorer с привилегиями LocalSystem (фактически привилегии операционной системы):

PROCESS_INFORMATION pi;

// Ну а это инициализация STARTUPINFO

STARTUPINFO si;

memset(&si, 0, sizeof(STARTUPINFO)); // Обнуление

si.cb = sizeof(STARTUPINFO); // И установка полей

si.lpDesktop = "WinSta0\\Default"; // Это рабочий стол по умолчанию!

si.dwFlags = STARTF_USESHOWWINDOW;

si.wShowWindow = SW_SHOW;

if (CreateProcess(NULL, "explorer", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))

{

    // Закрываем все дескрипторы в конце программы

    CloseHandle(pi.hProcess);

    CloseHandle(pi.hThread);

}

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

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

Для создания потока в WinNT необходимо применить функцию CreateThread. Учтите, что работа с потоками кардинальным образом отличается от работы с процессами, т.к. нет необходимости применять межпроцессные коммуникации ОС и с потоком можно работать также, как и с любой другой функцией, например, передавать в неё параметры. Кроме этого, так как все потоки используют общее адресное пространство, то глобальные и статические переменные одинаковы во всех нитях (иногда встают проблемы синхронизации потоков), хотя межпроцессные коммуникации также возможны (у нити есть собственный дескриптор), что делает нить неким «гибридом» между функцией и процессом по способу взаимодействия.

HANDLE CreateThread(

    // Атрибуты безопасности (NULL)

    LPSECURITY_ATTRIBUTES lpThreadAttributes,

    // Размер стека для потока (если 0, то используется умолчание

    DWORD dwStackSize,

    // Указатель на функцию потока

    LPTHREAD_START_ROUTINE lpStartAddress,

    // Аргументы, передаваемые потоку

    LPVOID lpParameter,

    // Флаги создания

    DWORD dwCreationFlags,

    // Получает ID потока

    LPDWORD lpThreadId

);

Синтаксис данной команды довольно простой, но я хотел бы рассказать следующее: если вы передаёте нити аргументы, то их нужно прямо приводить к типу void *, а в самом потоке делать обратное преобразование. Данный способ позволяет передать потоку абсолютно любые параметры. Если вы не хотите, чтобы поток сразу же запускался, то укажите флаг CREATE_SUSPENDED, тогда нить создастся, но запускаться не будет. Функция CreateThread возвращает дескриптор потока или NULL при ошибке. Функция потока имеет вид DWORD WINAPI ThreadFunc(LPVOID data), где data – передаваемые параметры.

Для управления состоянием потоков используются функции SuspendThread и ResumeThread для соответственно приостанавливания и восстановления потоков. Функции принимают один аргумент – дескриптор потока и возвращают 1 в случае ошибки. Для убиения потока используется функция TerminateThread, она принимает два параметра: дескриптор потока и код ошибки. Если поток хочет сообщить вызывающему потоку код завершения, то он должен использовать функцию ExitThread(DWORD dwExit-Code), а вызывающий поток должен вызвать GetExitThread-Code(HANDLE hThread, LPDWORD lpdwExitCode), и поле lpdwExitCode заполняется кодом завершения потока.

Пример создания простого сервиса:

// Формат функции потока жёстко задан

DWORD WINAPI ThreadFunc(LPVOID lpParam)

{

    char szMsg[80];

    wsprintf( szMsg, "Поток: параметр = %d ", *lpParam );

    MessageBox( NULL, szMsg, "Поток создан.", MB_OK );

    return 0;

}

int main(void)

{

    //Параметр для потока

    DWORD dwThreadId, dwThrdParam = 1;

    HANDLE hThread;

    hThread = CreateThread(

           NULL,  // безопасность по умолчанию

           0,     // размер стека по умолчанию 

           ThreadFunc,  // функция потока

           (void *) &dwThrdParam, // параметр для функции нити

           0,     // нет специальных флагов создания

           &dwThreadId); // получает ID нити

   // Проверяем правильность работы потока.

    if (hThread == NULL)

      return -1;

    // Негласное правило – закрываем дескрипторы вручную

    CloseHandle(hThread);

    return 0;

}

Потоки – это мощное и хорошо реализованное в WinNT средство. Потоки очень часто применяются в серверных приложениях, сервисах и прочих программах, использующих «разделение труда». Потоки легко использовать и управлять ими. Единственное, о чём приходится заботиться, – это синхронизация данных между потоками, работающими одновременно, для этого существуют механизмы семафоров и мьютексов, но опять же это другая тема...

В других операционных системах также есть свои функции создания процессов и потоков. Наиболее переносимой является семейство функций exec*, которые производят запуск дочернего процесса в адресном пространстве родительского, т.е. после выполнения exec дочерний процесс заменяет родительский. Данная функция есть практически во всех ОС: POSIX, Windows, DOS, OS/2. Формат функции зависит от суффикса функции, например: execl, execlp, execve. Буквы l и v обозначают способ передачи аргументов дочернему процессу: в виде массива строк (суффикс v) или в виде последовательных строк, заканчивающихся нулевой строкой (суффикс l). Суффикс e сигнализирует, что дочернему процессу также передаётся массив строк окружения (имя_переменной=значение). Суффикс p означает, что нужно произвести поиск исполняемого файла в PATH. Итак, общий формат функции:

// указываем переменные окружения

int execve(char *path, char *args[], char *env[])

// идет поиск в PATH

int execlp(char *path, char *arg1, char *arg2 ... char *argn, NULL)

При удачном выполнении функция не возвращает ничего (возвращать-то некому! родительский процесс уже заменён на дочерний), иначе произошла ошибка. Замечу, что в DOS, Windows и OS/2 есть ещё семейство функций spawn*, подобных exec*, но не заменяющих родительский процесс, а создающих параллельный. Причём возвращают они идентификатор процесса. Кроме этого, функции семейства spawn принимают первым параметром режим запуска, который может принимать следующие значения:

  • P_WAIT – родительский процесс дожидается завершения дочернего, а затем продолжает выполнение;
  • P_NOWAIT – родительский и дочерний процессы выполняются одновременно, но родительский может применить функцию wait для ожидания завершения дочернего процесса (такой режим доступен только для Win32 и OS/2);
  • P_NOWAITO – идентично P_NOWAIT, но невозможен вызов wait (работает везде);
  • P_DETACH – то же, что и P_NOWAITO, но дочерний процесс выполняется в фоновом режиме без доступа к клавиатуре и дисплею.

Ещё раз повторяю: функции spawn* не поддерживаются POSIX-стандартом. Для ожидания окончания дочернего процесса используется функция pid_t wait(int *process_stat). Данная функция дожидается окончания дочернего процесса и заполняет параметр process_stat. При нормальном завершении дочернего процесса старшие биты данной структуры содержат код завершения программы, иначе устанавливаются биты 1, 2, 3. Если дочерний процесс уже завершился, то функция wait немедленно возвращается, а ресурсы, занятые потомком, освобождаются. В POSIX-системах возможно также применение функции wait к нитям, но об этом далее.

Пример использования функций exec и spawn:

int main()

{

    //Формирование массива аргументов

    char *ar[2] = {"test.c" "-otest"};

    //А теперь нашего процесса уже нет, он заменился gcc

    return execvp("gcc", ar);

}

---------------------------------------------------------

int main()

{

    //Запускаем gcc параллельно

    spawnlp(P_NOWAIT, "gcc", "test.c", "-otest", NULL);

    // Для чего-то ждём завершения gcc и выходим

    return wait(NULL);

}

В POSIX определён также замечательный системный вызов fork, который выполняет «разделение» существующего процесса. После вызова fork происходит полное копирование адресного пространства существующего процесса, и далее продолжают выполняться 2 процесса, отличающихся только идентификатором процесса. Причём родителю fork возвращает идентификатор дочернего процесса, а дочернему – 0. Так можно определить тип процесса: родитель или потомок. У двух процессов после fork идентично содержимое памяти, стека, файловых дескрипторов, идентификаторов пользователя процесса (UID), каналов и т. д. Не наследуются только те участки памяти, которые были закреплены с помощью mlock(void *mem, size_t size). Можно сказать, что после вызова fork происходит разделение исходного процесса на два идентичных, но при этом между собой не связанных процесса (т.е. адресное пространство у них разное). Далее обычно дочерний и родительский процессы начинают вести себя по-разному, в зависимости от значения, возвращённого fork. C помощью fork можно выполнить множество полезных действий, например, создание демонов, сокетных серверов и т. д. Например, с помощью функции fork можно организовать поведение, аналогичное spawn в win32:

void main()

{

    pid_t pid; // Идентификатор процесса

    pid = fork(); // А теперь уже 2 процесса

    if(pid == -1) // Произошла ошибка

           return;

 

    if(pid == 0){ // Это дочерний процесс

           execlp("gcc", "test.c", "-otest", NULL);

    }

    else{

           printf("Compiling now\n");

           // Так как ждём мы другой процесс, то надо указать его PID

           waitpid(pid, NULL, 0);

           printf("That`s all\n");

    }

}

Кроме вышеперечисленных функций существуют также функции system и popen, выполняющие команды средствами операционной системы. Первая из них int system(char * command) выполняет файл command и возвращает вызывающей программе код завершения. Функция блокирует выполнение родительского процесса до завершения работы потомка. В системах POSIX command выполняется вызовом /bin/sh –c command. Функция system не должна употребляться с программами, устанавливающими биты suid sgid, во избежание «странного» поведения. Функция popen используется в POSIX-системах аналогично system (выполняется /bin/sh –c), но данная функция возвращает файловый поток, связанный с дочерней программой (аналог конвейера | в shell), что позволяет взаимодействовать с потомком. FILE *popen(char *path, char *mode), где mode – режим открытия канала, аналогичный режимам открытия файла в fopen (учтите, что нельзя открывать такой поток одновременно для чтения и записи), при этом реально создается неименнованный канал.

После этого возможно взаимодействие с потомком через этот поток стандартными функциями работы с файлами (fprintf, fscanf, fread, fwrite). Работа с потоком является буферизованной и блокирующей, поэтому всегда надо готовиться к худшему (к получению сигнала SIGCHILD). Если потомок завершился, то попытки чтения из трубы неудачны, а feof возвращает не ноль. Поток, открытый popen, должен завершаться функцией pclose, ждущей окончания процесса и закрывающей созданный канал.

int main()

{

    char buf[1024];

    FILE *f = NULL;

    //Открываем канал в режиме чтения

    f = popen("ls -l", "r");

    if(f==NULL){ //Что-то не так

           perror("Failed to execute ls!");

           return -1;

    }

    while(!feof){ //Пока потомок работает  

           fgets(buf, 1024, f); //Читаем из канала

           printf("%s", buf);

    }

    pclose(f); //Закрываем канал

    return 0;

}

В форуме журнала был вопрос, связанный с поиском файлов в UNIX. Одним из решений может служить взаимодействие со стандартной утилитой find при помощи функции popen("find some_file", "r"). В качестве альтернативы можно также использовать команды locate и whereis.

Во многих UNIX-подобных системах (в частности в Linux) организована поддержка нитей. К сожалению, возможность эта ещё пока недостаточно отлажена и использовать её не так-то просто. Но я, тем не менее, расскажу об основных принципах создания нитей в UNIX. Нити в UNIX представляют собой особый вид процессов и создание нити очень похоже на вызов fork. Для создания нити необходимо применять функции библиотеки pthread. Для каждого потока схема создания следующая: инициализация атрибутов нити, создание нити, уничтожение нити и уничтожение её атрибутов.

int pthread_attr_init(pthread_attr_t *a)

– установка атрибутов по умолчанию в структуре a. Далее можно устанавливать приоритет потока и другие его атрибуты функциями, специальными для каждого атрибута (таких функций 5 пар, отвечающих за включение и выключение отдельных атрибутов).

int pthread_create(pthread_t *thread, pthread_attr_t *attr, void * (*start_func)(void *), void *arg)

– создаёт нить с атрибутами attr на основе функции start_func и передающей этой функции параметры arg типа void * (здесь всё, как и в WinNT). Если создание нити прошло удачно, функция возвращает 0, а поле thread заполняется идентификатором потока.

int pthread_join(pthread_t thread, void **retval)

– данный поток приостанавливает своё исполнение и ждёт завершения потока thread, который заполняет поле retval.

void pthread_exit(void *retval)

– завершает текущий поток и записывает результат выхода retval.

int pthread_attr_destroy(pthread_attr_t *attr)

– очистка структуры атрибутов нити.

Итак, приведу простенький пример создания нити в Linux.

#include <pthread.h>

void *thread_func(void *arg)

{

    printf("Thread is running\n");

    //Выходим из потока

    pthread_exit(NULL);

}

int main()

{

    //Атрибуты

    pthread_attr_t a;

    //Два потока

    pthread_t thread1;

    pthread_t thread2;

    //Создаём атрибуты по умолчанию

    pthread_attr_init(&a);

    //Создаём потоки

    pthread_create(&thread1, &a, thread_func, NULL);

    pthread_create(&thread2, &a, thread_func, NULL);

    //Ждём завершения 2-го потока

    pthread_join(thread2, NULL);

    //Убиваем атрибуты

    pthread_attr_destroy(&a);

    return 0;

}

Компилируем так:

gcc test_thread.c -lpthread


Комментарии отсутствуют

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

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

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-45
E-mail: sa@samag.ru