Do spisu tresci tematu 1

1.7 Funkcje systemowe zwiazane z czasem

Spis tresci


Typy danych zwiazane z czasem

Oprocz standardowych typow danych beda nam potrzebne:

typedef long time_t
Czas mierzony w sekundach. Logiczniej byloby zdefiniowac go jako unsigned long, uniemozliwiloby to ustawianie zlych wartosci i rozszerzyloby dostepny zakres. Byc moze autorom chodzilo o mozliwosc reprezentacji czasow sprzed 1 stycznia 1970 ? (patrz dalej)
typedef long clock_t
Czas mierzony w okresach przerwania zegarowego. Poniewaz "okres przerwania zegarowego" to troche dluga nazwa, odtad bedziemy mowic "takt zegara" lub po prostu "takt" (ang. tick). Konwersja z taktow na czas rzeczywisty: zazwyczaj dlugosc jednego okresu jest rowna 10 ms (1 / HZ), ale teoretycznie mozliwa jest jej modyfikacja (zmienna tick i funkcja adjtimex()). Rowniez tutaj chyba lepiej byloby uzyc unsigned long. Uwaga: liczba taktow nie zawsze jest rowna ilosci obsluzonych przerwan zegarowych - czasami z jakis powodow nalezy odlozyc obsluge przerwania i wtedy jadro moze wywolac jedna procedure obslugi dla kilku zaleglych przerwan zegarowych. Wiecej na ten temat powinno byc na stronie o przerwaniach (1.6).

Uwaga: wymienione wyzej typy sa stosowane raczej sporadycznie i podane wyzej opisy mozna stosowac tylko w jedna strone, tzn. jezeli zmienna jest typu clock_t, to reprezentuje ilosc taktow, ale z faktu ze zmienna reprezentuje ilosc taktow nie wynika, ze musi byc ona typu clock_t (np. zmienna jiffies jest typu unsigned long, ale funkcja times() zwraca ja juz jako clock_t).


Struktury danych zwiazane z czasem

Przy opisie struktur przyjeto nastepujaca zasade: jezeli struktura wystepuje jako typ ktorejs ze zmiennych jadra, lub jako pole innej struktury spelniajacej ten warunek, opisujemy znaczenie pol. Jezeli struktura wystepuje jedynie w interfejsie funkcji systemowych (struktury tms i timex), podajemy tylko jej definicje i odnosnik do miejsca zawierajacego opis odpowiadajacych jej polom zmiennych.

timeval

(plik include/linux/time.h)

struct timeval {
        long     tv_sec;         /* sekundy */
        long     tv_usec;        /* mikrosekundy */
};

timespec

(plik include/linux/time.h)

struct timespec {
        long     tv_sec;         /* sekundy */
        long     tv_nsec;        /* nanosekundy */
};

Struktura ta ma w przyszlosci byc wykorzystywana przez procesy czasu rzeczywistego, potrzebujace bardzo precyzyjnego okreslenia czasu. W wersji 2.0 Linuxa zaimplementowane zostaly jedynie funkcje konwersji z timespec na clock_t (timespectojiffies()) i z powrotem (jiffiestotimespec()), oraz funkcja sys_nanosleep(), pozwalajaca procesom czasu rzeczywistego zasnac na krotki okres czasu (co najwyzej 2ms, czyli 2000000ns) z duza dokladnoscia, bez zwalniania procesora (nie nastepuje zmiana kontekstu i nie trzeba czekac na wywlaszczenie nowego uzytkownika). Ze wzgledu na nikle (na razie) znaczenie praktyczne, nie bedziemy ich tutaj omawiac


timezone

(plik include/linux/time.h)

struct timezone {
        int     tz_minuteswest;
        int     tz_dsttime;
};

Struktura ta okresla strefe czasowa. Znaczenie pol:

tz_minuteswest
Przesuniecie czasowe na zachod od Greenwich, mierzone w minutach.
tz_dsttime
DST = Daily Saving Time = czas letni. Pole to okresla tryb zmiany czasu z letniego na zimowy.

task_struct

(plik include/linux/sched.h)

Struktura ta opisuje proces (patrz temat 1.3). Z jej pol wymienimy tylko piec, bezposrednio zwiazane z czasem:

