
서비스 · 프로세스 관리 15 / 50 · Part 2. 시그널·자원·세션
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정analyst)
"로그 파일을 지웠는데 디스크 사용률이 그대로입니다." 운영에서 매우 흔한 장애 문의다. 원인은 거의 항상 같다. 어떤 프로세스가 그 파일을 아직 열고 있다. 이 상황을 이해하려면 프로세스가 파일을 다루는 단위인 파일 디스크립터(fd) 를 알아야 한다.
이번 글에서는 lsof와 /proc/PID/fd로 프로세스가 연 파일을 확인하고, 삭제됐지만 열려 있는 파일을 재현해 찾고, 비우고, 복구하는 방법을 실습한다. 이 기법은 침해 사고에서 공격자가 지운 파일을 되살리는 포렌식 기법이기도 하다.
| 항목 | 내용 |
|---|---|
| 정의 | 프로세스가 연 파일·소켓·파이프를 가리키는 정수 번호 |
| 기본 fd | 0 = 표준입력, 1 = 표준출력, 2 = 표준에러 |
| 새 fd | 가장 작은 빈 번호부터 할당 (보통 3부터) |
| 대상 | 일반 파일, 디렉터리, 장치, 소켓, 파이프, eventfd 등 모든 것 |
| 확인 | ls -l /proc/PID/fd, lsof -p PID |
| 한도 | ulimit -n (17편) |
| 명령 | 용도 |
|---|---|
lsof -p PID | 특정 프로세스가 연 파일 |
lsof /path/file | 특정 파일을 연 프로세스 |
lsof +D /dir | 디렉터리 아래 파일을 연 프로세스 (재귀) |
lsof -u USER | 사용자별 |
lsof -c NAME | 명령 이름별 |
lsof +L1 | 링크 수가 1 미만(삭제된) 파일 |
lsof -i :PORT | 네트워크 (16편) |
-n -P | IP·포트 이름 변환 생략 (빠르고 정확) |
| FD | 의미 | TYPE | 의미 |
|---|---|---|---|
cwd | 작업 디렉터리 | REG | 일반 파일 |
rtd | 루트 디렉터리 | DIR | 디렉터리 |
txt | 실행 파일 | CHR | 문자 장치 |
mem | 메모리 매핑 파일 | unix / IPv4 | 소켓 |
3r / 3w / 3u | fd 3 읽기 / 쓰기 / 읽기쓰기 | a_inode | eventpoll 등 커널 객체 |
W (예: 3uW) | 파일 전체 쓰기 잠금 | FIFO | 파이프 |

rm은 파일을 지우는 명령이 아니라 디렉터리에서 이름(링크)을 제거하는 명령이다(unlink). 커널은 inode의 링크 수와 열린 fd 수가 모두 0 이 될 때 데이터 블록을 해제한다.
dd → big.log 생성 (링크 1, 열린 fd 0)
tail -f big.log → 링크 1, 열린 fd 1
rm big.log → 링크 0, 열린 fd 1 ← 이름은 없지만 데이터는 살아 있다 (df 변화 없음)
: > /proc/1332/fd/3 → 크기 0 으로 잘라냄 → 블록 해제 (df 0%)
tail 종료 → 열린 fd 0 → inode 완전 해제
같은 원리로 01편의 실행 중 삭제된 실행 파일 도 프로세스가 inode를 잡고 있어 살아 있었다.
# 1) 현재 셸의 fd 와 lsof
exec 3> ~/app.log; echo "hello" >&3 # 셸에 fd 3 을 열어 둔다
lsof -p $$ | head -12
ls -l /proc/$$/fd
exec 3>&- # fd 3 닫기
# 2) (root) 삭제됐지만 열린 파일 재현 — 200MB tmpfs 에서
mkdir -p /mnt/lab && mount -t tmpfs -o size=200m tmpfs /mnt/lab
dd if=/dev/zero of=/mnt/lab/big.log bs=1M count=150 status=none; df -h /mnt/lab
tail -f /mnt/lab/big.log > /dev/null &
rm /mnt/lab/big.log; ls /mnt/lab; df -h /mnt/lab
lsof +L1 | grep -e COMMAND -e /mnt/lab
ls -l /proc/$(pgrep -x tail)/fd | grep deleted
: > /proc/$(pgrep -x tail)/fd/3; df -h /mnt/lab # 파일 비우기
kill %1; wait; umount /mnt/lab
# 3) (Ubuntu) 서비스가 연 파일
lsof -c cron | awk 'NR==1 || $4 ~ /[0-9]/'
lsof -nP -u systemd-resolve -a -d 0-20 | head -8 # -a: 조건 AND, -d: fd 범위
실습은 운영 디스크가 아닌 tmpfs 에서 한다. 운영 서버에서
: > /proc/PID/fd/N은 해당 프로세스가 쓰던 로그를 잘라내는 작업이므로, 로그 보존 정책을 확인한 뒤 수행한다.



