Devfs 在Linux上的架構介紹

Devfs 在Linux上的架構介紹

一, 前言
在大多數的Linux環境下,驅動程式所註冊的裝置檔案主要是透過major 與 minor 號碼來辨認的,因此在Linux的/dev目錄下,會存在許多系統中預先設定好的裝置檔案。因為存在這樣的機制,所造成的問題就是有時我們並沒有用到的裝置也會在/dev目錄下存在有它的裝置檔案。隨著Linux套件的演進,支援的硬體週邊相對的會更為廣泛,如現在的RedHat7.0的/dev目錄下,就存在有約6000個裝置檔案,而這樣多的裝置檔案中,其實絕大多數都是我們系統中所不存在的,為了要解決這樣的問題,Devfs檔案系統於是有了它存在的必要,透過Devfs我們會發現只有真正在系統中存在的裝置才會有它的裝置檔案存在系統中,也因為這樣,我們無須再像之前的Linux環境一樣,預先在/dev目錄下建立許多我們 ”可能”會用到的裝置檔案,只需在真正的裝置驅動程式載入系統時,由驅動程式對Devfs環境註冊即可。
如下所示,為筆者在RedHat7.0中所列出的/dev目錄內容


    
/dev
MAKEDEV* md5 sdag7 sdci15 sdr1 ttyF185 ttySR323
X0R@ md6 sdag8 sdci2 sdr10 ttyF186 ttySR324
aaa md7 sdag9 sdci3 sdr11 ttyF187 ttySR325
adbmouse md8 sdah sdci4 sdr12 ttyF188 ttySR326
agpgart md9 sdah1 sdci5 sdr13 ttyF189 ttySR327
amigamouse mdsp1 sdah10 sdci6 sdr14 ttyF19 ttySR328
amigamouse1 mdsp10 sdah11 sdci7 sdr15 ttyF190 ttySR329
apm_bios mdsp11 sdah12 sdci8 sdr2 ttyF191 ttySR33
atarimouse mdsp12 sdah13 sdci9 sdr3 ttyF192 ttySR330
atibm mdsp13 sdah14 sdcj sdr4 ttyF193 ttySR331
atimouse mdsp14 sdah15 sdcj1 sdr5 ttyF194 ttySR332
audio mdsp15 sdah2 sdcj10 sdr6 ttyF195 ttySR333
audio1 mdsp16 sdah3 sdcj11 sdr7 ttyF196 ttySR334
audioctl mdsp2 sdah4 sdcj12 sdr8 ttyF197 ttySR335
aztcd mdsp3 sdah5 sdcj13 sdr9 ttyF198 ttySR336
bpcd mdsp4 sdah6 sdcj14 sds ttyF199 ttySR337
bttv0@ mdsp5 sdah7 sdcj15 sds1 ttyF2 ttySR338
capi20 mdsp6 sdah8 sdcj2 sds10 ttyF20 ttySR339
………
…………



















































Devfs檔案系統最大的好處就是建立了一個NameSpace的機制,讓裝置的檔案不需要由系統再透過major與minor的號碼來辨認,而可以透過較有意義的裝置名稱來辨別。例如我們現在的Major號碼主要的意義就是Device ID,例如﹕軟碟機floppy=02,音效裝置audio=14,ISDN設備isdn=45,第一個排線上的硬碟機hda=3…….等。而Minor號碼則是代表了所屬Device ID的裝置編號,例如以硬碟機為例子,hda的Partition Table部分的Minor號碼為0、第一個分割區為1、第二個分割區為2………。
不過所有要支援Devfs檔案系統的驅動程式,都必須要加入Devfs的註冊函式,也就是說在我們所使用的環境中可能會存在有舊版本的驅動程式,如果為了相容性的問題,其實就算同時保留了過去的裝置檔案也是可以的。有支援Devfs的驅動程式,則只要在初始化的過程中呼叫函式devfs_register()即可對Devfs檔案系統註冊該裝置檔案。

其實使用Devfs檔案系統的好處不只在於系統中只會存在有真正有用到的裝置檔案,以節省資源增進搜尋的效率。其實Devfs檔案系統還建立了一個較為簡潔的裝置檔案目錄架構,如下所示,為筆者電腦的Devfs檔案系統目錄結構


    
/devfs檔案系統目錄
./shm
./vc
./misc
./pty
./pts
./vcc
./tts
./cua
./ide/host0/bus0/target0/lun0
./ide/host0/bus0/target0
./ide/host0/bus0/target1/lun0
./ide/host0/bus0/target1
./ide/host0/bus0
./ide/host0
./ide
./cdroms
./discs
./floppy




