struct task_struct {
        /* ... */
        long utime, stime, cutime, cstime, start_time;
        /* ... */

};
utime
Czas CPU wykorzystany przez proces w trybie uzytkownika.
stime
Czas CPU wykorzystany przez proces w trybie jadra (systemowym).
cutime
Czas CPU wykorzystany przez potomkow procesu w trybie uzytkownika. Uwaga: czasy potomkow zbierane sa przez proces macierzysty dopiero po wykonaniu funkcji wait() lub podobnej. Tak wiec w rzeczywistosci nie jest to "sumaryczny czas potomkow", lecz "sumaryczny czas potomkow ktorzy zakonczyli prace" (i nie istnieja nawet w stanie TASK_ZOMBIE) .
cstime
Czas CPU wykorzystany przez potomkow procesu w trybie systemowym. Uwaga jak wyzej.
start_time
Czas rozpoczecia wykonywania procesu (mierzony od zaladowania systemu).

Wszystkie czasy podane sa w taktach (okresach) zegara, by uproscic obsluge tych zmiennych: podczas przerwania zegarowego jadro po prostu sprawdza, ktory proces sie aktualnie wykonywal i w jakim trybie, a nastepnie zwieksza odpowiednie pole o 1 (to stwierdzenie nie jest do konca scisle - patrz uwaga przy opisie typu clock_t).

tms

(plik include/linux/times.h)

struct tms {
        clock_t tms_utime;
        clock_t tms_stime;
        clock_t tms_cutime;
        clock_t tms_cstime;
};

Struktura ta wystepuje jako interfejs funkcji times(). Znaczenie pol - patrz opis task_struct.

timex

(plik include/linux/timex.h)

struct timex {
        unsigned int modes;     /* tablica bitowa modyfikacji */
        long offset;            /* roznica faz [us]  */
        long freq;              /* roznica czestotliwosci [ppm] */
        long maxerror;          /* maksymalny blad [us] */
        long esterror;          /* szacowany blad [us] */
        int status;             /* status synchronizacji */
        long constant;          /* stala pll */
        long precision;         /* (RO) dokladnosc zegara [us] */
        long tolerance;         /* (RO) stand odchyl czestotliwosci [ppm] */
        struct timeval time;    /* (RO) aktualny czas */
        long tick;              /* dlugosc taktu [us] */
        long ppsfreq;           /* (RO) (pps) czestotliwosc sygnalu */
        long jitter;            /* (RO) (pps) ? */
        int shift;              /* (RO) (pps) ? */
        long stabil;            /* (RO) (pps) stabilnosc sygnalu [ppm] */
        long jitcnt;            /* (RO) (pps) ? */
        long calcnt;            /* (RO) (pps) czas kalibrowania */
        long errcnt;            /* (RO) (pps) blad kalibrowania */
        long stbcnt;            /* (RO) (pps) odchylenia anormalne */
        int  :32; int  :32; int  :32; int  :32;
        int  :32; int  :32; int  :32; int  :32;
        int  :32; int  :32; int  :32; int  :32;
};

Struktura ta jest wykorzystywana w interfejsie funkcji adjtimex(), sluzacej do synchronizacji zegara z zegarem zewnetrznym. Wszystkie pola, z wyjatkiem modes, maja swoje odpowiedniki w zmiennych jadra, najwazniejsze sa omowione w rozdziale Zmienne - synchronizacja z zegarem zewnetrznym. Pola z komentarzem (RO) sa przeznaczone tylko do odczytu (Read Only), pola z komentarzem (pps) sa wykorzystywane tylko wtedy, gdy do synchronizacji uzywamy urzadzenia generujacego sygnal PPS.

modes
Tablica bitowa okreslajace ktore z pol chcemy modyfikowac. Tylko superuser ma prawo modyfikowac jakiekolwiek pola, i nawet on nie moze modyfikowac wszystkich. Pola niemodyfikowalne sa zaznaczone wyzej jako (RO). Dopuszczalne modyfikacje:
#define ADJ_OFFSET              0x0001  /* roznica faz */
#define ADJ_FREQUENCY           0x0002  /* roznica czestotliwosci */
#define ADJ_MAXERROR            0x0004  /* maksymalny blad */
#define ADJ_ESTERROR            0x0008  /* szacowany blad */
#define ADJ_STATUS              0x0010  /* status synchronizacji */
#define ADJ_TIMECONST           0x0020  /* stala PLL */
#define ADJ_TICK                0x4000  /* dlugosc taktu */
#define ADJ_OFFSET_SINGLESHOT   0x8001  /* dozwolona roznica faz   *
                                         * wieksza niz pol sekundy */