텍스트 원본(실제 출력):
[analyst@rocky9-lab ~]$ exec 3> ~/app.log; echo "hello" >&3
[analyst@rocky9-lab ~]$ lsof -p $$ | head -12
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 1266 analyst cwd DIR 0,42 4096 951186 /home/analyst
bash 1266 analyst rtd DIR 0,42 4096 984222 /
bash 1266 analyst txt REG 0,42 1389024 795509 /usr/bin/bash
bash 1266 analyst mem REG 0,42 346132 796773 /usr/lib/locale/C.utf8/LC_CTYPE
bash 1266 analyst mem REG 0,42 50 796780 /usr/lib/locale/C.utf8/LC_NUMERIC
bash 1266 analyst mem REG 0,42 3360 796783 /usr/lib/locale/C.utf8/LC_TIME
bash 1266 analyst mem REG 0,42 1406 796772 /usr/lib/locale/C.utf8/LC_COLLATE
bash 1266 analyst mem REG 0,42 270 796778 /usr/lib/locale/C.utf8/LC_MONETARY
bash 1266 analyst mem REG 0,42 48 796777 /usr/lib/locale/C.utf8/LC_MESSAGES/SYS_LC_MESSAGES
bash 1266 analyst mem REG 0,42 34 796781 /usr/lib/locale/C.utf8/LC_PAPER
bash 1266 analyst mem REG 0,42 62 796779 /usr/lib/locale/C.utf8/LC_NAME
[analyst@rocky9-lab ~]$ ls -l /proc/$$/fd
total 0
lrwx------ 1 analyst analyst 64 Sep 24 11:50 0 -> /dev/pts/0
lrwx------ 1 analyst analyst 64 Sep 24 11:50 1 -> /dev/pts/0
lrwx------ 1 analyst analyst 64 Sep 24 11:50 10 -> /dev/pts/0
lrwx------ 1 analyst analyst 64 Sep 24 11:50 2 -> /dev/pts/0
lr-x------ 1 analyst analyst 64 Sep 24 11:50 255 -> /tmp/run_15_1.sh
l-wx------ 1 analyst analyst 64 Sep 24 11:50 3 -> /home/analyst/app.log
[analyst@rocky9-lab ~]$ exec 3>&-
[root@rocky9-lab ~]# mkdir -p /mnt/lab && mount -t tmpfs -o size=200m tmpfs /mnt/lab
[root@rocky9-lab ~]# dd if=/dev/zero of=/mnt/lab/big.log bs=1M count=150 status=none; df -h /mnt/lab
Filesystem Size Used Avail Use% Mounted on
tmpfs 200M 150M 50M 75% /mnt/lab
[root@rocky9-lab ~]# tail -f /mnt/lab/big.log > /dev/null & sleep 0.3
[root@rocky9-lab ~]# rm /mnt/lab/big.log; ls /mnt/lab; df -h /mnt/lab
Filesystem Size Used Avail Use% Mounted on
tmpfs 200M 150M 50M 75% /mnt/lab
[root@rocky9-lab ~]# lsof +L1 | grep -e COMMAND -e /mnt/lab
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
tail 1332 root 3r REG 0,80 157286400 0 2 /mnt/lab/big.log (deleted)
[root@rocky9-lab ~]# ls -l /proc/$(pgrep -x tail)/fd | grep deleted
lr-x------ 1 root root 64 Sep 24 11:50 3 -> /mnt/lab/big.log (deleted)
[root@rocky9-lab ~]# : > /proc/$(pgrep -x tail)/fd/3; df -h /mnt/lab
tail: /mnt/lab/big.log: file truncated
Filesystem Size Used Avail Use% Mounted on
tmpfs 200M 0 200M 0% /mnt/lab
[root@rocky9-lab ~]# kill %1; wait; umount /mnt/lab && echo unmounted
unmounted
root@ubuntu-lab:~# lsof -c cron | awk 'NR==1 || $4 ~ /[0-9]/'
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
cron 80 root 0r CHR 1,3 0t0 27 /dev/null
cron 80 root 1u unix 0xffff88811d841f80 0t0 5209 type=STREAM (CONNECTED)
cron 80 root 2u unix 0xffff88811d841f80 0t0 5209 type=STREAM (CONNECTED)
cron 80 root 3uW REG 0,65 3 146 /run/crond.pid
cron 80 root 4u unix 0xffff8881257b2d00 0t0 5228 type=DGRAM (CONNECTED)
root@ubuntu-lab:~# lsof -nP -u systemd-resolve -a -d 0-20 | head -8
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
systemd-r 67 systemd-resolve 0r CHR 1,3 0t0 27 /dev/null
systemd-r 67 systemd-resolve 1u unix 0xffff88813a46db00 0t0 4436 type=STREAM (CONNECTED)
systemd-r 67 systemd-resolve 2u unix 0xffff88813a46db00 0t0 4436 type=STREAM (CONNECTED)
systemd-r 67 systemd-resolve 3u unix 0xffff88813a46f180 0t0 4461 type=DGRAM (CONNECTED)
systemd-r 67 systemd-resolve 4u a_inode 0,16 0 1037 [eventpoll:5,6,7,8,9,10,11,13,14,15,16,17,18,19,20]
systemd-r 67 systemd-resolve 5u a_inode 0,16 0 1037 [signalfd]
systemd-r 67 systemd-resolve 6u a_inode 0,16 0 1037 [timerfd]
| 관찰 | 의미 |
|---|---|
cwd, rtd, txt, mem 행 | 셸이 연 것은 파일만이 아니다. 작업 디렉터리, 루트, 실행 파일, 로케일 파일까지 모두 보인다 |
/proc/$$/fd의 3 -> /home/analyst/app.log (l-wx) | exec 3>로 연 쓰기 전용 fd |
0 1 2 10 -> /dev/pts/0 | 표준 입출력과 bash가 내부적으로 복제한 터미널 fd(10) |
255 -> /tmp/run_15_1.sh | bash가 실행 중인 스크립트 파일 을 fd 255로 열어 두고 한 줄씩 읽는다 |
rm 후 ls 결과 없음, df 150M 그대로 | 이름은 사라졌지만 데이터 블록은 해제되지 않았다 |
lsof +L1 → tail 1332 ... NLINK 0 ... big.log (deleted) | 링크 수 0인데 열려 있는 파일. 용량을 잡고 있는 범인 을 바로 찾았다. SIZE 157286400 = 150MB |
/proc/1332/fd/3 -> ... (deleted) | 같은 정보를 /proc에서 확인했다 |
: > /proc/.../fd/3 후 df 0% | fd를 통해 파일 크기를 0으로 잘라 블록을 해제했다. tail은 "file truncated"를 알렸지만 계속 실행된다 |
Ubuntu cron 3uW /run/crond.pid | cron이 PID 파일에 쓰기 잠금(W) 을 걸어 중복 실행을 막는다 |
systemd-resolve unix, a_inode [eventpoll], [signalfd], [timerfd] | 서비스는 소켓과 이벤트 처리용 커널 객체를 fd로 들고 있다 |
| 주제 | 내용 |
|---|---|
| 삭제 파일 복구 | 공격자가 도구·로그를 rm해도 프로세스가 열고 있으면 cp /proc/PID/fd/N 증거로 복구할 수 있다. 프로세스를 먼저 죽이면 이 기회가 사라진다 |
| 로그 삭제 흔적 | rsyslog 등 로그 데몬이 연 로그 파일이 (deleted)로 보이면 누군가 로그 파일을 삭제 했다는 뜻이다. 데몬 메모리에 아직 기록이 남아 있을 수 있다 |
| 숨은 파일 활동 | lsof -u www-data로 웹 서비스 계정이 연 파일 중 웹 루트 밖(/tmp, /dev/shm) 파일이 있는지 확인한다 |
| 디스크 고갈 DoS | 큰 파일을 열어 둔 채 삭제하면 du로는 찾을 수 없는 용량 고갈을 만들 수 있다. du와 df가 크게 다르면 lsof +L1을 본다 |
[Detection] /var 사용률 경보, 그러나 du -sh /var/* 합계와 df 가 크게 다름
↓
[확인] lsof +L1 -nP | awk '$7 > 100000000' ← 100MB 이상 삭제된 열린 파일
↓
[분류] ① 로그 로테이션 후 reload 안 한 서비스 (정상 운영 이슈)
② /var/log/secure 등 보안 로그가 (deleted) → 로그 삭제 의심
↓
[증거 ②] cp /proc/<rsyslog PID>/fd/<N> /evidence/secure.recovered
sha256sum /evidence/secure.recovered
↓
[Response] ① 서비스 reload / ② 삭제 시각·주체 조사, 복구본 분석
| 명령 | 목적 |
|---|---|
lsof +L1 | 삭제됐지만 열린 파일 전체 |
find /proc/*/fd -lname '*(deleted)' 2>/dev/null | lsof 없이 같은 확인 |
lsof +D /tmp | /tmp 파일을 쓰는 프로세스 |
| 실수 | 결과 | 예방 |
|---|---|---|
큰 로그를 rm으로 삭제 | 용량이 해제되지 않음 | : > file로 비우거나 logrotate의 copytruncate/reload 사용 |
용량 부족 시 du만 확인 | 삭제된 열린 파일을 못 찾음 | df와 비교, lsof +L1 |
| 침해 서버에서 의심 프로세스부터 kill | 열린 삭제 파일(증거) 소실 | fd 복사 후 종료 |
lsof 이름 변환으로 느려짐 | 대형 서버에서 수십 초 소요 | -n -P 사용 |
lsof를 일반 계정으로 실행 | 다른 사용자 프로세스가 빠짐 | root로 실행 |
[ ] lsof -p 와 /proc/PID/fd 로 프로세스가 연 파일을 확인했다
[ ] FD 열의 cwd, txt, mem, 3w 의미를 설명할 수 있다
[ ] 열린 파일을 rm 해도 df 가 줄지 않는 것을 재현했다
[ ] lsof +L1 로 삭제됐지만 열린 파일을 찾았다
[ ] /proc/PID/fd/N 을 통해 파일을 비워 용량을 회수했다
[ ] 삭제된 파일을 /proc/PID/fd 로 복구할 수 있다는 것을 이해했다
rm은 이름만 제거한다. 링크 수와 열린 fd가 모두 0이어야 공간이 해제된다.lsof +L1은 삭제됐지만 열린 파일을 찾는다 — 용량 장애와 로그 삭제 탐지 모두에 쓴다./proc/PID/fd/N으로 삭제된 파일을 비우거나 복구 할 수 있다.다음 글 「16. 프로세스와 네트워크 소켓 (ss -p)」 에서는 fd 중에서도 소켓 에 집중한다. 어떤 프로세스가 어떤 포트를 열고 누구와 연결되어 있는지 ss -tnp, lsof -i로 확인하고, 도구 없이 /proc/net/tcp의 inode로 프로세스를 역추적한다.