作者: rock (遊手好閒的石頭成) E-mail: shirock@mail.educities.edu.tw -------------------------------------- read() 及 write() 被 signal 中斷的處理 當 read() 或 write() 在處理資料時,若剛好產生了一個 signal ,系統為了要 處理這個 signal ,便會中斷 read() 或 write() ,將程序狀態切換到 signal 的處理動作中。 而當 signal 的處理動作結束後,再將程序狀態切換到 read() 或 write() 的後 續處理動作。 這個後續動作的處理,會影嚮資料讀取或寫入的完整性,但是在這一方面,各系 統間卻有很大的差異存在,看看強調移植性的 POSIX 標準的說明即可。 在 POSIX.1 中是這樣說的: Section 6.4.1.2 of POSIX states, "If a read() is interrupted by a signal after it has successfully read some data, either it shall return -1 with errno set to EINTR, or it shall return the number of bytes read." 在 POSIX.1 中,關於 read() 或 write() 被 signal 中斷時,系統所允許採取 的兩種方法如下: 1.回傳 -1 並設定變數 errno 的值為 EINTR 。 2.回傳已處理的 bytes 數目。 由於兩種方法都有系統在使用,因此 POSIX 就採用了折衷的方法,兩種方法都允 許。 有些系統只採用其中一種,但也許有的系統是兩種方法都用,當一個 signal 中 斷了 read() 或 write() 後,如果已經讀取或寫入了部份資料,就採第二種方法 處理,如果還沒有任何資料被處理,就採第一種方法處理。 由於連 BSD 或 SVR 在這一方面,都沒有明顯而強制的規定,因此對一個程式設 計人員來說,最好是照 POSIX 的方式,兩種情形都要兼顧到。 針對第一種處理方法,應採用如下的程式碼: while( read(fd, buf, nbytes) < 0 ) { if( errno == EINTR ) continue; else FATAL; } 如果錯誤是因為被 signal 中斷的話,就再讀一次,如果是其他原因導致的錯誤 ,則視為致命錯誤,應該中止程式繼續。 不過在有些文件中,則建議應將 EINTR 也視為致命錯誤,我個人並不完全贊同這 種看法,因為在 unix 系統中, signal 本來就是最基本的 IPC 技巧,是一種普 遍存在的事件,如果因此就要中止程式的進行,未免奇怪。 但是我倒認為,如果系統中支援 SA_RESTART 這個 sigaction() 的設定旗標,則 應該使用此旗標來處理 signal ,要求系統自動繼續未完成的 read() 或 write() 動作,此時將不會回傳 EINTR 。 SA_RESTART 是 SVR4 中所定義的,不在 POSIX 文件中,因此不具移植性。 針對第二種方法,則應採用如下的程式碼: void* bp; bp = buf; while( (rc=read(fd, bp, nbytes)) < nbytes ) { bp += rc; nbytes -= rc; } 當發現資料並未全部讀取時,則扣掉已讀取的資料數,再要求 read() 繼續讀取 尚未讀取的部份。 將兩種寫法結合起來後,得到的安全寫法如下: void* bp; bp = buf; while( (rc=read(fd, bp, nbytes)) < nbytes ) { if( rc > 0 ) { bp += rc; nbytes -= rc; } else { if( errno == EINTR ) continue; else FATAL; } } 可以簡化為: void* bp; bp = buf; while( (rc=read(fd, bp, nbytes)) < nbytes ) { if( rc > 0 ) { bp += rc; nbytes -=rc; } else if( errno != EINTR ) FATAL; } 因此,一個具有安全性(可確保資料被完整處理)的 safe read() 應寫成: ssize_t saferead(int fd, void *buf, size_t nbyte) { size_t nbr; /* number of bytes readed */ ssize_t rc; /* return code of read() */ void* bp; bp = buf; nbr = nbyte; while( (rc=read(fd, bp, nbr)) < nbr ) { if( rc > 0 ) { bp += rc; nbr -= rc; } else if( errno != EINTR ) abort(); } } ps. 函數原型仿造 POSIX 對 read() 的定義。 ssize_t read(int fd, void *buf, size_t nbyte); 同樣地,一個具有安全性的 safe write() 應寫成: ssize_t safewrite(int fd, void *buf, size_t nbyte) { size_t nbw; /* number of bytes written */ ssize_t rc; /* return code of write() */ void* bp; bp = buf; nbw = nbyte; while( (rc=write(fd, bp, nbw)) < nbw ) { if( rc > 0 ) { bp += rc; nbw -= rc; } else if( errno != EINTR ) abort(); } } 關於上面的討論,是針對 read() 及 write() 對正規檔案的資料處理方式,所應 採取的應對措施,如果是對非正規檔案,如 pipe , FIFOs, socket 的話,則上 面的應對措施就不適用。 例如對 socket 進行 read() 時,系統是對方一次輸出多少資料,我方就讀取多 少資料,而不會等到讀滿指定的資料量時才回傳,因此 read() 的回傳值少於指 定的資料量是正常情形。 而 POSIX 文件有說明,對 pipe 進行 write() 時, write() 從不會回傳 EINTR,因此在寫入 pipe 時,可不考慮被 signal 中斷的處理。 在美國聯邦政府的政府採構規格 (FIPS) 中,則是明確地要求 read() 或 write() 必須採取第二種方法處理。 The U.S. Government (in FIPS 151-1) requires that read() return the number of bytes read. Since the Federal Government is the world's largest buyer of POSIX systems, it is a good bet that most POSIX systems will return the number of bytes read. 我的看法是,這種處理方式,是比較合理的,可以明確的知道有多少資料已被處 理了,減少資料遺失的情形。 如果是採回傳 EINTR 的方式,那我們無法知道有多少資料已經處理了,如果要再 進行一次,就勢必整個資料重送一次,這時候已經處理過的資料,我們卻無法得 知系統將如何處理,問題就比較多了,這也是為何有的文件會建議將 EINTR 也視 為致命錯誤。