分類彙整: 程式語言

一個長整數各自表述 (in 64-bit system)

Size of long integer may be different between 64-bit systems (一個長整數各自表述)

不知道是不是我太落伍了…

我一直以為 C/C++ 下面 short, long, long long 三種資料型態都固定是 2, 4, 8 個 bytes 大小。只有 int 這個資料型態會因為 16-bit/32-bit 系統的不同而變成 2 或 4 bytes 的大小,所以理所當然 int 在 64-bit 的電腦也應該會變成 8 bytes (64-bit) 的大小囉 ?!

在整理前一篇文章《Bypass the 2GB file size limit on 32-bit Linux》的時候,讓我驚覺在 64-bit 的系統下,long 的長度也是各自表述的!

首先,int 的大小即使到了 64-bit 的機器上,大部分的系統仍然使用 4 bytes 的大小而已,這主要是為了避免程式從 32-bit 系統轉換到 64-bit 系統需要修改太多地方

再來,請參考 Wikipedia: 64-bit data models 的說明

絕大多數的 UNIX 系統在 64-bit 下面採用 LP64 這種 data model,這時候 long 就不再是固定為 4 bytes 大小,而是變成 8 bytes 的大小了!

然而,Win64 卻不是使用 LP64,而是採用 LLP64 這個 data model,這時候 long 的大小仍然還是 4 bytes

Many 64-bit compilers today use the LP64 model (including Solaris, AIX, HP, Linux, Mac OS X, and IBM z/OS native compilers). Microsoft’s VC++ compiler uses the LLP64 model.

兩種 data model 的最大差異點就是 long 這個資料型態的大小,LP64 是 64-bit,而 LLP64 則是 32-bit

LLP64 data model 基本上可以說跟 32-bit 的系統一樣,唯一差別只有位址(pointer)改成了 64-bit 而已。資料物件(class, structure) 等如果沒有包含 pointer 的成員的話,整個物件的大小是與 32-bit 系統一樣的!

而 LP64 則是除了位址(pointer)改成 64-bit 之外,long 的大小也變成了 64-bit 大小。所以在 UNIX 下面,要把 32-bit 程式 porting 到 64-bit 可能要比 Windows 多花費多一點功夫。

所以呢,我們觀察到兩個問題影響著程式的相容性

  1. 在 UNIX 下面,long 的大小在 32-bit 與 64-bit 的系統下是不一樣的
  2. 同樣是 64-bit 系統,UNIX 與 Windows 對於 long 的大小看法是不一致的

為了使程式在 32-bit 與 64-bit 之間以及 UNIX 與 Windows 之間的相容性提昇,改用固定長度的資料型態是寫程式的一個好習慣

在 UNIX 下面,我們可以改用 stdint.h 這個 header file 中對於資料型態的定義:

int8_t     8-bit signed interger
int16_t    16-bit signed interger
int32_t    32-bit signed interger
int64_t    64-bit signed interger
uint8_t    8-bit unsigned interger
uint16_t   16-bit unsigned interger
uint32_t   32-bit unsigned interger
uint64_t   64-bit unsigned interger

在 Windows 下面,則改用下面這些整數固定大小的資料型態

INT8       8-bit signed integer
INT16      16-bit signed integer
INT32      32-bit signed integer
INT64      64-bit signed integer
UINT8      8-bit unsigned integer
UINT16     16-bit unsigned integer
UINT32     32-bit unsigned integer
UINT64     64-bit unsigned integer

絕對不要再使用 int 和 long 了!

尤其是寫網路程式時,很可能 client 是 Windows 而 server 是 UNIX,然後又有 32-bit 及 64-bit 系統混在裡面,一不小心就發生不相容的問題了…

當然,在 64-bit 的系統下寫程式,要考慮的絕對不只上面這些基本的資料型態。除了 pointer 的大小變成 64-bit 外,許多系統內建函式會用到的 size_t 及 off_t 的大小也變成 64-bit 了…. 寫程式時若有用到這些資料型態,需特別注意,尤其是 casting 時,千萬不要用 32-bit 的整數去裝這些資料,免得造成不可預期的結果!

