NAME

dup, dup2, dup3 - 파일 디스크립터 복제하기

SYNOPSIS

#include <unistd.h>

int dup(int oldfd);
int dup2(int oldfd, int newfd);

#define _GNU_SOURCE             /* feature_test_macros(7) 참고 */
#include <fcntl.h>              /* O_* 상수 정의 얻기 */
#include <unistd.h>

int dup3(int oldfd, int newfd, int flags);

DESCRIPTION

dup() 시스템 호출은 파일 디스크립터 oldfd의 사본을 만든다. 안 쓰는 가장 낮은 파일 디스크립터 번호를 새 디스크립터에 쓴다.

성공 반환 후에는 이전 파일 디스크립터와 새 파일 디스크립터를 바꿔 가며 쓸 수도 있다. 같은 열린 파일 기술 항목(open(2) 참고)을 가리키기 때문에 오프셋과 파일 상태를 공유한다. 예를 들어 한쪽 파일 디스크립터에서 lseek(2)으로 파일 오프셋을 변경하면 다른 쪽에서도 오프셋이 바뀐다.

파일 디스크립터 플래그(exec에서 닫기 플래그)는 공유하지 않는다. 복제본 디스크립터에는 exec에서 닫기(FD_CLOEXEC, fcntl(2) 참고) 플래그가 꺼져 있다.

dup2()

dup2() 시스템 호출은 dup()과 같은 일을 하되 안 쓰는 가장 낮은 파일 디스크립터 번호를 쓰는 게 아니라 newfd에 지정한 파일 디스크립터 번호를 쓴다. 파일 디스크립터 newfd가 이미 열려 있으면 조용히 닫고서 재사용한다.

파일 디스크립터 newfd를 닫고 재사용하는 단계를 원자적으로 수행한다. 이 점이 중요한 건 close(2)dup()을 이용해 동등한 기능성을 구현하려 하면 newfd가 두 단계 사이에서 재사용될 수도 있는 경쟁 조건에 걸리게 되기 때문이다. 메인 프로그램을 중단시킨 시그널 핸들러에서 파일 디스크립터를 할당하거나 병렬 스레드에서 파일 디스크립터를 할당한다면 그런 재사용이 일어날 수 있을 것이다.

다음 사항에 주의해야 한다.

dup3()

dup3()는 다음 사항을 제외하면 dup2()와 같다.

RETURN VALUE

성공 시 이 시스템 호출들은 새 파일 디스크립터를 반환한다. 오류 시 -1을 반환하며 오류를 나타내도록 errno를 설정한다.

ERRORS

EBADF
oldfd가 열린 파일 디스크립터가 아니다.
EBADF
newfd가 파일 디스크립터 허용 범위를 벗어난다. (getrlimit(2)RLIMIT_NOFILE 설명 참고.)
EBUSY
(리눅스 한정) open(2)dup()의 경쟁 조건에서 dup2()dup3()에서 이 값을 반환할 수 있다.
EINTR
dup2()dup3() 호출이 시그널에 의해 중단되었다. signal(7) 참고.
EINVAL
(dup3()) flags에 유효하지 않은 값이 있다.
EINVAL
(dup3()) oldfdnewfd와 같다.
EMFILE
열린 파일 디스크립터 개수에 대한 프로세스별 제한에 도달했다. (getrlimit(2)RLIMIT_NOFILE 설명 참고.)

VERSIONS

리눅스 2.6.27에서 dup3()가 추가되었다. glibc 버전 2.9부터 지원을 이용할 수 있다.

CONFORMING TO

dup(), dup2(): POSIX.1-2001, POSIX.1-2008, SVr4, 4.3BSD.

dup3()는 리눅스 전용이다.

NOTES

newfd가 범위를 벗어날 때 dup2()가 반환하는 오류가 fcntl(..., F_DUPFD, ...)이 반환하는 오류와 다르다. 어떤 시스템에서는 dup2()F_DUPFD처럼 EINVAL을 반환하기도 한다.

newfd가 열려 있었을 때 close(2) 시점에 오류가 보고되더라도 그 정보가 유실된다. 이 점이 신경쓰이는 경우에 (프로그램이 단일 스레드이고 시그널 핸들러에서 파일 디스크립터를 할당하지 않는 경우가 아니라면) dup2() 호출 전에 newfd를 닫는 것은 위에서 설명한 경쟁 조건 때문에 올바른 해법이 아니다. 대신 다음과 같은 식의 코드를 쓸 수 있을 것이다.

/* 'newfd'를 복제해서 아래에서 close() 오류 검사에 사용.
   EBADF 오류는 'newfd'가 열려 있지 않다는 뜻. */

tmpfd = dup(newfd);
if (tmpfd == -1 && errno != EBADF) {
    /* 예상 외의 dup() 오류 처리 */
}

/* 'oldfd'를 'newfd'로 원자적으로 복제 */

if (dup2(oldfd, newfd) == -1) {
    /* dup2() 오류 처리 */
}

/* 이제 'newfd'가 원래 가리키던 파일에 대해서
   close() 오류 검사 */

if (tmpfd != -1) {
    if (close(tmpfd) == -1) {
        /* close 오류 처리 */
    }
}

SEE ALSO

close(2), fcntl(2), open(2), pidfd_getfd(2)


2021-03-22