Fejezet 18. Témák haladóknak

18.1. Honnan lehet többet megtudni a FreeBSD belső felépítéséről?
18.2. Hogyan lehet bekapcsolódni a FreeBSD fejlesztésébe?
18.3. Mik azok a pillanatkiadások és kiadások?
18.4. Hogyan lehet saját kiadást készíteni?
18.5. A make world parancs miért írja felül a korábban telepített binárisokat?
18.6. Miért nem forgó (“round robin”) névfeloldással lehet elérni a CVSup szervereket és így megosztani köztük a terhelést?
18.7. A -CURRENT forrásait korlátozott interneteléréssel is lehet követni?
18.8. Hogyan lehet 1392 KB-os darabokra felosztani az egyes terjesztéseket?
18.9. Hova lehet küldeni a rendszermaghoz írt kiegészítéseket?
18.10. A rendszer hogyan érzékeli és inicializálja a Plug and Play ISA kártyákat?
18.11. Hogyan lehet főeszközazonosítót rendelni egy általunk fejlesztett meghajtóhoz?
18.12. A könyvtárakra vonatkozóan milyen más kiosztási házirendek léteznek még?
18.13. Hogyan lehet kinyerni a legtöbb információt a rendszermag összeomlásából?
18.14. A dlsym() függvény miért nem működik már az ELF állományokra?
18.15. Hogyan növelhető vagy csökkenthető a rendszermag címtere i386™ architektúrán?

18.1. Honnan lehet többet megtudni a FreeBSD belső felépítéséről?

Jelen pillanatban csak egyetlen mű foglalkozik az operációs rendszerek felépítésével a FreeBSD szemszögéből, név szerint a Marshall Kirk McKusick és George V. Neville-Neil által írt “The Design and Implementation of the FreeBSD Operating System” című könyv (ISBN 0-201-70245-2), amely a FreeBSD 5.X változatára koncentrál.

Emellett a UNIX® típusú rendszerek használatával kapcsolatos ismeret remekül alkalmazható a FreeBSD esetén is.

A témához tartozó többi könyvet a kézikönyv Az operációs rendszerek belső működésével foglalkozó irodalomjegyzékben találhatjuk meg.

18.2. Hogyan lehet bekapcsolódni a FreeBSD fejlesztésébe?

Pontosabb tanácsokat akkor kapunk, ha elolvassuk a FreeBSD fejlesztéséről szóló cikket. Nagyon is számítunk mindenki segítségére!

18.3. Mik azok a pillanatkiadások és kiadások?

Jelenleg három aktív és félig aktív ág van a FreeBSD CVS repositoryjában. (A korábbi ágakat már csak nagyon ritkán módosítják, ezért is csak három aktív fejlesztési ágon fejlesztenek):

  • RELENG_7 avagy 7-STABLE

  • RELENG_8 avagy 8-STABLE

  • HEAD avagy -CURRENT avagy 9-CURRENT

A HEAD nem olyan ág, mint a másik kettő. Ez egyszerűen csak “a jelenlegi, még el nem ágaztatott fejlesztési irány” jelentéssel bír, amire pedig sokszor röviden csak -CURRENT néven hivatkoznak.

Jelen pillanatban a -CURRENT a 9.X fejlesztési irányát képviseli; az 6-STABLE ág, a RELENG_6, 2005 novemberében, a 7-STABLE ág, a RELENG_7, 2008 februárjában, míg a 8-STABLE ág, a RELENG_8, 2009 novemberében vált le a “-CURRENT” ágból.

18.4. Hogyan lehet saját kiadást készíteni?

18.5. A make world parancs miért írja felül a korábban telepített binárisokat?

Mert alapvetően ez lenne a cél: ahogy a neve is sugallja, a rendszer újrafordítása, vagyis a make world parancs feladata a rendszerben található összes bináris újrafordítása, aminek eredményeképpen egy tiszta és összefüggő környezetet kapunk (ezért is tart ilyen sokáig).

Ha a make world vagy a make install parancs futtatása előtt megadjuk a DESTDIR környezeti változót, akkor a frissen létrehozott binárisok az általa mutatott könyvtárba fognak kerülni pontosan úgy, ahogy az eredeti rendszer. Az osztott könyvtárak bizonyos módosításai és egyes programok fordítása azonban könnyen térdre kényszerítheti a make world futását.