這樣的目錄結構,不像過去我們所使用的裝置檔案系統是把所有的裝置檔案置於同一個目錄下,相對的它依每個裝置的屬性不同而建立了不同的目錄,透過這樣的機制我們會發現裝置檔案系統的環境更為簡潔與易於了解。
有了這樣的機制後,原本筆者RedHat7.0 /dev目錄擁有超過6000個以上的裝置檔案,而現在只會擁有裝置過的硬體設備,其它目前所尚未置入的裝置,就不會顯示在系統目錄下。帶來的好處很多,像是如果我們現在要開啟某一個裝置檔案的話,就不需要透過檔案系統去搜尋那麼多的檔案,而可以在精簡化的/dev目錄下,用有意義的裝置名稱來辨認我們的裝置,取代了過去透過major 與 minor 號碼來辨認的缺點。
最後,感謝澳洲的 Richard Gooch 先生對於Devfs系統的貢獻,如果沒有他如此無私的付出與實做整套系統,就不會有現在所得到的Devfs檔案系統。
二, 採用Devfs
Devfs目前可以在2.2.x與2.4.x系列的核心運作,不過Liunx核心2.2.x尚未把Devfs附屬在Linux Kernel的原始碼當中,所以需要在2.2.x環境中使用Devfs的話,還需要經過一些Kernel的Patch,本文主要使用的環境為Linux2.4.5,在這個版本中的核心已經把Devfs納入了,所以我們只需要在選定核心功能時,將Devfs選入即可。
要在系統中提供Devfs的機制,除了要有2.4.x版本的核心以外,我們還需要一個devfsd的工具程式,原本筆者是在自己的RedHat6.0上面安裝devfsd的程式,不過由於那個環境所使用的gcc、binutiles與glibc都需要經過升級才能順利安裝devfsd,所以我後來是在另一台安裝RedHat7.0的電腦上面安裝devfsd的程式碼。因此,如果要嘗試自行安裝devfsd程式碼的話,如果所屬的Linux環境函式庫以及開發工具比較舊的話,那很可能各位需要自行把執行環境加以升級,或是直接選用比較新的環境。
在核心編譯的選項,需要把如下的選項打開
/dev file system support (EXPERIMENTAL) (CONFIG_DEVFS_FS) [Y/n/?]
如果希望可以在開機時自動的啟動Devfs服務,則須把以下選項打開
Automatically mount at boot (CONFIG_DEVFS_MOUNT) [Y/n/?]
不過有一點要注意的是,如果我們選擇了一開機時就啟動Devfs的話,那樣我們必須在開機時Script檔案前先把使用者端的常駐程式devfsd載入,例如在筆者的RedHat7.0中,就是在rc.sysinit加入Script來在開機過程中載入devfsd常駐程式,如下

 
    
[root@hlchou linux]# more /etc/rc.d/rc.sysinit
#!/bin/bash
………
………
# If we’re using devfs, start devfsd now – we need the old device names
[ -e /dev/.devfsd -a -x /sbin/devfsd ] && /sbin/devfsd /dev




























如果我們不打算在一開機時就啟動Devfs的話,就可以關閉該選項,只需在開機進入系統後,透過手動方式執行以下指令即可
mount -t devfs /mnt1 /mnt2
mnt1使用者可以輸入任意的目錄名稱。mnt2Devfs所要載入的目錄,例如我們可以設定為/devfs。如筆者下了以下的指令

 
    
[root@hlchou /root]# ls /devfs
[root@hlchou /root]# mount -t devfs /mnt1 /devfs
[root@hlchou /root]# ls /devfs
cdroms/ discs/ ide/ misc/ ptmx random tts/ vc/
console floppy/ kmem null pts/ root@ tty vcc/
cua/ full mem port pty/ shm/ urandom zero
[root@hlchou /root]# echo “test /devfs/tty” > /devfs/tty
test /devfs/tty
[root@hlchou /root]#




























Devfs檔案系統載入到/devfs後,透過echo指令測試我們所載入的Devfs檔案系統當中的Device是否真的有發生作用把所輸入的字元透過tty裝置秀出。
在核心載入Devfs後,我們還需要透過使用者端的devfsd常駐程式來建立與之前裝置檔案系統的相容性,如下所示

 
    
[root@hlchou /root]# devfsd /devfs

Started device management daemon for /devfs

