Helsingin yliopisto Tietojenkäsittelytieteen laitos
 

Tietojenkäsittelytieteen laitos

Tietoa laitoksesta:

 
Helsingin yliopisto / Tietojenkäsittelytieteen laitos / Copyright © 2001 Jan Lindström. Tämän oppimateriaalin käyttö on sallittu vain yksityishenkilöille opiskelutarkoituksissa. Materiaalin käyttö muihin tarkoituksiin, kuten kaupallisilla tai muilla kursseilla, on kielletty.

2. Ohjelman suoritusympäristö


Sisältö:


2 Ohjelman suoritusympäristö


2.1 Komentoriviargumentit

Prosessi voidaan käynnistää komentotulkin kautta:

      $ prog -kc puppu.dat
Koodin suoritus alkaa funktiosta int main(int agrc, char *argv[]);. Komentotulkki välittää argumentit ohjelmalle pinossa. Niihin voi viitata muuttujilla argc ja argv[].


/*    
argc  argumenttien lukumäärä
argv[]      ohjelman nimi ja argumentit merkkijonoina
   
Yo. esimerkissä
   
            argc    = 4
            argv[0] = "prog"
            argv[1] = "-kc"
            argv[2] = "puppu.dat"
            argv[3] = NULL

*/   
int main(int argc, char *argv[])
{
    int i;
      
    for (i=0; i < argc; i++)
        printf("argv[%d]: %s\n", i, argv[i]);
    exit(0);
}

Prosessin suoritus päättyy "normaalisti", kun suoritetaan:

exit() tekee standardikirjastoihin liittyvää siivousta ja kutsuu edelleen funktiota _exit(). Normaalisti päättyvän ohjelman tulisi palauttaa 0. Käytä myös main-funktiossa funktiota exit(). Prosessin suoritus päättyy myös

Prosessin päättyessä suoritetaan funktiolla atexit() rekisteröidyt funktiot, viimeiseksi rekisteröity ensin.

#include < stdlib.h >
 
int atexit(void (*func)(void));


static void       my_exit1(void), my_exit2(void);

int main(void)
{
         if (atexit(my_exit2) != 0)
                  perror("can't register my_exit2");
         if (atexit(my_exit1) != 0)
                  perror("can't register my_exit1");
         if (atexit(my_exit1) != 0)
                  perror("can't register my_exit1");
         printf("main is done\n");
         return(0);
}

static void my_exit1(void)
{
         printf("first exit handler\n");
}

static void my_exit2(void)
{
         printf("second exit handler\n");
}


2.2 Ympäristömuuttujat

Tietoa suoritusympäristöstä voi välittää prosessille myös ympäristömuuttujien avulla. Komentoriviargumentit ovat tarkoitettu yksittäisen sovelluksen omien parametrien välittämiseen. Ympäristömuuttujien arvot pysyvät kauan samoina ja ne ovat usealle sovellukselle yhteisiä. Ympäristömuuttujalla on nimi ja arvo. Käytännössä se on merkkijono, joka on muotoa nimi=arvo Tcsh-komentotulkissa ympäristömuuttuja asetetaan komennolla setenv, esim.
    % setenv KURSSI Unso 
Jos ympäristömuuttuja halutaan asetaa vain yhden ohjelman suoritusajaksi, voi käyttää komentoa env, esim
    $ env KURSSI=Unso prog -kc puppu.dat
Optiolla -i voi kieltää vielä äitiprosessin ympäristön periytymisen. Ympäristömuuttujaan viitattaessa on käytettävä tunnuksen edessä $-merkkiä
  
    $ echo $KURSSI
Kaikkien ympäristömuuttujien arvot saa tcsh:ssä listattua komennolla
      $ printenv 
Lapsiprosessi perii äitiprosessilta ympäristömuuttujien arvot. Komentotulkki välittää ympäristömuuttujiensa arvot käynnistyvälle prosessille pinossa. Niihin voi viitata C-ohjelmassa muuttujan envp[] kautta.

int 
main(int argc,char *argv[],char *envp[])

envp[]    ympäristömuuttujat merkkijonoina
          envp[0] = "ARCH=sun4"
          envp[1] = "DISPLAY=:0.0"
          ...
          envp[n] = NULL

Ympäristömuuttujia voi käsitellä myös esittelemällä koodissa muuttujan extern char **environ; ja käyttämällä sitten funktioita:

      printf("Tunnus on %s\n",getenv("USER"));

      if (putenv("KURSSI=Unso") == 0)
                  ... onnistui ...
      else
                  ... muistiongelmia ...

putenv() ei muuta main-funktion "envp"-argumenttia, sensijaan extern-muuttuja environ muuttuu. getenv() näkee uudet arvot. Lapsiprosessi ei voi muuttaa sen käynnistäneen prosessin ympäristömuuttujien arvoja.


