파일 디스크립터란?
- 각 프로세스가 관리하는 열린 파일 목록의 인덱스
- 즉, 프로세스는 자신이 열어 놓은 파일들을 배열처럼 0, 1, 2… 번호로 접근할 수 있다
- 이 번호는 커널이 유저 프로세스에게 주는 핸들(handle) → fd 는 유저 프로세스가 쓰는 식별자이다
- 이 식별자는 파일 디스크립터 테이블이라는 곳에 매핑된다
비유로 알아보는 파일 디스크립터의 역할
전화 한 통만 하면 필요한 논문을 복사해주는 곳이 있다. 그리고 그 곳의 단골 손님 영수(개발자)가 있다. 그런데 영수는 매번 똑같은 논문의 일부분을 복사해달라 한다. “아저씨(시스템)~ ‘고도의 정보화 사회에 되어가면서 … 포켓몬의 영향 … 어쩌고 저쩌고 … (엄청 긴 이름)’ 라는 논문 26쪽부터 30쪽까지 복사해주세요”. 영수는 하루에도 몇번씩 주문을 한다. 그리고 말하는 속도도 엄청 느리다. 그래서 아저씨는 “그 논문은 이제부터 너의 18번이다! 앞으로는 18번 논문의 26쪽부터 30쪽 복사해달라 해” 라고 얘기한다. 영수가 새로운 논문을 요청하면, 아저씨는 그 때마다 논문에 새로운, 중복되지 않는 번호를 할당한다. 그래야 영수와의 대화에서 스트레스를 덜 받기 때문이다.
(출처 : mintnlatte.tistory.com/266)
👉 운영체제로 보면 영수는.. :
- 영수는 말하는 속도가 느려! 왜냐면 “논문 제목”이 엄청 길어서 말하는데 한참 걸린다
- 파일을 열 때는 경로 + 파일 이름을 문자열로 전달해야 한다
- 이건 CPU 입장에서 비용이 큰 작업이죠 (느리고 복잡함)
👉 운영체제에서 아저씨는.. :
- 아저씨는 새로운 요청이 올 때마다 그 파일에 대한 고유한 번호(fd)를 부여함
open("long-file-name.txt")
→ 내부에서 새 fd = 3 배정- 이후부터
read(3, ...)
,write(3, ...)
이런 식으로 사용
파일 디스크립터의 규칙
- 0, 1, 2는 예약된 fd
- fd 0 : stdin
- fd 1 : stdout
- fd 2 : stderr
- 일반 파일은 보통 3번부터 시작해서 번호가 순차적으로 증가한다.
- 각 프로세스 마다 독립된 fd 테이블을 가진다
- 부모와 자식이 같은 파일을 열어도 각자의 fd 번호로 접근한다 → 같은 파일이더라도 서로 다른 fd가 배정된다
- 영수가 요청한 논문A의 번호와 영철이가 요청한 논문A는 각각 다른 번호이다. 왜냐면 보통 요청한 순서대로 아저씨가 번호를 부여하는데, 영수는 18번째에 요청했지만 영철이는 5번째에 요청했을 수 있기 때문.
- 여기서 영수와 영철이는 각각 독립적인 프로세스이다. 즉,
pintos -q run 논문A
라는 명령어를 두 번 실행하여 독립된 프로세스가 생긴 경우. - 동일한 명령어인데 왜 fd가 다른가? → 각 프로세스 내에서 파일을 연 순서가 다를 수 있고, 그 순서에 따라 fd번호가 매겨지기 때문
- 파일 디스크립터는 매번 새로 생성된다
- 동일한 파일을 여러번 열면, 각각 다른 fd를 반환한다
- 즉 같은 파일이더라도 각각 fd는 파일 포인터를 따로 가진다.
- 영수가 논문을 27쪽까지 읽고 포인터로 읽은곳을 표시한 후, 영철이가 똑같은 파일을 열면 27쪽을 가리키지 않음. 논문의 첫 글자를 가리킴
- fd는 close(fd)로 독립적으로 닫아야 한다
- 독립적인 fd 번호를 가지기 때문에 별도로 닫아줘야 한다.
파일 디스크립터 테이블
- 프로세스마다 독립적으로 가지는 자료구조로
- 열린 파일의 fd 번호와 매핑해두는 테이블 역할을 한다
- 이 테이블을 통해 유저 프로그램은 파일을 int 번호로 다루게 된다.
- Pintos에선
struct thread
안에struct file **fdt
같은 포인터 배열 또는 file descriptor list로 구현하며 - 시스템 콜 open, close, read, write 등이 이 테이블을 이용해 동작한다
👉 비유로 돌아오면…
- 파일 디스크립터 테이블은 아저씨(시스템/커널)이 관리하는 테이블이다
- 영수는 논문 이름을 모르더라도, 18번 논문 주세요 라고 할 수 있다.
그러면 영수는 자기가 원하는 논문의 번호(fd)를 어떻게 알까?
- 파일 디스크립터 테이블의 관리 주체는 커널이다. 즉 사용자 프로세스는 직접 테이블 구조를 볼 수 없다 (메모리 보호 때문)
- 하지만 영수(사용자 프로세스)는 이 테이블에 접근은 가능하다.
- 이 말은 파일을 열 때, 아저씨(커널)는 “이 논문은 너에게 fd 3번이야!” 라며 fd 번호를 리턴한다
- 영수 : “오키 내가 원하는거 3번” 이라고 기억하고.. 앞으로도 아저씨게에 요청할 때 3번 논문 주세요~ 라고 요청하는 것이다.
시스템 콜
FORK
FORK 로 생긴 자식 프로세스는 부모의 FD 테이블을 복제한다.
영수의 자식 영철은 영수와 동일한 FD 테이블 (장부)를 공유한다!
- fork() 는 부모 프로세스의 거의 모든 상태를 복제한다 → FD 테이블 포함
- 자식은 부모가 열여둔 파일과 같은 FD 번호, 같은 파일 포인터, 같은 open file 구조체를 공유한다
👉 비유로 돌아오자면..
- 영수와 영철이는 논문A를 요청할때 18번으로 동일한 FD 번호로 요청할 수 있다
- 만약 영수가 논문A의 27페이지까지 읽고 표시해두었다면, 영철이가 논문A를 열면 같은 27쪽의 포인터부터 읽기 시작한다. (포인터를 공유하기 때문)
EXEC
FORK 후 EXEC 시, FD를 초기화 한다.
exec() 은 현재 프로세스의 코드와 메모리를 완전히 바꾸고 새로운 프로그램으로 덮어쓰고자 한다.
→ 새로운 프로세스를 시작하는 것처럼 보이지만, 기존의 프로세스 안에서 프로그램을 교체하는 동작이다
→ 단, PID는 유지된다
👉 비유로 돌아오자면…
영철이가 성인이 되며 이렇게 선언한다…
”이제 아빠(영수)랑 똑같이 사는 건 싫어! 난 나만의 삶을 살꺼야! 보여줄게 완전히 달라진 나~!”