[root@hlchou /root]# ls /devfs
agpgart@ ptyb3@ ptydd@ ptyq7@ ptyt1@ ptyvb@ ptyy5@ tty13@ tty51@
cdroms/ ptyb4@ ptyde@ ptyq8@ ptyt2@ ptyvc@ ptyy6@ tty14@ tty52@
console ptyb5@ ptydf@ ptyq9@ ptyt3@ ptyvd@ ptyy7@ tty15@ tty53@
cua/ ptyb6@ ptye0@ ptyqa@ ptyt4@ ptyve@ ptyy8@ tty16@ tty54@
cua0@ ptyb7@ ptye1@ ptyqb@ ptyt5@ ptyvf@ ptyy9@ tty17@ tty55@
cua1@ ptyb8@ ptye2@ ptyqc@ ptyt6@ ptyw0@ ptyya@ tty18@ tty56@
discs/ ptyb9@ ptye3@ ptyqd@ ptyt7@ ptyw1@ ptyyb@ tty19@ tty57@
fd@ ptyba@ ptye4@ ptyqe@ ptyt8@ ptyw2@ ptyyc@ tty2@ tty58@
floppy/ ptybb@ ptye5@ ptyqf@ ptyt9@ ptyw3@ ptyyd@ tty20@ tty59@
full ptybc@ ptye6@ ptyr0@ ptyta@ ptyw4@ ptyye@ tty21@ tty6@
hda@ ptybd@ ptye7@ ptyr1@ ptytb@ ptyw5@ ptyyf@ tty22@ tty60@
hda1@ ptybe@ ptye8@ ptyr2@ ptytc@ ptyw6@ ptyz0@ tty23@ tty61@
hda2@ ptybf@ ptye9@ ptyr3@ ptytd@ ptyw7@ ptyz1@ tty24@ tty62@
ide/ ptyc0@ ptyea@ ptyr4@ ptyte@ ptyw8@ ptyz2@ tty25@ tty63@
kmem ptyc1@ ptyeb@ ptyr5@ ptytf@ ptyw9@ ptyz3@ tty26@ tty7@
mem ptyc2@ ptyec@ ptyr6@ ptyu0@ ptywa@ ptyz4@ tty27@ tty8@
misc/ ptyc3@ ptyed@ ptyr7@ ptyu1@ ptywb@ ptyz5@ tty28@ tty9@
null ptyc4@ ptyee@ ptyr8@ ptyu2@ ptywc@ ptyz6@ tty29@ ttyS0@
port ptyc5@ ptyef@ ptyr9@ ptyu3@ ptywd@ ptyz7@ tty3@ ttyS1@
psaux@ ptyc6@ ptyp0@ ptyra@ ptyu4@ ptywe@ ptyz8@ tty30@ urandom
ptmx ptyc7@ ptyp1@ ptyrb@ ptyu5@ ptywf@ ptyz9@ tty31@ vc/
pts/ ptyc8@ ptyp2@ ptyrc@ ptyu6@ ptyx0@ ptyza@ tty32@ vcc/
pty/ ptyc9@ ptyp3@ ptyrd@ ptyu7@ ptyx1@ ptyzb@ tty33@ vcs@
ptya0@ ptyca@ ptyp4@ ptyre@ ptyu8@ ptyx2@ ptyzc@ tty34@ vcs1@
ptya1@ ptycb@ ptyp5@ ptyrf@ ptyu9@ ptyx3@ ptyzd@ tty35@ vcs2@
ptya2@ ptycc@ ptyp6@ ptys0@ ptyua@ ptyx4@ ptyze@ tty36@ vcs3@
ptya3@ ptycd@ ptyp7@ ptys1@ ptyub@ ptyx5@ ptyzf@ tty37@ vcs4@
ptya4@ ptyce@ ptyp8@ ptys2@ ptyuc@ ptyx6@ radeon@ tty38@ vcs5@
ptya5@ ptycf@ ptyp9@ ptys3@ ptyud@ ptyx7@ random tty39@ vcs6@
ptya6@ ptyd0@ ptypa@ ptys4@ ptyue@ ptyx8@ root@ tty4@ vcsa@
ptya7@ ptyd1@ ptypb@ ptys5@ ptyuf@ ptyx9@ shm/ tty40@ vcsa1@
ptya8@ ptyd2@ ptypc@ ptys6@ ptyv0@ ptyxa@ stderr@ tty41@ vcsa2@
ptya9@ ptyd3@ ptypd@ ptys7@ ptyv1@ ptyxb@ stdin@ tty42@ vcsa3@
ptyaa@ ptyd4@ ptype@ ptys8@ ptyv2@ ptyxc@ stdout@ tty43@ vcsa4@
ptyab@ ptyd5@ ptypf@ ptys9@ ptyv3@ ptyxd@ tdfx@ tty44@ vcsa5@
ptyac@ ptyd6@ ptyq0@ ptysa@ ptyv4@ ptyxe@ tts/ tty45@ vcsa6@
ptyad@ ptyd7@ ptyq1@ ptysb@ ptyv5@ ptyxf@ tty tty46@ zero
ptyae@ ptyd8@ ptyq2@ ptysc@ ptyv6@ ptyy0@ tty0@ tty47@
ptyaf@ ptyd9@ ptyq3@ ptysd@ ptyv7@ ptyy1@ tty1@ tty48@
ptyb0@ ptyda@ ptyq4@ ptyse@ ptyv8@ ptyy2@ tty10@ tty49@
ptyb1@ ptydb@ ptyq5@ ptysf@ ptyv9@ ptyy3@ tty11@ tty5@
ptyb2@    ptydc@  ptyq6@  ptyt0@  ptyva@  ptyy4@  tty12@   tty50@





























