Dodatkowe ograniczenia


Wybrane zmienne i stale jadra

Przerwanie zegarowe

#define HZ 100
Czestotliwosc przerwania zegarowego w herzach.
long tick = 1000000 / HZ;
Dlugosc taktu, czyli okresu przerwania zegarowego w mikrosekundach. Standartowo rowny 10000 us = 10 ms, moze byc nieznacznie modyfikowany funkcja adjtimex().

Aktualny czas

volatile struct timeval xtime;
Aktualny czas z dokladnoscia do mikrosekundy, znaczenie pol - patrz opis struktury timeval. Jest to tak zwany czas uniwersalny (UTC = Coordinated Universal Time) - czas ktory minal od polnocy (00:00) 1 stycznia 1970 roku czasu GMT. Konwersja do czasu lokalnego i ewentualna zmiana z zimowego na letni dokonywana jest juz na poziomie funkcji uzytkownika, jadro zawsze pracuje w czasie uniwersalnym.
unsigned long volatile jiffies = 0;
Czas od momentu zaladowania systemu w taktach zegara.
#define CURRENT_TIME (xtime.tv_sec)
Czas UTC z dokladnoscia do sekundy (makro uzywane miedzy innymi przez funkcje time()).

Strefa czasowa

struct timezone sys_tz = { 0, 0};
Strefa czasowa w ktorej unieszczony jest system, znaczenie pol - patrz opis struktury timezone. Standartowo czas zimowy Greenwich, moze byc modyfikowany funkcja settimeofday(). W zasadzie zmienna ta nie jest wykorzystywana przez jadro - jest to tylko informacja dla uzytkownika; wyjatek zostanie opisany przy okazji funkcji settimeofday().

Zuzycie CPU

struct task_struct task[NR_TASKS];
Tablica procesow. Zawiera miedzy innymi zuzycie czasu CPU przez poszczegolne procesy - patrz opis struktury task_struct i funkcji times.

Synchronizacja z zegarem zewnetrznym

