Vad händer när man kör ett program? Program och processer När man kompilerar ett program och sedan länkar ihop det så stoppar länkaren in lite extra kod i programmet. Det är denna kod som i sin tur anropar main-funktionen ungefär som ni anropar underfunktioner. Den extra kod som kommer in har bland annat två syften: Se till att ditt C program får rätt omgivning att köras i och att städa upp efter ditt program när det är klart. 119 120 Funktionsanrop Vad händer vid ett funktionsanrop (Principiell beskrivning) Man lägger upp nuvarande adress och parametrarna på stacken, sedan hoppar man iväg till funktionen. När denna funktion är klar så plockar man fram den gamla stackpekaren och den gamla adressen och återgår till att exekvera den koden. På samma sätt med main Den extra koden gör alltså likadant för main och ser till att kommandoradsargumenten går att nå som argument till main. Kommandoradsargument Här gäller samma sak som vid ett vanligt funktionsanrop; extrakoden läser in argumenten och lagrar dem i en strängarray, sedan så skickar den upp antalet argument och pekaren på stacken följt av ett anrop till main 121 Avsluta programmet Normal Ett program kan avslutas normalt, dvs. genom att man "rasar ut ur" main, man gör return från main, man anropar eller _. Denna rutin städar upp lite granna efter ditt program innan den låter kärnan ta över igen. _ Här sker ingen städning alls utan man hoppar direkt tillbaka till kärnan Onormal Detta sker genom ett anrop till abort eller att en signal gör att programmet avslutas. Vi kommer att gå igenom detta på en senare lektion. Exithandlers Genom funktionen at så kan man registrera funktioner som exekveras sedan man har anropat. Se exempel i boken 7.3 122
How a C program is started and how it terminated user main C start-up routine exec USER Process KERNEL _ handler.. handler handler standard I/O clean up 123 Minneshantering Som ni känner till så kan man få mera minne till variabler etc vid behov. I C gör man det genom att anropa någon funktion i alloc-familjen malloc Den kanske vanligaste är malloc som ger dig en pekare till ett minnesutrymme av en viss storlek. Innehållet i minnesutrymmet är odefinierat när du få tillgång till det. calloc calloc ger dig möjlighet att får utrymme för X antal objekt av storleken Y. Det utrymme man då får har "nollats realloc Om redan har ett utrymme för data men behöver ändra storleken så kan du använda dig av realloc. Notera att det existerade datat kan flyttas om så använd dig av den nya pekaren. Innehållet i det eventuella nya utrymmet är odefinierat 124 Minneshantering forts. alloca alloca är lite speciellt i och med att den plockar utrymmet från stacken i stället för heapen. Det innebär att man inte behöver göra en free när rutinen är klar men å andra sidan så kan man bara använda den i funktionen eller i rutiner som anropas av den. free När man är klar med ett minnessegment så kan man lämna tillbaka det genom att anropa free. 125 Processer Processer i kärnan Varje program körs i sin egen process. Använd getpid för att få reda på identifikationsnummer. Barn En process kan yngla av sig ("spawn") sub-processer genom att använda funktionen fork. Processerna kommer direkt efter anropet att se identiska ut (utom vad gäller returvärdet från fork) De delar ej minne med varandra. Innehållet i minnet kopieras ofta först då vi försöker förändra det (för att undvika att kopiera saker i onödan) Race conditions Det finns en uppenbar risk när fler än en process kör och de kommunicerar med varandra. Det är att processerna hamnar i en "tävlan" om vilken som kommer först till en viss punkt i koden. Detta kan ställa till stora problem (vi kommer att titta mer på detta senare i kursen). 126
ps kill och ps listar processer som körs. Kolla man ps för lämpliga flaggor (varierar lite mellan systemen) kill Kan användas för att avsluta en process kill pid exec Man kan starta ett program genom att först göra en fork och sedan göra ett exec-anrop som i sin tur startar programmet. Det intressanta är att detta nya program kör i den process som skapades av forkanropet. Det finns flera olika exec-funktioner. Beroende på vad man vill göra, hur man vill skicka med parametrar och vilka omgivningsvariabler man vill att programmet ska använda sig av 127 128 Exec forts. int execl(const char *path, const char *arg0,.../* (char *)0 */); int execle(const char *path, const char *arg0,.../* (char *)0 */, char * envp[]); int execlp(const char *file, const char *arg0,.../* (char *)0 */); int execv(const char *path, char *const *argv); int execve(const char *path, char *const *argv, char * envp[]); int execvp(const char *file, char * argv[]); 129 exec forts. execlp execl execle build argv build argv build argv execvp execv execve try each PATH prefix use environ 130
environment char *getenv(const char *name) funktion för att läsa av ett värde som finns i en environment variable int putenv(const char *str) tar en sträng på formen name=value och lägger till den till omgivningen int setenv(const char *name, const char *value, int rewrite) unsetenv(const char *name) tar bort en definition av name 131 wait pid_t wait(int *status) pid_t waitpid(pid_t pid, int *status, int options) ett anrop till wait/waitpid kan: blockera om alla barn fortfarande körs returnera omedelbart med statusen för barnet returnera omedelbart med en felkod (om processen saknar barn) Om barnet avslutas innan föräldern lagras info om processen (bl.a. status) tills dess föräldern avslutas eller anropar wait för att läsa av infon barnet blir en zombie process Om föräldraprocessen avslutas innan barnet kommer föräldrarollen att övertas av init (PID 1) 132 Macron för att undersöka statusen WEXITSTATUS(status) Tar fram status WIFEXITED(status) Kollar om processen avslutades normalt WIFSIGNALED(status) Sant om status indikerade att processen avslutades felaktigt (genom en signal) WIFSTOPPED(status) Sant om barnet stoppat Pipe Ett sätt kommunicera mellan olika processer är att använda pipes. Det finns flera sätt att kommunicera på (se sid 496), men pipes är den äldsta metoden och finns implementerat på alla system. Ett sätt att se på en pipe är just som ett enkelriktat rör, man skickar in på ena sidan och plockar ut från den andra. För att skapa en pipe så använder man funktionen int pipe( int filedes[2] ) som returnerar två stycken 'file descriptors'. Den file descriptor som finns på index 0 är "läsändan' och den på 1 är "skrivändan". 133 134
Skapa en pipe Säg att jag nu vill skapa att ett program ska skapa en barnprocess och sätta upp en pipe till den så att föräldern kan skicka information till barnet. Då kan man göra på följande sätt: 1. Anropa pipe för att skapa själva pipen 2. Gör en fork för att få barnprocessen. Kom ihåg att barnet får en kopia av förälderns variabler etc, dvs. barnet kommer att ha tillgång till de file descriptorer som pipen har. 3. Nu bör barnet stänga sin "skrivända 4. Och föräldern bör stänga sin "läsända" 135