話說某一天,一位同事發現某個在 UNIX 上用 C 寫的程式,跑一陣子後似乎會吃掉很多硬碟空間,吃掉的硬碟空間用 du 去算卻跟 df 的結果差異很大,而且把 process 停掉後,空間竟然又自動恢復正常了
最後,用 fstat 去仔細分析,終於找到原因:
已經開啟的檔案,即使開啟中被強制砍掉(unlink),對原 file descriptor 持續寫入的部份仍會繼續佔用硬碟空間,寫得越多,佔用的空間也越多
實務上最常遇到這種狀況的就是 log rotation,尤其是 rotation 後的舊 LOG 是壓縮過的情況。因為經過 gzip 壓縮過後,原始的 LOG 會被刪除,只留下 XXX.gz。這個時候如果沒有人通知原來寫 LOG 的程式要重新開啟一次 LOG (重新寫一個檔案),就會導致程式在不知情狀況下繼續寫 LOG,然後空間就莫名其妙被用掉了!
例如 FreeBSD 下專門作 log rotation 的 newsyslog 設定檔 (newsyslog.conf) 就有個欄位可以設定在 log rotation 後送一個 signal 給 process,而 apache (httpd) 就接受 SIGUSR1 來當作重新開啟 LOG 檔案的訊號(事實上對 apache 而言是 graceful restart)。很多人以為這只是為了讓 LOG 能繼續寫不會漏掉,但其實更重要的是:如果不這麼作,你的硬碟可能很快就爆掉啦…
我們可以寫個簡單的程式來測試一下這種狀況:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <stdio.h> #include <fcntl.h> int main() { int fd, i; char cmd[32], buf[1024]; memset( buf, 0, 1024); snprintf(cmd,sizeof(cmd),"df ."); printf("==> open file for write and delete it ...\n"); fd = open( "test-file.log", O_CREAT|O_WRONLY|O_TRUNC ); unlink("test-file.log"); system(cmd); printf("\n==> write 100MB to file ...\n"); for( i = 0 ; i < 1000*100 ; i++ ) write( fd, buf, 1024); system(cmd); printf("\n==> close file ...\n"); close(fd); system(cmd); } |
首先,這個小程式會先開啟一個檔案,然後馬上砍掉它(但先不關閉檔案),接下來執行 “df .” 來查看目前硬碟用量。第二步驟是寫入100MB的垃圾資料到這個已開啟的檔案(file descriptor)中,然後再執行 “df .” 來取得硬碟用量。最後關閉檔案後,再執行一次 “df .”。執行結果如下:
==> open file for write and delete it ... Filesystem 1K-blocks Used Avail Capacity Mounted on /dev/ad8s1d 144520482 28011428 104947416 21% /home ==> write 100MB to file ... Filesystem 1K-blocks Used Avail Capacity Mounted on /dev/ad8s1d 144520482 28111508 104847336 21% /home ==> close file ... Filesystem 1K-blocks Used Avail Capacity Mounted on /dev/ad8s1d 144520482 28011428 104947416 21% /home
我們可以看到程式寫了100MB之後,空間真的被佔掉了,即使我們已經刪除這個檔案,且從目錄的檔案列表中無法直接看到這個檔案了。而當被開啟的檔案關掉後,這些空間也立即被釋放回來了
接下來我們把程式中的 df 改成 fstat,可以更清楚看到狀況
1 | snprintf(cmd,sizeof(cmd),"fstat -f -p %d .", getpid()); |
這是最後的結果:
==> open file for write and delete it ... USER CMD PID FD MOUNT INUM MODE SZ|DV R/W cdsheen a.out 91475 wd /home 12694528 drwxr-xr-x 2048 r cdsheen a.out 91475 text /home 12694672 -rwxr-xr-x 7910 r cdsheen a.out 91475 3 /home 12694673 ----r-x--x 0 w ==> write 100MB to file ... USER CMD PID FD MOUNT INUM MODE SZ|DV R/W cdsheen a.out 91475 wd /home 12694528 drwxr-xr-x 2048 r cdsheen a.out 91475 text /home 12694672 -rwxr-xr-x 7910 r cdsheen a.out 91475 3 /home 12694673 ----r-x--x 102400000 w ==> close file ... USER CMD PID FD MOUNT INUM MODE SZ|DV R/W cdsheen a.out 91475 wd /home 12694528 drwxr-xr-x 2048 r cdsheen a.out 91475 text /home 12694672 -rwxr-xr-x 7910 r