同樣的,我們可以透過以下的步驟來得知Devfs與原本的裝置檔案系統相容性
首先,透過/dev/devfs 目錄查看第一個磁碟機的第一個分割區所對應到的裝置檔案
[root@hlchou /root]# ls /dev/hda -l
brw-rw—- 1 root disk 3, 0 8 24 2000 /dev/hda
[root@hlchou /root]# ls /devfs/hda -l
lr-xr-xr-x 1 root root 32 6 13 20:02 /devfs/hda -> ide/host0/bus0/target0/lun0/disc
透過不同的裝置檔案系統架構﹝過去的/dev與目前的/devfs﹞,來讀取第一個磁碟機的MBR
[root@hlchou /root]# dd if=/dev/hda of=/mbr_dev bs=512 count=1
1+0 records in
1+0 records out
[root@hlchou /root]# dd if=/devfs/hda of=/mbr_devfs bs=512 count=1
1+0 records in
1+0 records out
[root@hlchou/root]# dd if=/devfs/ide/host0/bus0/target0/lun0/disc of=/mbr_devfs2 bs=512 count=1
1+0 records in
1+0 records out
分別透過hexdump來查看剛剛所讀取出來的磁碟機MBR資料是否正確無誤
[root@hlchou /root]# hexdump /mbr_dev
0000000 ebfa 6c7c 6162 494c 4f4c 0001 0415 005a
0000010 0000 0000 cbd5 3b20 ac80 4cc0 8101 c0ac
0000020 014c ac7f 4cc0 0101 5a44 ac83 4cc0 8401
0000030 c0ac 014c 36c0 4cc0 c101 c036 014c 36c2
0000040 4cc0 c301 c036 014c 36c4 4cc0 c501 c036
…………
00001f0 0000 0000 0000 0000 0000 0000 0000 aa55
0000200
[root@hlchou /root]# hexdump /mbr_devfs
0000000 ebfa 6c7c 6162 494c 4f4c 0001 0415 005a
0000010 0000 0000 cbd5 3b20 ac80 4cc0 8101 c0ac
0000020 014c ac7f 4cc0 0101 5a44 ac83 4cc0 8401
0000030 c0ac 014c 36c0 4cc0 c101 c036 014c 36c2
0000040 4cc0 c301 c036 014c 36c4 4cc0 c501 c036
………..
00001f0 0000 0000 0000 0000 0000 0000 0000 aa55
0000200
[root@hlchou /root]# hexdump /mbr_devfs2
0000000 ebfa 6c7c 6162 494c 4f4c 0001 0415 005a
0000010 0000 0000 cbd5 3b20 ac80 4cc0 8101 c0ac
0000020 014c ac7f 4cc0 0101 5a44 ac83 4cc0 8401
0000030 c0ac 014c 36c0 4cc0 c101 c036 014c 36c2
0000040 4cc0 c301 c036 014c 36c4 4cc0 c501 c036
………..
00001f0 0000 0000 0000 0000 0000 0000 0000 aa55
0000200
當然囉,如以上所見到的,因為我們把Devfs與原本的/dev並沒有載入到相同的目錄,可是我們同樣都透過兩個不同的裝置檔案系統來存取他的磁碟機MBR磁區,並且加以比對,驗證所讀取的資料是正確的。不過我們可以了解到一點,那就是Devfs與原本的裝置檔案系統彼此間是可以共同存在的,由於要透過Devfs裝置檔案系統的話,所有的驅動程式都必須做一些微幅的修改動作,例如加入 devfs_register()函式,來註冊Device的名稱…..等,所以並非所有的驅動程式都會修正完整,因此在與過去裝置檔案系統的相容上,Devfs算是維持的相當不錯。
同樣的,如果希望Devfs服務可以提供除錯的資訊,那樣就要把以下的選項打開,
Debug devfs (CONFIG_DEVFS_DEBUG) [Y/n/?]
這樣使用者可以透過常駐程式devfsd的選項-D來設定除錯的模式,例如
devfsd /devfs -D 0x1FFFF
其中,”/devfs”是我們Devfs檔案系統所載入的目錄,而-D 後面的0x1FFFF表示目前的Debug Mask,在Linux Kernel 2.4.5中所附的Devfs共有以下幾個Debug Mask
#define DEBUG_MODULE_LOAD 0x00001
#define DEBUG_REGISTER 0x00002
#define DEBUG_UNREGISTER 0x00004
#define DEBUG_SET_FLAGS 0x00008
#define DEBUG_S_PUT 0x00010
#define DEBUG_I_LOOKUP 0x00020
#define DEBUG_I_CREATE 0x00040
#define DEBUG_I_READ 0x00080
#define DEBUG_I_WRITE 0x00100
#define DEBUG_I_UNLINK 0x00200
#define DEBUG_I_RLINK 0x00400
#define DEBUG_I_FLINK 0x00800
#define DEBUG_I_MKNOD 0x01000
#define DEBUG_F_READDIR 0x02000
#define DEBUG_D_DELETE 0x04000
#define DEBUG_D_RELEASE 0x08000
#define DEBUG_D_IPUT 0x10000
所以說,如果我們要打開所有的除錯選項的話,就把上面所有的選項拿來做一次 or 運作,而得到 0x1FFFF。同理,如果要關掉除錯功能的話,只需要如下指令即可
devfsd /devfs -D 0x00000
我使用的環境中,Linux核心是採用Kernel 2.4.5的版本,而devfsd是採用版本1.3.11。如果說,我們把Devfs檔案系統在核心設為一開機時就自動啟動的話,那樣Devfs在開機啟動時所預設的載入目錄為/dev,不過這樣會造成一個問題就是,每次一開機時,Ext2檔案系統都會透過Script進行檢修,查看我們是否有不正常關機或是檔案系統載入次數已達需要檢查的次數,如果是的話,那就會透過該工具進行檢修的動作,可是像這些工具基本上都還是使用過去的裝置檔案,例如﹕ fsck /dev/hda1。
一個啟動了Devfs檔案系統的目錄如下

 
    