18.6. Miért nem forgó (“round robin”) névfeloldással lehet elérni a CVSup szervereket és így megosztani köztük a terhelést?

Habár a CVSup tükrözések óránként frissítik magukat a központi CVSup szerverről, maga a frissítés azonban bármikor megtörténhet. Ennek következményeképpen egyes szervereken frissebb kód található, miközben a többin még az egy órával ezelőtti állapot szerepel. Ha a cvsup.FreeBSD.org forgó névfeloldással működne, akkor a felhasználók mindig egy véletlenszerűen választott CVSup szervert kapnának, és ezért a CVSup egymás utáni futtatásakor könnyen előfordulhatna, hogy a rendszer régebbi forrásait kapjuk vissza.

18.7. A -CURRENT forrásait korlátozott interneteléréssel is lehet követni?

Igen, ezt a CTM használatával anélkül is megtudjuk tenni, hogy le kellene töltenünk az egész forrásfát.

18.8. Hogyan lehet 1392 KB-os darabokra felosztani az egyes terjesztéseket?

Az újabb BSD alapú rendszerekben a split(1) parancsnak már van egy -b paramétere, amellyel tetszőleges méretűre fel tudunk darabolni állományokat.

Íme erre egy példa a /usr/src/release/Makefile állományból:

ZIPNSPLIT=              gzip --no-name -9 -c | split -b 1392k -

18.9. Hova lehet küldeni a rendszermaghoz írt kiegészítéseket?

Erre vonatkozóan vessünk egy pillantást a FreeBSD továbbfejlesztéséről szóló cikkre.

Köszönjük, hogy gondolt ránk!

18.10. A rendszer hogyan érzékeli és inicializálja a Plug and Play ISA kártyákat?

Frank Durda IV () válasza:

Dióhéjban úgy tudnám ezt elmagyarázni, hogy van néhány I/O port, amelyet lekérdezve a PnP kártya képes válaszolni, hogy elérhető-e. Ezért a PnP eszközök keresése azzal kezdődik, hogy a rendszer felteszi a kérdést, van-e PnP kártya a számítógépben. Erre aztán a különböző kártyák a típusuk megjelölésével válaszolnak, amelyet ugyanezen az I/O porton kell visszaolvasni, így ha már legalább egy bitet beállít valaki, akkor folytatható a keresés. Ezután a keresést végző kódrész letiltja az X alatti (a Microsoft® és az Intel® által kiosztott) azonosítóval rendelkező kártyákat, majd ismét megnézi, hogy valaki továbbra is válaszol-e. Amennyiben a válasz 0, az arra utal, hogy már nincs aktív kártya az X azonosító felett. Ezt követően a rendszer megpróbálkozik az X alatti azonosítók lekérdezésével. Végül folytatja az X alatti keresést az X -(korlát / 4) feletti azonosítók letiltásával, majd megismétli az iménti kérdést. Ezzel a félig-meddig bináris keresési módszerrel aztán képes 264 lépésnél jóval kevesebből felderíteni a rendszerünkben megtalálható PnP kártyákat.

Az azonosítók két 32 bit hosszúságú mezőből (ezért írtunk az előbb 264 lépést) és egy 8 bites ellenőrzőösszegből állnak. Az első 32 bit a gyártót azonosítja. Ugyan soha nem vallják be, de úgy tűnik, hogy még ugyanannak a gyártónak is lehetnek eltérő gyártóazonosítóval rendelkező kártyái. A gyártók számára fenntartott 32 bites mező ezért valamennyire túlzás.

A második 32 bit lehet a kártya sorozatszáma vagy bárki más, amely alapján egyértelműen beazonosítható. A gyártó ugyanazzal a 32 bites értékkel nem gyárthat egy másik kártyát, csak abban az esetben, ha a másik 32 bit is eltér. Ennek köszönhetően egy gépen belül még az azonos típusú kártyák is el fognak térni 64 biten.

Az iménti 32 bites csoportok nem lehetnek teljesen nullák, ezért lehetséges, hogy a bináris keresés során a válaszban legalább egy bit mindig aktív lesz.

Miután a rendszer sikeresen beazonosította a rendelkezésre álló kártyákat, egyenként újra elindítja ezeket (ugyanazon az I/O porton keresztül), és megpróbálja kitalálni, hogy az adott eszközöknek milyen erőforrásokra van szüksége, milyen megszakítást akarnak használni stb. Az összes kártyától lekérdezi ezeket az információkat.

