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

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

Электронный документооборот  

5 способов повысить безопасность электронной подписи

Область применения технологий электронной подписи с каждым годом расширяется. Все больше задач

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

Рынок труда  

Системные администраторы по-прежнему востребованы и незаменимы

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

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

Учебные центры  

Карьерные мечты нужно воплощать! А мы поможем

Школа Bell Integrator открывает свои двери для всех, кто хочет освоить перспективную

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

Гость номера  

Дмитрий Галов: «Нельзя сказать, что люди становятся доверчивее, скорее эволюционирует ландшафт киберугроз»

Использование мобильных устройств растет. А вместе с ними быстро растет количество мобильных

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

Прошу слова  

Твердая рука в бархатной перчатке: принципы soft skills

Лауреат Нобелевской премии, специалист по рынку труда, профессор Лондонской школы экономики Кристофер

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

1001 и 1 книга  
19.03.2018г.
Просмотров: 9941
Комментарии: 0
Потоковая обработка данных

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

19.03.2018г.
Просмотров: 8153
Комментарии: 0
Релевантный поиск с использованием Elasticsearch и Solr

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

19.03.2018г.
Просмотров: 8258
Комментарии: 0
Конкурентное программирование на SCALA

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

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

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

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

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

Друзья сайта  

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

Архив номеров / 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-41
Fax: (499) 277-12-45
E-mail: sa@samag.ru