[root@hlchou /root]# ls /devfs
cdroms/ discs/ ide/ misc/ ptmx random tts/ vc/
console floppy/ kmem null pts/ root@ tty vcc/
cua/ full mem port pty/ shm/ urandom zero



























這裡面並沒有之前/dev目錄下的hda1裝置檔案,所以我們需要在系統一開始啟動時,就把使用者端的常駐程式啟動。Devfs作者所建立的網站網站﹝http://www.atnf.csiro.au/~rgooch/linux/docs/devfs.html﹞建議我們把 “/sbin/devfsd /dev”加入到 Script檔案起始的部分 ,在我們啟動devfsd程式後,它會先到我們指定的目錄去開啟檔案.devfsd,如果成功就繼續執行,如果失敗的話就顯示錯誤訊息並且終止程式。透過檔案.devfsd,程式devfsd可以與核心的Devfs檔案系統進行通訊,例如﹕devfsd可以開啟核心Devfs的除錯模式,或是取得核心Devfs目前所提供的Devfs通訊版本,設定通訊的事件….等。
在我們啟動程式devfsd後,Devfs檔案系統目錄會變成如下的內容

 
    
[root@hlchou /root]# devfsd /devfs
Started device management daemon for /devfs
[root@hlchou /root]# ls /devfs
agpgart@ ptyb3@ ptydd@ ptyq7@ ptyt1@ ptyvb@ ptyy5@ tty13@ tty51@
cdroms/ ptyb4@ ptyde@ ptyq8@ ptyt2@ ptyvc@ ptyy6@ tty14@ tty52@
console ptyb5@ ptydf@ ptyq9@ ptyt3@ ptyvd@ ptyy7@ tty15@ tty53@
cua/ ptyb6@ ptye0@ ptyqa@ ptyt4@ ptyve@ ptyy8@ tty16@ tty54@
cua0@ ptyb7@ ptye1@ ptyqb@ ptyt5@ ptyvf@ ptyy9@ tty17@ tty55@
cua1@ ptyb8@ ptye2@ ptyqc@ ptyt6@ ptyw0@ ptyya@ tty18@ tty56@
discs/ ptyb9@ ptye3@ ptyqd@ ptyt7@ ptyw1@ ptyyb@ tty19@ tty57@
fd@ ptyba@ ptye4@ ptyqe@ ptyt8@ ptyw2@ ptyyc@ tty2@ tty58@
floppy/ ptybb@ ptye5@ ptyqf@ ptyt9@ ptyw3@ ptyyd@ tty20@ tty59@
full ptybc@ ptye6@ ptyr0@ ptyta@ ptyw4@ ptyye@ tty21@ tty6@
hda@ ptybd@ ptye7@ ptyr1@ ptytb@ ptyw5@ ptyyf@ tty22@ tty60@
hda1@ ptybe@ ptye8@ ptyr2@ ptytc@ ptyw6@ ptyz0@ tty23@ tty61@
hda2@ ptybf@ ptye9@ ptyr3@ ptytd@ ptyw7@ ptyz1@ tty24@ tty62@
ide/ ptyc0@ ptyea@ ptyr4@ ptyte@ ptyw8@ ptyz2@ tty25@ tty63@
kmem ptyc1@ ptyeb@ ptyr5@ ptytf@ ptyw9@ ptyz3@ tty26@ tty7@
mem ptyc2@ ptyec@ ptyr6@ ptyu0@ ptywa@ ptyz4@ tty27@ tty8@
misc/ ptyc3@ ptyed@ ptyr7@ ptyu1@ ptywb@ ptyz5@ tty28@ tty9@
null ptyc4@ ptyee@ ptyr8@ ptyu2@ ptywc@ ptyz6@ tty29@ ttyS0@
port ptyc5@ ptyef@ ptyr9@ ptyu3@ ptywd@ ptyz7@ tty3@ ttyS1@
psaux@ ptyc6@ ptyp0@ ptyra@ ptyu4@ ptywe@ ptyz8@ tty30@ urandom
ptmx ptyc7@ ptyp1@ ptyrb@ ptyu5@ ptywf@ ptyz9@ tty31@ vc/
pts/ ptyc8@ ptyp2@ ptyrc@ ptyu6@ ptyx0@ ptyza@ tty32@ vcc/
pty/ ptyc9@ ptyp3@ ptyrd@ ptyu7@ ptyx1@ ptyzb@ tty33@ vcs@
















































如下,以磁碟機裝置為例,我們可以檢示啟動程式devfsd後,會建立與原本/dev 目錄下一樣的 hda、hda1與hda2三個裝置檔案,不過像筆者電腦第一個硬碟只有分個成兩個Partition,所以只有hda、hda1與hda2。而在使用原本的/dev目錄下,卻幫我建立了hda、hda1、hda2….hda16共17個裝置檔案。

 
    
