ps ilman mitään optioita näyttää ainoastaan kyseisestä komentotulkista käynnistetyt, suorituksessa olevat prosessit
[luuma@telinux1]$ ps PID TTY TIME CMD 23716 pts/105 00:00:00 bash 26607 pts/105 00:00:00 ps [luuma@telinux1]$Edellä näytettiin prosessi, joka suorittaa komentotulkkia eli bash:ia sekä prosessi, joka suoritti ps-komennon. Jokaisella prosessilla on sen yksikäsitteisesti erittelevä numero, prosessi id, eli PID. Edellä bashia suorittavan prosessin PID oli 23716 ja ps-komentoa suorittavan prosessin PID 26607.
Komentotulkki on siis myös prosessi. Aina kun komentotulkista käynnistetään joku ohjelma, esim. [luuma@telinux1]$ ps, luo komentotulkki uuden prosessin, joka alkaa suorittamaan käynnistettävää ohjelmaa.
ps-komennolla on todella suuri määrä optioita. Esim. seuravat muodot ovat hyödyllisiä:
ps ax näytä järjestelmän kaikki prosessit ps axu kaikki prosessit, "u"-muodossa eli mm. muistin ja cpu:n käyttö ps axl kaikki prosessit, "l"-muodossa eli mm. prioriteetit ps u Uluuma näytä käyttäjän luuma kaikki prosessit "u"-muodossa ps l Uluuma näytä käyttäjän luuma kaikki prosessit "l"-muodossaHuom: kaksi viimeistä eivät välttämättä toimi opiskelijoiden käyttäjätunnuksella (ainakaan cs.stadia.fi:ssa). Omat prosessit saa näytettyä myös listaamalla kaikki prosessit ja rajoittamalla näytettävät grep-komennolla.
ps axu | grep luuma
Komento pstree näyttää prosessilistan siten, että prosessien väliset suhteet tulevat näkyviin:
[luuma@telinux1]$ pstree luuma bash---man---less bash---pstree bash---emacsKäyttäjä luuma on pstreen perusteella kirjautunut koneeseen kolmelle eri terminaalille. Jokainen terminaali suorittaa komentotulkkia eli bashia. Yhdessä terminaalissa on avattuna emacs, yhdessä suoritetaan pstree-komentoa ja yhdessä man:nia joka taas käyttää less-ohjelmaa tulostuksen muotoiluun. Komento pstree ilman argumenttia kertoo kaikista järjestelmän prosesseista, ote seuraavassa:
init-+-amavisd---2*[amavisd] |-3*[bash] |-klogd |-kscand |-ksoftirqd/0 |-ksoftirqd/1 |-kswapd |-sshd-+-sshd---bash---ssh | |-12*[sshd---bash---screen] | |-7*[sshd---bash] | |-3*[sshd---bash---emacs] | |-sshd---bash---screen---screen---irssi | |-sshd---bash---pstree | `-sshd---bash---nanoPuun juuressa on kaikkien prosessien äiti init, jonka lapsina on erinäinen määrä prosesseja, mm. sshd, joka huolehtii kaikista ssh:lla sisään kirjautumisista. Systeemiin on kirjautunt 12 käyttäjää, jotka suorittavat komentotulkissa screeniä, kolme emacsin käyttäjää jne.
NAME fork - create a child process SYNOPSIS #include < sys/types.h> #include < unistd.h> pid_t fork(void); DESCRIPTION fork creates a child process that differs from the parent process only in its PID and PPID. RETURN VALUE On success, the PID of the child process is returned in the parent's thread of execution, and a 0 is returned in the child's thread of exe- cution. On failure, a -1 will be returned in the parent's context, no child process will be created, and errno will be set appropriately.Seuraavassa ote ohjelmasta 5-1.c:
pid_t id; id = fork(); // luodaan uusi prosessi if ( id==0 ) { // lapsen koodi printf("olen lapsi, pid=%d, ppid=%d\n", getpid(), getppid()); sleep(2); // viivytellään 2 sekunnin ajan printf("lapsi lopettaa nyt\n"); exit(0); } // vanhempi jatkaa täältä printf("olen vanhempi, pid=%d, lapsen pid=%d\n", getpid(), id); wait( NULL ); printf("lapsi lopetti\n"); return 0; }Samalla kun tehdään fork, syntyy myös lapsiprosessi, joka on täydellinen klooni vanhemmasta. Lapsi siis rupeaa suorittamaan samaa koodia kun vanhempi ja suorituskohta on heti forkin jälkeinen komento. fork-komento palauttaa vanhemmalle syntyneen lapsiprosessin PID:in. Syntyneessä lapsessa sensijaan forkin palauttama arvo on nolla.
forkin jälkeen siis molemmat prosessit, vanhempi ja lapsi suorittavat samaa koodia, mutta vanhemmalla lapsen PID, esimerkissä muuttuja id saa arvokseen syntyneen lapsen PID:in mutta lapsella muuttujan arvo on 0. Esimerkissä forkin jälkeen seuraavaksi suoritettava komento on if ( id==0 ). Lapsella tämä ehto on tosi, ja lapsi rupeaa suorittamaan if:in sisällä olevaa koodia ja vanhempi hyppää if:in yli. Näin vanhempi ja lapsi menevät molemmat suorittamaan omaa, toisistaan erillistä koodia.
Lapsi printtaa ruudulle oman ja vanhempansa PID:it käyttäen komentoja getpid ja getppid, joista jälkimmäinen siis palauttaa vanhemman PID:in. Tämän jälkeen lapsi odottaa 2 sekuntia käyttäen sleep-komentoa ja lopettaa sitten.
Vanhempi tulostaa oman PID:in sekä lapsen PID:in joka siis on forkin palauttamana tallessa muuttujassa id. Tämän jälkeen vanhempi rupeaa odottamaan lapsen lopettamista komennolla wait. Parametrina on NULL koska vanhempi ei ole kiinnostunut lapsen exitin yhteydessä palauttamasta arvosta.
Komento wait odottaa seuraavana kuolevaa lasta. Komennolle voidaan antaa parametri, jollon saadaan selville lapsen lopetusstatus:
int status; wait(&status);wait-komento blokkaa suorittavan prosessin niin kauaksi aikaa kunnes joku sen lapsista kuolee. Jos vanhemmalla ei ole yhtään lasta, palaa wait heti.
Seuraavassa ote ohjelmasta 5-2.c, joka toimii muuten samoin paitsi vanhempi käyttää waitpid-komentoa lapsen odottamiseen. waitpid toimii muuten kuten wait, mutta waitpid:issä voidaan parametrin avulla kertoa mitä lasta odotetaan, lisäksi on mahdollista asettaa waitpid toimimaan aynkroonisesti eli siten, että se ainoastaan tarkastaa onko lapsi jo kuollut ja palaa välittömästi.
Seuraavassa vanhemman koodi:
int status; waitpid ( id, &status, 0 ); printf("lapsi lopetti "); if ( WIFEXITED(status) ){ prinf(" ja palautti arvon %d\n", WEXITSTATUS(status)); }Nyt waitpidissa kerrotaan että odotetaan lasta jonka PID on muuttujassa id. Jos ensimmäisen parametrin arvo on -1, odotetaan mitä tahansa lasta. Toinen parametri on osoitin muuttujaan, johon luetaan lapsen lopetusstatus. Kolmannen parametrin arvona 0, eli waitpid odottaa niin kauan kunnes lapsi kuolee. Vaihtoehtona olisi WNOHANG, jolloin ainoastaan tarkastetaan onko lapsi kuollut, ks. man-sivu.
waitpidin jälkeen tarkastetaan onko lapsi todellakin kuollut: if ( WIFEXITED(status). waitpid (samoin kuin wait) nimittäin palaa myös niissä tapauksissa, joissa lapsiprosessi menee taustasuoritustilaan eli ns. suspended-tilaan. Normaalisti tälläistä ei tapahdu. Suspend-tilaan voi joutua esim. jos prosessia suorittavassa konsolissa painetaan ctrl+z. Jos lapsi on lopettanut, tulostetaan lopetusstatus. Tämä tapahtuu valmiiksi määritellyn WEXITSTATUS(status) makron avulla joka erottaa lapsen exitillä palauttaman lopetusstatuksen waitpidillä saadun parametrin arvon sisältä.
Jos vanhemmalla on lapsen syntyhetkellä avoinna tiedostoja, ovat tiedostot avoinna myös lapsella. Näin vanhemmalta lapselle "periytynyt" avoin tiedosto sulkeutuu vasta kun sekä lapsi että vanhempi ovat sen sulkeneet. Lapsen ja vanhemman näkemä tiedosto on siis sama.
Kuollutta prosessia, jolle ei ole tehty wait:ia sanotaan zombieksi. Kun vanhempi sitten tekee wait:in tai waitpid:in, zombie häviää.
On myös mahdollista, että vanhempi kuolee, ennen lasta. Tällöin prosessista tulee orpo ja init-prosessi perii lapsen, eli lapsen vanhemmaksi tulee init jonka PID on 1. init suorittaa aika-ajoin wait-komentoa, jotta lopetaneet orvot eivät jää zombieiksi.
Esim. komentotulkki (joka siis itsekin on normaali prosessi) toimii siten, että kun käyttäjä käynnistää uuden ohjelman, luodaan ensin prosessi komennon suorittamista varten. Uusi prosessi sitten lataa suoritettavan komennon koodin ja alkaa suorittamaan uutta koodia.
Uuden koodin lataaminen prosessille tapahtuu jollakin exec-komentoperheen komennoista:
NAME execl, execlp, execle, execv, execvp - execute a file SYNOPSIS #include < unistd.h> extern char **environ; int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg , ..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); DESCRIPTION The exec family of functions replaces the current process image with a new process image. The functions described in this manual page are front-ends for the function execve(2). (See the manual page for execve for detailed information about the replacement of the current process.) ...Vaihtoehtoja exec:eissä on monia.
Seuraavassa ote ohjelmasta 5-3.c, joka käyttää execl-komentoa.
id = fork(); // luodaan uusi prosessi if ( id==0 ) { // lapsen koodi // suoritetaan ls-komento optiolla -l execl("/bin/ls", "ls", "-l", NULL); } // vanhempi jatkaa täältä wait( NULL ); printf("lapsi lopetti\n"); return 0; }execl-tomii siten, että ensimmäisenä parametrina on suoritettava ohjelmakoodi. Seuraavaksi tulevat ohjelman käynnistävät komentoriviparametrit alkaen ohjelman nimestä. Viimeiseksi tulee NULL kertomaan että muita parmetrejä ei ole.
Komento execl("/bin/ls", "ls", "-l", NULL); toimii samoin kun komentoriviltä annettu komento [luuma@telinux1]$ ls -l.
Jos halutaan antaa enemmän käynnistusoptioita, esim. ls -l -a -u onnistuu se helposti: execl("/bin/ls", "ls", "-l", "-a", "-u", NULL);
Jos execl onnistuu, ei komennosta palata ikinä, sillä prosessin suorittama koodi korvautuu täydellisesti ladattavalla koodilla. Jos execl epäonnistuu, palauttaa se arvon -1. Tämäkin tilanne on useimmiten syytä tarkastaa:
int s = execl("/bin/xyx", "xyz", "-l", NULL); if ( s==-1 ){ perror("execv epäonnistui"); exit(0); }
execlp toimii täysin samoin, mutta ensimmäisenä parametrina ei tarvitse antaa kokonaista polkunimeä suoritettavaan ohjelmaan jos suoritettava ohjelma löytyy PATH-muuttujassa määritellyllä suorituspolulla. Koska ls-ohjelma on suorituspolulla, olisi edellä voitu käyttää execlp:tä seuraavasti: execlp("ls", "ls", "-l", NULL); Tässä komennon nimi siis toistuu kahteen kertaan, ensin polulta etsittävänä ohjelmakoodin nimenä ja vielä uudelleen komentoriviparametrina.
execv toiminta on esitelty seuraavassa (ks. 5-4.c):
if ( id==0 ) { // lapsen koodi char *arg[3]; arg[0] = "ls"; arg[1] = "-l"; arg[2] = NULL; // suoritetaan ls-komento optiolla -l execv("/bin/ls",arg); }Parametrina on nyt suoritettava kooditiedosto ja tämän jälkeen ohjelman komentoriviparametrit merkkijono-osoitintaulukkona, eli muodossa char *arg[], missä arg[0], arg[1], ... muodostavat ohjelman käynnistävän komentorivin siten, että viimeisenä on NULL-pointteri.
execv:stä on olemassa myös versio execvp, joka etsii käynnistettävää ohjelmaa PATH-muuttujassa määritellyltä suorituspolulta.
execv ja execvp ovat käyttökelpoisia erityisesti silloin kun käynnistettävän ohjelman komentoriviparametrien määrä ei ole ennalta tiedossa vaan joudutaan rakentamaan ajon aikana.