Az így megszerzett információkat aztán még kiegészíti a merevlemezen vagy az MLB BIOS-ban található ECU állományok tartalmával. Az ECU és az MLB BIOS PnP támogatása általában viszont nem valódi, és az ilyen eszközök igazából nem is állítanak be semmit maguktól. A BIOS és az ECU átvizsgálása azonban segít a felderítést végző rutinnak értesíteni a tényleges PnP eszközöket, hogy ne foglaljanak el olyan erőforrásokat, amelyeket a rendszer nem tud áthelyezni.

Ezután a PnP eszközöket a kód még egyszer végigjárja és átadja nekik a működésükhöz szükséges I/O, DMA, IRQ és memóracímek hozzárendeléseit. Az eszközök ekkor a megadott helyeken elérhetővé válnak és úgy is maradnak a rendszer következő indításáig, de igazából semmi sem rögzíti ezeket.

Talán túlságosan is egyszerűsítettem a fentieket, de szerintem már ennyi is elegendő az alapok megértéséhez.

A Microsoft néhány elsődleges nyomtatási állapotot jelző portot átrakott PnP-re, azzal a címszóval, hogy egyik kártya sem kódolta át ezeket a címeket az ellenkező I/O ciklusok számára. Találtam is egy eredeti IBM nyomtatókártyát, amely valóban át tudta írni az állapotjelző portot a PnP kezdeti változataiban, de arra a Microsoft csak annyit mondott, hogy “fogós”. Ezért a nyomtatási állapotot jelző portot a címek beállítására használja, illetve még a 0x800-as portot és egy harmadik I/O portot valahol a 0x200 és a 0x3ff környékén.

18.11. Hogyan lehet főeszközazonosítót rendelni egy általunk fejlesztett meghajtóhoz?

2003 februárja óta a FreeBSD képes dinamikusan és önműködően futás közben lefoglalni főeszközazonosítókat a meghajtóknak (lásd devfs(5)), ezért erre tulajdonképpen már nincs szükség.

18.12. A könyvtárakra vonatkozóan milyen más kiosztási házirendek léteznek még?

A könyvtárak más fajta kiosztására vonatkozóan annyit tudok válaszolni, hogy a jelenleg is alkalmazott sémát az 1983-ban megalkotott változata óta változatlanul használjuk. Eredetileg a gyors állományrendszerhez készítettem, de soha nem ragaszkodtam hozzá. Remekül megoldja a cilindercsoportok betelésének problémáját, azonban sokan megjegyezték már, hogy a find(1) esetén gyengén működik. A legtöbb állományrendszert mélységi bejárással hozzák létre, így a könyvtárak szétszóródnak a cilindercsoportok közt és ezzel a későbbi mélységi keresések számára a lehető legrosszabb helyzetet alakítják ki. Ha valaki például tudja előre a létrehozni kívánt könyvtárak számát, akkor ezt úgy lehet megoldani, ha a művelet során (összes / cilindercsoportok) mennyiségű könyvtárat hozunk létre az egyes cilindercsoportokban. Ennek meghatározására nyilvánvalóan lehet adni valamilyen heurisztikát. Már egy kisebb előre rögzített szám, mint például a 10 kiválasztása is legalább egy nagyságrendnyi javulást jelent. Ha szeretnénk megkülönböztetni az állományrendszerek visszaállítását a hagyományos működéstől (amire a jelenlegi algoritmus sokkal érzékenyebb), akkor érdemes tizes csoportokba összefogni a könyvtárakat, feltéve, hogy 10 másodpercen belül hoztuk létre ezeket. Mindenesetre elmondható, hogy ezzel nyugodtan lehet kísérletezni.

Kirk McKusick , 1998 szeptembere

18.13. Hogyan lehet kinyerni a legtöbb információt a rendszermag összeomlásából?

Általában így néz ki a rendszermag összeomlása:

Fatal trap 12: page fault while in kernel mode
fault virtual address   = 0x40
fault code              = supervisor read, page not present
instruction pointer     = 0x8:0xf014a7e5
stack pointer           = 0x10:0xf4ed6f24
frame pointer           = 0x10:0xf4ed6f28
code segment            = base 0x0, limit 0xfffff, type 0x1b
                        = DPL 0, pres 1, def32 1, gran 1
processor eflags        = interrupt enabled, resume, IOPL = 0
current process         = 80 (mount)
interrupt mask          =
trap number             = 12
panic: page fault

