NAME

close - 파일 디스크립터 닫기

SYNOPSIS

#include <unistd.h>

int close(int fd);

DESCRIPTION

close()는 파일 디스크립터를 닫아서 더이상 어떤 파일도 가리키지 않고 재사용 가능하게 만든다. 연계된 파일에 레코드 락(fcntl(2) 참고)이 잡힌 게 있고 그 프로세스 소유라면 (그 락을 얻는 데 어떤 파일 디스크립터를 썼는지 상관없이) 그 락을 제거한다.

fd가 하위 열린 파일 기술 항목(open(2) 참고)을 가리키는 마지막 파일 디스크립터면 그 열린 파일 기술 항목 관련 자원들이 해제된다. 또 그 파일 디스크립터가 unlink(2)로 제거된 파일에 대한 마지막 참조면 파일이 삭제된다.

RETURN VALUE

성공 시 close()는 0을 반환한다. 오류 시 -1을 반환하며 오류를 나타내도록 errno를 설정한다.

ERRORS

EBADF
fd가 유효한 열린 파일 디스크립터가 아니다.
EINTR
close() 호출이 시그널에 의해 중단되었다. signal(7) 참고.
EIO
I/O 오류가 발생했다.
ENOSPC, EDQUOT
NFS에서는 이 오류들이 가용 저장 공간을 초과하는 첫 쓰기에서 정상적으로 보고되지 않고 이후의 write(2), fsync(2), close()에서 보고된다.

오류 시 close()를 재시도하지 말아야 하는 이유에 대한 NOTES의 설명을 보라.

CONFORMING TO

POSIX.1-2001, POSIX.1-2008, SVr4, 4.3BSD.

NOTES

닫기에 성공했다는 게 데이터가 성공적으로 디스크에 저장됐다는 걸 보장하지는 않는다. 커널에서 버퍼 캐시를 써서 기록을 연기하기 때문이다. 일반적으로 파일 시스템에서는 파일이 닫힐 때 버퍼를 플러시하지 않는다. 기반 디스크에 데이터가 물리적으로 저장되게 해야 한다면 fsync(2)를 사용하라. (그 다음부터는 디스크 하드웨어에게 달렸다.)

exec에서 닫기 플래그를 쓰면 execve(2) 성공 시 파일 디스크립터가 자동으로 닫히게 할 수 있다. 자세한 내용은 fcntl(2) 참고.

다중 스레드 프로세스와 close()

같은 프로세스의 다른 스레드에서 시스템 호출로 이용하고 있을 수도 있는 동안에 파일 디스크립터를 닫는 건 그리 현명한 일이 아니다. 파일 디스크립터가 재사용될 수도 있기 때문에 의도치 않은 부작용을 유발할 수 있는 잡아내기 힘든 경쟁 조건이 있다.

그리고 한 파일 디스크립터에 두 스레드가 동작을 수행하는 다음 시나리오를 생각해 보자.

  1. 한 스레드가 파일 디스크립터에 대한 I/O 시스템 호출 안에서 블록돼 있다. 예를 들면 이미 가득 찬 파이프에 write(2)를 시도하거나, 현재 가용 데이터가 없는 스트림 소켓에 read(2)를 시도하고 있다.

  2. 다른 스레드에서 그 파일 디스크립터를 닫는다.

이 상황에서의 동작 방식은 시스템에 따라 다르다. 어떤 시스템에선 파일 디스크립터가 닫힐 때 블로킹 시스템 호출이 즉시 오류와 함께 반환한다.

리눅스에선 (그리고 아마도 몇몇 다른 시스템에선) 동작 방식이 다르다. 블로킹 상태의 I/O 시스템 호출이 기반 열린 파일 기술 항목에 대한 참조를 잡고 있으며, 그래서 그 I/O 시스템 호출이 완료될 때까지 기술 항목이 열린 상태로 유지된다. (열린 파일 기술 항목에 대한 설명은 open(2) 참고.) 따라서 두 번째 스레드의 close() 후에 첫 번째 스레드의 블로킹 시스템 호출이 성공적으로 완료될 수도 있다.

close()의 오류 반환 다루기

세심한 프로그래머라면 close()의 반환 값을 확인할 것이다. 앞선 write(2) 동작에서의 오류가 열린 파일 기술 항목을 해제하는 마지막 close()에서야 보고되는 일도 충분히 가능하기 때문이다. 파일을 닫을 때 반환 값 검사를 하지 않으면 조용한 데이터 유실로 이어질 수도 있다. 특히 NFS와 디스크 쿼터에서 이런 경우를 볼 수 있다.

단, 실패 반환 값은 진단 용도(즉 미처리 I/O가 아직 있거나 실패한 I/O가 있었을 수도 있음을 응용에게 알려주기)나 보완 용도(가령 파일에 한 번 더 쓰거나 백업 만들기)로만 쓰는 게 좋다.

실패 반환 후에 close()를 재시도하는 건 바람직하지 않은 처리 방식이다. 다른 스레드에서 재사용된 파일 디스크립터를 닫게 될 수도 있기 때문이다. 리눅스 커널에서 닫기 동작 초반에 항상 파일 디스크립터를 해제하여 재사용될 수 있게 만들기 때문에 그런 일이 생길 수 있다. 파일 시스템이나 장치로 데이터 플러시하기 같이 오류를 반환할 수 있는 단계들은 닫기 동작 후반에 이뤄진다.

여러 다른 구현들에서도 마찬가지로 이후 close() 반환에서 오류를 보고하더라도 (파일 디스크립터가 유효하지 않다는 뜻인 EBADF 경우를 제외하고) 파일 디스크립터를 항상 닫는다. POSIX.1에서 현재는 이 점에 대해 아무 언급이 없지만 표준의 다음 주요 릴리스 때 이 동작을 강제화할 계획이 있다.

I/O 오류에 대해 알고 싶은 세심한 프로그래머라면 close() 전에 fsync(2) 호출을 할 수 있다.

EINTR 오류는 다소 특별한 경우다. EINTR 오류와 관련해 POSIX-1.2008에서는 다음처럼 말한다.

잡게 돼 있는 시그널에 의해 close()가 중단되는 경우 errnoEINTR로 설정해서 -1을 반환해야 하며 fildes의 상태는 명세돼 있지 않다.

이는 리눅스와 기타 여러 구현에서 이뤄지는, close()에서 보고하는 다른 오류들처럼 파일 디스크립터가 확실히 닫히는 동작 방식을 허용한다. 하지만 다른 가능성도 허용한다. 즉, 구현에서 EINTR 오류를 반환하면서 파일 디스크립터를 계속 열어 둘 수도 있다. (문서에 따르면 HP-UX의 close()에서 그렇게 한다.) 그러면 호출자는 파일 디스크립터 누출을 막기 위해 한 번 더 close()를 사용해 파일 디스크립터를 닫아야 한다. 이러한 구현 동작 방식의 차이는 이식 가능한 응용에 어려운 장애물이 된다. 많은 구현에서는 EINTR 오류 후에 close()를 다시 호출해선 안 되지만 적어도 한 구현에서는 close()를 다시 호출해야 하는 것이다. POSIX.1 표준의 다음 주요 릴리스에서 이 난제를 다루려는 계획이 있다.

SEE ALSO

close_range(2), fcntl(2), fsync(2), open(2), shutdown(2), unlink(2), fclose(3)


2021-03-22