最後提供一個小程式讓你得知你的系統主要資料型態的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <sys types.h="">
int main()
{
        printf("sizeof(short)     = %d\n", sizeof(short));
        printf("sizeof(int)       = %d\n", sizeof(int));
        printf("sizeof(long)      = %d\n", sizeof(long));
        printf("sizeof(long long) = %d\n\n", sizeof(long long));
 
        printf("sizeof(size_t)    = %d\n", sizeof(size_t));
        printf("sizeof(off_t)     = %d\n", sizeof(off_t));
        printf("sizeof(void *)    = %d\n", sizeof(void *));
}
</sys></stdio.h>

參考資料:

  1. Wikipedia: 64-bit data models
  2. 64-Bit Programming Models: Why LP64?
  3. Introduction to Win32/Win64
  4. Porting 32-bit Applications to the Itanium® Architecture
  5. Preparing Code for the IA-64 Architecture (PDF)

消失的硬碟空間

話說某一天,一位同事發現某個在 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

File handle is always global in Perl

Perl 的 file handle 及 directory handle 一定是 global 的,無法宣告成 local

一般使用上沒有大問題,但是當用在遞迴式呼叫時,就會產生錯誤了,下面是一個把目錄下包含子目錄中所有檔案列出來的 perl 程式,採用遞迴式呼叫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/perl
die "Usage: $0 [directory]\n" unless @ARGV == 1;
die "ERROR: $ARGV[0] is not a directory\n" unless -d $ARGV[0];
&show_dir($ARGV[0]);
exit;
sub show_dir
{
        my( $dir ) = @_;
        my( $fname );
        opendir( DIR, $dir );
        while( $fname = readdir(DIR) )
        {
                next if $fname eq '.' || $fname eq '..';
                if( -d "$dir/$fname" ) {
                        &show_dir("$dir/$fname");
                }
                else {
                        print "$dir/$fname\n";
                }
        }
        closedir(DIR);
}

由於 DIR 這個 directory-handle 會被當成 global 變數,因此這段程式會出錯,無法列出所有檔案。

解決方法是把 DIR 這一個 directory-handle 直接用 $dir 來代替,這樣在遞迴的時候就不會因為重複使用而造成錯誤了,下面是改過的程式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/perl
die "Usage: $0 [directory]\n" unless @ARGV == 1;
die "ERROR: $ARGV[0] is not a directory\n" unless -d $ARGV[0];
&show_dir($ARGV[0]);
exit;
sub show_dir
{
        my( $dir ) = @_;
        my( $fname );
        opendir( $dir, $dir );
        while( $fname = readdir($dir) )
        {
                next if $fname eq '.' || $fname eq '..';
                if( -d "$dir/$fname" ) {
                        &show_dir("$dir/$fname");
                }
                else {
                        print "$dir/$fname\n";
                }
        }
        closedir($dir);
}

使用 bcompiler 來編譯(加密)您的 PHP 原始碼

Using bcomipler to compile (encode) your PHP script (in FreeBSD)

eAccelerator 的 encoder 在 PHP 5.1 以後就無法運作了,使是最新的版本 0.9.5 也不行,因此如果您還想用 eAccelerator 的 encoder,乖乖待在 PHP 4.x or PHP 5.0 吧,相關資訊可參考之前的兩篇文章:

根據 mailing list 上面作者最新的說法,短期內應該都還不會修復,甚至有可能還會放棄支援 encoder 部份,作者還推薦大家改用 bcompiler … orz
所以如果你跟我一樣必須要用 PHP 5.1 的話,就跟我一起來玩看看 bcompiler

