Egy szolgáltatás naplóbejegyzéséhez naptári időpont kell, egy hálózati kérés timeoutjához eltelt idő, teljesítményelemzéshez pedig akár CPU-idő. Ezek nem ugyanannak a mennyiségnek különböző formátumai. Hasonlóképpen más szerződés szükséges egy reprodukálható teszt véletlenszámaihoz és egy hozzáférési tokenhez.
Az időzóna, a locale, a véletlengenerátor és a környezeti változók további közös tulajdonsága a rejtett, megosztott állapot. A megfelelő függvény kiválasztása ezért egyszerre jelent időmodell-, biztonsági és élettartam-döntést. A hibák kezelésénél a Hibakód, errno és hibaadatok élettartama, a numerikus átalakításoknál a Lebegőpontos reprezentáció és numerikus hibák szabályaira építünk.
A falióra (wall clock) a naptári időhöz kapcsolódó óra. Alkalmas események időbélyegzésére, de az értéke adminisztratív beavatkozás vagy időszinkronizáció miatt változhat. A monoton óra nem lép visszafelé; elsősorban időtartamokhoz és határidőkhöz használjuk. A CPU-idő azt közelíti, mennyi processzoridőt használt fel a program, nem azt, meddig volt életben.
| API és szabványhatár | Jelentés | Hibajelzés és korlát |
|---|---|---|
time, ISO C17 |
Aktuális naptári idő | (time_t)-1, ha nem érhető el |
timespec_get, ISO C17 |
A megadott időalap szerinti idő; szabványos alap:
TIME_UTC |
Siker: a megadott alap értéke; hiba: 0 |
clock, ISO C17 |
A program processzoridejének közelítése | (clock_t)-1, ha nem áll rendelkezésre vagy nem
ábrázolható |
clock_gettime, POSIX |
Kiválasztott rendszeróra | Siker: 0; hiba: -1, részletek az
errno-ban |
time_t,
struct tm és időpontok különbségeISO C17: a time_t naptári idő
reprezentálására szolgáló aritmetikai típus. A hordozható C-program nem
feltételezheti, hogy egy adott szélességű egész, vagy hogy Unix-epoch
óta eltelt másodperceket tartalmaz.
Két érvényes naptári időpont különbségéhez ezért:
/* ISO C17-részlet; szükséges fejléc: <time.h>.
begin és end érvényes, sikeresen lekért naptári időpontok. */
double elapsed_seconds = difftime(end, begin);A difftime az első argumentum mínusz a második
különbségét adja másodpercben. A közvetlen end - begin nem
helyettesíti hordozhatóan: a reprezentációra támaszkodna, és a kivonás a
time_t típusában történne.
A struct tm felbontott naptári időt tárol. Például a
tm_year az 1900 óta eltelt éveket, a tm_mon a
januártól számított, nullával kezdődő hónapszámot tartalmazza. Nem
célszerű saját mezőaritmetikával kezelni a hónapváltást vagy a nyári
időszámítást.
ISO C17: a gmtime UTC szerinti, a
localtime helyi idő szerinti felbontást készít. Mindkettő
null pointerrel jelezheti, hogy az átalakítás nem végezhető el. A
visszaadott struct tm nem a hívó tulajdona.
ISO C17: a
/* ISO C17-részlet; szükséges fejléc: <time.h>. */
struct timespec stamp;
int ok = timespec_get(&stamp, TIME_UTC);hívás után csak ok == TIME_UTC esetén használható az
eredmény. A tv_sec az implementáció által meghatározott
epochhoz viszonyított másodpercrészt, a tv_nsec a
másodpercen belüli nanoszekundumos mezőt tartalmazza.
A mező mértékegysége nem azonos az óra tényleges felbontásával vagy
pontosságával. A TIME_UTC nem biztosít monoton órát:
timeout mérésére ugyanúgy veszélyes lehet, mint a time.
A clock más problémát old meg. Két sikeres mérésből,
ábrázolható és körbefordulástól mentes intervallumon:
A kivonást és konverziót úgy kell megvalósítani, hogy ne történjen
előbb egész túlcsordulás. A CLOCKS_PER_SEC skálatényező,
nem garantált mérési felbontás. Alvás vagy I/O-várakozás alatt a falióra
halad, miközben a program CPU-ideje alig nőhet.
POSIX: a
clock_gettime(CLOCK_MONOTONIC, ...) időtartamokhoz
használható. Az óra kezdőpontjának nincs naptári jelentése. Két értéket
csak azonos órából és összehasonlítható rendszerkörnyezetből szabad
összevetni.
Linux: a CLOCK_MONOTONIC nem számolja
bele a rendszer felfüggesztésében töltött időt. Ha a timeoutnak ezt is
tartalmaznia kell, külön Linux-specifikus óraválasztás, például
CLOCK_BOOTTIME szükséges. A monotonitás nem jelent
változatlan frekvenciát vagy valós idejű ütemezési garanciát.
Timeoutot célszerű egyszer kiszámított határidővel (deadline) kezelni. Ha minden megszakítás után újraindítjuk a teljes várakozást, a teljes művelet túllépheti az eredeti keretet.
Legyen a kezdőérték , a megengedett idő pedig , normalizált nanoszekundumos mezőkkel. Ekkor:
Az összeadásokat csak akkor szabad elvégezni, ha az eredmények ábrázolhatók a használt típusokban. Szemléltető példa:
A következő teljes fordítási egység egy már létrehozott határidőt ellenőriz:
/* deadline.c — ISO C17 szintaxis, POSIX API */
#define _POSIX_C_SOURCE 200809L
#include <time.h>
/*
* Előfeltétel:
* deadline nem NULL, élő, olvasható objektumra mutat;
* CLOCK_MONOTONIC alapján készült;
* 0 <= tv_nsec < 1000000000.
*
* Eredmény: 1 = lejárt, 0 = még nem járt le,
* -1 = óraleolvasási hiba, errno szerint.
*/
int deadline_expired(const struct timespec *deadline)
{
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
return -1;
}
return now.tv_sec > deadline->tv_sec ||
(now.tv_sec == deadline->tv_sec &&
now.tv_nsec >= deadline->tv_nsec);
}Bizonyításvázlat: normalizált mezők esetén egy nagyobb másodpercérték minden lehetséges nanoszekundumos mező mellett későbbi időpontot jelent. Azonos másodpercnél a nanoszekundumos mezők döntenek. Az egyenlőség már lejárt határidőt jelent.
A függvény nem várakoztat. Egy blokkoló műveletnek a még hátralévő időt kell átadni, megszakítás után pedig ugyanahhoz a határidőhöz kell visszatérni. Az ellenőrzés önmagában nem tudja megakadályozni, hogy az ütemező később engedje tovább a folyamatot.
Javasolt, itt nem fordítással ellenőrzött laborprofil: Linux x86-64, glibc 2.39, GCC 14.2 és Clang 19.1, explicit C17 módban:
gcc -std=c17 -Wall -Wextra -Wpedantic -c deadline.c -o deadline-gcc.o
clang -std=c17 -Wall -Wextra -Wpedantic -c deadline.c -o deadline-clang.oEz objektumfájlt készítő modul, ezért nincs main
függvénye. Más, különösen régebbi platformon a linkelési követelményeket
külön ellenőrizni kell.
A naptári időpont és annak megjelenítése külön réteg. Ugyanazt az
időpontot a localtime az időzóna szerint bontja fel, a
strftime pedig a választott formátum és részben a locale
szerint írja ki.
ISO C17: a setlocale megosztott
locale-állapotot módosít; a strftime többek között a
LC_TIME kategóriától függ. POSIX: a
TZ környezeti változó és a tzset az
időzóna-kezelés része. Ezeket nem szabad kérésenként, párhuzamos
kiszolgálás közben biztonságosan elszigetelt beállításnak tekinteni.
| Hozzáférés | Tulajdonos és élettartam | Érvénytelenítés, hibakimenet |
|---|---|---|
gmtime/localtime eredménye |
Könyvtári statikus tároló; kölcsönzött pointer | Kapcsolódó időkonverziós hívások felülírhatják; hiba: null pointer |
getenv eredménye |
Implementáció által kezelt tároló; kölcsönzött sztring | Újabb getenv felülírhatja; környezetmódosításra sem
tartható meg biztonságosan; hiány: null pointer |
deadline_expired argumentuma |
A hívó által fenntartott objektum | Élettartamvége után nem használható; párhuzamos, szinkronizálatlan módosítása hibás |
Hívói struct tm másolat |
A hívó saját objektuma | A könyvtár későbbi hívása nem írja felül |
Ez például hibás mentális modell:
/* ISO C17, szemléltető részlet; <time.h>.
Nem megfelelő: a két pointer nem jelent két független eredményt. */
struct tm *local = localtime(&now);
struct tm *utc = gmtime(&now);A második hívás felülírhatja az első eredményt. Egyszálú, közbeeső hívástól mentes kódban a null pointer ellenőrzése utáni azonnali struktúramásolat megoldás. Párhuzamos hívásoknál azonban a másolás önmagában nem szinkronizáció.
POSIX: a localtime_r és
gmtime_r hívói tárolóba ír. Ez megszünteti a visszatérési
objektum megosztását, de nem teszi problémamentessé az időzóna vagy
környezet egyidejű átállítását.
ISO C17: a rand a
tartományból ad pszeudovéletlen értéket. A srand
inicializálja a generátor állapotát. Ugyanaz a seed ugyanazon
implementációban ugyanazt a sorozatot eredményezi; különböző C
könyvtárak között nem garantált azonos sorozat.
A rand nem kriptográfiailag biztonságos
generátor, azaz nem CSPRNG. A
/* ISO C17-részlet; <stdlib.h>, <time.h>.
Biztonsági token előállítására alkalmatlan. */
srand((unsigned)time(NULL));nem változtat ezen: a kezdőérték gyakran becsülhető, és azonos kezdőértékek ismétlődhetnek. A megosztott generátorállapot miatt egy másik komponens hívásai a tesztek reprodukálhatóságát is befolyásolhatják.
A rand() % k még ideálisan egyenletes bemenetből is
torzíthat. Ha matematikailag
akkor
maradékhoz
,
a többihez
bemeneti érték tartozik. Ez matematikai képlet: a
RAND_MAX + 1 C-kifejezés int típusban
túlcsordulhat. A modulo-torzítás javítása sem teszi kriptográfiailag
biztonságossá a generátort.
Linux-specifikus: titkokhoz a kernel
véletlenforrását használó getrandom megfelelő kiindulópont,
nem ISO C17 API. A visszaadott bájtszámot ellenőrizni kell; rövid
eredmény, megszakítás és inicializálási várakozás is része lehet a
szerződésnek. Hibakor nem elfogadható „tartalékmegoldásként”
rand-ra váltani.
ISO C17: a getenv null pointert ad, ha
a változó nem található. A megtalált üres sztring ettől különböző
állapot. A visszaadott sztringet nem szabad módosítani vagy
free-vel felszabadítani.
POSIX: a setenv és
unsetenv környezetet módosít. A párhuzamos olvasás és
módosítás biztonságát nem szabad automatikusan feltételezni. A
másolatkészítés is olvasás: ha közben a forrás változhat vagy
érvénytelenné válhat, a másolás sem biztonságos.
Szolgáltatásnál jó szerződés az induláskori konfigurációs pillanatkép: még a munkaszálak előtt kiolvasni, validálni és saját tárolóba másolni az értékeket. Ezután a program ne a folyamat környezetét használja élő konfigurációs adatbázisként.
Óraválasztás. Válassz API-t napló-időbélyeghez, kétmásodperces timeout méréséhez és egy számítás CPU-költségéhez. Mi történhet, ha a faliórát közben visszaállítják?
Kódolás. Készíts copy_service_mode
függvényt, amely a SERVICE_MODE értékét hívói bufferbe
másolja. Eredmény: 0 siker, 1 hiányzó változó,
-1 elégtelen kapacitás. Hibánál maradjon változatlan a
buffer.
Diagnosztika. Egy szolgáltatás minden kérés
előtt újrahívja a srand(time(NULL)) kifejezést,
rand-ból készít hozzáférési tokent, globálisan módosítja a
TZ értékét, és minden megszakítás után új teljes timeouttal
vár. Nevezd meg a négy független hibát.
Naplóhoz ISO C17 time vagy
timespec_get(TIME_UTC), timeout méréséhez POSIX
CLOCK_MONOTONIC, CPU-költséghez ISO C17 clock
megfelelő.
A falióra visszaállítása negatív időeltérést vagy megnyúlt timeoutot okozhat. Ellenőrzési feltétel: a timeout minden újrapróbálkozása ugyanahhoz a monoton határidőhöz viszonyítson.
/* env_copy.c — ISO C17 */
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
/*
* dst legalább cap C bájt írható tárolóra mutat, ha cap > 0.
* A forrás és cél nem fed át.
* A környezet és a getenv-hozzáférés a művelet alatt stabil.
*/
int copy_service_mode(char *dst, size_t cap)
{
const char *value = getenv("SERVICE_MODE");
if (value == NULL) {
return 1;
}
size_t length = strlen(value);
if (length >= cap) {
return -1;
}
memcpy(dst, value, length + 1);
return 0;
}A korábbi laborprofilban:
gcc -std=c17 -Wall -Wextra -Wpedantic -c env_copy.cA cél tulajdonosa a hívó; a függvény nem őrzi meg egyik pointert sem. A kapacitásellenőrzés a másolás előtt történik, ezért a hibautak nem módosítják a célt. Ellenőrizendő: hiányzó és üres érték, pontosan elegendő kapacitás, valamint a lezáró nullkarakter számára már elégtelen buffer.
Az ismételt seedelés ismétlődő sorozatokat okozhat; a
rand nem alkalmas tokenhez; a globális időzóna-módosítás
összekeverheti a párhuzamos kérések állapotát; a teljes timeout
újraindítása túllépi a teljes időkeretet.
A javítás: megfelelő OS-véletlenforrás ellenőrzött hibakezeléssel, rögzített vagy explicit időzóna-kezelés, valamint egyetlen monoton határidő. A tesztgenerátor seedjét külön, reprodukálható tesztkonfigurációként kell kezelni.
Naptári időpont, eltelt idő és CPU-idő külön fogalom. Timeout
méréséhez monoton óra kell; a nanoszekundumos mező önmagában nem
biztosít sem monotonitást, sem nanoszekundumos pontosságot. A
rand nem biztonsági eszköz. Az időkonverzió, locale és
környezet pedig megosztott állapotot és kölcsönzött tárolót hozhat az
API-ba: az ownership, az élettartam és a párhuzamos módosítás
szerződését ezeknél is explicitté kell tenni.