C言語: write(2)の正しい使い方
Created: Kazuki Ohta, 2006/04/17
Last Update: Kazuki Ohta, 2006/06/14
Last Update: Kazuki Ohta, 2006/06/14
学校の課題(OS演習)で、open, read, write, close等のシステムコールを使用した課題が出された。システムコールなので当然失敗する事が有り、エラーチェックを正しく行う必要が有るのだが、writeについてはweb上に有る資料も使い方を間違っているモノが多かった。僕も課題では間違えて再提出をくらってしまった。なので、ここにwriteシステムコールの正しい使い方を書き記しておく。
まずは $man 2 write の一部を抜粋してみる。
NAME
write - write to a file descriptor
SYNOPSIS
#include
ssize_t write(int fd, const void *buf, size_t count);
DESCRIPTION
write writes up to count bytes to the file referenced by the file descriptor fd from the buffer
starting at buf. POSIX requires that a read() which can be proved to occur after a write() has
returned returns the new data. Note that not all file systems are POSIX conforming.
RETURN VALUE
On success, the number of bytes written are returned (zero indicates nothing was written). On
error, -1 is returned, and errno is set appropriately. If count is zero and the file descrip-
tor refers to a regular file, 0 will be returned without causing any other effect. For a spe-
cial file, the results are not portable.
例えば1024書き込もうとした時に一回目のwriteが256を返した時は、2回目のwriteをどのように呼べば良いのか考えてみよう。答えはこうだ。
(1) write(fd, buf, 1024) => 256 (2) write(fd, buf+256, 768) => 768
/* 注: 下部により簡潔なコードが有るのでこれは使わないで下さい */
int n = bytes_to_write;
int w = 0;
int r = 0;
while ((w < n) && ((r = write(fd, buf+w, n-w)) >= 0)) {
w += r;
}
if (r < 0) {
close(fd);
perror("write failed");
exit(EXIT_FAILURE);
}
自戒もこめて記事にしました。
2006/05/11: より簡潔なコード
改善案。
const char *p = buf;
const char * const endp = p + size;
while (p < endp) {
int num_bytes = write(fd, p, endp - p);
if (num_bytes < 0) {
perror("write failed");
break;
}
p += num_bytes;
}
2006/06/08: EINTRに対処する
システムコール処理中に割り込みが起こった場合、そのシステムコールは一時中断されてしまう。この時、システムコール呼び出しをもう一度やりなおす必要が有る。割り込みによって中断された場合errnoにはEINTRが入っているのでそれをチェックする。
const char *p = buf;
const char * const endp = p + size;
while (p < endp) {
const int num_bytes = write(fd, p, endp - p);
if (num_bytes < 0) {
if (errno == EINTR) continue;
perror("write failed");
break;
}
p += num_bytes;
}
/*
* gauche.h - Gauche scheme system header
*
* Copyright (c) 2000-2005 Shiro Kawai, All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the authors nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $Id: gauche.h,v 1.442 2005/10/13 08:14:13 shirok Exp $
*/
/* System call wrapper */
#define SCM_SYSCALL3(result, expr, check) \
do { \
(result) = (expr); \
if ((check) && errno == EINTR) { \
ScmVM *vm__ = Scm_VM(); \
errno = 0; \
SCM_SIGCHECK(vm__); \
} else { \
break; \
} \
} while (1)
#define SCM_SYSCALL(result, expr) \
SCM_SYSCALL3(result, expr, (result < 0))
const char *p = buf;
const char * const endp = p + size;
while (p < endp) {
int num_bytes;
SCM_SYSCALL(num_bytes, write(fd, p, endp - p));
if (num_bytes < 0) {
perror("write failed");
break;
}
p += num_bytes;
}
2006/06/14: EPIPEに対処する
ネットワークアプリケーションを作成する場合等にはさらに気を付けるべきことがある。それはSIGPIPEシグナルだ。man 2 writeを見るとEPIPEというエラーが有る事がわかる。これは書き込む先がソケットであり、そのソケットがクライアント側で既にclose(2)されている場合に起こるエラーなのだが、注意したいのはこのエラーが起こる場合には同時にSIGPIPEシグナルが送出されるという点だ。
SIGPIPEシグナルを受け取った場合、プロセスはデフォルトの動作として自分自身をストップさせてしまう。これは連続稼働すべきサーバーアプリケーションを作る場合には非常にまずい。Programming UNIX Sockets in C - Frequently Asked Questionsによると、SIGPIPEは単に無視するべきだそうだ。サーバーアプリケーションでwrite(2)を使用する場合には必ずSIGPIPEを無視するべきだ。
[ return ]