Když proces hlídá proces
... Neznámá předtucha mě nutí otevřít Task Manager. Spěšně ho prohlížím. V záplavě nejrůznějších procesů upoutá mou pozornost jeden s názvem kernel64.exe. Co to je za nesmysl? Proces vypnu a jdu si nalít trochu čaje. Když se znovu podívám do Task Managera, proces kernel64.exe je zpět. WTF?...
Cílem následujícího článku je ukázat, jak jednoduše lze hlídat běh procesu a v případě potřeby ho znovu nastartovat. Před pár dny jsem si při sledování filmu Robin Hood vzpomněl, jak jsem před mnoha lety četl článek o vtipném pokusu jedné společnosti, která konkurenci (XEROX nebo IBM?) do tiskárny nasadila dvě binárky, které se ve formě procesů neustále hlídaly a pokud byl jeden z procesů zabit, druhý ho okamžitě znovu spustil. Tento vtip nadělal administrátorům hodně vrásek na čele. Paradoxně dal také vzniknout jedné z technik, s níž jsme se mohli, můžeme a pravděpodobně i budeme moci setkávat u malware. Z dnešního pohledu už se nejedná o nijak závažný problém. Ale před třemi čtyřmi desítkami let se odstranění tohoto žertíku opravdu rovnalo neřešitelnému úkolu. Možná se teď leckdo z vás ptá, proč jsem si na tohle vzpomněl zrovna u filmu Robin Hood. Odpověď je jednoduchá: Ty dva procesy se totiž jmenovaly Robin a Big John :)
Oč se tedy jedná? Princip je velice jednoduchý. Máme dva procesy, které se vzájemně hlídají. Pokud jeden z nich zjistí, že ten druhý "umřel", okamžitě ho znovu nastartuje. Způsobů, jak tento úkol realizovat je mnoho. Pokusím se obecně popsat tři nejzákladnější a navrch přidám popis čtvrtého, trošku komplikovanějšího řešení.
První a úplně nejjednodušší řešení napadlo snad každého. Každý program bude procházet cyklicky procesy a hledat v nich název svého souputníka. Pokud ho najde, pokračuje dál. Pokud ho nenajde, spustí ho znovu a pokračuje dál. Mezi obecně nejpoužívanější Windows API pro tento účel je sada funkcí z knihovny Tool Help. Konkrétně CreateToolhelp32Snapshot, Process32First a Process32Next. Windows API funkce CreateToolhelp32Snapshot s prvním argumentem TH32CS_SNAPPROCESS, jenž říká, že budeme pracovat s procesy, vytvoří handle. Tento handle se dále předá do API funkce Process32First jako první argument. Jako druhý je pak pointer na strukturu PROCESSENTRY32. Tuto strukturu API funkce Process32Fist naplní informacemi o prvním běžícím procesu. Pro nás je nejdůležitější pole szExeFile udávající název souboru, z kterého byl daný proces vytvořen. Pro vylistování dalších procesů použijeme API funkci Process32Next. Tato API funkce přebírá stejné argumenty jako předchozí. Jednoduchý kód pro ověření, zda daný proces v paměti existuje, může vypadat následovně:
#include <tlhelp32.h>
#include <stdio.h>
#define MY_COMRADE "processxx.exe"
int main(){
PROCESSENTRY32 pe;
STARTUPINFO si;
PROCESS_INFORMATION pi;
HANDLE hSnap = NULL;
BOOL bIsDown = true;
ZeroMemory(&pe, sizeof(pe));
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);
pe.dwSize = sizeof(PROCESSENTRY32);
hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
// is handle valid?
if(hSnap != INVALID_HANDLE_VALUE){
// get data of first process
if(Process32First(hSnap, &pe) != false){
do{
// is this process my comrade?
if(strcmp(pe.szExeFile, MY_COMRADE) == 0){
// YES! This is my comrade! ;)
bIsDown = false;
printf("%s\n", pe.szExeFile);
}
}while(Process32Next(hSnap, &pe));
// was found my comrade?
if(bIsDown == true){
// NO! my comrade is dead! :(
CreateProcessA(MY_COMRADE, NULL,
NULL, NULL,
FALSE, NORMAL_PRIORITY_CLASS,
NULL, NULL, &si, &pi);
Sleep(100);
}else{
bIsDown = true;
}
}
}
CloseHandle(hSnap);
return 0;
}
Jak je vidět, kód je velice jednoduchý a prakticky nepotřebuje dalšího vysvětlování. Druhou možností je využití takzvaného mutexu. Mutex je synchronizační objekt, který může, ale nemusí mít jméno/pojmenování. Využívá signalizaci, aby dal procesům nebo vláknům, která se mají synchronizovat, echo, jak celý proces synchronizace probíhá (ještě se stále synchronizuje nebo už můžeme pokračovat?). Z těch několika málo API funkcí sloužících pro práci s mutexy si vystačíme s jednou, maximálně se dvěma (v závislosti na chutích jedinců ;)). První je CreateMutex a případná druhá OpenMutex. API funkce CreateMutex přebírá tři argumenty. První nastavuje bezpečnostní atributy pro práci s mutexem, druhým si může tvůrce mutexu zajistit počáteční vlastnictví mutexu a konečně třetí argument tvoří název mutexu. Důležitou vlastnostní mutexu je pro nás fakt, že jméno mutexu je v rámci systému unikátní, a proto existuje vždy pouze v jediném exempláři. Pokusíme-li se vytvořit mutex se jménem, které již existuje, systém nastaví funkci GetLastError na hodnotu ERROR_ALREADY_EXISTS. Díky tomu jsme schopni zjistit, zda náš spřízněný proces stále běží. Vyjádřeno programovacím jazykem:
#include <stdio.h>
#define BROTHERMUTEXNAME "Kain_XYZ" /*Abel_XYZ*/
#define MYMUTEXNAME "Abel_XYZ" /*Kain_XYZ*/
#define PROCESSNAME "Kain.exe" /*Abel.exe*/
int main(){
HANDLE hMutex = NULL;
STARTUPINFO si;
PROCESS_INFORMATION pi;
CreateMutexA(NULL, FALSE, MYMUTEXNAME);
while(1){
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);
hMutex = CreateMutexA(NULL, FALSE, BROTHERMUTEXNAME);
if(GetLastError() != ERROR_ALREADY_EXISTS){
if(CreateProcessA(PROCESSNAME, NULL,
NULL, NULL,
FALSE, NORMAL_PRIORITY_CLASS,
NULL, NULL, &si, &pi)){
Sleep(100);
}
}
if(hMutex != NULL){
CloseHandle(hMutex);
hMutex = NULL;
}
}
return 0;
}
Opět se jedná o velice jednoduchý kód. Stejným způsobem můžeme využít i semafor. Semafor je 'inteligentnější' verze mutexu. 'Inteligentnější' je o schopnost počítat maximální počet procesů nebo vláken přistupujících k mutexu. Ostatní se holt musí hezky seřadit do řady a tiše čekat, až na ně přijde řada. Pro práci se semafory se nejčastěji setkáte s API funkcemi CreateSemaphore pro vytvoření semaforu, OpenSemaphore pro otevření existujícího semaforu a ReleaseSemaphore pro navýšení počítadla semaforu.
Tři nejjednodušší řešení máme za sebou. Na začátku jsem slíbil ještě jedno trochu složitější řešení. Ve skutečnosti se opět jedná o velice jednoduché řešení ;) Myšlenka je jednoduchá: Aplikace si vytvoří klienta a server. Server slouží přípojný bod pro druhý proces, aby byl schopný zjistit existenci prvního procesu. Klientem se pokusí připojit na server spolubojovníka. Pokud se mu to povede, čeká dokud nedostane zprávu o chybě. Pak se znovu pokusí připojit. Pokud se mu to nepodaří, spustí kamarádský proces a pokračuje s připojováním. Vytvoření tohoto kódu již nechám jako cvičení na každém ze čtenářů :)
- Pro psaní komentářů se přihlašte