← System Programming DUJINLABS.COM

File Descriptor / I/O · Linux userspace / kernel ABI

fcntl lock과 ioctl ABI

fd 제어 명령을 한데 묶어 보지 않고, advisory record lock, open-file-description lock, device-specific ioctl의 소유권과 ABI를 구분합니다.

Series
16 / 37
Build
cc -std=c17 -Wall -Wextra -O2 lock_range.c -o lock_range
Run
./lock_range locked.dat
Kernel
Linux 6.18.37 LTS

fcntl과 ioctl은 둘 다 fd를 받는데 무엇을 제어하는가?

fcntl은 fd slot flag, open file status flag, duplication, byte-range lock처럼 VFS가 공통으로 이해하는 제어를 제공한다. ioctl은 terminal, block device, network device 등 subsystem별 command ABI로 내려간다.

전통적인 POSIX record lock은 process-associated라 같은 process가 해당 inode의 어떤 fd 하나를 close해도 lock에 영향이 생길 수 있다. Linux open-file-description lock은 struct file과 연결돼 thread/process 간 ownership을 더 명확히 할 수 있다.

구조 그림

그림 1. inode byte 범위에 겹쳐 있는 advisory lock
0163248648096
Process A · WRLCK
Process B · RDLCK
Process C · WRLCK 대기

ioctl argument는 이 lock map과 달리 device UAPI 구조체이며 command별 size·direction·version을 따로 검증한다.

lock은 파일 전체가 아니라 [start, start+len) 범위다. 겹치지 않는 read lock은 공존할 수 있지만 write lock과 겹치면 충돌한다.

호출 흐름

그림 2. 사용자 코드에서 관찰 가능한 결과까지
fd file type 확인
fcntl/ioctl command + argument
VFS dispatch 공통 또는 f_op
lock/device subsystem state 변경
return result/errno

command 번호만 기록하지 말고 argument 구조체의 크기, 방향, ownership과 lock이 어느 객체에 붙는지 함께 기록한다.

그림 3. 커널 내부에서 지나가는 주요 지점
do_fcntl cmd switch
fcntl_setlk file_lock
sys_ioctl security hook
vfs_ioctl unlocked_ioctl
driver uapi struct copy

함수 이름을 외우기 위한 그림이 아니다. 반환값, 파일 디스크립터, 메모리 매핑, 대기 큐 가운데 무엇이 다음 단계로 전달되는지 확인한다.

Linux 6.18.37 LTS 소스 위치

glibc 함수에서 멈추지 않고 syscall 구현과 커널 객체가 만나는 파일까지 내려간다. 링크는 동일한 태그의 원본 파일을 가리킨다.

파일함수·구조체여기서 볼 것
fs/fcntl.c do_fcntl(), f_dupfd() fcntl command 분기와 fd/open-file flag 구분
fs/locks.c fcntl_setlk(), locks_lock_inode_wait() record lock 충돌, 대기, owner
fs/ioctl.c sys_ioctl(), do_vfs_ioctl() 공통 ioctl과 file operation dispatch

실행 예제 원본

아래 코드는 설명을 위해 중간 줄을 생략한 의사 코드가 아니다. 파일로 빌드해 실행할 수 있는 최소 예제다.

빌드cc -std=c17 -Wall -Wextra -O2 lock_range.c -o lock_range
01#define _GNU_SOURCE
02#include <fcntl.h>
03#include <stdio.h>
04#include <string.h>
05#include <unistd.h>
06
07int main(int argc, char **argv)
08{
09    if (argc != 2)
10        return 2;
11    int fd = open(argv[1], O_RDWR | O_CREAT | O_CLOEXEC, 0644);
12    if (fd < 0)
13        return 1;
14
15    struct flock lock;
16    memset(&lock, 0, sizeof(lock));
17    lock.l_type = F_WRLCK;
18    lock.l_whence = SEEK_SET;
19    lock.l_start = 0;
20    lock.l_len = 16;
21
22    if (fcntl(fd, F_OFD_SETLKW, &lock) < 0) {
23        perror("F_OFD_SETLKW");
24        return 1;
25    }
26    puts("locked bytes [0, 16); press Enter");
27    getchar();
28    lock.l_type = F_UNLCK;
29    int rc = fcntl(fd, F_OFD_SETLK, &lock);
30    close(fd);
31    return rc < 0;
32}