2.3 Käyttäjätiedot

Unix on monen käyttäjän moniajojärjestelmä ja siksi tarvitaan resurssien hallittua jakelua, kiintiöintiä, tiedostojen yhteiskäyttöä ja tiedon suojausta luvattomalta käytöltä. Toteutus perustuu käyttäjätunnuksiin, salasanoihin ja käyttäjäryhmiin.

      Käyttäjätunnukset ja salasanat "loytyvät" tiedostosta /etc/passwd 
      Käyttäjäryhmät löytyvät tiedostosta /etc/group
      Tiedostojen käyttöoikeuteen liittyvät attribuutit, ns. rwx-bitit eli esim
   
      drwxr-xr-x tiedosto

Käyttäjän yksikäsitteinen tunniste on :

Käyttäjätunnusta tarvitaan sisäänkirjoittautumisessa ja käyttäjien välisessä kommunikoinnissa. Käyttäjänumeroa käytetään prosessien suorituksessa ja tiedostojen käyttöoikeuksien määräämisessä. Käyttäjän perustiedot ovat yhdellä rivillä tavallisesti tiedostossa

      /etc/passwd                                (tai  /var/yp/passwd tms.)

Rivin rakenne on tavallisesti:

      kjätunnus:salasana:uid:gid:gecos:hakemisto:ohjelma

Uid-numeron saa selville funktiolla getuid() ja käyttäjätunnuksen funktiolla getlogin(). Muut tiedot voi hakea käyttäjätunnuksen tai -numeron perusteella funktioilla getpwuid() ja getpwnam().

 
#include < pwd.h > 
struct passwd { 
  char *pw_name; käyttäjätunnus 
  char *pw_passwd; koodattu salasana 
  uid_t pw_uid; käyttäjä numero 
  gid_t pw_gid; ensisijainen ryhmä 
  char *pw_age; salasanan ikä 
  char *pw_comment; lisätietoa 
  char *pw_gecos; lisätietoa 
  char *pw_dir; kotihakemisto 
  char *pw_shell; komentotulkki 
}; 

POSIX vaatii vain, että rakenteessa on vähintään kentät pw_name, pw_uid, pw_gid, pw_dir ja pw_shell. Ryhmän yksikäsitteinen tunniste on:

Ryhmätunnusta käytetään prosessien suorituksessa ja tiedostojen käyttöoikeuksien määräämisessä. Ensisijainen ryhmä on kirjattu salasanatiedostoon. Sen lisäksi käyttäjä voi kuulua toissijaisiin ryhmiin. Toissijaisten ryhmien tiedot ovat tiedostossa:

      /etc/group
Rivin rakenne on:

 
      ryhmätunnus:salasana:gid:jäsenten kjätunnukset 

Komentotulkissa omat ryhmätunnukset saa komennolla

  
      $ groups

Sovellus saa ensisijaisen ryhmätunnuksen funktiolla getgid() ja toisijaiset ryhmänumerot funktiolla getgroups().

#include < sys/types.h >
#include < unistd.h >
  
gid_t getgid(void);
int getgroups(int gidsetsize, gid_t grouplist[]);
                                   Palauttaa: toissijaisten ryhmien lkm

Funktio täyttää taulukon grouplist toissijaisilla ryhmänumeroilla. Funktion ens. parametri on taulukon koko. Jos kooksi annetaan 0, palauttaa ryhmien lkm:n eli saa selville taulukon tilantarpeen. Muut tiedot voi hakea ryhmätunnuksen tai -numeron perusteella funktioilla getgrgid() ja getgrname().

#include < sys/types.h >
#include < grp.h >
     
struct passwd *getgrgid(gid_t gid);
struct passwd *getgrnam(const char *name);
                                        Palauttaa: osoitin passwd-rakenteeseen
#include < grp.h >
         
struct group {
    char *gr_name;       ryhmätunnus
    char *gr_passwd;     koodattu salasana
    gid_t gr_gid;        ryhmänu
                         mero
    char **gr_mem;       käyttäjätunnukset
 };

POSIX vaatii vain, että rakenteessa on vähintään kentät gr_name, gr_gid, ja gr_mem. Koko ryhmätiedoston käsittelyä varten on funktiot setgrent(), getgrent() ja endgrent().

#include < sys/types.h >
#include < grp.h >
     
void setgrent(void);
void endgrent(void); 
struct group *getgrent(void);        
  Palauttaa: osoitin seuraavaan group-rakenteeseen, NULL kun tdsto lopussa


2.4 Kirjanpito istuntojen kestosta

Tiedostoissa

