Linux DRM - open()과 ioctl() 코드 흐름
The code path of open() and ioctl() in Linux DRM v6.19: drm_stub_open, drm_file_alloc, drm_ioctl dispatch and permission checks, plus a libdrm probe example.
October 28, 2026
Linux DRM - open()과 ioctl() 코드 흐름
Reqruies
- Kernel: Linux v6.19 소스 기준
- Library: libdrm 2.4.129
- 선행 글: Linux DRM 2편 코어 초기화와 드라이버 등록
Introduction
| 편 | 주제 |
|---|---|
| 1편 | 구조와 전체 그림 |
| 2편 | 코어 초기화와 드라이버 등록 |
2편에서
/dev/dri/card0 같은 노드가 만들어지는 과정을 봤습니다. 이 글은 유저가 그 노드를 쓰는 순간, 즉 open()과 ioctl()이 커널 안에서 어떤 함수들을 거치는지 따라갑니다. 마지막에는 같은 일을 유저 공간에서 libdrm으로 해 보는 예제를 봅니다.Code Flow 3: open()
유저가
open("/dev/dri/card0")를 호출하면 커널은 주 번호 226으로 앞서 등록한 stub fops를 찾습니다. stub은 진짜 일을 하지 않고 파일 연산 표를 드라이버의 것으로 갈아 끼웁니다.drivers/gpu/drm/drm_drv.c - drm_stub_open() (L1188-1216)c
소스 drivers/gpu/drm/drm_drv.c L1188-L1216 (Linux v6.19)
open("/dev/dri/card0") 처리 흐름MermaidsequenceDiagram participant U as 유저 프로세스 participant V as VFS (chrdev 226) participant S as drm_stub_open participant O as drm_open / drm_open_helper U->>V: open("/dev/dri/card0") V->>S: drm_stub_fops.open S->>S: drm_minor_acquire(iminor(inode))<br/>→ struct drm_minor S->>S: replace_fops(filp, driver->fops) S->>O: filp->f_op->open() (= drm_open) O->>O: drm_file_alloc() → struct drm_file O->>O: primary라면 drm_master_open() O-->>U: fd
드라이버의
fops가 가진 .open은 drm_open()입니다. 이것은 매크로 한 줄에서 옵니다.include/drm/drm_gem.h - #define DRM_GEM_FOPS (L464-473)c
소스 include/drm/drm_gem.h L464-L473 (Linux v6.19)
이 매크로 안에 앞으로 시리즈에서 보게 될 모든 진입점이 있습니다.
.unlocked_ioctl = drm_ioctl(ioctl), .read = drm_read(이벤트 읽기), .mmap = drm_gem_mmap(버퍼 매핑), .poll = drm_poll입니다. drm_open()과 drm_open_helper()는 다음과 같습니다.drivers/gpu/drm/drm_file.c - drm_open() (L369-403)c
소스 drivers/gpu/drm/drm_file.c L369-L403 (Linux v6.19)
drm_open()은 minor를 찾고 drm_open_helper()에 넘깁니다.drivers/gpu/drm/drm_file.c - drm_open_helper() (L316-355)c
소스 drivers/gpu/drm/drm_file.c L316-L355 (Linux v6.19)
drm_file_alloc()이 open() 하나에 해당하는 상태를 만듭니다. 앞서 본 object_idr, 이벤트 큐 등이 여기서 초기화됩니다.drivers/gpu/drm/drm_file.c - drm_file_alloc() (L132-197)c
소스 drivers/gpu/drm/drm_file.c L132-L197 (Linux v6.19)
drm_gem_open()이 handle 표를 초기화하는데, 이 호출은
idr_init_base(&file_private->object_idr, 1)입니다. handle 번호가 1부터 시작하는 이유가 여기 있습니다(0은 "없음"으로 쓰기 위해서입니다). filp->private_data = priv 덕분에 이후의 모든 ioctl, read, mmap은 file->private_data에서 drm_file을 바로 얻습니다.Code Flow 4: ioctl()
ioctl(fd, DRM_IOCTL_xxx, arg)은 fops->unlocked_ioctl인 drm_ioctl()로 들어옵니다.drm_ioctl() 의 디스패치Mermaidflowchart TD A["ioctl(fd, DRM_IOCTL_xxx, arg)"] --> B["drm_ioctl()"] B --> C{"nr ≥ DRM_COMMAND_BASE<br/>(0x40) ?"} C -->|"예 : 드라이버 ioctl"| D["dev->driver->ioctls[nr - 0x40]"] C -->|"아니오 : 코어 ioctl"| E["drm_ioctls[nr]"] D --> F["copy_from_user → kdata"] E --> F F --> G["drm_ioctl_kernel()"] G --> H{"drm_ioctl_permit()<br/>AUTH · MASTER · ROOT_ONLY · RENDER_ALLOW"} H -->|"거부"| I["-EACCES"] H -->|"통과"| J["func(dev, kdata, file_priv)"] J --> K["copy_to_user → 결과"]
drivers/gpu/drm/drm_ioctl.c - drm_ioctl() (L821-919)c
소스 drivers/gpu/drm/drm_ioctl.c L821-L919 (Linux v6.19)
핵심만 정리하면 이렇습니다.
- ioctl 번호의
type이'd'(DRM_IOCTL_BASE)인지 확인합니다. - 번호가 0x40 이상이면 드라이버 표(
dev->driver->ioctls), 아니면 코어 표(drm_ioctls[])에서 항목을 찾습니다. - 유저가 준 크기를 믿지 않고 커널에 있는 정의(
ioctl->cmd)로 버퍼 크기를 계산합니다(Do not trust userspace). copy_from_user()후drm_ioctl_kernel()이 권한을 검사하고 실제 함수를 부릅니다.
drivers/gpu/drm/drm_ioctl.c - drm_ioctl_kernel() (L787-805)c
소스 drivers/gpu/drm/drm_ioctl.c L787-L805 (Linux v6.19)
코어 ioctl 표는 매크로로 만든 배열입니다. 이 시리즈에서 만날 항목만 골라 보면 다음과 같습니다. 오른쪽 끝의 플래그가 앞서 본 권한 규칙입니다.
drivers/gpu/drm/drm_ioctl.c - drm_ioctls (L632-735)c
소스 drivers/gpu/drm/drm_ioctl.c L632-L735 (Linux v6.19)
libdrm: The View from User Space
이제 유저 공간에서 위 흐름을 직접 따라가 보겠습니다. 아래 프로그램은 DRM 장치를 열어서 드라이버 정보와 KMS 객체를 나열합니다.
drm_probe.c - 드라이버 정보와 KMS 객체 나열c
각 libdrm 함수는 커널 ioctl 하나로 이어집니다. 위 코드에서
drm_ioctls[] 표의 이름을 그대로 찾아볼 수 있습니다.| libdrm 함수 | ioctl | 커널 핸들러 | 플래그 |
|---|---|---|---|
drmGetVersion() | DRM_IOCTL_VERSION | drm_version() | RENDER_ALLOW |
drmGetCap() | DRM_IOCTL_GET_CAP | drm_getcap() | RENDER_ALLOW |
drmSetClientCap() | DRM_IOCTL_SET_CLIENT_CAP | drm_setclientcap() | 없음 |
drmModeGetResources() | DRM_IOCTL_MODE_GETRESOURCES | drm_mode_getresources() | 없음 |
drmModeGetConnector() | DRM_IOCTL_MODE_GETCONNECTOR | drm_mode_getconnector() | 없음 |
drmModeGetPlaneResources() | DRM_IOCTL_MODE_GETPLANERESOURCES | drm_mode_getplane_res() | 없음 |
drmModeObjectGetProperties() | DRM_IOCTL_MODE_OBJ_GETPROPERTIES | drm_mode_obj_get_properties_ioctl() | 없음 |
이 프로그램은
libdrm 2.4.129로 컴파일되는 것을 확인했습니다. 실행하려면 /dev/dri/card0에 접근 권한이 필요합니다(데스크톱 로그인 세션이면 보통 가능하고, 아니면 video/render 그룹이 필요합니다). strace -e trace=openat,ioctl ./drm_probe로 실행하면 유저가 호출하는 ioctl 순서를 볼 수 있습니다(strace 버전에 따라 DRM_IOCTL_VERSION 같은 이름까지 풀어서 보여 줍니다).Code Locations
| 단계 | 함수 | 위치 (Linux v6.19) |
|---|---|---|
| open: 파일 상태 만들기 | drm_open_helper() | drivers/gpu/drm/drm_file.c |
| open: drm_file 할당 | drm_file_alloc() | drivers/gpu/drm/drm_file.c |
| open: master 지정 | drm_master_open() | drivers/gpu/drm/drm_auth.c |
| ioctl: 진입과 디스패치 | drm_ioctl() | drivers/gpu/drm/drm_ioctl.c |
| ioctl: 권한 검사 | drm_ioctl_permit() | drivers/gpu/drm/drm_ioctl.c |
| ioctl: 코어 ioctl 표 | drm_ioctls | drivers/gpu/drm/drm_ioctl.c |
Summary
이 Post 에서 확인한 것은 다음과 같습니다.
- open: stub이 드라이버의 fops로 갈아 끼우고,
drm_file_alloc()이open()하나의 상태(handle 표, 이벤트 큐)를 만듭니다. - ioctl:
drm_ioctl()이 코어 표와 드라이버 표에서 항목을 찾고,drm_ioctl_permit()이 권한을 검사한 뒤 핸들러를 부릅니다. - libdrm 함수는 대부분 이
ioctl의 얇은 포장입니다.
다음부터는 렌더링 쪽입니다. 먼저
drm_file의 object_idr에 들어 있는 GEM handle이 어떻게 만들어지고, 그 버퍼를 CPU에서 어떻게 읽고 쓰는지 봅니다.