Amikor egy ilyen üzenetet látunk, akkor nem elegendő újra előcsalni a hibát és beküldeni. Az utasításszámláló (“instruction pointer”) értéke ugyan nagyon fontos, de sajnos konfigurációk szerint eltérhet. Más szóval úgy fogalmazhatnék, hogy ennek az értéke a használatban levő rendszermag értékétől függően változhat. Ha a GENERIC rendszermagot használjuk valamelyik kiadásból, akkor viszont már elképzelhető, hogy valaki más is le tudja nyomozni a hibát okozó függvényt. Ha viszont egy saját beállításokkal rendelkező rendszermagot használunk, akkor egyedül csak mi vagyunk képesek megmondani a hiba pontos helyét.

Ezért a javaslatom a következő:

  1. Jegyezzük le az utasításszámláló értékét. A 0x8: rész ebben az esetben annyira nem fontos, egyedül csak a 0xf0xxxxxx részre van szükségünk.

  2. A rendszer újraindításakor írjuk be a következőt:

    % nm -n /a.hibát.okozó.rendszermag | grep f0xxxxxx

    ahol az f0xxxxxx az utasításszámláló értéke. Könnyen előfordulhat, hogy ilyenkor még nem találunk egyezést, mivel a rendszermag szimbólumtáblájában csak az egyes függvények belépési pontjai találhatóak, és ha az utasításszámláló általában valamelyikük belsejébe mutat, nem az elejükre. Ha tehát nem még látunk semmit, akkor egyszerűen hagyjuk el az utolsó számjegyet és próbálkozzunk így:

    % nm -n /a.hibát.okozó.rendszermag | grep f0xxxxx

    Ha még ez sem hoz eredményt, akkor vágjunk le a végéről egy újabb számjegyet. Egészen addig csináljuk, amíg nem kapunk valami értékelhető eredményt. Ilyennek tekintjük például azokat a függvényeket, amelyek a hibát okozhatták. Ez ugyan egy nem annyira pontos felderítési eszköz, viszont még ez is jobb a semminél.

A legjobb viszont mégis az, amikor sikerül lementeni a hiba bekövetkezésekor a memória tartalmát, majd a kgdb(1) használatával előbányászni belőle egy hívási láncot.

Ehhez többnyire a következő módszer javasolt:

  1. A rendszermag konfigurációs állományába (/usr/src/sys/arch/conf/RENDSZERMAGKONFIG) vegyük fel a következő sort:

    makeoptions     DEBUG=-g          # A rendszermag fordítása gdb(1) szimbólumokkal
  2. Lépjünk be a /usr/src könyvtárba:

    # cd /usr/src
  3. Fordítsuk le a rendszermagot:

    # make buildkernel KERNCONF=RENDSZERMAGKONFIG
  4. Várjuk meg, amíg a make(1) befejezi a fordítást.

  5. # make installkernel KERNCONF=RENDSZERMAGKONFIG
  6. Indítsuk újra a gépet.

Megjegyzés: A KERNCONF használata nélkül a GENERIC rendszermag fordul és telepítődik.

A make(1) programnak a folyamat végeredményeként két rendszermagot kell készítenie: a /usr/obj/usr/src/sys/RENDSZERMAGKONFIG/kernel és a /usr/obj/usr/src/sys/RENDSZERMAGKONFIG/kernel.debug. Ezek közül a kernel /boot/kernel/kernel néven mentődik el, miközben a kernel.debug használható nyomonkövetésre a kgdb(1) programmal.

A rendszer csak akkor fogja elmenteni összeomláskor a memória tartalmát, ha az /etc/rc.conf állományban beállítjuk a dumpdev értékét a lapozóállományt tároló partícióra (vagy az AUTO értékre). Ennek hatására az rc(8) szkriptek a dumpon(8) paranccsal képesek engedélyezni a memória lementését. A dumpon(8) természetesen manuálisan is elindítható. Az összeomlást követően a memória lementett tartalmához a savecore(8) programmal férhetünk hozzá. Amikor viszont az /etc/rc.conf állományban megadjuk a dumpdev értékét, az rc(8) szkriptek maguktól lefuttatják a savecore(8) parancsot és átrakják a mentést a /var/crash könyvtárba.