/var/adm/utmp     tunnukset, joilla istunto menossa
/var/adm/wtmp     istuntojen alkamis- ja päättymisajat

Login-prosessi kirjoittaa rivin sekä utmp-tiedostoon että wtmp-tiedostoon. Logout-komennon suoritus poistaa rivin utmp-tiedostosta ja täydentää wtmp- tiedoston riviä. Komento

      $ who
näyttää utmp-tiedoston sisällön ja komennolla
      $ last käyttäjätunnus
voi tutkailla wtmp-tiedoston sisältöä.


2.5 Muita ympäristökyselyjä

Komento

      $ uname -a
kertoo koneen nimen ja käyttöjärjestelmäversion. Sovellus voi kysyä vastaavat tiedot funktiolla uname().

#include < sys/utsname.h >
     
struct utsname {
    char sysname[SYS_NMLN];      KJ:n nimi
    char nodename[SYS_NMLN];     Koneen solmunimi
    char release[SYS_NMLN];      KJ:n releasenumero
    char version[SYS_NMLN];      KJ:n versionumero
    char machine[SYS_NMLN];      Laitteiston tyyppi
 };
   
int uname(struct utsname *name);
                                        Palauttaa: ei-negatiivinen arvo

Pistokkeet (engl. socket) toteuttavissa järjestelmissä voi solmunimen kysyä komennolla:

      $ hostname
ja sovelluksessa funktiolla gethostname()
#include 
      
int gethostname(char *name, int namelen);
Komento
      $ hostid
antaa koneen yksikäsitteisen tunnusnumeron. Sillä on käyttöä esim. ohjelmistolisenssien tarkistuksessa. Koneen solmunimi, käyttäjätunnus, käyttöjärjestelmän tyyppi yms. on yleensä asetettu myös sopivien ympäristömuuttujien (HOST, LOGNAME, ARCH, OS jne.) arvoiksi.


2.6 Aika ja kalenteri

Unix tallettaa esim. tiedoston attribuutteihin kalenteriajan sekunteina omassa sisäisessä esitysmuodossaan. Nollakohta on 00:00:00, 1.1.1970 GMT. Päiväys ja kellonaika saadaan komennolla date.

      $ date
      Wed Jan 22 10:21:58 1996

Aikaviipaleita talletetaan kellokeskeytysten lukumääränä (engl. ticks). Sekunti on järjestelmästä riippuen 50, 60 tai 100 "tiksausta". Tässä muodossa on talletettu mm. prosessin kuvaajaan.

Komennon tai komentojonon suoritukseen kuluvat em. ajat saa komennolla time.

      $ time grep xyz *.txt > /dev/null 
      real 0m19.81s
      user 0m0.43s
      sys 0m4.53s

Sovellus saa kalenteriajan Unixin sisäisessä esitysmuodossa funktiolla time().

 
#include < time.h > 

time_t time(time_t *calptr); Palauttaa: kalenteriaika Unixin sisäisessä esitysmuodossa,
 myös muuttujassa calptr, jos ei NULL kutsuttaessa 

Kalenteriajan saa pilkottua helpommin käsiteltäviin osiin funktioilla gmtime() ja localtime().

   
#include < time.h >
     
struct tm {
    int   tm_sec;        sekunnit [0..61]
    int   tm_min;        minuutit [0..59]
    int   tm_hour;       tunnit [0..23]
    int   tm_mday;       päivä [1..31]
    int   tm_mon;        kuukausi[0..11]
    int   tm_year;       vuosi kahdella numerolla
    int   tm_wday;       vkonpvä [0..6], 0=sun
    int   tm_yday;       päivä vuoden alusta [0..365]
    int   tm_isdst;      1, jos kesäaika
};
    
struct tm *gmtime(const time_t *calptr);
struct tm *localtime(const time_t *calptr);
    

Funktioille on myös käänteisfunktiot timegm() ja timelocal(). Rakenteena esitetyn ajan voi pakata Unixin esitysmuotoon funktiolla time_t mktime(struct tm *tmptr); Edellä esitetyt muodot (time_t ja struct tm) voi muuttaa merkkijonomuotoon funktioilla char *asctime(const struct tm *tmptr); ja char *ctime(const time_t *calptr); Molemmat palauttavat ajan muodossa (26 merkkiä):

      Wed Jan 22 10:21:58 1997\n\0

tm-rakenteessa olevan ajan voi muuttaa vapaastimuotoilluksi merkkijonoksi funktiolla size_t strftime(char *buf, size_t buflen, const char *format, const struct tm *tmptr);, joka palauttaa taulukkoon buf talletettujen merkkien lukumäärän.



Jan Lindström (Jan.Lindstrom@cs.Helsinki.FI)