- 영철이는 이전의 모든 기억들을 지우고, 새로운 사람으로 태어난다
- 이 때 영철이의 코드, 데이터, 힙, 스택 등이 전부 새롭게 교체된다
- 하지만 PID (주민번호) 는 바뀌지 않으며, 여전히 호적 상 영철이는 영수의 자식이다.
EXIT
자식 프로세스가 exit을 호출하면 :
- 프로세스가 메모리와 자원을 해제할 준비를 한다
exit_status
값이 저장되며, 이 값은 부모에게 전달된다- 부모는 자식 프로세스가 종료될때 까지 기다린다 (wait)
- 자식이
exit
을 호출한 경우sema_up(&exit_sema)
와 같은 방식으로 부모를 깨운다
- 자식이
thread_exit()
을 호출하여 커널 스레드를 종료한다
여기서부터 비유는 생략하겠다.. 비유가 너무 무서워질 것 같다. 😭
WAIT
부모 프로세스가 wait을 호출하면 :
- 자신이 가진 자식 리스트를 확인한다
- child_pid 에 해당하는 자식이 있는지 찾고
- 만약 없다면 → -1 을 반환한다 (자식이 없거나, 이미 기다리고 있다)
- 자식이 아직 안 죽은 경우
- sema_down(&wait_sema) 로 대기 상태에 들어간다
- 부모는 항상 자식이 종료될 때 까지 기다린다 → 좀비 프로세스 방지를 위해
- 자식이 죽었다!
- 자식이 exit 에서 sema_up(&wait_sema)로 부모를 깨워준다
- 자식의 exit_status를 받아온다
- 자식의 struct thread 을 제거한다
- list_remove, palloc_free_page 등으로 할당되었던 자식의 자원을 모두 해제한다
왜 자식의 자원을 부모가 정리할까?
✅ 이유 1 : 부모가 자식의 exit_status 를 참조하기 위해
- 자식 프로세스가 exit() 시점에 자신의 메모리를 모두 정리하면, 부모는 wait(pid)를 호출했을 때 자식의 종료 상태 (exit_status)를 참조할 수 없다.
- 따라서 자식은 “ 나 종료함! “ 이라는 신호만 부모에게 보내주고, 실제 자원 해제는 부모가 종료 상태를 회수한 뒤 실행된다.
✅ 이유 2 : 좀비 프로세스 방지
- 만약 자식이 먼저 종료되었는데, 부모가 wait()을 호출하지 않거나, 자식보다 부모가 먼저 죽어버리는 경우, 자식은 좀비 프로세스로 남게 된다
- 이런 좀비 프로세스가 많아지면 리소스 누수 등 시스템에 큰 부하가 생긴다
- 운영체제는 :
- 고아가 된 자식 프로세스의 부모를 init 으로 재할당(입양)하고
- init은 자식을 wait() 하여 자원을 정리한다
- 이 때, 자식의 최소한의 정보를 참조해야 하기 때문에 pid, exit_status, struct thread 등의 정보를 자식이 직접 정리하지 않는다.
'IT 성장기 (교육이수) > 크래프톤정글 (2025.03-07)' 카테고리의 다른 글
[PintOS] Project 1 : Priority Donation 구현 (0) | 2025.05.30 |
---|---|
[PintOS] GDB Debugging Tool 사용 방법 (1) | 2025.05.29 |
[PintOS] Project 1 : Priority Scheduling 구현 (0) | 2025.05.19 |
[PintOS] Project 1 : Alarm Clock 구현 (0) | 2025.05.19 |
[CS] CSAPP : 11장 네트워크 프로그래밍 (0) | 2025.05.09 |