Jezeli dysponujemy zegarem zewnetrznym, mozemy, za pomoca funkcji adjtimex(), sprobowac zsynchronizowac z nim nasz zegar wewnetrzny. Zegary uwazamy za zsynchronizowane, gdy "wybijaja" sekundy jednoczesnie (oczywiscie z pewna ustalona tolerancja). Niestety, sprawna synchronizacja wymaga duzej ilosci dodatkowych zmiennych i dosyc skomplikowanego algorytmu (autorstwa Davida Mills'a). Ze wzgledu na znaczna objetosc, ograniczymy sie tutaj do pobieznego omowienia wybranych zmiennych:

int time_state = TIME_ERROR;
Status synchronizacji. Gdy nie ma z czym synchronizowac, lub zegar jest rozstrojony, time_state = TIME_ERROR (lub TIME_BAD w starszych wersjach). Gdy jest synchronizowany, time_state = TIME_OK. Pozostale wartosci dotycza sytuacji zwiazanych z wstawianiem / usuwaniem dodatkowej sekundy.
int time_status = STA_UNSYNC;
Tablica bitowa zawierajaca dodatkowe informacje na temat statusu, oraz polecenia dla zegara. Wybrane bity z tej tablicy (oznaczone RO) dotycza statusu sprzetu i przeznaczone sa tylko do czytania):
#define STA_INS		0x0010 /* wstaw dodatkowa sekunde */
#define STA_DEL		0x0020 /* pomin jedna sekunde */
#define STA_UNSYNC	0x0040 /* zegar nie jest synchronizowany */
#define STA_PPSSIGNAL	0x0100 /* (RO) dostepny jest sygnal PPS */
#define STA_CLOCKERR	0x1000 /* (RO) wystapil sprzetowy blad zegara */
long time_precision = 1;
Dokladnosc zegara (w mikrosekundach).
#define MAXPHASE 512000L
Maksymalna roznica w fazie pomiedzy zegarami, rowna 512000us, czyli w przyblizeniu pol sekundy.
long time_offset = 0;
Aktualne przesuniecie w fazie wzgledem zegara zewnetrznego (w mikrosekundach). Jadro bedzie staralo sie uzgodnic fazy, przesuwajac kolejne przerwania zegarowe o niewielka wartosci (time_adjust_step), odpowiednio zmniejszajac wartosc time_offset. Z oczywistych wzgledow ograniczone przez MAXPHASE: -MAXPHASE<time_offset<+MAXPHASE
int tickadj = 500/HZ;
Gorne ograniczenie na wartosc time_adjust_step (patrz nizej). W sytuacji normalnej (tick = 1000000/HZ), oznacza to roznice pomiedzy kolejnymi okresami nie wieksze niz 1%.
long time_adjust_step = 0;
Dokonywana przez jadro, wyliczana na nowo przy kazdym przerwaniu, modyfikacja dlugosci taktu (w mikrosekundach). Uwaga: nie nalezy jej mylic z mozliwoscia zewnetrzej modyfikacji dlugosci taktu (wartosci zmiennej tick). Pierwsza jest stosowana, gdy nalezy uzgodnic czestotliwosci zegarow, druga - gdy oba zegary juz pracuja z mniej wiecej ta sama czestotliwoscia, ale nalezy jeszcze uzgodnic fazy (time_offset). Tak wiec w rzeczywistosci dlugosc taktu wynosi tick+time_adjust_step.
long time_tolerance = MAXFREQ;
Tolerancja (maksymalne odchylenie) od sredniej czestotliwosci zegara wewnetrznego wyrazona w ppm (parts per milion; 1ppm = 0.0001%).
long time_freq = 0;
Roznica czestotliwosci pomiedzy zegarami (w ppm).
long esterror = MAXPHASE;
Szacowane odchylenie wartosci time_offset od rzeczywistej roznicy faz.
long maxerror = MAXPHASE;
Maksymalne odchylenie wartosci time_offset od rzeczywistej roznicy faz.

To nie wszystko: do synchronizacji mozemy uzyc rowniez podlaczanego przez zlacze RS232 urzadzenia generujacego sygnal PPS (pulse-per-second). Zmienne zwiazane z jego obsluga latwo rozpoznac - poprzedzone sa prefiksem pps_.

Patrz takze: temat 1.6 oraz opis funkcji adjtimex().


Funkcje systemowe

Algorytmy ponizej opisanych funkcji sa raczej trywialne - zazwyczaj ograniczaja sie do odczytania / zapisania wartosci odpowiednich zmiennych. Osoby zainteresowane implementacja tych funkcji zapraszamy do skorzystania z odpowiednich odnosnikow do kodu. Czytajac kod zrodlowy warto wiedziec, ze:


Funkcja time()

- odczytanie aktualnego czasu systemowego (z dokladnoscia do 1 sekundy).

DEFINICJA: time_t time(int *tloc)
    WYNIK: aktualny czas mierzony w sekundach od 00:00 1.01.1970 UTC

Funkcja odczytuje zawartosc zmiennej xtime.tv_sec, wykorzystujac makro CURRENT, i zwraca ja jako wynik. Dodatkowo, jezeli tloc != NULL, probuje ja zapisac pod wskazywany adres.

Funkcja stime()

- ustawienie aktualnego czasu systemowego (z dokladnoscia do 1 sekundy).

DEFINICJA: int stime(int * tptr)
    WYNIK: 0 w przypadku sukcesu
           -1, gdy blad: errno = EPERM (wywolujacy nie jest superuserem)

Funkcja sprawdza czy wywolujacy funkcje jest superuserem (tylko on ma prawo modyfikacji czasu systemowego), nastepnie z wylaczonymi przerwaniami modyfikuje odpowiednie zmienne:

        xtime.tv_sec = value;      /* wartosc wskazywana przez tptr */
        xtime.tv_usec = 0;
        time_state = TIME_ERROR;
        time_maxerror = MAXPHASE;
        time_esterror = MAXPHASE;

Po takiej ingerencji, system uwaza zegar za rozstrojony (TIME_ERROR).


Funkcja gettimeofday()

- odczytanie aktualnego czasu i strefy czasowej