附帶一提,原本是要買 ionCube 公司開發的 PHP Encoder 的,這看來是目前編譯(加密)PHP 原始碼最好的選擇,價格也不算貴,而且花的是公司的錢,不過還是先試用看看 bcompiler 好了,真的不好用的話,再來買囉

以下的安裝與使用說明均以 FreeBSD 為範例,其他作業系統應該也差不多,請自己試看看吧

在正式安裝之前提醒你,bcompiler 會用到 bzip2 這個 extension,所以先安裝一下 php5-bz2:

# cd /usr/ports/archivers/php5-bz2
# make install clean

bcompiler 是一個收容於 PECL 的 PHP extension,您可以直接使用 pecl 這個命令來安裝,而 pecl 的使用方法與 pear 相同,因此你必須先安裝 pear :

# cd /usr/ports/devel/pear
# make install clean

pear 安裝好後,同時會有一個 pecl 命令可用 (/usr/locel/bin/pecl),可用它來安裝 bcompiler

pecl install channel://pecl.php.net/bcompiler-0.8

不過呢,我發現直接執行 pecl 會發生錯誤(至少在 FreeBSD 下是如此):

# pecl
Fatal error: Call to undefined function preg_match() in ...

問題出在 /usr/local/bin/pecl 這個 script 的最後一行:

exec $PHP -C -n -q $INCARG -d output_buffering=1 -d safe_mode=0 ...

這一行的 -n 這個選項代表不載入 php.ini 設定檔,因此 pcre.so 就沒有被載入,
然後你就看到上面的錯誤了,所以自己去改一下程式把 -n 拿掉吧….

拿掉之後,安裝程式應該就可以正常運作了了:

pecl install channel://pecl.php.net/bcompiler-0.8

安裝好後,記得要改設定把 bcompiler 這個 extension 載入

# echo "extension=bcompiler.so" >> /usr/local/etc/php/extensions.ini

bcompiler 全名是 PHP bytecode Compiler,詳細的使用方法可以參考 PHP 網站上的詳細說明: PHP bytecode Compiler: http://www.php.net/manual/en/ref.bcompiler.php

看不懂的話,bcompiler_write_file() 這個 function 的說明裡面有範例:

$fh = fopen("example.phb", "w");
bcompiler_write_header($fh);
bcompiler_write_file($fh, "example.php");
bcompiler_write_footer($fh);
fclose($fh);

為了易於使用,我寫了一個簡單的 script,使用方法如下:

BENCODER v1.3 - Encode your PHP script using bcompiler

Usage: bencoder [-f] [-q] -o FILE    file1.php
       bencoder [-f] [-q] -o OUTDIR  file1.php file2.php ...
       bencoder [-f] [-q] -o OUTDIR  -s SRCDIR  [-e SUFFIX] [-r] [-c] [-l]

  -o FILE   : the file name to write the encoded script
              (default to '-encoded.XXX' suffix)
  -o OUTDIR : the directory to write all encoded files

  -a SRCDIR
  -s SRCDIR : encode all files in this source directory

  -r        : encode directories recursively (no by default)
  -f        : force overwriting even if the target exists
  -e SUFFIX : encode the files with the SUFFIX extension only (default: php)
              (regular expression allowed, ex: "php|inc")
  -c        : copy files those shouldn't be encoded (no by default)
  -l        : follow symbolic link (no by default)
  -q        : do not print the file name while encoding or copying
  -b
  -bz2      : compress the encoded files with bz2 (needs bzip2-extension)

想用的人到 這邊 自己抓回去玩玩看吧

# wget http://bencoder.urdada.net/bencoder
# chmod 755 bencoder
# mv bencoder /usr/local/bin/

在把程式使用 bcompiler 編碼之後,執行的時候發覺 $_SERVER 以及 $_ENV 的變數都不見了,後來查了資料,才發現要把 auto_globals_jit 關掉 (Off) 才行,請直接修改您的 php.ini :

