2012. február 14., kedd

fuser - némileg másként

A múltkor blogbejegyzés után kedvet kaptam némi kis turkászásban a kernel térben, úgy hogy íme egy újabb szösszenet arról, hogy a rendszernek mit is kell csinálnia amikor mi csak úgy dobálózunk a parancsokkal :)

Na szóval.. A mostani alkalomra a fuser nevű parancsot néztem ki magamnak.. Sokat nem kell róla mondani: Általános ismérve, hogy ha egy FS nyitva van, akkor ezt szoktuk meghívni, hogy megnézzük mi is fogja. Nézzük, hogy ezt a funkciót a kernel térből hogy is lehetne elérni:

Első körben kell nekünk egy mount point.. Az én esetemben ez a /opt/IBM lesz, mert miért ne :)

Első körben a kontroll teszt: Nézzük, hogy a rendszer mit is tud róla:
test_server@root:/ # fuser -cux /opt/IBM
/opt/IBM:  1130574c(root) 1253494c(root)
Szupi.. Na akkor nézzük ugyan ezt a kernel térben. A módszer az alábbi dióhéjban:
- Meg kell keresni az adott mount point memória pointerjét
- Ezt a pointert használva megnézni az elérhető I-node bejegyzések pointereit, majd leválogatni amire van bármiféle hivatkozás
- A leválogatott hivatkozásokat alapul véve meg kell nézni, hogy melyiknek mi a Gnode (generic node structure) szegmense
- Ezen segmensekből már le tudjuk válogatni, hogy melyiknek milyen thread a szülője, amiből meg direkt megtalálhatjuk a thread-hez rendelt PID-et.

A valóságban:
Első körben ugye a mount-hoz tartozó cím kell. Annyi probléma van, hogy a mount-ot kikérve ilyen összevisszaságot kapunk:
(0)> mount
                           GFS             DATA TYPE    FLAGS
  1 F10001001D6868B0 014D4D60 F1000100206D2080 JFS2    DEVMOUNT
... /dev/hd4 mounted over /
  2 F10001001D686950 014D4D60 F1000100206A4480 JFS2    DEVMOUNT
... /dev/hd2 mounted over /usr
  3 F10001001D6869F0 014D4D60 F1000100206FB080 JFS2    DEVMOUNT
... /dev/hd9var mounted over /var
  4 F10001001D686810 014D4D60 F1000100206FC880 JFS2    DEVMOUNT
... /dev/hd3 mounted over /tmp
És így tovább.. Ez persze felvet 2 gondot:
- Komolyan végig kell mindent böngésszek, úgy hogy közben kifolyik a szemem?
- Miért nem lehet ezt 1 sorban kirakni, hogy legalább greppelni lehessen rá?

Persze azért annyira mégse rossz a helyzet, csak kicsit bűvészkedni kell az awk-al, és nyomban meglesz az ami nekünk kell:
(0)> mount |awk '{a[NR]=$0} /\/opt\/IBM$/ {print a[NR],a[NR-1]}'
... /dev/IBMlv mounted over /opt/IBM  14 F100010020684550 014D4D60 F1000100226C2C80 JFS2    DEVMOUNT

Ugye hogy így szebb.. Na szóval megvan a pointerünk (F100010020684550). Akkor kérjük le milyen i-node bejegyzések vannak alatta:
(0)> mount F100010020684550 |grep -E "DIR|REG|SOCK" |tail -5
 648 F1000100241643E8     0 F1000100241640B0 F100010020684550 DIR
 649 F1000100240E03E8     0 F1000100240E00B0 F100010020684550 DIR
 650 F100010024092FE8     1 F100010024092CB0 F100010020684550 REG
 651 F10001002411B7E8     2 F10001002411B4B0 F100010020684550 DIR
 652 F1000100226D2FE8     1 F1000100226D2CB0 F100010020684550 DIR  ROOT
Na itt egy újabb probléma.. Mi ez a sok szám, és mit jelentenek? Nos A válasz a következő:
- Az első és második oszlop lényegében ugyan azt takarja. Az első az index-szám, a második pedig a VFS_VNODE pointer.
- A 3. szám egy úgy nevezett counter. Ez mutatja, hogy hány kernel thread használja éppen az adott VFS_VNODE-ot.
- A 4. lesz a VFS_VNODE-hoz tartozó GNODE.
- Az 5.-et most bocs de kihagyom, mert érdektelen jelen szempontból

Ami nekünk ezek közül kell, az azok a GNODE regiszterek, amelyeknek COUNT-ja nagyobb mint 0. Ezt kb az alábbi scriptelési módszerrel tudjuk előhozni:
(0)> mount F100010020684550|grep -E "DIR|REG|SOCK" |awk ' { if ($3 != "0") print }'
 154 F100010036F6A3E8     1 F100010036F6A0B0 F100010020684550 REG
 157 F100010036F3A3E8     1 F100010036F3A0B0 F100010020684550 REG
 160 F100010036F99FE8     1 F100010036F99CB0 F100010020684550 REG
 163 F100010036F69FE8     1 F100010036F69CB0 F100010020684550 REG
 266 F100010036FA6FE8     1 F100010036FA6CB0 F100010020684550 REG
 556 F1000100244D2FE8     1 F1000100244D2CB0 F100010020684550 REG
 557 F1000100243B57E8     1 F1000100243B54B0 F100010020684550 REG
 558 F1000100244CC3E8     1 F1000100244CC0B0 F100010020684550 REG
 559 F1000100244EBBE8     1 F1000100244EB8B0 F100010020684550 REG
 560 F1000100243B0BE8     1 F1000100243B08B0 F100010020684550 REG
 561 F1000100244CB7E8     1 F1000100244CB4B0 F100010020684550 REG
 562 F1000100244A93E8     1 F1000100244A90B0 F100010020684550 REG
 563 F1000100244C8BE8     1 F1000100244C88B0 F100010020684550 REG
 652 F1000100226D2FE8     1 F1000100226D2CB0 F100010020684550 DIR  ROOT