[root@hlchou linux]# ls /devfs/hda* -l
lr-xr-xr-x 1 root root 32 6 15 11:56 /devfs/hda -> ide/host0/bus0/target0/lun0/disc
lr-xr-xr-x 1 root root 33 6月 15 11:56 /devfs/hda1 -> ide/host0/bus0/target0/lun0/part1
lr-xr-xr-x 1 root root 33 6 15 11:56 /devfs/hda2 -> ide/host0/bus0/target0/lun0/part2
[root@hlchou linux]# ls /dev/hda* -l
brw-rw—- 1 root disk 3, 0 8 24 2000 /dev/hda
brw-rw—- 1 root disk 3, 1 8 24 2000 /dev/hda1
brw-rw—- 1 root disk 3, 10 8 24 2000 /dev/hda10
brw-rw—- 1 root disk 3, 11 8 24 2000 /dev/hda11
brw-rw—- 1 root disk 3, 12 8月 24 2000 /dev/hda12
brw-rw—- 1 root disk 3, 13 8 24 2000 /dev/hda13
brw-rw—- 1 root disk 3, 14 8 24 2000 /dev/hda14
brw-rw—- 1 root disk 3, 15 8 24 2000 /dev/hda15
brw-rw—- 1 root disk 3, 16 8 24 2000 /dev/hda16
brw-rw—- 1 root disk 3, 2 8 24 2000 /dev/hda2
brw-rw—- 1 root disk 3, 3 8 24 2000 /dev/hda3
brw-rw—- 1 root disk 3, 4 8 24 2000 /dev/hda4
brw-rw—- 1 root disk 3, 5 8 24 2000 /dev/hda5
brw-rw—- 1 root disk 3, 6 8 24 2000 /dev/hda6
brw-rw—- 1 root disk 3, 7 8 24 2000 /dev/hda7
brw-rw—- 1 root disk 3, 8 8 24 2000 /dev/hda8
brw-rw—- 1 root disk 3, 9 8 24 2000 /dev/hda9
[root@hlchou linux]#























































當然囉,對於懂得自行新增與刪除/dev內裝置檔案的使用者來說,如果在包裝自己縮小化的檔案系統時,可以自行把用不到的裝置檔案刪去。可是對於大多數的使用者來說,通常現在安裝的套件都會先幫使用者把可能會用到的裝置檔案產生在/dev目錄下,也因此才會隨著週邊裝置的增加而造成原本的/dev目錄下的裝置檔案數目越來越龐大。
透過這樣的機制,我們可以應用在對於硬體需求比較低的系統上,讓裝置檔案系統更為簡化與單純,例如應用在嵌入式的環境下,通常會把所需要用到的檔案系統透過cramfs存在Flash或是透過Ramdisk使用記憶體空間來儲存,而Devfs這樣精簡型的架構就可以讓這些目前仍嫌昂貴的儲存媒體有更好的利用率。
三, devfs啟動流程
如圖﹝二﹞所示,在系統初始化時,會先進入函式start_kernel﹝﹞,之後進入init﹝﹞。而在函式init﹝﹞中會先進入do_basic_setup﹝﹞再由函式do_initcalls﹝﹞來初始化Devfs的檔案系統,如圖所示init_devfs_fs﹝﹞函式會呼叫register_filesystem﹝﹞對系統註冊Devfs檔案系統。
透過這樣的註冊動作,我們可以由圖﹝一﹞了解Devfs檔案系統與VFS在系統核心上的相對關係。


圖﹝一﹞
在函式init﹝﹞中,do_basic_setup﹝﹞函式結束後,會進入函式prepare_namespace﹝﹞,之後會呼叫mount_devfs_fs﹝﹞去確認我們是否選擇要在一開機時就把Devfs自動的載入檔案系統,不過自動載入的預設目錄為/dev,對於希望把Devfs檔案系統與原本的裝置檔案系統目錄分開的使用者來說,其實可以透過mount指令自行把Devfs檔案系統選擇載入到特定的目錄。
圖﹝二﹞
如下圖﹝三﹞,是系統中Devfs運作的基本概念,我假設Devfs檔案系統並不會在一開機時就自動載入,而是在系統開機完成後,由使用者透過”mount”指令自行載入。
首先在Kernel初始化的過程中,Devfs驅動程式會緊接著被初始化。之後,會陸續的載入其他裝置的驅動程式,而那些有支援Devfs檔案系統的驅動程式,會透過呼叫devfs_register﹝﹞函式來對Devfs進行註冊動作,所有對Devfs註冊的驅動程式都會被加入Devfs檔案系統環境中。
之後,開機程序完成,使用者登入系統再透過”mount –t devfs /mnt1 /devfs”載入Devfs檔案系統,並啟動Devfsd後即完成Devfs的啟動程序。