; When enabled, the SERVER and ENV variables are created when they're first
; used (Just In Time) instead of when the script starts. If these variables
; are not used within a script, having this directive on will result in a
; performance gain. The PHP directives register_globals, register_long_arrays,
; and register_argc_argv must be disabled for this directive to have any affect.
auto_globals_jit = Off

eAccelerator 無法使用於 PHP 5.1

上個月 PHP 5.1 正式發表了

昨天一時興起,把 PHP 5.0 利用 portupgrade 升級成 PHP 5.1,過程沒遇到什麼阻礙
安裝完後也覺得應該沒什麼問題,但是後來卻發現 eAccelerator 無法使用了

不管是 eAccelerator 0.9.3 或者 0.9.4-rc1 都一樣不能使用(連編譯都會發生錯誤)

根據 討論區 上面的說法,要修復這個問題似乎還需要一段時間

即使是將要發表的 0.9.4 版本,也不會支援 PHP 5.1

因此,有重度依賴 eAccelerator 的使用者,請乖乖先待在 PHP 4.4 或者 PHP 5.0 吧

Ref: eAccelerator – PHP 網頁加速及編碼

eAccelerator – PHP 網頁加速及編碼

eAccelerator – PHP 網頁加速及編碼軟體

http://eaccelerator.net/HomeUk

eAccelerator 是一套開放原始碼的網頁加速軟體,它能加快 PHP 網頁的執行速度。其原理是把原始 PHP 程式碼編譯過 (compiled) 的二進位碼快取起來,下次執行同一個程式時,就不需要再編譯一次,可以節省很多時間

同時,eAccelerator 還有保護 PHP 原始碼的效果,透過 eAccelerator 提供的 encoder 程式,程式開發者不需要再把 PHP 程式碼公開出去就可以提供服務,對於商業軟體的開發有很大的幫助

以下簡介在 FreeBSD 上面安裝與使用 eAccelerator 的過程

首先,請先確定您的機器上面已經安裝了 Apache Web ServerPHP 模組,Web Server 部分,eAccelerator 支援 Apache 1.3 與 2.0,PHP 則是支援 mod_php4 與 mod_php5,eAccelerator 並不支援 PHP 以 CGI 模式執行。

1. 安裝 eAccelerator

# cd /usr/ports/www/eaccelerator/
# make install

2. 啟用 eAccelerator extension

# vi /usr/local/etc/php/extensions.ini

在檔案的最後面加上:

extension=eaccelerator.so

請注意: extension=eaccelerator.so 一定要加上 extension=session.so 之後,否則會發生錯誤

3. 設定 php.ini

# cd /usr/local/etc/
# vi php.ini

如果你還沒有設定過 php.ini 的話,建議從 php.ini-recommended 拷貝一份來改:

# cp -i php.ini-recommended php.ini
# chmod 644 php.ini

在 php.ini 設定檔的最後面加上這些設定:

eaccelerator.shm_size="16"
eaccelerator.cache_dir="/tmp/eaccelerator"
eaccelerator.enable="1"
eaccelerator.optimizer="1"
eaccelerator.check_mtime="1"
eaccelerator.debug="0"
eaccelerator.filter=""
eaccelerator.shm_max="0"
eaccelerator.shm_ttl="0"
eaccelerator.shm_prune_period="0"
eaccelerator.shm_only="0"
eaccelerator.compress="1"
eaccelerator.compress_level="9"

每個設定參數的功能請參考 http://eaccelerator.net/IniSettingsUk 的說明

4. 建立快取暫存目錄

# mkdir /tmp/eaccelerator
# chmod 0777 /tmp/eaccelerator

5. 重新啟動 Apache,大功告成

# /usr/local/rc.d/apache2.sh stop
# /usr/local/rc.d/apache2.sh start

安裝完畢之後,您所有的程式都不須修改,自動就可以享受速度加倍的好處了,eAccelerator 會把快取的檔案放在 /tmp/eaccelerator/ 下面

eAccelerator 也有提供線上監控的介面,就像 phpinfo() 一樣,只要呼叫 eaccelerator() 即可,寫一個網頁如下即可:

