Bypass the 2GB file size limit on 32-bit Linux (在 Linux 上面突破 2GB 的檔案大小限制)
在 32 位元的 Linux 上面寫超過 2GB 的檔案會發生錯誤,甚至導致程式終止執行
這是因為 Linux 的系統內部處理檔案時用的指標定義為 long,而 long 在 32 位元的系統上的大小為 32 位元,因此最大只能支援 2^31-1 = 2,147,483,647 bytes 等於是 2GB 扣掉 1 byte 的檔案大小
64 位元的系統 (例如 AMD64 或 IA64) 則因為 long 定義成 64 位元,所以不會有問題..
# if __WORDSIZE == 64 typedef long int int64_t; # endif |
不過在 FreeBSD 上面,即使是 32 位元的系統,也不會有 2GB 檔案大小的限制,這是因為 FreeBSD 內部處理檔案時,本來就是使用 64 位元的數字當作指標,所以不會有問題
因此在 32 位元的 Linux 上面,程式需要作一些額外處理才能正確寫超過 2GB 的檔案
我們先寫一個小程式來測試一下 (large.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #include <stdio.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <signal.h> #include <unistd.h> #include <errno.h> void sig_xfsz(int sig) { printf("ERROR: SIGXFSZ (%d) signal received!\n", sig); } int main() { int i, fd; char dummy[4096]; signal( SIGXFSZ, sig_xfsz ); unlink("large.log"); fd = open("large.log", O_CREAT|O_WRONLY, 0644 ); bzero( dummy, 4096 ); /* 2GB = 4KB x 524288 */ for( i = 0 ; i < 524287 ; i++ ) write( fd, dummy, 4096 ); write( fd, dummy, 4095 ); printf("large.log: 2147483647 bytes\n"); if( write( fd, dummy, 1 ) < 0 ) printf("ERROR: %s [errno:%d]\n",strerror(errno),errno); else printf("large.log: 2147483648 bytes\n"); close(fd); exit(0); } |
在 32 位元的 Linux 下面,以上程式編譯後若沒有特殊處理,執行結果如下:
# gcc -o large32 large.c # ./large32 large.log: 2147483647 bytes ERROR: SIGXFSZ (25) signal received! ERROR: File too large [errno:27] |
在寫第 2147483648 byte 的時候,程式會收到 signal SIGXFSZ,同時 write() 會回傳 -1 錯誤,errno 則為 27 (File too large)。更甚者,如果程式沒有像上面一樣去處理 SIGXFSZ 的話,內定的 signal handler 甚至會造成程式停止執行並產生 core dump
接下來,我們在編譯同一個程式的時候加入 -D_FILE_OFFSET_BITS=64 再試看看:
# gcc -D_FILE_OFFSET_BITS=64 -o large64 large.c # ./large64 large.log: 2147483647 bytes large.log: 2147483648 bytes |
果然順利突破 2GB 的限制了!
而同樣的程式在 32 位元的 FreeBSD 下面,不論有沒有加這個定義,跑起來都是正確的
不過處理這些大檔案的時候,除了編譯程式時的參數不同外,有些函數的使用上也要作一些調整,例如 fseek() 與 ftell() 這兩個原本使用到 long integer 當作 offset 的函數:
1 2 | int fseek(FILE *stream, long offset, int whence); long ftell(FILE *stream); |
只要系統是 32 位元,即使是在 FreeBSD 下面,都需要改為使用 off_t 的版本:
1 2 | int fseeko(FILE *stream, off_t offset, int whence); off_t ftello(FILE *stream); |
在 Linux 下面,如果 _FILE_OFFSET_BITS 定義為 64,則 off_t 這個型態會自動轉成 64 位元的大小(在 FreeBSD 上面,off_t 本來就是 64 位元的大小)
每種系統支援大於 2GB 的檔案讀寫所需要的編譯選項都會有一些差異,即使是同樣是 Linux 也會因為 32 位元或 64 位元而有不同。有一個簡單的方法可以判斷,就是利用 glibc 提供的 getconf 來取得編譯(compile)以及連結(linking)時所需的參數:
# getconf LFS_CFLAGS -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 # getconf LFS_LDFLAGS # |
上面是在 32 位元的 Redhat Linux 上面跑出來的結果,代表的是在這個系統上,若要讓程式支援 2GB 的檔案讀寫,編譯(compile)時需要加上 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 這兩個參數,連結(linking)時則不用加任何參數
參考資料:
- Large File Support in Linux
- LFS: Large File Support (Wikipedia)
自動引用通知: 一個長整數各自表述 (on 64-bit system) | Dada's Blog
自動引用通知: 一個長整數各自表述 (Size of long integer may vary in 64-bit systems) | Dada's Blog