개념
유닉스 및 유닉스 계열 운영 체제에서 파일 또는 파이프나 네트워크 소켓과 같은 기타 입출력 리소스에 대한 프로세스 고유 식별자이자 핸들이다.
일반적으로 음수가 아닌 정수 값이며, 음수는 에러를 나타내기 위해 예약되어있다.
추가 설명
전통적인 유닉스 구현에서 파일 디스크립터 (이하 fd) 는 커널에 의해 유지되는 프로세스별 fd 테이블에 색인되고,
이 테이블은 다시 모든 프로세스에서 열린 파일 테이블에 색인되고, 파일이 열린 모드(읽기, 쓰기, 등)가 기록된다.
이 테이블은 또 파일의 데이터의 속성(변경 시간, 권한 등)과 파일 위치가 적혀있는 inode 테이블에 색인된다.
입력 또는 출력을 수행하기 위해 프로세스는 시스템 콜을 통해 fd를 커널에 전달하고 커널은 프로세스를 대신하여 파일에 액세스한다. 고로 프로세스는 파일 또는 inode 테이블에 직접 액세스할 수 없다.
유닉스 계열 운영체제는 0, 1, 2가 표준 스트림으로 구현되어 있으며, 다음에 열린 파일(혹은 파이프, 소켓 등)은 3번부터 차례로 할당받는다.
표준 스트림
표준 스트림은 유닉스 및 유닉스 계열 운영 체제에서 프로세스와 그 환경(단말기) 사이에 미리 연결된 입출력 통로다.
파일 디스크립터는 POSIX API의 일부이며 각 Unix 프로세스에는 다음과 같이 3가지 표준 스트림에 해당하는 fd가 있어야 한다.
Integer value |
Name |
<unistd.h> symbolic constant |
0 |
표준 입력 |
STDIN_FILENO |
1 |
표준 출력 |
STDOUT_FILENO |
2 |
표준 에러 |
STDERR_FILENO |
보통 입출력은 물리적으로 연결된 키보드와 모니터를 통해 일어나는데, 이를 추상화한 것이다.
특징
자식 프로세스는 부모 프로세스의 표준 스트림을 상속 받는다.
통상적으로 터미널에 Shell이 실행되는데, 이 때 터미널에 부착된 세 개의 표준 스트림이 상속된다.
이후, Shell에서 실행되는 모든 명령들은, Shell을 부모 프로세스로 삼아 세 개의 표준 스트림을 다시 상속 받는다.
배경
유닉스 이전의 대부분의 운영 체제에서는 프로그램은 명시적으로 적절한 입력 장치와 출력 장치에 연결해줄 필요가 있었다. 이 작업은 각 OS 마다 처리 방식이 달랐기 때문에 매우 방대한 작업이었다.
하지만, 유닉스의 획기적인 발전 덕분에 프로그램은 더 이상 어떤 장치와 연결되는지 알 필요가 없었고, 입출력 장치를 연결하기 위한 그 어떤 추가 작업도 필요하지 않게 되었다.
표준 입력
프로그램으로 들어가는 데이터 스트림이다. 프로그램은 read 명령을 이용하여 데이터 전송을 요청한다.
별도의 redirection 없이 프로그램을 시작한 경우, 키보드에서 받아온다.
<iostream> 에서는 std::cin 이다.
표준 출력
프로그램이 출력 데이터를 기록하는 스트림이다. write 명령을 이용하여 데이터 전송을 요청한다.
별도의 redirection 없이 프로그램을 시작한 경우, 프로그램을 시작한 터미널에 출력한다.
<iostream> 에서는 std::cout 이다.
표준 에러
프로그램이 오류 메시지를 출력하기 위해 일반적으로 쓰는 또다른 출력 스트림이다.
표준 출력과는 독립적으로 사용 가능하다.
<iostream> 에서는 std::cerr 이다.
파이프라인에서 프로그램의 출력은 다음 프로그램이나 텍스트 파일의 입력으로 redirection 되지만,
각 프로그램의 오류는 여전히 텍스트 터미널로 직접 전달되므로 사용자가 실시간으로 검토할 수 있다.
일반적인 상황에서 표준 에러 스트림은 버퍼링되지 않지만 표준 출력 스트림은 버퍼링 (line-buffered) 되어,
표준 출력 스트림 버퍼가 가득 차 있지 않으면 보다 나중에 쓰여진 표준 에러 텍스트가 먼저 터미널에 표시될 수 있다.
fd 색인 할당 확인
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int open_and_print(const char* file_path)
{
int fd = open(file_path, O_RDONLY);
printf("%d\n", fd);
return fd;
}
int main()
{
int fd1 = open_and_print("test");
int fd2 = open_and_print("test");
int fd3 = open_and_print("test");
int fd4 = open_and_print("test");
int fd5 = open_and_print("test");
close(fd2);
close(fd4);
int fd6 = open_and_print("test");
}
실행 결과
3
4
5
6
7
4 // 4번과 6번 fd에 해당하는 파일을 close()한 이후 새로 색인된 fd
<unistd.h>의 open() 함수를 통해 파일을 열면 반환되는 fd는 3번 부터 차례로 색인되며,
fd 테이블 중간의 파일들을 close() 한 경우, 다음 열릴 fd는 작은 값부터 비어있는 곳에 색인된다.
fd 최댓값 확인
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int open_and_print(const char* file_path)
{
int fd = open(file_path, O_RDONLY);
printf("%d\n", fd);
return fd;
}
int main()
{
while (open_and_print("test") > -1);
perror(NULL);
}
실행 결과 (WSL Ubuntu 환경)
3
4
5
...
802952
802953
802954
802955
-1
Too many open files in system
실패시 -1을 반환하며, errno에는 23이 기록되고, 설명은 Too many open files in system 이다.
다음과 같은 명령어로 시스템 자체의 사용 가능한 fd 총량을 확인해볼 수 있다.
cat /proc/sys/fs/file-max
참고 문헌
https://en.wikipedia.org/wiki/File_descriptor
https://en.wikipedia.org/wiki/Standard_streams
https://en.wikipedia.org/wiki/Inode
개인 공부용 포스팅인 점을 참고하시고, 잘못된 부분이 있다면 댓글로 남겨주시면 감사드리겠습니다.