NAME
fcntl - 파일 디스크립터 조작하기
SYNOPSIS
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
DESCRIPTION
fcntl()은 열린 파일 디스크립터 fd에서 아래 설명하는 동작들 중 하나를 수행한다. cmd에 따라 동작이 정해진다.
fcntl()은 선택적인 세 번째 인자를 받을 수 있다. 그 인자가 필요한지 여부는 cmd에 따라 정해진다. 각 cmd 이름 뒤의 괄호 안에 필요한 인자 타입이 나와 있으며 (대부분의 경우 필요한 타입이 int이며, arg라는 이름으로 그 인자를 부른다.) 인자가 필요치 않으면 void라고 되어 있다.
아래 동작들 중 일부는 리눅스 커널 특정 버전부터 지원된다. 호스트의 커널이 어떤 동작을 지원하는지 확인하는 바람직한 방법은 원하는 cmd 값으로 fcntl()을 호출해서 커널이 그 값을 모른다는 뜻인 EINVAL로 실패하는지 확인하는 것이다.
파일 디스크립터 복제
F_DUPFD(int)-
arg이상인 가장 낮은 가용 파일 디스크립터를 사용해 파일 디스크립터fd를 복제한다. 정확히 지정한 파일 디스크립터를 사용하는 dup(2)과 차이가 있다.성공 시 새 파일 디스크립터가 반환된다.
자세한 내용은 dup(2) 참고.
F_DUPFD_CLOEXEC(int, 리눅스 2.6.24부터)F_DUPFD동작에 더해서 복제된 파일 디스크립터에 exec에서 닫기 플래그를 설정한다. 이 플래그를 지정하면FD_CLOEXEC플래그를 설정하기 위해 프로그램에서 추가로fcntl()F_SETFD동작을 할 필요가 없게 된다. 그 플래그가 유용한 이유에 대해선 open(2)의O_CLOEXEC설명을 보라.
파일 디스크립터 플래그
다음 명령들은 파일 디스크립터에 연계된 플래그들을 조작한다. 현재는 한 가지 플래그만 정의돼 있는데, exec에서 닫기 플래그인 FD_CLOEXEC다. FD_CLOEXEC 비트가 설정돼 있으면 execve(2) 성공 실행 과정에서 파일 디스크립터가 자동으로 닫히게 된다. (execve(2)가 실패하면 파일 디스크립터가 그대로 열려 있다.) FD_CLOEXEC 비트가 설정돼 있지 않으면 execve(2)를 거치면서 파일 디스크립터가 계속 열려 있게 된다.
F_GETFD(void)- 파일 디스크립터 플래그들을 (함수 결과로) 반환한다.
arg는 무시한다. F_SETFD(int)- 파일 디스크립터 플래그들을
arg에 지정된 값으로 설정한다.
다중 스레드 프로그램에서 다른 스레드가 fork(2) 및 execve(2)를 수행할 때 동시에 fcntl() F_SETFD로 exec에서 닫기 플래그를 설정하는 것은 경쟁 조건에 취약하며, 그래서 자식 프로세스에서 실행되는 프로그램으로 파일 디스크립터가 의도치 않게 누출될 수 있다. 자세한 내용과 해결책은 open(2)의 O_CLOEXEC 설명을 보라.
파일 상태 플래그
각 열린 파일 기술 항목에는 어떤 상태 플래그들이 연계돼 있어서 open(2)에서 초기화되고 fcntl()로 변경할 수 있다. (dup(2), fcntl(F_DUPFD), fork(2) 등에 의해) 복제된 파일 디스크립터들은 같은 열린 파일 기술 항목을 가리키며, 따라서 같은 파일 상태 플래그들을 공유한다.
파일 상태 플래그들과 그 의미를 open(2)에서 설명한다.
F_GETFL(void)- 파일 접근 모드 및 파일 상태 플래그들을 (함수 결과로) 반환한다.
arg는 무시한다. F_SETFL(int)- 파일 상태 플래그들을
arg에 지정된 값으로 설정한다.arg에서 파일 접근 모드(O_RDONLY,O_WRONLY,O_RDWR)와 파일 생성 플래그들(O_CREAT,O_EXCL,O_NOCTTY,O_TRUNC)은 무시한다. 리눅스에서 이 명령은O_APPEND,O_ASYNC,O_DIRECT,O_NOATIME,O_NONBLOCK플래그만 바꿀 수 있다.O_DSYNC와O_SYNC플래그는 바꾸는 게 불가능하다. 아래의 BUGS 참고.
권고형 레코드 락
리눅스는 전통적인 ("프로세스 연계") 유닉스 레코드 락을 POSIX 표준대로 구현하고 있다. 더 나은 동작 방식의 리눅스 전용 대안에 대해선 아래의 열린 파일 기술 항목 락 설명을 보라.
F_SETLK와 F_SETLKW, F_GETLK를 이용해 레코드 락(바이트 범위 락, 파일 구간 락, 파일 영역 락이라고도 함)을 획득하고, 해제하고, 존재 여부를 확인한다. 세 번째 인자 lock은 적어도 (순서가 명세되지 않은) 다음 필드들을 담은 구조체의 포인터다.
struct flock {
...
short l_type; /* 락 종류:
F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* l_start 해석 방법:
SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* 락의 시작 오프셋 */
off_t l_len; /* 잠글 바이트 수 */
pid_t l_pid; /* 우리 락에 블록 중인 프로세스의 PID
(F_GETLK 및 F_OFD_GETLK에서 설정) */
...
};
이 구조체의 l_whence, l_start, l_len 필드는 잠그고 싶은 바이트 범위를 지정한다. 파일 끝 너머의 바이트들을 잠글 수도 있지만 파일 시작 앞의 바이트들은 불가능하다.
l_start는 락의 시작 오프셋이며, (l_whence가 SEEK_SET이면) 파일 시작이나 (l_whence가 SEEK_CUR이면) 현재 파일 오프셋, (l_whence가 SEEK_END이면) 파일 끝을 기준으로 해석한다. 뒤의 두 경우에서 오프셋이 파일 시작점 앞에 위치하지만 않는다면 l_start가 음수일 수도 있다.
l_len은 잠글 바이트 수를 나타낸다. l_len이 양수면 l_start 번째부터 l_start+l_len-1 번째까지의 바이트들을이 잠글 범위가 된다. l_len에 0을 지정하는 건 특별한 의미가 있는데, l_whence와 l_start로 지정한 위치부터 파일이 얼마나 자라든 파일 끝까지의 모든 바이트를 잠근다.
POSIX.1-2001에서는 구현체에서 음수 l_len 값을 지원하는 걸 허용한다. (요구하진 않는다.) l_len이 음수인 경우 lock이 기술하는 구간은 l_start+l_len 번째부터 l_start-1 번째까지의 바이트들이다. 리눅스에서는 커널 버전 2.4.21 및 2.5.49부터 지원한다.
l_type 필드를 이용해 파일에 읽기 락(F_RDLCK)이나 쓰기 락(F_WRLCK)을 둘 수 있다. 어떤 파일 영역에서 읽기 락(공유형 락)은 얼마든지 많은 프로세스가 잡을 수 있지만 쓰기 락(배타형 락)은 한 프로세스만 잡을 수 있다. 한 프로세스는 어떤 파일 영역에 대해 한 가지 락만 잡을 수 있다. 이미 잠근 영역에 새 락을 적용하는 경우에는 기존 락이 새 락 종류로 바뀐다. (새 락의 바이트 범위가 우연히 기존 락의 범위와 정확히 일치하는 경우가 아니라면 그 과정에서 기존 락이 분할되거나 축소되거나 병합될 수 있다.)
F_SETLK(struct flock *)lock의l_whence,l_start,l_len필드가 나타내는 바이트들에 대해 (l_type이F_RDLCK이나F_WRLCK이면) 락을 잡거나 (l_type이F_UNLCK이면) 락을 놓는다. 다른 프로세스가 충돌하는 락을 잡고 있으면 호출이 -1을 반환하며errno를EACCES나EAGAIN으로 설정한다. (이 경우에 반환되는 오류가 구현체에 따라 다르므로 POSIX에서는 이식성 있는 응용에서 두 오류를 모두 확인하기를 요구한다.)F_SETLKW(struct flock *)F_SETLK과 같되, 파일에 충돌하는 락이 잡혀 있으면 그 락이 해제될 때까지 기다린다. 대기 중에 시그널을 잡으면 호출이 중단되어 (시그널 핸들러가 반환한 후에) 즉시 반환한다. (반환 값이 -1이고errno를EINTR로 설정한다. signal(7) 참고.)F_GETLK(struct flock *)-
이 호출의 입력에서
lock은 파일에 두고 싶은 락을 기술한다. 락을 둘 수 있을 경우에fcntl()은 실제로 락을 두지는 않고lock의l_type필드로F_UNLCK을 반환하며 구조체의 나머지 필드들은 그대로 둔다.하나 이상의 호환 안 되는 락 때문에 그 락을 둘 수 없을 경우에는
fcntl()이lock의l_type,l_whence,l_start,l_len필드로 그 락들 중 하나에 대한 세부 정보를 반환한다. 충돌하는 락이 전통적인 (프로세스 연계) 레코드 락인 경우에는l_pid필드가 그 락을 잡고 있는 프로세스의 PID로 설정된다. 충돌하는 락이 열린 파일 기술 항목 락인 경우에는l_pid가 -1로 설정된다. 반환된 정보를 호출자가 들여다보는 시점에는 그 정보가 더이상 유효하지 않을 수도 있다는 점에 유의하자.
읽기 락을 두려면 fd가 읽기 가능하게 열려 있어야 한다. 쓰기 락을 두려면 fd가 쓰기 가능하게 열려 있어야 한다. 두 종류의 락을 모두 두려면 읽기-쓰기로 파일을 열면 된다.
F_SETLKW로 락을 둘 때 커널에선 둘 이상 프로세스의 락 요청이 다른 프로세스가 잡고 있는 락에 의해 서로 막혀 있는 상태인 교착을 탐지한다. 예를 들어 프로세스 A가 어느 파일의 100번 바이트에 쓰기 락을 잡고 있고 프로세스 B가 200번 바이트에 쓰기 락을 잡고 있다고 하자. 각 프로세스가 상대 프로세스가 이미 잽고 있는 바이트를 F_SETLKW로 잠그려고 하는 경우, 교착 탐지가 없다면 두 프로세스가 무한정 블록된 상태로 있게 된다. 커널에서 그런 교착을 탐지하면 블록 중인 락 요청들 중 하나를 즉시 EDEADLK으로 실패하게 만든다. 그 오류를 받은 응용에선 자기 락 일부를 해제해서 다른 응용이 진행을 할 수 있게 한 다음에 필요한 락을 다시 획득하려 시도해야 한다. 셋 이상의 프로세스가 수반된 순환 교착도 탐지한다. 하지만 커널의 교착 탐지 알고리즘에 한계가 있다는 점에 유의해야 한다. BUGS 참고.
레코드 락은 명시적인 F_UNCLK에 의해서도 제거되고, 프로세스가 종료할 때도 자동으로 해제된다.
fork(2)를 통해 생성된 자식이 레코드 락을 물려받지 않는다. 하지만 execve(2)를 거치면서 유지된다.
stdio(3) 라이브러리에서 수행하는 버퍼링 때문에 그 패키지의 루틴과 레코드 락을 함께 쓰는 걸 피하는 게 좋다. 대신 read(2)와 write(2)를 사용하라.
위에서 설명한 레코드 락은 (아래에서 설명하는 열린 파일 기술 항목 락과 달리) 프로세스에 연계된다. 그로 인해 몇 가지 안 좋은 상황이 발생한다.
-
프로세스에서 어느 파일을 가리키는 아무 파일 디스크립터라도 닫으면 어느 파일 디스크립터에서 어느 락을 획득했는지와 상관없이 그 파일에 있는 프로세스의 락 모두가 해제된다. 안 좋은 동작이다. 어떤 이유로 어느 라이브러리 함수에서 같은 파일을 열어서 읽고 닫기로 한다면
/etc/passwd나/etc/mtab같은 파일에 대한 락을 잃을 수 있다는 뜻이다. -
프로세스의 스레드들이 락을 공유한다. 달리 말해 다중 스레드 프로그램에서 스레드들이 파일의 같은 영역에 동시에 접근하지 못하게 하는 데 레코드 락을 쓸 수 없다.
열린 파일 기술 항목 락에서 두 문제가 모두 해결된다.
열린 파일 기술 항목 락 (비 POSIX)
열린 파일 기술 항목 락은 권고형 바이트 범위 락이며 그 동작이 위에서 설명한 전통적 레코드 락과 거의 동일하다. 이 락 유형은 리눅스 전용이며 리눅스 3.15부터 이용 가능하다. (이 락 유형을 POSIX.1 다음 판에 포함시키자는 제안이 Austin Group에 제출돼 있다.) 열린 파일 기술 항목에 대한 설명은 open(2)을 보라.
두 락 유형의 주된 차이는 전통적 레코드 락이 프로세스에 연계되는데 반해 열린 파일 기술 항목 락은 flock(2)으로 얻은 락과 마찬가지로 그 락을 획득한 열린 파일 기술 항목에 연계된다는 점이다. 그래서 (전통적 권고형 레코드 락과 달리) 열린 파일 기술 항목 락은 fork(2)를 (그리고 CLONE_FILES를 쓴 clone(2)을) 거치면서 상속되며, 파일 중 하나라도 닫을 때 해제되는 게 아니라 열린 파일 기술 항목에 마지막 닫기가 이뤄질 때만 자동으로 해제된다.
충돌하는 락 조합(읽기 락과 쓰기 락, 또는 두 개의 쓰기 락)에서 하나는 열린 파일 기술 항목 락이고 다른 하나는 전통적 레코드 락일 때, 한 프로세스가 같은 파일 디스크립터에서 획득한 경우라도 두 락이 충돌한다.
같은 열린 파일 기술 항목을 통해 (같은 파일 디스크립터를 통해, 또는 fork(2), dup(2), fcntl() F_DUPFD 등으로 생성된 파일 디스크립터 복제본을 통해) 두는 열린 파일 기술 항목 락은 항상 호환된다. 즉, 이미 잠겨 있는 영역에 새 락을 두면 기존 락이 새 락 유형으로 바뀐다. (그 변환으로 인해 위에서 설명한 것처럼 기존 락이 분할되거나 축소되거나 병합될 수 있다.)
한편으로 다른 열린 파일 기술 항목을 통해 획득한 경우에는 열린 파일 기술 항목 락이 서로 충돌할 수 있다. 따라서 다중 스레드 프로그램의 각 스레드에서 같은 파일에 open(2)을 수행해 얻은 파일 디스크립터를 통해 락을 적용하면 열린 파일 기술 항목 락을 이용해 파일 영역에 대한 접근을 동기화할 수 있다.
전통적 권고형 락에서처럼 fcntl()의 세 번째 인자인 lock은 flock 구조체의 포인터다. 전통적 레코드 락에서와 달리 아래 설명하는 명령들을 사용할 때 그 구조체의 l_pid 필드가 0으로 설정돼 있어야 한다.
열린 파일 기술 항목 락을 다루기 위한 명령들은 전통적 락과 비슷하다.
F_OFD_SETLK(struct flock *)lock의l_whence,l_start,l_len필드가 나타내는 바이트들에 대해 (l_type이F_RDLCK이나F_WRLCK이면) 열린 파일 기술 항목 락을 잡거나 (l_type이F_UNLCK이면) 열린 파일 기술 항목 락을 놓는다. 다른 프로세스가 충돌하는 락을 잡고 있으면 호출이 -1을 반환하며errno를EAGAIN으로 설정한다.F_OFD_SETLKW(struct flock *)F_OFD_SETLK과 같되, 파일에 충돌하는 락이 잡혀 있으면 그 락이 해제될 때까지 기다린다. 대기 중에 시그널을 잡으면 호출이 중단되어 (시그널 핸들러가 반환한 후에) 즉시 반환한다. (반환 값이 -1이고errno를EINTR로 설정한다. signal(7) 참고.)F_OFD_GETLK(struct flock *)- 이 호출의 입력에서
lock은 파일에 두고 싶은 락을 기술한다. 락을 둘 수 있을 경우에fcntl()은 실제로 락을 두지는 않고lock의l_type필드로F_UNLCK을 반환하며 구조체의 나머지 필드들은 그대로 둔다. 하나 이상의 호환 안 되는 락 때문에 그 락을 둘 수 없을 경우에는 위의F_GETLK설명처럼fcntl()이lock을 통해 그 락들 중 하나에 대한 세부 정보를 반환한다.
현재 구현에서는 열린 파일 기술 항목 락에 대해 교착 탐지를 수행하지 않는다. (반면 프로세스 연계 레코드 락에 대해선 커널에서 교착 탐지를 수행한다.)
강제형 락
경고: 리눅스의 강제형 락 구현은 믿을 만하지 않다. 아래 BUGS 참고. 그 버그들 때문에, 그리고 이 기능이 거의 쓰이지 않는 것 같다는 점 때문에 리눅스 4.5부터 강제형 락이 구성 항목(CONFIG_MANDATORY_FILE_LOCKING)에 의해 제어되는 선택적 기능이 되었다. 이는 완전한 기능 제거로 가는 첫 단계다.
기본적으로 전통적 (프로세스 연계) 레코드 락과 열린 파일 기술 항목 레코드 락은 모두 권고형이다. 권고형 락은 강제가 아니므로 협력하는 프로세스들 사이에서 유용하다.
두 락 유형 모두 강제형이 될 수 있다. 강제형 락은 모든 프로세스에 강제된다. 호환되지 않는 강제형 락이 있는 파일 영역에 어떤 프로세스가 호환되지 않는 접근(가령 read(2)나 write(2))을 수행하려 하는 경우 열린 파일 기술 항목에 O_NONBLOCK 플래그가 켜져 있는지에 따라 결과가 달라진다. O_NONBLOCK 플래그가 켜져 있지 않으면 그 락이 제거되거나 접근 동작과 호환되는 모드로 바뀔 때까지 시스템 호출이 블록한다. O_NONBLOCK 플래그가 켜져 있으면 시스템 호출이 EAGAIN 오류로 실패한다.
강제형 락을 이용하려면 잠글 파일을 담은 파일 시스템과 그 파일 모두에서 강제형 락킹이 켜져 있어야 한다. 파일 시스템에서는 mount(8)의 "-o mand" 옵션이나 mount(2)의 MS_MANDLOCK 플래그로 강제형 락킹을 켠다. 파일에서는 그룹 실행 권한을 끄고 set-group-ID 권한 비트를 켜서 (chmod(1) 및 chmod(2) 참고) 강제형 락킹을 켠다.
강제형 락은 POSIX에 명세돼 있지 않다. 일부 다른 시스템에서도 강제형 락을 지원하지만 자세한 사용 방식은 시스템에 따라 다르다.
락 상실
NFS 같은 네트워크 파일 시스템에서 권고형 락을 얻었을 때 그 락이 상실될 수도 있다. 서버에서의 관리 행위로 인해서, 또는 클라이언트가 더는 동작하지 않는다고 서버에서 간주할 만큼 오래 지속된 네트워크 분할(즉 서버와의 네트워크 연결 상실)로 인해 발생할 수 있다.
파일 시스템에서 락이 상실됐다고 판단하면 이후의 read(2) 내지 write(2) 요청이 EIO 오류로 실패할 수 있다. 락을 제거하거나 파일 디스크립터를 닫을 때까지 그 오류가 지속된다. 리눅스 3.12부터 적어도 (모든 하위 버전을 포함한) NFSv4에선 그렇게 동작한다.
일부 유닉스 버전들에선 이 경우에 시그널(SIGLOST)을 보낸다. 리눅스에선 이 시그널을 정의하고 있지 않으며 비동기 락 상실 알림을 제공하지 않는다.
시그널 관리
F_GETOWN, F_SETOWN, F_GETOWN_EX, F_SETOWN_EX, F_GETSIG, F_SETSIG를 이용해 I/O 가용 시그널을 관리한다.
F_GETOWN(void)- 파일 디스크립터
fd의 이벤트에 대해 현재SIGIO및SIGURG시그널을 받을 프로세스 ID나 프로세스 그룹 ID를 (함수 결과로) 반환한다. 프로세스 ID는 양수 값으로 반환되고 프로세스 그룹 ID는 음수 값으로 반환된다. (하지만 아래 BUGS 참고.)arg는 무시한다. F_SETOWN(int)-
파일 디스크립터
fd의 이벤트에 대해SIGIO및SIGURG시그널을 받을 프로세스 ID나 프로세스 그룹 ID를 설정한다.arg로 대상 프로세스 ID 내지 프로세스 그룹 ID를 지정한다. 프로세스 ID는 양수 값으로 지정하고 프로세스 그룹 ID는 음수 값으로 지정한다. 대부분은 호출 프로세스가 자신을 소유자로 지정한다. (즉, getpid(2)로arg를 지정한다.)파일 디스크립터 소유자 설정만 하면 되는 게 아니라 파일 디스크립터에서 시그널 생성을 켜야 한다.
fcntl()F_SETFL명령으로 파일 디스크립터에O_ASYNC파일 상태 플래그를 설정하면 된다. 그러면 그 파일 디스크립터에서 입력이나 출력이 가능해질 때마다SIGIO시그널이 전송된다.fcntl()F_SETSIG명령을 이용해SIGIO외의 시그널을 전달받을 수 있다.F_SETOWN으로 지정한 소유자 프로세스 (그룹)으로 시그널을 보내는 동작에는 kill(2)에서 설명하는 것과 같은 권한 검사가 이뤄지는데,F_SETOWN사용 프로세스가 송신 프로세스가 된다. (하지만 아래 BUGS 참고.) 이 권한 검사가 실패하면 시그널이 조용히 폐기된다. 주의:F_SETOWN동작은fcntl()호출 시점에 호출자의 크리덴셜을 기록하며, 그렇게 저장해 둔 크리덴셜을 권한 검사에 쓴다.파일 디스크립터
fd가 소켓을 가리키는 경우F_SETOWN은 그 소켓으로 대역외 데이터가 왔을 때 전달되는SIGURG시그널의 수신자도 함께 정한다. (select(2)에서 소켓이 "예외적 상태"라고 보고하게 될 모든 경우에SIGURG가 전송된다.)커널 2.6.11까지를 포함한 2.6.x 커널에서 다음이 참이었다.
스레드 그룹을 지원하는 스레드 라이브러리(가령 NPTL)를 사용해 동작하는 다중 스레드 프로세스에서
F_SETSIG에 0 아닌 값을 준 경우에F_SETOWN에 주는 양수 값의 의미가 다른데, 전체 프로세스를 나타내는 프로세스 ID가 아니라 프로세스의 특정 스레드를 나타내는 스레드 ID다. 그래서F_SETSIG를 쓸 때 적절한 결과를 얻으려면F_SETOWN에 getpid(2)가 아니라 gettid(2)의 결과를 줘야 할 수 있다. (현재의 리눅스 스레드 구현에선 주 스레드의 스레드 ID가 프로세스 ID와 같다. 따라서 단일 스레드 프로그램에선 이 경우에 gettid(2)와 getpid(2) 어느쪽을 써도 같다.) 단, 이 문단의 내용은 소켓의 대역외 데이터에 대해 생성되는SIGURG시그널에는 적용되지 않는다. 그 시그널은 항상F_SETOWN에 준 값에 따라 프로세스 아니면 프로세스 그룹에게 간다.리눅스 2.6.12에서 위 동작이 우연히 없어졌으며, 앞으로 복원되지 않을 것이다. 리눅스 2.6.32부터는
SIGIO와SIGURG시그널의 대상을 특정 스레드를 하려면F_SETOWN_EX를 쓰면 된다. F_GETOWN_EX(struct f_owner_ex *) (리눅스 2.6.32부터)-
앞선
F_SETOWN_EX동작에 의한 현재 파일 디스크립터 소유자 설정을 반환한다.arg가 가리키는 다음 형태의 구조체로 정보를 반환한다.struct f_owner_ex { int type; pid_t pid; };type필드는F_OWNER_TID,F_OWNER_PID,F_OWNER_PGRP중 하나다.pid필드는 스레드 ID, 프로세스 ID, 프로세스 그룹 ID를 나타내는 양의 정수다. 자세한 내용은F_SETOWN_EX를 보라. F_SETOWN_EX(struct f_owner_ex *) (리눅스 2.6.32부터)-
F_SETOWN과 비슷한 작업을 수행한다. 호출자가 이 동작을 이용해 I/O 가용 시그널을 특정 스레드나 프로세스, 프로세스 그룹으로 가도록 할 수 있다. 호출자가f_owner_ex구조체의 포인터인arg를 통해 시그널 수신자를 지정한다.type필드는 다음 값들 중 하나이며, 그에 따라pid를 해석하는 방식이 정해진다. F_GETSIG(void)- 입력이나 출력이 가능해질 때 보내는 시그널을 (함수 결과로) 반환한다. 0 값은
SIGIO를 보낸다는 뜻이다. 다른 값(SIGIO포함)은 그 시그널을 대신 보낸다는 뜻이며, 그 경우에SA_SIGINFO를 써서 시그널 핸들러를 설치하면 추가 정보를 얻을 수 있다.arg는 무시한다. F_SETSIG(int)-
입력이나 출력이 가능해질 때 보내는 시그널을
arg에 준 값으로 설정한다. 0 값은 기본인SIGIO시그널을 보낸다는 뜻이다. 다른 값(SIGIO포함)은 그 시그널을 대신 보낸다는 뜻이며, 그 경우에SA_SIGINFO를 써서 시그널 핸들러를 설치하면 추가 정보를 얻을 수 있다.0 아닌 값으로
F_SETSIG를 하고 시그널 핸들러에SA_SIGINFO를 설정하면 (sigaction(2) 참고) I/O 이벤트에 대한 추가 정보가 핸들러에게siginfo_t구조체로 전달된다.si_code필드에서 출처가SI_SIGIO로 돼 있으면si_fd필드로 그 이벤트에 연계된 파일 디스크립터를 알 수 있다. 그렇지 않으면 어떤 파일 디스크립터가 미처리 상태인지 알 방법이 없으며, 따라서 어느 파일 디스크립터에 I/O가 가능한지 알아내기 위해 일반적 메커니즘(select(2), poll(2),O_NONBLOCK설정한 read(2) 등)을 이용해야 한다.si_fd에 들어가는 파일 디스크립터는F_SETSIG동작 때 지정한 값이다. 이 때문에 가끔 특이한 상황이 발생할 수 있다. 파일 디스크립터를 (dup(2) 등으로) 복제하고서 원래 파일 디스크립터를 닫은 경우에 I/O 이벤트는 계속 생성되지만si_fd필드에 이젠 닫힌 파일 디스크립터 번호가 들어가게 된다.실시간 시그널(값 >=
SIGRTMIN)을 택하면 같은 시그널 번호로 여러 I/O 이벤트가 큐에 저장될 수 있다. (가용 메모리에 따라 큐 저장 여부가 달라진다.) 마찬가지로 시그널 핸들러에SA_SIGINFO를 설정하면 추가 정보를 얻을 수 있다.리눅스에선 한 프로세스에서 큐에 저장되는 실시간 시그널 수에 제한을 두고 있다. (getrlimit(2) 및 signal(7) 참고.) 그 한도에 도달하면 커널에선 다시
SIGIO전달로 되돌아가며, 그 시그널은 특정 스레드가 아니라 프로세스 전체로 전달된다.
이 메커니즘들을 이용하면 대부분 경우에 select(2)나 poll(2)을 쓰지 않고도 완전한 비동기 I/O를 구현할 수 있다.
O_ASYNC 사용은 BSD와 리눅스 전용이다. POSIX.1에서 F_GETOWN 및 F_SETOWN 사용에 대해 명세하는 것은 소켓의 SIGURG 시그널과 함께 쓰는 것뿐이다. (POSIX에선 SIGIO 시그널을 명세하고 있지 않다.) F_GETOWN_EX, F_SETOWN_EX, F_GETSIG, F_SETSIG는 리눅스 전용이다. POSIX에도 비슷한 결과를 얻을 수 있는 비동기 I/O 동작과 aio_sigevent 구조체가 있다. 리눅스에선 그 역시도 GNU C 라이브러리(Glibc)에 포함되어 이용할 수 있다.
임차권
(리눅스 2.4부터) F_SETLEASE와 F_GETLEASE를 사용해 파일 디스크립터 fd가 가리키는 열린 파일 기술 항목에 새 임차권을 설정하고 현재 임차권을 확인한다. 파일 임차를 통해 임차권을 가진 프로세스("임차권 소유자")는 그 파일 디스크립터가 가리키는 파일에 다른 프로세스("임차 종료자")가 open(2)이나 truncate(2)를 시도할 때 (시그널 전달을 통해) 알림을 받게 된다.
F_SETLEASE(int)-
정수
arg로 지정한 다음 값들 중 하나에 따라 파일 임차권을 설정하거나 제거한다.F_RDLCK- 읽기 임차권을 얻는다. 파일이 쓰기용으로 열리거나 절단될 때 호출자가 알림을 받게 된다. 읽기용으로 연 파일 디스크립터에서만 읽기 임차권을 둘 수 있다.
F_WRLCK- 쓰기 임차권을 얻는다. 파일이 읽기용 또는 쓰기용으로 열리거나 절단될 때 호출자가 알림을 받게 된다. 그 파일에 대해 열린 다른 파일 디스크립터가 없는 경우에만 쓰기 임차권을 둘 수 있다.
F_UNLCK- 파일에서 임차권을 제거한다.
임차권은 열린 파일 기술 항목(open(2) 참고)에 연계된다. 그래서 (예를 들어 fork(2)나 dup(2)으로 만든) 복제 파일 디스크립터들이 같은 임차를 가리키며, 그래서 디스크립터들 중 어느 것으로도 임차를 조작하거나 해제할 수 있다. 임의의 파일 디스크립터에서 이뤄지는 명시적 F_UNLCK 동작에 의해서, 또는 그 파일 디스크립터들이 모두 닫힐 때 임차가 해제된다.
정규 파일에서만 임차권을 얻을 수 있다. 비특권 프로세스는 파일 UID(소유자)가 프로세스의 파일 시스템 UID와 일치하는 경우에만 임차권을 얻을 수 있다. CAP_LEASE 역능을 가진 프로세스는 아무 파일에서나 임차권을 얻을 수 있다.
F_GETLEASE(void)- 파일 디스크립터
fd에 어떤 종류의 임차권이 연계돼 있는지 알려 준다.F_RDLCK,F_WRLCK,F_UNLCK중 하나를 반환하며, 각각 읽기 임차권, 쓰기 임차권, 임차권 없음을 나타낸다.arg는 무시한다.
어느 프로세스("임차 종료자")가 F_SETLEASE를 통해 수립된 임차와 충돌하는 open(2) 내지 truncate(2)를 수행하면 그 시스템 호출이 블록되고 커널에서 임차권 소유자에게 시그널(기본은 SIGIO)을 보내서 알린다. 그 시그널을 수신한 임차권 소유자는 다른 프로세스가 파일에 접근하기 위해 필요한 준비(가령 캐싱된 버퍼 플러시하기)를 한 다음 임차를 제거하거나 격하시키는 방식으로 대응해야 한다. 임차 제거를 위해선 arg를 F_UNLCK으로 지정해서 F_SETLEASE 명령을 수행하면 된다. 임차권 소유자가 현재 파일에 쓰기 임차권을 가지고 있고 임차 종료자가 읽기용으로 파일을 열려는 경우에는 임차권 소유자가 임차권을 읽기로 격하시키는 것으로도 충분하다. 이를 위해선 arg를 R_RDLCK으로 지정해서 F_SETLEASE 명령을 수행하면 된다.
/proc/sys/fs/lease-break-time에 지정된 초 안에 임차권 소유자가 임차를 격하 내지 제거하지 못하면 커널에서 강제로 임차권 소유자의 임차를 제거하거나 격하시킨다.
임차 종료가 개시되고 나면 임차권 소유자가 자발적으로 임차를 격하 내지 제거하거나 종료 타이머가 만료된 후 커널에서 강제로 그렇게 할 때까지는 F_GETLEASE가 목표 임차 종류(F_RDLCK과 F_UNLCK 가운데 임차 종료자와 호환될 종류)를 반환한다.
임차가 자발적으로 또는 강제적으로 제거 내지 격하되고 나면 임차 종료자의 시스템 호출이 블록에서 해제된 경우가 아니면 커널에서 임차 종료자의 시스템 호출이 계속 진행시킨다.
임차 종료자가 블록돼 있던 open(2) 내지 truncate(2) 호출이 시그널 핸들러에 의해 중단되는 경우에는 시스템 호출이 EINTR 오류로 실패하되, 나머지 단계들이 위에서 설명한 대로 이뤄진다. open(2) 내지 truncate(2)에서 블록돼 있는 중에 임차 종료자가 시그널에 의해 죽는 경우에는 나머지 단계들이 위에서 설명한 대로 이뤄진다. 임차 종료자가 open(2) 호출 시 O_NONBLOCK 플래그를 지정한 경우에는 호출이 EWOULDBLOCK 오류로 즉시 실패하되, 나머지 단계들이 위에서 설명한 대로 이뤄진다.
임차권 소유자에게 알리는 데 쓰는 기본 시그널은 SIGIO지만 fcntl()의 F_SETSIG 명령으로 바꿀 수 있다. F_SETSIG 명령을 (SIGIO를 지정해서라도) 수행하고 SA_SIGINFO를 써서 시그널 핸들러를 설정한 경우에는 핸들러에서 두 번째 인자로 siginfo_t 구조체를 받게 되며 그 인자의 si_fd 필드에 다른 프로세스가 접근한 임차 파일의 파일 디스크립터가 들어간다. (호출자가 여러 파일에 임차권을 가지고 있을 때 쓸모가 있다.)
파일 및 디렉터리 변경 알림 (dnotify)
F_NOTIFY(int)-
(리눅스 2.4부터)
fd가 가리키는 디렉터리나 그 안의 파일이 바뀔 때 알려 준다. 알릴 이벤트를arg에 지정하는데, 다음 비트를 0개 이상 OR 한 비트 마스크다.DN_ACCESS- 파일에 접근했음. (read(2), pread(2), readv(2) 등)
DN_MODIFY- 파일을 변경했음. (write(2), pwrite(2), writev(2), truncate(2), ftruncate(2) 등)
DN_CREATE- 파일을 생성했음. (open(2), creat(2), mknod(2), mkdir(2), link(2), symlink(2), 이 디렉터리로 rename(2))
DN_DELETE- 파일 링크를 끊었음. (unlink(2), 다른 디렉터리로 rename(2), rmdir(2))
DN_RENAME- 이 디렉터리 안에서 파일 이름을 바꿨음. (rename(2))
DN_ATTRIB- 파일 속성을 바꿨음. (chown(2), chmod(2), utime(2), utimensat(2) 등)
(이 정의들을 얻으려면 헤더 파일을 포함시키기 전에 기능 검사 매크로
_GNU_SOURCE가 정의돼 있어야 한다.)디렉터리 알림은 기본적으로 "단발성"이며 추가 알림을 받으려면 응용에서 재등록을 해야 한다. 아니면
arg에DN_MULTISHOT을 포함시키면 명시적으로 제거하기 전까지 알림 효력이 유지된다.F_NOTIFY요청을 연달아 하면 누적된다. 즉,arg의 이벤트들이 이미 감시 중인 이벤트 세트에 추가된다. 모든 이벤트 알림을 끄려면arg를 0으로 해서F_NOTIFY호출을 하면 된다.시그널 전달을 통해 알림이 이뤄진다. 기본 시그널은
SIGIO지만fcntl()의F_SETSIG명령으로 바꿀 수 있다. (참고로SIGIO는 큐 처리가 없는 표준 시그널이다. 실시간 시그널을 쓰게 바꾸면 여러 알림이 큐처럼 처리되게 할 수 있다.) 바꾸는 경우 시그널 핸들러에서는 (SA_SIGINFO를 써서 핸들러를 설정했다면) 두 번째 인자로siginfo_t구조체를 받게 되며 그 구조체의si_fd필드에 알림을 발생시킨 파일 디스크립터가 들어간다. (여러 디렉터리에 알림을 설정할 때 쓸모가 있다.)특히
DN_MULTISHOT을 쓸 때는 알림에 실시간 시그널을 사용해서 여러 알림이 큐처럼 처리되게 하는 게 좋다.참고: 신규 응용에서는 훨씬 우수한 파일 시스템 이벤트 알림 인터페이스를 제공하는 inotify 인터페이스(커널 2.6.13부터 이용 가능)를 쓰는 게 좋다. inotify(7) 참고.)
파이프의 용량 바꾸기
F_SETPIPE_SZ(int, 리눅스 2.6.35부터)-
fd가 가리키는 파이프의 용량을 최소arg바이트가 되도록 바꾼다. 비특권 프로세스는 시스템 페이지 크기와/proc/sys/fs/pipe-max-size(proc(5) 참고)에 규정된 제한치 사이 아무 값으로나 파이프 용량을 조정할 수 있다. 페이지 크기보다 작게 파이프 용량을 설정하려 하면 조용히 페이지 크기로 올림된다. 비특권 프로세스가/proc/sys/fs/pipe-max-size의 제한치보다 크게 파이프 용량을 설정하려 하면EPERM오류가 난다. 특권 프로세스(CAP_SYS_RESOURCE)는 그 제한을 넘을 수 있다.커널에서 파이프에 버퍼를 할당할 때 구현 편의를 위해
arg보다 큰 용량을 쓸 수도 있다. (현재 구현에선 요청 크기에 가장 가까운 2의 거듭제곱으로 올려서 할당한다.) 설정한 실제 (바이트 단위) 용량이 함수 결과로 반환된다.파이프 용량을 현재 파이프에서 데이터 저장에 쓰고 있는 버퍼 공간보다 작게 설정하려고 하면
EBUSY오류가 발생한다.파이프에 데이터를 쓸 때 파이프 버퍼의 페이지들을 이용하는 방식 때문에 기록 크기에 따라선 기록할 수 있는 바이트 수가 명목 크기보다 작을 수도 있다.
F_GETPIPE_SZ(void, 리눅스 2.6.35부터)fd가 가리키는 파이프의 용량을 (함수 결과로) 반환한다.
파일 봉인
파일 봉인은 어떤 파일에 허용되는 동작들을 제한한다. 파일에 어떤 봉인을 설정하면 이후 그 파일에 대한 특정 동작들이 EPERM으로 실패하게 되며, 그 파일은 봉인되었다고 한다. 기본 봉인 세트는 기반 파일과 파일 시스템 종류에 따라 정해진다. 파일 봉인 소개와 그 용도 설명, 약간의 예시 코드를 memfd_create(2)에서 볼 수 있다.
현재는 (MFD_ALLOW_SEALING을 사용한) memfd_create(2)가 반환한 파일 디스크립터에만 파일 봉인을 적용할 수 있다. 다른 파일 시스템에선 봉인에 대한 모든 fcntl() 동작이 EINVAL을 반환한다.
봉인은 아이노드의 속성이다. 따라서 같은 아이노드를 가리키는 모든 열린 파일 디스크립터들이 같은 봉인 세트를 공유한다. 그리고 봉인은 추가만 되고 절대 제거할 수 없다.
F_ADD_SEALS(int, 리눅스 3.17부터)- 비트 마스크 인자
arg에 준 봉인들을 파일 디스크립터fd가 가리키는 아이노드의 봉인 세트에 추가한다. 봉인을 다시 제거할 수 없다. 이 호출이 성공하면 커널에서 즉시 그 봉인들을 강제한다. 현재 봉인 세트에F_SEAL_SEAL(아래 참고)이 들어 있으면 이 호출이EPERM으로 거부된다.F_SEAL_SEAL이 설정돼 있지 않을 때 이미 설정된 봉인을 추가하면 아무 일도 일어나지 않는다. 봉인을 추가하려면 파일 디스크립터fd가 쓰기 가능해야 한다. F_GET_SEALS(void, 리눅스 3.17부터)fd가 가리키는 아이노드의 현재 봉인 세트를 (함수 결과로) 반환한다. 설정된 봉인이 없으면 0을 반환한다. 파일에서 봉인을 지원하지 않으면 -1을 반환하며errno를EINVAL로 설정한다.
다음 봉인들이 있다.
F_SEAL_SEAL- 이 봉인이 설정돼 있으면 이후
F_ADD_SEALS를 쓴fcntl()호출이EPERM오류로 실패한다. 그래서 이 봉인은 봉인 세트 자체의 변경을 막는다. 파일의 최초 봉인 세트에F_SEAL_SEAL이 포함돼 있으면 실질적으로 봉인 세트가 고정돼서 잠겨지게 된다. F_SEAL_SHRINK- 이 봉인이 설정돼 있으면 해당 파일의 크기를 줄일 수 없다.
O_TRUNC플래그를 쓴 open(2), 그리고 truncate(2)와 ftruncate(2)에 영향을 준다. 해당 파일을 줄이려고 하면 그 호출이EPERM으로 실패한다. 파일 크기를 늘이는 건 가능하다. F_SEAL_GROW- 이 봉인이 설정돼 있으면 해당 파일의 크기를 늘일 수 없다. 파일 끝 너머 write(2), truncate(2), ftruncate(2), fallocate(2)에 영향을 준다. 이를 이용해 파일 크기를 늘이려고 하면 그 호출이
EPERM으로 실패한다. 크기를 유지하거나 줄이는 경우에는 기대하는 대로 동작한다. F_SEAL_WRITE-
이 봉인이 설정돼 있으면 파일 내용물을 변경할 수 없다. 하지만 파일 크기를 줄이거나 늘이는 게 여전히 가능하고 허용된다. 그래서 보통 이 봉인은 다른 봉인과 조합해서 쓴다. write(2)와 (
FALLOC_FL_PUNCH_HOLE플래그를 쓴 경우에 한해) fallocate(2)에 영향을 준다. 이 봉인이 설정돼 있으면 그 호출이EPERM으로 실패한다. 또한 mmap(2)을 통해 쓰기 가능 공유 메모리 매핑을 새로 만들려고 하면EPERM으로 실패하게 된다.쓰기 가능 공유 메모리 매핑이 존재하는 경우
F_SEAL_WRITE를F_ADD_SEALS동작으로 설정하려 하면EBUSY로 실패한다. 이 봉인을 추가하려면 먼저 그런 매핑을 해제해야 한다. 또한 파일에 대기 중인 비동기 I/O 동작(io_submit(2))이 있으면 미완료 쓰기 동작이 모두 파기된다. F_SEAL_FUTURE_WRITE(리눅스 5.1부터)-
이 봉인의 효과는
F_SEAL_WRITE와 비슷하되, 봉인 설정 전에 만들어진 쓰기 가능 공유 매핑을 통해선 파일 내용물을 변경할 수 있다. mmap(2)을 통해 쓰기 가능 공유 매핑을 새로 만들려고 하면EPERM으로 실패하게 된다. 마찬가지로 write(2)를 통해 파일에 쓰기를 하려고 하면EPERM으로 실패하게 된다.이 봉인을 이용하면 한 프로세스에서 메모리 버퍼를 만들어 계속해서 변경을 하면서도 그 버퍼를 다른 프로세스들과 "읽기 전용" 방식으로 공유할 수 있다.
파일 읽기/쓰기 힌트
쓰기 수명 힌트를 사용하면 지정한 아이노드에서의, 또는 특정 열린 파일 기술 항목을 통한 쓰기의 상대적 예상 수명에 대해 커널에게 알려 줄 수 있다. (열린 파일 기술 항목에 대한 설명은 open(2) 참고.) 이 맥락에서 "쓰기의 수명"이란 매체 상에서 그 데이터가 덮어 써지거나 삭제되기까지 예상 시간을 뜻한다.
아래 명세된 다양한 힌트 값들을 사용해 응용에서 쓰기 요청들을 다양한 클래스로 나눠서 한 저장 백엔드 상에서 도는 여러 사용자 내지 응용들의 I/O 패턴을 일관적 방식으로 종합할 수 있다. 하지만 이 플래그들에는 어떤 기능적 의미도 함의돼 있지 않으며, 다양한 I/O 클래스들에선 일관적이기만 하다면 쓰기 수명 힌트를 각자의 방식으로 이용할 수 있다.
파일 디스크립터 fd에 다음 동작들을 적용할 수 있다.
F_GET_RW_HINT(uint64_t *, 리눅스 4.13부터)fd가 가리키는 기반 아이노드에 연계된 읽기/쓰기 힌트 값을 반환한다.F_SET_RW_HINT(uint64_t *, 리눅스 4.13부터)fd가 가리키는 기반 아이노드에 연계된 읽기/쓰기 힌트 값을 설정한다. 명시적으로 바꾸거나 기반 파일 시스템이 언마운트될 때까지 힌트가 지속된다.F_GET_FILE_RW_HINT(uint64_t *, 리눅스 4.13부터)fd가 가리키는 열린 파일 기술 항목에 연계된 읽기/쓰기 힌트 값을 반환한다.F_SET_FILE_RW_HINT(uint64_t *, 리눅스 4.13부터)fd가 가리키는 열린 파일 기술 항목에 연계된 읽기/쓰기 힌트 값을 설정한다.
열린 파일 기술 항목에 읽기/쓰기 힌트를 지정하지 않으면 아이노드에 지정된 값을 쓴다.
리눅스 4.13부터 다음 읽기/쓰기 힌트들이 유효하다.
RWH_WRITE_LIFE_NOT_SET- 어떤 힌트도 설정돼 있지 않다. 기본값이다.
RWH_WRITE_LIFE_NONE- 이 파일 내지 아이노드에 어떤 구체적 쓰기 수명도 연계돼 있지 않다.
RWH_WRITE_LIFE_SHORT- 이 아이노드에 또는 이 열린 파일 기술 항목을 통해 기록한 데이터가 수명이 짧을 것으로 예상된다.
RWH_WRITE_LIFE_MEDIUM- 이 아이노드에 또는 이 열린 파일 기술 항목을 통해 기록한 데이터가
RWH_WRITE_LIFE_SHORT으로 기록한 데이터보다 수명이 길 것으로 예상된다. RWH_WRITE_LIFE_LONG- 이 아이노드에 또는 이 열린 파일 기술 항목을 통해 기록한 데이터가
RWH_WRITE_LIFE_MEDIUM으로 기록한 데이터보다 수명이 길 것으로 예상된다. RWH_WRITE_LIFE_EXTREME- 이 아이노드에 또는 이 열린 파일 기술 항목을 통해 기록한 데이터가
RWH_WRITE_LIFE_LONG으로 기록한 데이터보다 수명이 길 것으로 예상된다.
쓰기용 힌트들은 서로에 대해 상대적인 값일 뿐이며 개별적으로 절대적 의미를 부여해선 안 된다.
RETURN VALUE
호출 성공 시 반환 값은 동작에 따라 정해진다.
F_DUPFD- 새 파일 디스크립터.
F_GETFD- 파일 디스크립터 플래그 값.
F_GETFL- 파일 상태 플래그 값.
F_GETLEASE- 파일 디스크립터에 잡아 둔 임차권 종류.
F_GETOWN- 파일 디스크립터 소유자 값.
F_GETSIG- 읽기나 쓰기가 가능해질 때 전송되는 시그널 값. 전통적인
SIGIO동작이면 0. F_GETPIPE_SZ,F_SETPIPE_SZ- 파이프 용량.
F_GET_SEALSfd가 가리키는 아이노드에 설정된 봉인들을 나타내는 비트 마스크.- 다른 모든 명령
- 0.
오류 시 -1을 반환하며 오류를 나타내도록 errno를 설정한다.
ERRORS
EACCES또는EAGAIN- 다른 프로세스가 잡은 락 때문에 동작을 수행할 수 없다.
EAGAIN- 파일을 다른 프로세스가 메모리 매핑했기 때문에 동작을 수행할 수 없다.
EBADFfd가 열린 파일 디스크립터가 아니다.EBADFcmd가F_SETLK나F_SETLKW이고 파일 디스크립터 열기 모드가 요청한 락 종류와 맞지 않는다.EBUSYcmd가F_SETPIPE_SZ이고arg에 지정한 새 파이프 용량이 현재 파이프에서 데이터 저장에 쓰고 있는 버퍼 공간보다 작다.EBUSYcmd가F_ADD_SEALS이고arg에F_SEAL_WRITE가 포함돼 있는데fd가 가리키는 파일에 쓰기 가능 공유 매핑이 존재한다.EDEADLK- 지정한
F_SETLKW명령이 교착을 유발하게 됨을 탐지했다. EFAULTlock이 접근 가능한 주소 공간 밖에 있다.EINTRcmd가F_SETLKW나F_OFD_SETLKW이고 동작이 시그널에 의해 중단됐다. signal(7) 참고.EINTRcmd가F_GETLK나F_SETLK,F_OFD_GETLK,F_OFD_SETLK이고 락을 확인 내지 획득하기 전에 동작이 시그널에 의해 중단됐다. 주로 원격 파일을 잠글 때 (가령 NFS 상에서 잠글 때) 발생하지만 때로는 지역적으로도 발생할 수 있다.EINVALcmd에 지정한 값을 이 커널에서 알지 못한다.EINVALcmd가F_ADD_SEALS이고arg에 알 수 없는 봉인 비트가 포함돼 있다.EINVALcmd가F_ADD_SEALS나F_GET_SEALS이고fd가 가리키는 아이노드를 담은 파일 시스템에서 봉인을 지원하지 않는다.EINVALcmd가F_DUPFD이고arg가 음수거나 최대 허용 값보다 크다. (getrlimit(2)의RLIMIT_NOFILE설명 참고.)EINVALcmd가F_SETSIG이고arg가 허용되는 시그널 번호가 아니다.EINVALcmd가F_OFD_SETLK나F_OFD_SETLKW,F_OFD_GETLK이고l_pid를 0으로 지정하지 않았다.EMFILEcmd가F_DUPFD이고 열린 파일 디스크립터 개수에 대한 프로세스별 제한에 도달했다.ENOLCK- 열린 파일 구간 락이 너무 많다. 또는 락 테이블이 가득 찼다. 또는 (가령 NFS를 통한 락킹에서) 원격 락킹 프로토콜이 실패했다.
ENOTDIRcmd에F_NOTIFY를 지정했는데fd가 디렉터리를 가리키지 않는다.EPERMcmd가F_SETPIPE_SZ이고 사용자 파이프에 대한 연성 또는 경성 제한에 도달했다. pipe(7) 참고.EPERM- 덧붙이기 전용 속성이 설정된 파일에서
O_APPEND플래그를 없애려고 시도했다. EPERMcmd가F_ADD_SEALS인데fd가 쓰기용으로 열리지 않았거나 파일의 현재 봉인 세트에F_SEAL_SEAL이 이미 포함돼 있다.
CONFORMING TO
SVr4, 4.3BSD, POSIX.1-2001. POSIX.1-2001에서는 F_DUPFD, F_GETFD, F_SETFD, F_GETFL, F_SETFL, F_GETLK, F_SETLK, F_SETLKW 동작만 명세하고 있다.
F_GETOWN과 F_SETOWN을 POSIX.1-2001에서 명세하고 있다. (그 정의를 얻으려면 _XOPEN_SOURCE를 500 이상 값으로 정의하거나 _POSIX_C_SOURCE를 200809L 이상 값으로 정의해야 한다.)
F_DUPFD_CLOEXEC는 POSIX.1-2008에 정의되어 있다. (그 정의를 얻으려면 _POSIX_C_SOURCE를 200809L 이상 값으로 정의하거나 _XOPEN_SOURCE를 700 이상 값으로 정의해야 한다.)
F_GETOWN_EX, F_SETOWN_EX, F_SETPIPE_SZ, F_GETPIPE_SZ, F_GETSIG, F_SETSIG, F_NOTIFY, F_GETLEASE, F_SETLEASE는 리눅스 전용이다. (그 정의를 얻으려면 _GNU_SOURCE 매크로를 정의해야 한다.)
F_OFD_SETLK, F_OFD_SETLKW, F_OFD_GETLK는 리눅스 전용이다. (그리고 그 정의를 얻으려면 _GNU_SOURCE를 정의해야 한다.) 하지만 POSIX.1 다음 버전에 포함시키려는 작업이 진행 중이다.
F_ADD_SEALS와 F_GET_SEALS는 리눅스 전용이다.
NOTES
dup2(2)가 반환하는 오류가 F_DUPFD가 반환하는 오류와 다르다.
파일 락
리눅스의 원래 fcntl() 시스템 호출은 (flock 구조체에서) 큰 파일 오프셋을 다룰 수 있도록 설계되지 않았다. 그래서 리눅스 2.4에서 fcntl64() 시스템 호출이 추가됐다. 새 시스템 호출에선 파일 락킹에 flock64라는 다른 구조체를 사용하며, 대응하는 F_GETLK64, F_SETLK64, F_SETLKW64 명령을 사용한다. 하지만 glibc를 쓰는 응용에서는 이런 세부 사항을 무시할 수 있다. glibc의 fcntl() 래퍼에서 이용 가능한 경우에 투명하게 최신 시스템 호출을 이용하기 때문이다.
레코드 락
커널 2.0부터 flock(2)으로 두는 락과 fcntl()로 두는 락 사이에 어떤 상호작용도 없다.
여러 시스템에는 struct flock에 l_sysid(락을 잡은 머신 식별) 같은 추가 필드들이 있다. 락을 잡은 프로세스가 다른 머신에 있다면 분명 l_pid만으로는 별 쓸모가 없을 것이다. 리눅스에선 이 필드가 없다. 다만 (MIPS32 같은) 일부 아키텍처에는 있다.
리눅스의 원래 fcntl() 시스템 호출은 (flock 구조체에서) 큰 파일 오프셋을 다룰 수 있도록 설계되지 않았다. 그래서 리눅스 2.4에서 fcntl64() 시스템 호출이 추가됐다. 새 시스템 호출에선 파일 락킹에 flock64라는 다른 구조체를 사용하며, 대응하는 F_GETLK64, F_SETLK64, F_SETLKW64 명령을 사용한다. 하지만 glibc를 쓰는 응용에서는 이런 세부 사항을 무시할 수 있다. glibc의 fcntl() 래퍼에서 이용 가능한 경우에 투명하게 최신 시스템 호출을 이용하기 때문이다.
레코드 락과 NFS
리눅스 3.12 전에선 NFSv4 클라이언트가 일정 시간 동안 서버와 접속이 끊기면 (90초 넘게 통신이 없으면) 알아채지도 못하게 락을 잃었다가 다시 획득하게 될 수도 있었다. (접속이 끊겼다고 보는 그 시간을 NFSv4 임대 시간이라고 한다. 리눅스 NFS 서버에선 /proc/fs/nfsd/nfsv4leasetime을 보면 그 시간을 알 수 있다. 그 파일의 기본값은 90이다.) 그 사이 시간에 다른 프로세스가 락을 획득해서 파일 I/O를 수행할 수도 있으므로 이 시나리오에는 데이터 오염 위험이 있다.
리눅스 3.12부터는 NFSv4 클라이언트가 서버와 접속이 끊기면 프로세스가 파일을 닫고 다시 열기 전까지는 그 프로세스가 락을 가지고 있다고 "알고 있는" 파일에 대한 I/O가 모두 실패하게 된다. 커널 매개변수 nfs.recover_lost_locks를 1로 설정하면 3.12 전의 동작 방식을 살릴 수 있고, 그러면 서버와 접속이 재수립될 때 클라이언트가 잃었던 락을 복구하려 시도하게 된다. 수반되는 데이터 오염 위험 때문에 이 매개변수 기본값은 0(꺼짐)이다.
BUGS
F_SETFL
F_SETFL로 O_DSYNC나 O_SYNC 플래그의 상태를 바꾸는 게 불가능하다. 그 플래그들의 상태를 바꾸려고 하면 조용히 무시된다.
F_GETOWN
리눅스 시스템 호출 규약의 제한 때문에 일부 아키텍처(특히 i386)에서는 F_GETOWN이 반환할 (음수) 프로세스 그룹 ID가 -1에서 -4095 범위에 있는 경우 glibc에서 그 반환 값을 시스템 호출 오류로 잘못 해석한다. 즉, fcntl()의 반환 값이 -1이 되고 errno가 (양수) 프로세스 그룹 ID를 담게 된다. 리눅스 전용인 F_GETOWN_EX 동작으로 이 문제를 피할 수 있다. glibc 버전 2.11부터는 F_GETOWN_EX로 F_GETOWN을 구현하기 때문에 F_GETOWN 문제가 드러나지 않는다.
F_SETOWN
리눅스 2.4까지에는 비특권 프로세스가 F_SETOWN으로 소켓 파일 디스크립터의 소유자를 호출자 외의 프로세스 (그룹)으로 지정할 때 발생할 수 있는 버그가 있다. 그 경우에 소유자 프로세스 (그룹)에게 호출자가 시그널을 보낼 권한이 있는 경우에도 fcntl()이 -1을 반환하고 errno를 EPERM으로 설정할 수 있다. 그 오류가 반환되더라도 파일 디스크립터 소유자는 설정이 돼서 그 소유자에게 시그널이 전송된다.
교착 탐지
F_SETLKW 요청을 다룰 때 커널에서 쓰는 교착 탐지 알고리즘에서 위음성 결과(교착 탐지에 실패해서 교착된 프로세스들이 무한정 블록)와 위양성 결과(교착이 없는데 EDEADLK 오류)가 나올 수 있다. 예를 들어 커널에서 락 의존성 탐색 깊이를 10단계로 제한하고 있는데, 그래서 그 크기를 넘는 순환 교착 고리를 탐지하게 못하게 된다. 그리고 clone(2)의 CLONE_FILES 플래그를 써서 생성된 둘 이상의 프로세스에서 (커널 관점에서) 충돌하는 것처럼 보이는 락을 두는 경우에 커널이 교착이라고 잘못 판단할 수 있다.
강제형 락
리눅스의 강제형 락 구현에는 경쟁 조건이 있기 때문에 믿을 만하지 않다. 락과 범위가 겹치는 write(2) 호출이 락 획득 후에 데이터를 변경할 수 있으며, 락과 범위가 겹치는 read(2) 호출이 쓰기 락 획득 후에만 이뤄질 수 있을 데이터 변경을 탐지하게 될 수 있다. 강제형 락과 mmap(2) 사이에도 비슷한 경쟁이 존재한다. 따라서 강제형 락킹에 의지하지 않는 게 좋다.
SEE ALSO
dup2(2), flock(2), open(2), socket(2), lockf(3), capabilities(7), feature_test_macros(7), lslocks(8)
리눅스 커널 소스 디렉터리 Documentation/filesystems/의 locks.txt, mandatory-locking.txt, dnotify.txt (옛날 커널에선 이 파일들이 Documentation/ 디렉터리에 있으며 mandatory-locking.txt가 mandatory.txt다.)
2021-03-22