<?
      eaccelerator();
?>

當然,最好還是把這個網頁經由一些安全機制保護起來。除了你自己對網頁加上權限控管外,你也可以使用 eAccelerator 提供的保護機制,設定方法如下:

執行 /usr/local/share/examples/eaccelerator/eaccelerator_password.php 這支程式:

# php /usr/local/share/examples/eaccelerator/eaccelerator_password.php
Changing password for eAccelerator Web Interface (eaccelerator.php)

Enter admin name: dada
New admin password: 12345
Retype new admin password: 12345

Add the following lines into your php.ini and restart HTTPD

eaccelerator.admin.name="dada"
eaccelerator.admin.password="$1$oEX8dazK$7F5FMsJQejEh2Vdazjkqd."

輸入想要設定的帳號密碼(藍色部分)後,這支程式會產生編碼過後的資訊(綠色部分),然後把綠色部分的兩行貼到 php.ini 中,重新啟動 Apache 就可以了。eAccelerator 提供的保護機制是使用 HTTP 認證方式..

這個介面長得就像這樣子:

http://photo.giga.net.tw/photo/2029/original/20050623131051-0001.jpg

裡面提供簡單的管理功能,同時會告訴你那個網頁已經被快取了

eAccelerator 提供的網頁編碼功能

eAccelerator 提供一支小程式,可對網頁做編碼,詳細使用方法可執行

# /usr/local/bin/encoder

執行後會看到簡單的使用說明:

Usage:  encoder [options] source_file_name
        encoder [options] source_file_name...
        encoder [options] source_directory_name...

Options:
        -s suffix
                encode files only with following suffix (default is "php")
        -a
                encode all files (no by default)
        -l
                follow symbolic links (no by default)
        -r
                encode directories recursively (no by default)
        -c
                copy files those shouldn't be encoded (no by default)
        -f
                overwrite existing files (no by default)
        -w
                exclude check for eaccelerator_load() and subsequent warning
        -o target
                If you encode only one script then 'target' specifyes an output
                file name. If you encode directory or several files at once
                then 'target' specifyes an output directory name.

Examples:
        encoder some_file.php
        encoder some_file.php -o some_encoded_file.php
        encoder *.php -o some_dir
        encoder ~/public_html/x -rcf -sphp -sinc -o ~/public_html/y

稍微玩一下應該就知道怎麼用了…

使用 encoder 去對 PHP 程式編碼,會產生另外一個檔案,你也可以對整個目錄作轉換,例如:

# encoder myhome -rcf -sphp -sinc -o myhome_encoded

這個命令會把 myhome/ 目錄下所有的 php / inc 結尾的檔案作編碼,並存放到 myhome_encoded 下面,同時,由於指定了 -c 這個選項,其他格式、不需編碼的檔案,如 jpg/gif/html/js 等,也會原封不動複製一份到 myhome_encoded 下面,這樣方便你直接以這個目錄來提供服務。

選項 -r 代表 recursively 的功能,可以幫你同時處理所有子目錄,不過有個討厭的地方就是,encoder 幫你建立的子目錄,目錄權限竟然是 0777,感覺很不好,所以我習慣上會自己去改 encoder 這支程式

# chmod u+w /usr/local/bin/encoder
# vi /usr/local/bin/encoder

然後,搜尋 0777 ,改成 0755 就可以了

--- /usr/local/bin/encoder.orig Wed Jun 22 09:30:49 2005
+++ /usr/local/bin/encoder      Wed Jun 22 09:40:07 2005
@@ -99,7 +99,7 @@

 function eaccelerator_mkdir($dir, $f, $web) {
   if (!empty($dir)) {
-    if (!@mkdir($dir,0777)) {
+    if (!@mkdir($dir,0755)) {
       if (!$f) {
         $error = "Can't create destination directory \"$dir\"";
         if (file_exists($dir)) {