코드 조각별 설명

실제 코드 16행memset(&lock, 0

uapi 구조체의 padding과 사용하지 않는 field까지 0으로 만들어 architecture별 쓰레기 값을 넘기지 않는다.

실제 코드 18행lock.l_whence = SEEK_SET

byte range 기준을 파일 시작으로 고정한다. SEEK_CUR이면 공유 file offset 변화가 lock range 계산에 영향을 줄 수 있다.

실제 코드 20행lock.l_len = 16

[0,16) 구간만 advisory write lock을 건다. 0이면 시작 위치부터 EOF 이후 성장 구간까지를 뜻한다.

실제 코드 22행F_OFD_SETLKW

충돌이 풀릴 때까지 대기하는 Linux OFD lock이다. ownership은 process PID가 아니라 open file description에 붙는다.

실제 코드 28행lock.l_type = F_UNLCK

같은 owner와 range로 명시적 unlock한다. 마지막 struct file reference close에서도 OFD lock은 해제된다.

세부 동작

01

advisory lock은 I/O를 강제로 막지 않는다

협력하는 process가 lock protocol을 지킬 때만 보호된다. lock을 얻지 않고 read/write하는 process를 VFS가 일반적으로 차단하지 않는다.

database나 log writer는 lock 범위와 acquire order를 application protocol에 포함해야 한다.

02

ioctl request number도 ABI다

_IO, _IOR, _IOW, _IOWR macro는 type, number, direction, size를 encoding하지만 kernel이 pointer 유효성과 구조체 version을 자동으로 해결해 주는 것은 아니다. compat task에는 32/64-bit layout 변환이 필요할 수 있다.

driver private ioctl을 설계할 때 fixed-width type, padding, reserved field, size/version 규칙을 둔다.

03

fd 종류별 지원 여부를 먼저 확인한다

regular file, pipe, socket, tty가 같은 command를 이해하지 않는다. ENOTTY는 fd가 terminal이 아니라는 뜻뿐 아니라 해당 ioctl command를 구현하지 않는다는 일반 반환으로도 사용된다.

fstat와 subsystem query로 type을 확인하고 command 실패를 장치 부재와 ABI mismatch로 나눈다.

객체와 수명

대상언제 생기고 없어지는가확인할 값
file_lockfcntl lock 요청에서 만들어져 inode lock tree/list에 연결된다owner, range, type
OFD ownerstruct file 수명과 함께 유지되며 dup/fork 공유 특성이 있다fl_owner, last file ref
ioctl argumentsyscall 동안 user memory에 있고 driver가 copy_from/to_user한다size, alignment, reserved field

실패 조건과 오해하기 쉬운 부분

겉으로 보이는 현상실제 원인 후보확인 방법
lock을 걸어도 다른 writer가 씀상대가 advisory protocol 미준수상대 fcntl 호출과 I/O 경로
예상보다 lock이 일찍 풀림POSIX lock과 close semantics 혼동F_SETLK/F_OFD_SETLK 구분
ioctl ENOTTY잘못된 fd type 또는 command 미지원fstat, driver version, UAPI header

직접 확인

  1. 두 terminal에서 예제를 실행해 두 번째 process가 첫 번째 unlock까지 block되는지 확인한다.
  2. F_OFD_SETLKW를 F_SETLKW로 바꾸고 dup fd/별도 open fd를 close할 때 lock 수명을 비교한다.
  3. 터미널 fd에 TIOCGWINSZ ioctl을 호출하고 redirected stdin에서 ENOTTY가 되는 조건을 확인한다.
실행./lock_range locked.dat
추적strace -e trace=openat,fcntl,ioctl,close ./lock_range locked.dat

원문