Megjegyzés: A FreeBSD által létrehozott memóriamentések mérete általában a számítógépünkben levő fizikai memória mennyiségével egyezik meg. Tehát ha 512 MB RAM van a gépünkben, akkor egy 512 MB méretű mentést fogunk kapni. Ezért gondoskodjunk róla, hogy a /var/crash könyvtárban mindig legyen elegendő hely az állomány tárolásához. A savecore(8) kézzel is lefuttathazó, és ilyenkor a memóriát akár egy másik könyvtárba is menthetjük. A mentés méretét options MAXMEM=N beállítással is korlátozhatjuk, ahol az N értéke a rendszermag által használható memória mérete KB-okban. Például, ha 1 GB RAM van a gépünkben, de a rendszermag által használható memóriát lekorlátozzuk 128 MB-ra, akkor a mentés mérete sem 1 GB lesz, hanem csak 128 MB.

Ahogy sikerült hozzájutnunk a memóriamentéshez, azonnal is kérhetünk a kgdb(1) használatával egy hívási láncot belőle:

% kgdb /usr/obj/usr/sys/RENDSZERMAGKONFIG/kernel.debug /var/crash/vmcore.0
(kgdb) backtrace

Előfordulhat, hogy ilyenkor több oldalnyi információ özönlik hirtelen a képernyőre, ezért javasolt ezeket lementeni a script(1) programmal. A nyomkövetési szimbólumokat is tartalmazó rendszermag esetén még akár azt a sort is megkapjuk a rendszermagon belül, ahol a hiba történt. A hívási láncot általában alulról felfelé kell olvasni, és ebből deríthető, hogy pontosan milyen események is vezettek az összeomláshoz. A kgdb(1) használatával még a különböző változók és struktúrák értékeit is meg tudjuk vizsgálni, így még többet megtudhatunk a rendszer állapotáról az összeomlás pillanatában.

Tipp: Ha az iméntiek mentén nagyon fellelkesültünk volna és van egy másik számítógépünk is, akkor a kgdb(1) akár távoli nyomkövetésre is beállítható, aminek köszönhetően a kgdb(1) használatával az egyik rendszeren meg tudjuk állítani a másikon futó rendszermagot, ellenőrizhetjük a viselkedését, akárcsak bármelyik más felhasználói program esetében.

Ha netalán engedélyeztük volna a DDB beállítást, és a rendszermag beleáll a nyomkövetőbe, akkor a rendszert mi magunk is össze tudjuk omlasztani (és így a memóriát elmenteni) a ddb parancssorában a panic parancs kiadásával. Ilyenkor a nyomkövető általában még egyszer megáll az összeomláskor. Ekkor a continue paranccsal fejeztethetjük be a memória lementését.

18.14. A dlsym() függvény miért nem működik már az ELF állományokra?

Az ELF állományokhoz tartozó segédprogramok alapértelmezés szerint nem teszik láthatóvá a dinamikus linker számára a végrehajtható állományban definiált szimbólumokat. Ennek eredményeképpen a dlsym() a dlopen(NULL, flags) függvénytől kapott információk alapján nem találja meg a keresett szimbólumokat.

Ha szükségünk lenne ilyen keresésekre a dlsym() használata során a program végrehajtható állományán belül, akkor az adott programot a --export-dynamic opció megadásával kell linkelni (lásd ld(1)).

18.15. Hogyan növelhető vagy csökkenthető a rendszermag címtere i386™ architektúrán?

Az i386 platformon a rendszermag címtere alapértelmezés szerint 1 GB (PAE esetén 2 GB). Ha komolyabb hálózati forgalmat bonyolító szerverünk van (például egy nagyobb FTP vagy HTTP szerver) vagy rendszerükön használni akarjuk a ZFS állományrendszert, akkor könnyen kifuthatunk a címtérből.

A címtér méretének megváltoztatásához vegyük fel a következő sort a rendszermag konfigurációs állományába, majd fordítsuk újra a rendszermagot:

options KVA_PAGES=N

Az N megfelelő értékének megállapításához osszuk el a beállítani kívánt címtér (MB-okban megadott) méretét néggyel. (Tehát például 2 GB esetén ez 512 lesz.)

Ha kérdése van a FreeBSD-vel kapcsolatban, a következő címre írhat (angolul): <freebsd-questions@FreeBSD.org>.
Ha ezzel a dokumentummal kapcsolatban van kérdése, kérjük erre a címre írjon: <gabor@FreeBSD.org>.