Processes
, Chapter 26 Monitoring Child Processes
Process Management
Un proces este un program în execuție. Procesele sunt unitatea primitivă prin care sistemul de operare alocă resurse utilizatorilor. Orice proces are un spațiu de adrese și unul sau mai multe fire de execuție. Putem avea mai multe procese ce execută același program, dar oricare două procese sunt complet independente.
Informațiile despre procese sunt ținute într-o structură numită Process Control Block
(PCB), câte una pentru fiecare proces existent în sistem. Printre cele mai importante informații conținute de PCB regăsim:
PC
(contor program), SP
(indicator stivă)În momentul lansării în execuție a unui program, în sistemul de operare se va crea un proces pentru alocarea resurselor necesare rulării programului respectiv. Fiecare sistem de operare pune la dispoziție apeluri de sistem pentru lucrul cu procese: creare, terminare, așteptarea terminării. Totodată există apeluri pentru duplicarea descriptorilor de resurse între procese, ori închiderea acestor descriptori.
Procesele pot avea o organizare:
init
(pid = 1).
În general, un proces rulează într-un mediu specificat printr-un set de variabile de mediu. O variabilă de mediu este o pereche NUME = valoare
. Un proces poate să verifice sau să seteze valoarea unei variabile de mediu printr-o serie de apeluri de bibliotecă (Linux, Windows).
Pipe-urile (canalele de comunicație) sunt mecanisme primitive de comunicare între procese. Un pipe poate conține o cantitate limitată de date. Accesul la aceste date este de tip FIFO (datele se scriu la un capăt al pipe-ului pentru a fi citite de la celălalt capăt). Sistemul de operare garantează sincronizarea între operațiile de citire și scriere la cele două capete (Linux, Windows).
Există două tipuri de pipe-uri:
Lansarea în execuție a unui program presupune următorii pași:
fork
- procesul copil are o copie a resurselor procesului părinte. exec*
.În UNIX un proces se creează folosind apelul de sistem fork:
pid_t fork(void);
Efectul este crearea unui nou proces (procesul copil), copie a celui care a apelat fork
(procesul părinte). Procesul copil primește un nou process id (PID
) de la sistemul de operare.
Pentru aflarea PID
-ului procesului curent și al procesului părinte se vor apela funcțiile de mai jos.
Funcția getpid întoarce PID
-ul procesului apelant:
pid_t getpid(void);
Funcția getppid întoarce PID
-ul procesului părinte al procesului apelant:
pid_t getppid(void);
Familia de funcții exec va executa un nou program, înlocuind imaginea procesului curent, cu cea dintr-un fișier (executabil). Acest lucru înseamnă:
IP
(contorul program), SP
(indicatorul stivă) și registrele generale vor fi reinițializate. PID
-ul și descriptorii de fișier care nu au setat flag-ul CLOSE_ON_EXEC
rămân neschimbați (implicit, flag-ul CLOSE_ON_EXEC
nu este setat).int execl(const char *path, const char *arg, ...); int execv(const char *path, char *const argv[]); int execlp(const char *file, const char *arg, ...);
Exemplu de folosire a funcțiilor de mai sus:
execl("/bin/ls", "ls", "-la", NULL); char *const argvec[] = {"ls", "-la", NULL}; execv("/bin/ls", argvec); execlp("ls", "ls", "-la", NULL);
execl
și execv
nu caută programul dat ca parametru în PATH
, astfel că acesta trebuie însoțit de calea completă. Versiunile execlp
și execvp
caută programul și în PATH
.
Toate funcțiile exec*
sunt implementate prin apelul de sistem execve.
Familia de funcții wait suspendă execuția procesului apelant până când procesul (procesele) specificat în argumente fie s-a terminat, fie a fost oprit (SIGSTOP
).
pid_t waitpid(pid_t pid, int *status, int options);
Starea procesului interogat se poate afla examinând status
cu macrodefiniții precum WEXITSTATUS, care întoarce codul de eroare cu care s-a încheiat procesul așteptat, evaluând cei mai nesemnificativi 8 biți.
Există o variantă simplificată, care așteaptă orice proces copil să se termine. Următoarele secvențe de cod sunt echivalente:
wait(&status); | waitpid(-1, &status, 0);
În caz că se dorește doar așteptarea terminării procesului copil, nu și examinarea statusului, se poate folosi:
wait(NULL);
Pentru terminarea procesului curent, Linux pune la dispoziție apelul de sistem exit
.
void exit(int status);
ISO C
, un program care se termină cu return x
din main()
va avea același comportament ca unul care apelează exit(x)
.
Pentru terminarea unui alt proces din sistem, se va trimite un semnal către procesul respectiv prin intermediul apelului de sistem kill. Mai multe detalii despre kill
și semnale în laboratorul de semnale.
#include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int my_system(const char *command) { pid_t pid; int status; const char *argv[] = {command, NULL}; pid = fork(); switch (pid) { case -1: /* error forking */ return EXIT_FAILURE; case 0: /* child process */ execvp(command, (char *const *) argv); /* only if exec failed */ exit(EXIT_FAILURE); default: /* parent process */ break; } /* only parent process gets here */ waitpid(pid, &status, 0); if (WIFEXITED(status)) printf("Child %d terminated normally, with code %d\n", pid, WEXITSTATUS(status)); return status; } int main(void) { my_system("ls"); return 0; }
dup duplică descriptorul de fișier oldfd
și întoarce noul descriptor de fișier, sau -1
în caz de eroare:
int dup(int oldfd);
dup2 duplică descriptorul de fișier oldfd
în descriptorul de fișier newfd
; dacă newfd
există, mai întâi va fi închis. Întoarce noul descriptor de fișier, sau -1
în caz de eroare:
int dup2(int oldfd, int newfd);
Descriptorii de fișier sunt, de fapt, indecși în tabela de fișiere deschise. Tabela este populată cu pointeri către structuri cu informațiile despre fișiere. Duplicarea unui descriptor de fișier înseamnă duplicarea intrării din tabela de fișiere deschise (adică 2 pointeri de la poziții diferite din tabelă vor indica spre aceeași structură din sistem, asociată fișierului). Din acest motiv, toate informațiile asociate unui fișier (lock-uri, cursor, flag-uri) sunt partajate de cei doi file descriptori. Aceasta înseamnă că operațiile ce modifică aceste informații pe unul dintre file descriptori (de ex. lseek
) sunt vizibile și pentru celălalt file descriptor (duplicat).
CLOSE_ON_EXEC
nu este partajat (acest flag nu este ținut în structura menționată mai sus).
Descriptorii de fișier ai procesului părinte se moștenesc în procesul copil în urma apelului fork
. După un apel exec
, descriptorii de fișier sunt păstrați, excepție făcând cei care au flag-ul CLOSE_ON_EXEC
setat.
În cadrul unui program se pot accesa variabilele de mediu, prin evidențierea celui de-al treilea parametru (opțional) al funcției main
, ca în exemplul următor:
int main(int argc, char **argv, char **environ)
Acesta desemnează un vector de pointeri la șiruri de caractere, ce conțin variabilele de mediu și valorile lor. Șirurile de caractere sunt de forma VARIABILA=VALOARE
. Vectorul e terminat cu NULL
.
getenv întoarce valoarea variabilei de mediu denumite name
, sau NULL
dacă nu există o variabilă de mediu denumită astfel:
char* getenv(const char *name);
setenv adaugă în mediu variabila cu numele name
(dacă nu există deja) și îi setează valoarea la value
. Dacă variabila există și replace
e 0
, acțiunea de setare a valorii variabilei e ignorată; dacă replace
e diferit de 0
, valoarea variabilei devine value
:
int setenv(const char *name, const char *value, int replace);
unsetenv șterge din mediu variabila denumită name
:
int unsetenv(const char *name);
Pipe-ul este un mecanism de comunicare unidirecțională între două procese. În majoritatea implementărilor de UNIX, un pipe apare ca o zonă de memorie de o anumită dimensiune în spațiul nucleului. Procesele care comunică printr-un pipe anonim trebuie să aibă un grad de rudenie; de obicei, un proces care creează un pipe va apela după aceea fork
, iar pipe-ul se va folosi pentru comunicarea între părinte și fiu. În orice caz, procesele care comunică prin pipe-uri anonime nu pot fi create de utilizatori diferiți ai sistemului.
Apelul de sistem pentru creare este pipe:
int pipe(int filedes[2]);
Vectorul filedes
conține după execuția funcției 2 descriptori de fișier:
filedes[0]
, deschis pentru citire; filedes[1]
, deschis pentru scriere.
Mnemotehnică: STDIN_FILENO
este 0 (citire), STDOUT_FILENO
este 1 (scriere).
Observații:
PIPE_BUF
1) octeți. read
/write
. Majoritatea aplicațiilor care folosesc pipe-uri închid în fiecare dintre procese capătul de pipe neutilizat în comunicarea unidirecțională. Dacă unul dintre descriptori este închis se aplică regulile:
0
, ceea ce indică sfârșitul fișierului. Descriptorul de scriere poate fi duplicat astfel încât mai multe procese să poată scrie în pipe. De regulă, în cazul pipe-urilor anonime există doar două procese, unul care scrie și altul care citește, pe când în cazul fișierelor pipe cu nume (FIFO) pot exista mai multe procese care scriu date.SIGPIPE
. Dacă semnalul este captat și se revine din rutina de tratare, funcția de sistem write
returnează eroare și variabila errno
are valoarea EPIPE
.
EOF
prin pipe (citirea din pipe nu se termină) decât dacă sunt închise TOATE capetele de scriere din TOATE procesele care au deschis descriptorul de scriere în pipe (în cazul unui fork
, nu uitați să închideți capetele pipe-ului în procesul părinte).
Elimină necesitatea ca procesele care comunică să fie înrudite. Astfel, fiecare proces își poate deschide pentru citire sau scriere fișierul pipe cu nume (FIFO), un tip de fișier special, care păstrează în spate caracteristicile unui pipe. Comunicația se face într-un sens sau în ambele sensuri. Fișierele de tip FIFO pot fi identificate prin litera p
în primul câmp al drepturilor de acces (ls -l
).
Apelul de bibliotecă pentru crearea pipe-urilor de tip FIFO este mkfifo:
int mkfifo(const char *pathname, mode_t mode);
După ce pipe-ul FIFO a fost creat, acestuia i se pot aplica toate funcțiile pentru operații obișnuite pentru lucrul cu fișiere: open
, close
, read
, write
.
Modul de comportare al unui pipe FIFO după deschidere este afectat de flagul O_NONBLOCK
:
Atunci când se închide ultimul descriptor de fișier al capătului de scriere pentru un FIFO, se generează un „sfârșit de fișier” – EOF
– pentru procesul care citește din FIFO.
Informații suplimentare legate de depanarea unui proces se gǎsesc aici
În Windows, atât crearea unui nou proces, cât și înlocuirea imaginii lui cu cea dintr-un program executabil se realizează prin apelul funcției CreateProcess.
BOOL CreateProcess( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ); |
BOOL bRes = CreateProcess( NULL, // No module name "notepad.exe" // Command line NULL, // Process handle not inheritable NULL, // Thread handle not inheritable FALSE, // Set handle inheritance to false 0, // No creation flags NULL, // Use parent's environment block NULL, // Use parent's starting directory &si, // Pointer to STARTUPINFO structure &pi // Pointer to PROCESS_INFORMATION ); // structure |
API-ul Windows mai pune la dispoziție câteva funcții înrudite precum CreateProcessAsUser, CreateProcessWithLogonW ori CreateProcessWithTokenW, care permit crearea unui proces într-un context de securitate diferit de cel al utilizatorului curent.
Pentru a se obține un handle al unui proces, cunoscându-se PID
-ul procesului respectiv, se va apela funcția OpenProcess:
HANDLE OpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId );
iar pentru a obține un handle al procesului curent se va apela GetCurrentProcess:
HANDLE GetCurrentProcess(void);
Pentru a obține PID
-ul procesului curent se va apela GetCurrentProcessId:
DWORD GetCurrentProcessId(void);
Spre deosebire de Linux, în Windows nu se impune o ierarhie a proceselor în sistem. Teoretic există o ierarhie implicită din modul cum sunt create procesele. Un proces deține handle-uri ale proceselor create de el, însă handle-urile pot fi duplicate între procese ceea ce duce la situația în care un proces deține handle-uri ale unor procese care nu sunt create de el, deci ierarhia implicită dispare.
Pentru a suspenda execuția procesului curent până când unul sau mai multe alte procese se termină, se va folosi una din funcțiile de așteptare WaitForSingleObject ori WaitForMultipleObjects.
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
Exemplul următor așteaptă nedefinit terminarea procesului reprezentat de hProcess
.
DWORD dwRes = WaitForSingleObject(hProcess, INFINITE); if (dwRes == WAIT_FAILED) // handle error
Funcțiile de așteptare sunt folosite în cadrul mai general al mecanismelor de sincronizare între procese și vor fi prezentate în detaliu în laboratorul de sincronizare între procese.
Pentru a determina codul de eroare cu care s-a terminat un anumit proces, se va apela funcția GetExitCodeProcess:
BOOL GetExitCodeProcess(HANDLE hProcess, LPDWORD lpExitCode);
Dacă procesul hProcess
nu s-a terminat încă, funcția va întoarce în lpExitCode
codul de terminare STILL_ACTIVE
. Dacă procesul s-a terminat, se va întoarce codul său de terminare care poate fi:
main
sau WinMain
a procesuluilpExitCode
va fi 0 sau 1 în caz de eroare.Pentru terminarea procesului curent, Windows API pune la dispoziție funcția ExitProcess.
void ExitProcess(UINT uExitCode);
Consecinţele funcţiei ExitProcess
sunt:
DLL
-urile de care era atașat procesul sunt notificate și se apelează metode de distrugere a resurselor alocate de acestea în spațiul de adresă al procesului.
ExitProcess
nu se ocupă de eliberarea resurselor bibliotecii standard C. Pentru a asigura o
finalizare corectă a programului trebuie apelat exit
.
Pentru terminarea unui alt proces din sistem se va apela funcția TerminateProcess.
BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode);
Funcţia TerminateProcess
se ocupă de:
hProcess
și a tuturor firelor sale de execuție. Se vor revoca operațiile de intrare/ieșire neterminate după care funcția TerminateProcess va întoarce imediat.
DLL
-urile de care este atașat procesul hProcess
asupra detașării acestuia, lăsând astfel alocate eventualele date rezervate de DLL
în spațiul de adrese al procesului.
Terminarea unui proces nu implică terminarea proceselor create de acesta.
#include <windows.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "utils.h" void CloseProcess(LPPROCESS_INFORMATION lppi) { CloseHandle(lppi->hThread); CloseHandle(lppi->hProcess); } int main(void) { STARTUPINFO si; PROCESS_INFORMATION pi; DWORD dwRes; BOOL bRes; CHAR cmdLine[] = "mspaint"; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); /* Start child process */ bRes = CreateProcess( NULL, /* No module name (use command line) */ cmdLine, /* Command line */ NULL, /* Process handle not inheritable */ NULL, /* Thread handle not inheritable */ FALSE, /* Set handle inheritance to FALSE */ 0, /* No creation flags */ NULL, /* Use parent's environment block */ NULL, /* Use parent's starting directory */ &si, /* Pointer to STARTUPINFO structure */ &pi /* Pointer to PROCESS_INFORMATION structure */ ); DIE(bRes == FALSE, "CreateProcess"); /* Wait for the child to finish */ dwRes = WaitForSingleObject(pi.hProcess, INFINITE); DIE(dwRes == WAIT_FAILED, "WaitForSingleObject"); bRes = GetExitCodeProcess(pi.hProcess, &dwRes); DIE(bRes == FALSE, "GetExitCode"); return 0; }
După un apel CreateProcess, handle-urile din procesul părinte pot fi moștenite în procesul copil.
bInheritHandle
, al structurii SECURITY_ATTRIBUTES
, transmise lui CreateFile, trebuie să fie TRUE
Handle-urile moștenite sunt valide doar în contextul procesului copil.
Cei 3 descriptori speciali de fișier pot fi obținuți apelând funcția GetStdHandle:
HANDLE GetStdHandle(DWORD nStdHandle);
cu unul din parametrii:
STD_INPUT_HANDLE
STD_OUTPUT_HANDLE
STD_ERROR_HANDLE
Pentru redirectarea handle-urilor standard în procesul copil puteți folosi membrii hStdInput
, hStdOutput
, hStdError
ai structurii STARTUPINFO, transmise lui CreateProcess. În acest caz, membrul dwFlags
al aceleiași structuri trebuie setat la STARTF_USESTDHANDLES
. Dacă se dorește ca anumite handle-uri să rămână implicite, li se poate atribui handle-ul întors de GetStdHandle.
STARTUPINFO si; ... /* initialize process startup info structure */ ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); /* setup flags to allow handle inheritence (redirection) */ si.dwFlags |= STARTF_USESTDHANDLES;
hStdInput
, hStdOutput
, hStdError
din structura STARTUPINFO
trebuie inițializate.
Alte proprietăți ale procesului părinte care pot fi moștenite sunt variabilele de mediu și directorul curent. Nu vor fi moștenite handle-uri ale unor zone de memorie alocate de procesul părinte și nici pseudo-descriptori precum cei întorși de funcția GetCurrentProcess.
Handle-ul din procesul părinte și cel moștenit în procesul copil vor referi același obiect, exact ca în cazul duplicării. De asemenea, handle-ul moștenit în procesul copil are aceeași valoare și aceleași drepturi de acces ca și handle-ul din procesul părinte. Pentru a folosi handle-ul moștenit, procesul copil va trebui să-i cunoască valoarea și ce obiect referă. Aceste informații trebuie să fie pasate de părinte printr-un mecanism extern (IPC etc).
Pentru a afla valoarea unei variabile de mediu se va apela funcția GetEnvironmentVariable:
DWORD GetEnvironmentVariable( LPCTSTR lpName, LPTSTR lpBuffer, DWORD nSize );
care va umple lpBuffer
, de dimensiune nSize
, cu valoarea variabilei lpName
.
Pentru a seta o variabilă de mediu se va apela SetEnvironmentVariable:
BOOL SetEnvironmentVariable( LPCTSTR lpName, LPCTSTR lpValue );
care va seta variabila lpName
la valoarea specificată de lpValue
. Funcția se va folosi și pentru ștergerea unei variabile de mediu prin transmiterea unui parametru lpValue = NULL
. SetEnvironmentVariable are efect doar asupra variabilelor de mediu ale utilizatorului și nu poate modifica variabile de mediu globale.
În Windows există un set de variabile de mediu globale, valabile pentru toți utilizatorii. În plus, fiecare utilizator în parte are asociat un set propriu de variabile de mediu. Împreună, cele două seturi formează Environment Block
-ul utilizatorului respectiv. Acest Environment Block
este similar cu variabila environ
, din Linux.
Ca și pe Linux, pipe-urile anonime de pe Windows sunt unidirecționale. Fiecare pipe are două capete reprezentate de câte un handle: un handle de citire și un handle de scriere. Funcția de creare a unui pipe este CreatePipe:
BOOL CreatePipe( PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize ); |
CreatePipe( &hReadPipe, &hWritePipe, &sa, //pentru moștenire sa.bInheritHandle=TRUE 0 //dimensiunea default pentru pipe ); |
bInheritHandle
din structura LPSECURITY_ATTRIBUTES să fie setat pe TRUE.
CreatePipe
creează atât pipe-ul, cât și handler-urile folosite pentru scriere/citire din/în pipe cu ajutorul funcțiilor ReadFile și WriteFile.
ReadFile se termină în unul din cazurile:
WriteFile se termină atunci când toți octeții au fost scriși. Dacă bufferul pipe-ului este plin înainte ca toți octeții să fie scriși, WriteFile rămâne blocat până când alt proces sau thread folosește ReadFile pentru a face loc în buffer.
Pipe-urile anonime sunt implementate folosind un pipe cu nume unic. De aceea se poate pasa un handle al unui pipe anonim unei funcții care cere un handle al unui pipe cu nume.
În Windows, un pipe cu nume este un pipe unidirecțional (inbound ori outbound) sau bidirecțional ce realizează comunicația între un server pipe și unul sau mai mulți clienți pipe. Se numește server pipe procesul care creează un pipe cu nume și client pipe procesul care se conectează la pipe. Pentru a face posibilă comunicarea între server și mai mulți clienți prin același pipe, se folosesc instanțe ale pipe-ului. O instanță a unui pipe folosește același nume, dar are propriile handle-uri și buffere.
Pipe-urile cu nume au următoarele caracteristici care le diferențiază de cele anonime:
Serverul creează un pipe cu funcția CreateNamedPipe.
HANDLE CreateNamedPipe( LPCTSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut, LPSECURITY_ATTRIBUTES lpSecurityAttributes ); |
HANDLE hNamedPipe = CreateNamedPipe( "\\\\.\\pipe\\mypipe", // name PIPE_ACCESS_DUPLEX, // read/write access PIPE_TYPE_BYTE | PIPE_WAIT,// byte stream PIPE_UNLIMITED_INSTANCES, // max. instances BUFSIZE, // output buffer size BUFSIZE, // input buffer size 0, // default time out NULL // default security ); // attribute |
Funcția returnează un handle către capătul serverului la pipe. Acest handle poate fi transmis funcției ConnectNamedPipe pentru a aștepta conectarea unui proces client la o instanță a unui pipe.
BOOL ConnectNamedPipe(HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped);
Un client se conectează transmițând numele pipe-ului la una din funcțiile CreateFile sau CallNamedPipe - ultima funcţie este mai utilă pentru transmiterea de mesaje.
Un exemplu funcțional folosind pipe-uri cu nume se află aici
Mai multe detalii despre moștenirea pipe-urilor se pot găsi aici.
În rezolvarea laboratorului folosiți arhiva de sarcini lab03-tasks.zip
utils
din arhivă există un fișier utils.h
cu funcții utile.
1-system
.my_system.c
execută o comandă transmisă ca parametru, folosind funcția de bibliotecă system. make
)../my_system pwd
ls -la
)strace -e execve,clone -ff -o output ./my_system ls
-ff
? 2-orphan
.orphan.c
. ./orphan & watch ps -al
orphan
(coloana CMD
), pid-ul procesului părinte (coloana PPID
) devine 1?3-tiny
.exit
sau CTRL+D
.simple_cmd
primește ca argument un vector de șiruri ce conține comanda și parametrii acesteia.TODO-1
../tiny > pwd > ls -al > exit
set_var
și expand
; acestea sunt apelate deja atunci când se face parsarea liniei de comandă.TODO-2
../tiny > echo $HOME > name=Makefile > cat $name
do_redirect
.filename
nu există, va fi creat. Dacă există, trebuie trunchiat.TODO-3
../tiny > ls -al > out
.sln
) și compilați primul subproiect: 1-bomb
.1-bomb.c
. Ce credeți că face?1-bomb.exe
, porniți Task Manager
(CTRL + ALT + DEL; În mașina virtuală se va folosi CTRL + ALT + Insert).1-bomb.exe
. Ce s-a întâmplat? tiny.exe
se va realiza din Cygwin.$ cd c: $ cd Users/Student/Desktop/ #sau pentru mașina Windows2k3: $ cd c: $ cd Documents and Settings/Administrator/Desktop/
RunSimpleCommand
../tiny > ls -al > exit
RedirectHandle
../tiny > ls -al > out > exit
PipeCommands
.SECURITY_ATTRIBUTES sa
, respectiv structurile PROCESS_INFO pi1, pi2
;RedirectHandle
../tiny > cat Makefile | grep tiny > exit
server
și client
, care interacționează printr-un pipe cu nume.myfifo
. Dacă nu există, este creat de server.CreateNamedPipe
.ReadFile
întoarce FALSE, iar mesajul de eroare (ce întoarce GetLastError()) este ERROR_BROKEN_PIPE
, înseamnă că au fost închise toate capetele de scriere.lin/5-magic
și deschideți sursa magic.c
if
pentru a obține la rulare mesajul “Hello World”.main
.