DEFINICJA: int gettimeofday(struct timeval * tv, struct timezone * tz)
    WYNIK: 0

Dzialanie:

        if(tv != NULL) 
                skopiuj zawartosc zmiennej xtime
                pod adres wskazywany przez tv;
        if(tz != NULL)
                skopiuj zawartosc zmiennej sys_tz
                pod adres wskazywany przez tz;


Funkcja settimeofday()

- ustawienie aktualnego czasu i strefy czasowej

DEFINICJA: int settimeofday(struct timeval * tv, struct timezone * tz)
    WYNIK: 0 w przypadku sukcesu
           -1, gdy blad: errno = EPERM (wywolujacy nie jest superuserem)

Dzialanie:

        static int firsttime = 1;
        if(wywolujacy nie jest superuserem)
                zwroc blad EPERM;
        if(tv != NULL)
		skopiuj zawartosc struktury wskazywanej przez tv
                na zmienna xtime;
        if(tz != NULL)
                {
                skopiuj zawartosc struktury wskazywanej przez tz
                na zmienna sys_tz;
                if(firsttime)
                        {
                        firsttime = 0;
                        if(tv == NULL)
                                {
                                zablokuj przerwania;
                                xtime.tv_sec += sys_tz.tz_minuteswest * 60;
				odblokuj przerwania;
                                }
                        }
                }

Od dzialania oczywistego widzimy tutaj jeden wyjatek. Jezeli przy pierwszym ustawianiu strefy czasowej po zaladowaniu systemu nie podamy aktualnego czasu, jadro interpretuje to nastepujaco: do tej pory zegar chodzil w czasie lokalnym, i nalezy go niezwlocznie przestawic na UTC. Jest to zreszta jedyna sytuacja w ktorej strefa czasowa ma jakikolwiek wplyw na dzialanie lub zmienne jadra.

Funkcja adjtimex()

- odczytanie lub ustawienie parametrow synchronizacji z zegarem zewnetrznym

DEFINICJA: int adjtimex(struct timex * buf)
    WYNIK: status synchronizacji w przypadku sukcesu
                TIME_OK - zegar jest synchronizowany
                TIME_ERROR - zegar nie jest synchronizowany
           -1, gdy blad: errno = EPERM (tylko superuser moze modyfikowac)
                                 EINVAL (niedozwolona wartosc jednego z pol)

Funkcja modyfikuje wskazane przez pole buf->modes zmienne na wartosci okreslone przez odpowiednie pola. Po dokonaniu wszystkich modyfikacji funkcja uaktualnia zawartosc struktury wskazywanej przez buf, i zwraca status synchronizacji. Kliknij tutaj, by obejrzec zawartosc struktury timex i dopuszczane modyfikacje, oraz tutaj, by poczytac wiecej o synchronizacji.

Funkcja times()

- odczytanie danych o zuzyciu czasu procesora przez proces i jego potomkow

DEFINICJA: clock_t times(struct tms * tbuf)
    WYNIK: liczba taktow od momentu zaladowania systemu

Funkcja kopiuje zawartosc odpowiednich pol struktury task_struct, wskazywanej przez wskaznik current (aktualnie wykonujacy sie proces) pod adres wskazywany przez tms. Patrz opis struktur tms i task_struct. Jako wynik zwraca zawartosc zmiennej jiffies, czyli czas od momentu zaladowania systemu mierzony w taktach zegara.

Funkcja alarm()

- ustawienie (i/lub ew. wylaczenie) sygnalu pobudki.

DEFINICJA: unsigned int alarm(unsigned int seconds)
    WYNIK: liczba sekund pozostala do wywolania poprzedniego alarmu
           0, gdy nie bylo takowego

Efektem wywolania tej funkcji jest:


Kody zrodlowe programow demonstracyjnych

Teksty trzech prostych programow w C demonstrujacych dzialanie funkcji i pozwalajacych na obejrzenie aktualnego stanu odpowiednich zmiennych jadra:

  1. gettimeofday()
  2. times()
  3. adjtimex()

Uwaga: kompilujac times.c zmien nazwe, lub wywoluj program wynikowy przez ./times, istnieje bowiem rowniez polecenie shella o tej samej nazwie.


Bibliografia

  1. Pliki zrodlowe Linuxa:


Autor: Aleksander Buczynski