Egy baj van - Ez a lista helyenként iszonyat nagy.. Itt se épp rövid.. A problémám meg az, hogy az itt található GNODE ID-kat kéne mind végigjárjam az alábbi módon:
(0)> gnode F1000100243B08B0 |grep gn_seg
gn_seg........ 00000000000086A6
(0)> scb 2 00000000000086A6 |grep pvproc
pvproc pointer           (pvproc)   : 0000000000000000

(0)> gnode F1000100244C88B0 |grep gn_seg
gn_seg........ 00000000000246AD
(0)> scb 2 00000000000246AD |grep pvproc
pvproc pointer           (pvproc)   : 0000000000000000

(0)> gnode F100010036F99CB0 |grep gn_seg
gn_seg........ 00000000000732D8
(0)> scb 2 00000000000732D8 |grep pvproc
pvproc pointer           (pvproc)   : F100070F00045000
3.-ra csak ráhibáztam az egyikre, de persze ezt így egyesével végigjátszani valami halál.. Mindenesetre ha megvan a pvproc cím, akkor abból már az egyik process-t meg is találhatjuk:
(0)> proc F100070F00045000 |grep ^pvproc
pvproc+045000  276 java     ACTIVE 011404E 0132076 0000000030588400   0 0013
(0)> hcal 011404E
Value hexa: 0011404E          Value decimal: 1130574
Bingo.. 1130574 az egyik PID amint fentebb is láttunk.. Na de akkor hogy is lehet ezt normálisan végignézni, úgy hogy az összes GNODE ID-t végigjátsszuk? Hát, a szomorú hír, hogy a kdb nem tud belső scriptelést, így a shell-re kell támaszkodjunk, ami meg egy részről iszonyat erőforrás zabáló, más részről meg a sok fork miatt ágyúval verébre módszer is. Annyi segítségünk mondjuk van, hogy a kdbnek van '-script' kapcsolója, ami pont az ilyen munkákat igyekszik megkönnyíteni, de azért ez még mindig kevés az optimális működéshez.
test_server@root:/ # for GN_SEG in $(
>       for GNODE_ID in $(echo mount F100010020684550|kdb -script |grep -E "DIR|REG|SOCK" |awk ' { if ($3 != "0") print }')
>       do
>               echo gnode $GNODE_ID|kdb -script|awk '/gn_seg/ {print $NF}'
>       done)
> do
>       echo scb 2 $GN_SEG |kdb -script|awk '/pvproc/ {print}'
> done|sort -u|grep -v 0000000000000000
pvproc pointer           (pvproc)   : F100070F00045000
pvproc pointer           (pvproc)   : F100070F0004C800
Ebből kiindulva már nem nehéz megtalálni azt amire végső soron pályázunk (viszalépés kdb-be, majd.. )
(0)> proc F100070F00045000 |grep ^pvproc
pvproc+045000  276 java     ACTIVE 011404E 0132076 0000000030588400   0 000E
(0)> hcal 011404E
Value hexa: 0011404E          Value decimal: 1130574

(0)> proc F100070F0004C800 |grep ^pvproc
pvproc+04C800  306 sh       ACTIVE 0132076 0000001 0000000008466400   0 0001
(0)> hcal 0132076
Value hexa: 00132076          Value decimal: 1253494

1130574, 1253494. Amiket fentebb is láttunk.. Sőt, még azt is tudjuk, hogy egy sh, meg egy java processről van szó :)
Ha ne adj isten lusták vagyunk visszamenni a fenti script után a kdb-be, akkor némi kis módosítással nyomban ki is lehet kérni az ID-ket:

test_server@root:/ # MOUNT=$(echo mount |kdb -script|awk '{a[NR]=$0} /\/opt\/IBM$/ {print a[NR-1]}'|awk '{print $2}')
test_server@root:/ # for PVPROC in $(
>       for GN_SEG in $(
>               for GNODE_ID in $(echo "mount $MOUNT" |kdb -script|grep -E "DIR|REG|SOCK" |awk ' { if ($3 != "0") print $4}')
>               do
>                       echo gnode $GNODE_ID|kdb -script|awk '/gn_seg/ {print $NF}'
>               done)
>       do
>               echo scb 2 $GN_SEG |kdb -script|awk '/pvproc/ {print $NF}'
>       done|sort -u|grep -v 0000000000000000)
> do
>       echo proc $PVPROC|kdb -script|grep ^pvproc
> done
pvproc+045000  276 java     ACTIVE 011404E 0132076 0000000030588400   0 0017
pvproc+04C800  306 sh       ACTIVE 0132076 0000001 0000000008466400   0 0001

# Mint fent említettem: A módszer csak azokra a GNODE-okra működik aminek a countere (aktív thread-je) nagyobb mint 0. Ez azért problémás, mert ha egy process használ egy file-t, de az épp nem aktív (írás nélküli file, pl tail esetén), akkor ez a módszer is hibádzik. További probléma, hogy a submount-okat (amik szintén fogják az FS-t umount esetén) szintén nem képes kijelezni, szóval nyugodtan tekintsétek ezt is amolyan érdekességnek, tekintve, hogy mind a fuser, mind az lsof bőven alkalmasabb ezeknek a megkeresésére (általában - volt amikor ez a módszer talált meg nekem egy olyan thread-et, amiről más program sose hallott)