圖﹝三﹞
對於要在Devfs檔案系統中出現的裝置檔案,必須在初始化的過程中呼叫devfs_register﹝﹞,例如Linux2.4.5中的ATAPI CDROM Driver﹝/linux/drivers/ide/ide-cd.c﹞在驅動程式初始化的過程中,就有如下的一段程式碼
devinfo->de = devfs_register(drive->de, “cd”, DEVFS_FL_DEFAULT,
HWIF(drive)->major, minor,
S_IFBLK | S_IRUGO | S_IWUGO,
ide_fops, NULL);
這段程式碼就是在ATAPI CDROM Driver初始化的過程中,對Devfs註冊的必備動作。
devfs_register﹝﹞的函式原型如下
devfs_handle_t devfs_register (devfs_handle_t dir, const char *name,
unsigned int flags,
unsigned int major, unsigned int minor,
umode_t mode, void *ops, void *info)
dir: 為一個指向裝置檔案在devfs中所屬主目錄進入點﹝例如﹕/devfs/cdroms﹞的參數。如果為NULL的話,那就表示這個新的裝置檔案是相對於devfs的根目錄﹝例如﹕/devfs﹞。
name: 該裝置檔案進入點的名稱。
flags:設定該裝置檔案的旗標﹝詳細內容可參閱Linux原始碼/linux/include/linux/devfs_fs_kernel.h﹞。
major: The major number. Not needed for regular files.
minor: The minor number. Not needed for regular files.
mode: 設定裝置檔案的狀態﹝例如﹕區塊、字元裝置檔案……等,可參閱Linux原始碼/linux/include/linux/stat.h﹞。
ops: 定義對該裝置檔案操作的基本函式,例如對一個ide介面的裝置,其中ops宣告如下

 
    
struct block_device_operations ide_fops[] = {{
open: ide_open,
release: ide_release,
ioctl: ide_ioctl,
check_media_change: ide_check_media_change,
revalidate: ide_revalidate_disk
}};






























也就是說,之後對Devfs檔案系統中的檔案進行Open、Close與Ioctl..…等動作,都會分別轉送到核心的ide_open、ide_release與ide_ioctl函式,透過這樣的方式,使用者可以對裝置檔案進行開檔與讀取資料的動作,而所有的動作都會再透過核心的驅動程式來執行,並轉送給使用者程式。
Devfs啟動時會呼叫函式get_root_entry()﹝請參閱fs/devfs/base.c﹞,其中如下程式碼,會透過函式create_entry﹝﹞在我們指定的Devfs檔案系統目錄下產生檔案.devfsd。

 
    
//產生與程式devfsd溝通的裝置檔案.devfsd
if ( ( new = create_entry (root_entry, “.devfsd”, 0) ) == NULL )
return NULL;
//
new->registered = TRUE;
new->u.fcb.u.device.major = next_devnum_char >> 8;
new->u.fcb.u.device.minor = next_devnum_char & 0xff;
++next_devnum_char;
new->mode = S_IFCHR | S_IRUSR | S_IWUSR;
new->u.fcb.default_uid = 0;
new->u.fcb.default_gid = 0;
new->u.fcb.ops = &devfsd_fops;


































而在上述程式碼的最後,結構 devfsd_fops 是用來定義目前.devfsd所產生的檔案,當被使用者程式開啟後,執行各項動作所對應到的核心函式。
如下即為devfsd_fops的結構

 
    
/* Devfs daemon file operations */
static struct file_operations devfsd_fops =
{
read: devfsd_read,
ioctl: devfsd_ioctl,
release: devfsd_close,
};


















舉個例子來說,我們可以透過如下程式碼開啟/devfs/.devfsd的檔案
if ( ( fd = open (“/devfs/.devfsd”, O_RDONLY, 0) ) < 0 )
{
printf (“\n Error opening file: \”.devfsd\””);
exit (1);
}
而以下程式碼會進入核心Devfs中的devfsd_ioctl函式,其中參數DEVFSDIOC_GET_PROTO_REV是用來取得目前核心所支援的Devfsd Ioctl介面的協定版本,以確定所取得的使用者devfsd常駐程式與Linux核心的Devfs檔案系統彼此透過Ioctl介面溝通的傳遞協定一致,如此才可順利運作。
if (ioctl (fd, DEVFSDIOC_GET_PROTO_REV, &proto_rev) != 0)
{
printf (“\n Error getting protocol revision”);
exit (1);
}
其它像是
char tmp[20];
read(fd,tmp,20);
則會進入核心的devfsd_read (struct file *file, char *buf, size_t len, loff_t *ppos);函式,而close﹝fd﹞則會進入核心的devfsd_close (struct inode *inode, struct file *file)函式,透過這樣的檔案機制,我們就可以在裝置檔案系統的目錄下,建立驅動程式與使用者程式彼此的溝通介面。
在核心初始化的過程中,在 “/dev”目錄下建立一個.devfsd的檔案,這個檔案對於在使用者模式的devfsd Daemon來說,是相當重要的。
因為位於使用者模式的devfsd Daemon在啟動時,便會去確認這檔案是否存在,如果存在的話才會繼續執行,不然就會判斷系統核心並沒有支援Devfs檔案系統的功能,所以立刻會把程式終止執行。
除了由使用者端的程式主動透過ioctl介面與底下的驅動程式交換資料以外,一旦我們在裝置檔案系統中新增一個裝置或是有其他改變時,Devfs檔案系統也需要通知上層的使用者端程式devfsd做出對應的動作,整個運作的流程如下圖所示

