Всеволод Стахов
В данной статье я расскажу о работе с процессами в многозадачных
операционных системах, в частности о программировании процессов и нитей в 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; меньшее значение сигнализирует об ошибке:
n 0 – не хватает ресурсов;
n ERROR_BAD_FORMAT – указываемый файл неисполняемый;
n ERROR_FILE_NOT_FOUND – указываемый файл не
найден;
n 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 path\\very long filename.exe\"".
Учтите, что в WindowsNT для запуска 16-ти разрядных программ необходимо
указывать путь к файлу в lpCommandLine, а данный параметр должен равняться NULL
(в Win9x 16-ти разрядные файлы выполняются, как и все другие).
LPTSTR lpCommandLine – параметры,
передаваемые порождаемому процессу в командной строке для 16-ти разрядных
приложений в WinNT. При указании пути к 16-ти разрядному приложению надо путь к
нему заключать в кавычки, чтобы отделить конец самого пути и начало
передаваемых аргументов командной строки. Но такой метод срабатывает и для 32-х
разрядных приложений. Поэтому параметр lpApplicationName оставляют в NULL, а в
данном параметре прописывают полную командную строку, предварённую путём к файлу.
При этом учтите, что если путь неполный и файл не найден относительно текущей
директории, то происходит поиск в следующих местах:
n в директории системных файлов (\winnt\system32
или \windows\system);
n в директории 16-ти разрядных системных файлов
для WinNT \winnt\system;
n в директории windows (\winnt \windows);
n в директориях, описанных в переменной окружения
PATH.
LPSECURITY_ATTRIBUTES lpProcessAttributes –
атрибуты безопасности процесса. Для большинства случаев следует писать NULL,
т.е. атрибуты безопасности наследуются порождаемым процессом.
LPSECURITY_ATTRIBUTES lpThreadAttributes –
атрибуты безопасности потока, тоже обычно NULL.
BOOL bInheritHandles – флаг, определяющий
наследует ли дочерний процесс дескрипторы родителя. Данный флаг удобно применять
для межпроцессовых коммуникаций через неименованные трубки (pipe) и для
совместного использования файлов. Общее правило: если процесс собирается
общаться с потомком, этот параметр должен быть TRUE.
DWORD dwCreationFlags – флаги
создания процесса. Битовая маска, определяющая различные параметры создания
процесса. Обычно используется для указания приоритета процесса:
n HIGH_PRIORITY_CLASS – высокий приоритет
процесса (нельзя создать процесс данного класса, не получив соответствующих
привилегий).
n IDLE_PRIORITY_CLASS – низкий приоритет
процесса.
n NORMAL_PRIORITY_CLASS – нормальный приоритет
процесса.
n 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;
};
Теперь «краткое» описание полей:
n DWORD cb – размер структуры в байтах (sizeof(STARTUPINFO)).
n LPTSTR lpReserved – очередная шутка Microsoft,
должно быть NULL.
n LPTSTR lpDesktop – указатель на рабочий стол
для окна (полезно для сервисов, работающих с рабочим столом).
n LPTSTR lpTitle – используется при создании окна
консоли с заголовком, в остальных случаях должен быть NULL.
n DWORD dwX – размеры окна по х.
n DWORD dwY – размеры окна по у.
n DWORD dwXSize – ширина.
n DWORD dwYSize – высота.
Параметры размера игнорируются,
если только в dwFlags не установлен атрибут STARTF_USEPOSITION и/или
STARTF_USESIZE.
n DWORD dwXCountChars – количество символов по
ширине.
n DWORD dwYCountChars – количество символов по
высоте.
n Данные параметры применяются для консольных
приложений, если установлен флаг STARTF_USECOUNT-CHARS.
n DWORD dwFillAttribute – устанавливают цвет
(FORE-GROUND_BLUE, FOREGROUND_GREEN, FOREGRO-UND_RED, FOREGROUND_INTENSITY,
BACKGRO-UND_BLUE, BACKGROUND_GREEN, BACKGRO-UND_RED, и BACKGROUND_INTENSITY)
для консольных приложений, если установлен флаг STARTF_USEFILLATTRIBUTE.
n DWORD dwFlags – а вот это те самые флаги; кроме
тех, что были уже упомянуты выше, можно отметить флаг STARTF_USESTDHANDLES –
переопределение стандартных потоков для потомка.
n WORD wShowWindow – режим открытия окна (для его
изменения необходимо установить флаг STARTF_USESHOWWINDOW), приведу несколько самых
используемых:
n SW_HIDE – спрятать окно и
показать следующее;
n SW_MAXIMIZE – развернуть и
активизировать окно;
n SW_MINIMIZE – свернуть окно и
показать следующее;
n SW_RESTORE – восстановить
окно;
n SW_SHOWNORMAL – восстановить и
активизировать окно;
n SW_SHOW – просто
активизировать и показать окно.
n WORD cbReserved2 – NULL.
n LPBYTE lpReserved2 – NULL.
n HANDLE hStdInput – дескриптор стандартного
ввода.
n HANDLE hStdOutput – дескриптор стандартного
вывода.
n 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, &pi);
А теперь более интересный пример – создание
оболочки для программы 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\n", *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 принимают первым параметром
режим запуска, который может принимать следующие значения:
n P_WAIT – родительский процесс дожидается
завершения дочернего, а затем продолжает выполнение;
n P_NOWAIT – родительский и дочерний процессы
выполняются одновременно, но родительский может применить функцию wait для
ожидания завершения дочернего процесса (такой режим доступен только для Win32 и
OS/2);
n P_NOWAITO – идентично P_NOWAIT, но невозможен
вызов wait (работает везде);
n 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