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_SEALS
fd
가 가리키는 아이노드에 설정된 봉인들을 나타내는 비트 마스크.- 다른 모든 명령
- 0.
오류 시 -1을 반환하며 오류를 나타내도록 errno
를 설정한다.
ERRORS
EACCES
또는EAGAIN
- 다른 프로세스가 잡은 락 때문에 동작을 수행할 수 없다.
EAGAIN
- 파일을 다른 프로세스가 메모리 매핑했기 때문에 동작을 수행할 수 없다.
EBADF
fd
가 열린 파일 디스크립터가 아니다.EBADF
cmd
가F_SETLK
나F_SETLKW
이고 파일 디스크립터 열기 모드가 요청한 락 종류와 맞지 않는다.EBUSY
cmd
가F_SETPIPE_SZ
이고arg
에 지정한 새 파이프 용량이 현재 파이프에서 데이터 저장에 쓰고 있는 버퍼 공간보다 작다.EBUSY
cmd
가F_ADD_SEALS
이고arg
에F_SEAL_WRITE
가 포함돼 있는데fd
가 가리키는 파일에 쓰기 가능 공유 매핑이 존재한다.EDEADLK
- 지정한
F_SETLKW
명령이 교착을 유발하게 됨을 탐지했다. EFAULT
lock
이 접근 가능한 주소 공간 밖에 있다.EINTR
cmd
가F_SETLKW
나F_OFD_SETLKW
이고 동작이 시그널에 의해 중단됐다. signal(7) 참고.EINTR
cmd
가F_GETLK
나F_SETLK
,F_OFD_GETLK
,F_OFD_SETLK
이고 락을 확인 내지 획득하기 전에 동작이 시그널에 의해 중단됐다. 주로 원격 파일을 잠글 때 (가령 NFS 상에서 잠글 때) 발생하지만 때로는 지역적으로도 발생할 수 있다.EINVAL
cmd
에 지정한 값을 이 커널에서 알지 못한다.EINVAL
cmd
가F_ADD_SEALS
이고arg
에 알 수 없는 봉인 비트가 포함돼 있다.EINVAL
cmd
가F_ADD_SEALS
나F_GET_SEALS
이고fd
가 가리키는 아이노드를 담은 파일 시스템에서 봉인을 지원하지 않는다.EINVAL
cmd
가F_DUPFD
이고arg
가 음수거나 최대 허용 값보다 크다. (getrlimit(2)의RLIMIT_NOFILE
설명 참고.)EINVAL
cmd
가F_SETSIG
이고arg
가 허용되는 시그널 번호가 아니다.EINVAL
cmd
가F_OFD_SETLK
나F_OFD_SETLKW
,F_OFD_GETLK
이고l_pid
를 0으로 지정하지 않았다.EMFILE
cmd
가F_DUPFD
이고 열린 파일 디스크립터 개수에 대한 프로세스별 제한에 도달했다.ENOLCK
- 열린 파일 구간 락이 너무 많다. 또는 락 테이블이 가득 찼다. 또는 (가령 NFS를 통한 락킹에서) 원격 락킹 프로토콜이 실패했다.
ENOTDIR
cmd
에F_NOTIFY
를 지정했는데fd
가 디렉터리를 가리키지 않는다.EPERM
cmd
가F_SETPIPE_SZ
이고 사용자 파이프에 대한 연성 또는 경성 제한에 도달했다. pipe(7) 참고.EPERM
- 덧붙이기 전용 속성이 설정된 파일에서
O_APPEND
플래그를 없애려고 시도했다. EPERM
cmd
가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