圖﹝四﹞
也就是說,在使用者端的程式Devfsd會去開啟/devfs/.devfsd,並且透過read﹝﹞函式來取得目前核心Devfs檔案系統對程式devfsd所要求的事件。透過這樣的機制,一旦使用者端的程式devfsd進入read﹝﹞函式後,就會進入核心的函式devfsd_read﹝﹞,而這個devfsd_read﹝﹞函式會先把使用者端程式devfsd設為可插斷,並使得使用者端程式devfsd進入靜止的狀態,之後核心進入一個while迴圈,透過這個迴圈不斷的去檢查是否有要求程式devfsd處理的事件放在queue中,如果沒有的話,核心程式就會進入排程程序,把執行的權力交給其他需要執行的程式。
如以下函式 devfsd_queue_empty﹝﹞就是用來確認是否有尚未處理的事件還保留在queue中。

   
static inline int devfsd_queue_empty (struct fs_info *fs_info)
{
printk(“\ndevfsd_queue_empty”);
return (fs_info->devfsd_buf_out == fs_info->devfsd_buf_in) ? TRUE : FALSE;
} /* End Function devfsd_queue_empty */



如果發現在queue中有需要程式devfsd處理的事件,就會立刻把使用者端程式devfsd設為可執行的,並且把要求devfsd處理的事件內容填入資料結構info中,並且最後結束函式devfsd_read﹝﹞的執行。在核心結束了devfsd_read﹝﹞函式後,使用者端所呼叫的read﹝﹞函式就會收到由核心送出的資料結構info,從中來得知目前在使用者端程式devfsd所需要執行的動作,並且透過函式service_name﹝﹞來解析info內容,並做出對應的反應。
既然系統會維持一個用來紀錄需要程式devfsd處理事件的queue,那樣我們就需要介紹函式devfsd_notify_one﹝﹞,在核心的Devfs架構中,如果使用者新增或是刪除了一個裝置,就會呼叫這個函式來把目前的動作加入事件queue,以便於讓使用者端的程式devfsd可以處理。

 
    
static int devfsd_notify_one (void *data, unsigned int type, umode_t mode,uid_t uid, gid_t gid, struct fs_info *fs_info)
{
unsigned int next_pos;
unsigned long flags;
struct devfsd_buf_entry *entry;
static spinlock_t lock = SPIN_LOCK_UNLOCKED;
//
if ( !( fs_info->devfsd_event_mask & (1 << type) ) )
return (FALSE);
next_pos = fs_info->devfsd_buf_in + 1;
if (next_pos >= devfsd_buf_size)
next_pos = 0;
if (next_pos == fs_info->devfsd_buf_out)
{
/* Running up the arse of the reader: drop it */
atomic_inc (&fs_info->devfsd_overrun_count);
return (FALSE);
}
spin_lock_irqsave (&lock, flags);
//
fs_info->devfsd_buffer_in_use = TRUE;
next_pos = fs_info->devfsd_buf_in + 1;
if (next_pos >= devfsd_buf_size)
next_pos = 0;
//
entry = (struct devfsd_buf_entry *) fs_info->devfsd_buffer + fs_info->devfsd_buf_in;
entry->data = data;
entry->type = type;
entry->mode = mode;
entry->uid = uid;
entry->gid = gid;
//
fs_info->devfsd_buf_in = next_pos;
fs_info->devfsd_buffer_in_use = FALSE;
spin_unlock_irqrestore (&lock, flags);
//
wake_up_interruptible (&fs_info->devfsd_wait_queue);
//
return (TRUE);
} /* End Function devfsd_notify_one */


















































如下圖﹝五﹞所示,當核心Devfs檔案系統有新的裝置產生或是有一些狀態的改變時,會呼叫函式devfsd_notify_one﹝﹞把需要處理的事件加入queue中。因為queue中新增了需要處理的事件,所以devfsd程式就會從Read動作中讀取到資料,進而分析所讀取的資料,再來對目前Devfs檔案系統所屬的目錄做出對應的動作。
圖﹝五﹞
四,結語
每一個Devfs的NameSpace Entry只需要72bytes的空間,而之前透過VFS儲存的檔案系統一個inode需要約256bytes。以現在的RedHat7.0來說如果/dev目錄下有6000個inode,那就要花去約1.536MBytes,如果是儲存在硬碟的話,那當然是沒有問題的,因為現在的儲存空間很便宜,不過如果是應用在嵌入式的環境或是硬體設備比較老舊的環境,那樣這種方式就顯得沒有經濟效益了。
對於筆者來說,撰寫這篇文章其實也學習到了不少經驗與知識。不過以客觀的角度來看,對於系統環境較有經驗的好手,其實用不用Devfs檔案系統真的無所謂,因為我們可以自行把系統用不到的裝置檔案移除。像是在嵌入式的環境中我們可以預先知道有哪些裝置是我們確定會用到的,那樣我們只要在/dev目錄下只產生我們會用到的裝置檔案即可,也因此其實得到的記憶體資源節省比率,是相當的有效率,並不會輸給Devfs。
所以囉,不同的技術是要看真正應用它的資訊人員如何透過巧思來構成一項產品,好的技術如果沒有善加應用其實也發揮不出它美好的一面。
歡迎各位有任何問題隨時可以E-Mail給我。
My E-Mail﹕ hlchou@mail2000.com.tw
五,參考資料
[1] Devfs pseudo file system http://www.linux.com/howto/SCSI-2.4-HOWTO/devfs.html
[2] Linux Devfs (Device File System) FAQRichard Goochhttp://www.atnf.csiro.au/~rgooch/linux/docs/devfs.html
[4]Linux 2.4.5 Source Code