파일 디스크립터(File Descriptor)란?
리눅스와 유닉스에서는 시스템을 전부 파일로 처리하여 관리한다. (하드웨어 등 모든 장치도 포함)
시스템에서 프로세스가 파일에 접근하기 위한 방법으로 파일 디스크립터(FIle Descriptor)라는 핸들이 필요하다.
해당 파일을 open할때( 해당파일에 접근할 때 ) 파일 디스크립터는 0부터 N까지 즉, 음수가 아닌 0부터 차례대로 숫자를 부여받으며, 0, 1, 2는 프로세스가 메모리에서 실행을 시작할때 기본적으로 할당되는 파일 디스크립터이다.
기본적으로 할당받는 파일 디스크립터
0번 : 표준 입력(Standard Input) / STDIN_FILENO
1번 : 표준 출력(Standard Output) / STDOUT_FILENO
2번 : 표준 에러(Standard Error) / STDERR_FILENO
그러므로 우리가 생성하는 파일 디스크립터들은 3번 부터 차례대로 할당받게된다. 쉽게 생각하면, 파일 디스크립터는 파일을 다루기 위해서 해당파일의 주소를 참조하여 접근하는 형태라고 생각하면 된다.
이 fd를 활용해서 파일을 읽을수가 있다. 바로 read, open, close 함수를 이용하는 것이다.
FD의 최대값은 OPEN_MAX라는 값이다.
즉, 하나의 프로세스 당 최대 OPEN_MAX개의 파일을 열 수 있다. OPEN_MAX 값은 플랫폼에 따라 다르다.
최대 파일 갯수는 다음의 방법을 사용하여 알아볼 수 있다. 내 환경에서는 4096개의 값이 나왔다.
방법 1) 터미널에서 getconf 명령어로 확인
getconf OPEN_MAX
/*결과값*/
4096
파일 디스크립터가 단순히 숫자인 이유는 프로세스가 유지하고 있는 file descriptors 테이블의 인덱스이기 때문이다.
파일 오픈 or 소켓생성 시 부여되는 파일 디스크립터는 3부터 시작한다.
프로세스가 실행 중에 파일을 Open 하면 커널은 해당 프로세스의 파일 디스크립터 숫자 중에 사용하지 않는 가장 작은 값을 할당해 준다.
그 다음 프로세스가 열려있는 파일에 시스템 콜을 이용해서 접근할 때, 파일 디스크립터 값을 이용해 파일을 지칭할 수 있다.
file descriptors 테이블의 각 항목은 FD 플래그와 파일 테이블로의 포인터를 가지고 있다.
이 포인터를 이용하여 FD 를 통해 시스템의 파일을 참조 할 수 있는 것이다.
프로세스는 이런 FD 테이블과 파일 테이블의 정보를 직접 고칠 수 없으며, 반드시 커널을 통해서 수정을 해야 한다.
open함수에 대하여
- fcntl.h 헤더에 포함 되어 있는 함수
ssize_t open(const char *pathname, int flags);
- pathname : 파일의 경로와 이름으로, 절대경로의 파일명과 상대경로의 파일명 모두 허용.
- flags : 파일을 어떻게 열지를 결정하는 플래그, 읽기전용으로 열때는 O_RDONLY, 쓰기 전용으로 열때는 O_WRONLY, 읽기 쓰기로 열고싶을때는 O_RDWR을 사용한다.
Return Value
- 성공적으로 파일을 열게 되면 파일 디스크립터를 반환한다. 그렇지 않으면 음수를 반환한다. open이 제대로 되었는지 확인하기 위해서는 int형 변수를 하나 선언해준 후, open의 결과값이 0 미만인지 확인한다.
close함수에 대하여
- unistd.h 헤더에 포함 되어 있는 함수
ssize_t close(int fd);
- fd : open하고 있는 프로세스를 닫을 fd를 인자로 받는다.
Return Value
- 정상적으로 close했다면 0을, 실패했다면 -1을 반환한다.
read 에 대하여
- unistd.h 헤더에 포함 되어 있는 함수
ssize_t read (int fd, void *buf, size_t nbytes)
- fd : 파일 디스크립터
- buf : 파일을 읽어 들일 버퍼
- nbytes : 버퍼의 크기
Return Value
- 정상적으로 실행되었다면 읽어들인 바이트 수를, 실패했다면 -1을 반환한다.
read 함수의 예제
- 예제에 사용될 test.txt의 내용
#test.txt
12345
ABCDE
abcde
67890
#include <stdio.h> // puts()에 필요한 헤더
#include <string.h> // strlen()
#include <fcntl.h> // O_WRONLY , open함수를 쓰기 위한 헤더
#include <unistd.h> // write(), close()
#define BUFF_SIZE 1024
int main()
{
char buff[BUFF_SIZE];
int fd;
if ( 0 < ( fd = open( "./test.txt", O_RDONLY)))
{
read( fd, buff, BUFF_SIZE);
puts( buff);
close( fd);
}
else
{
printf( "파일 열기 실패.\n");
}
return 0;
}
// 결과
$ ./a.out
12345
ABCDE
abcde
67890
$
하지만 만약에 buff의 크기를 벗어나게 된다면, 여러번 읽기를 시도할 수 있다.
#include <stdio.h> // puts()에 필요한 헤더
#include <string.h> // strlen()
#include <fcntl.h> // O_WRONLY , open함수를 쓰기 위한 헤더
#include <unistd.h> // write(), close()
#define BUFF_SIZE 5 // 버퍼의 크기가 작다.
int main()
{
char buff[BUFF_SIZE];
int fd;
size_t rd_size;
if ( 0 < ( fd = open( "./test.txt", O_RDONLY)))
{
while( 0 < ( rd_size = read( fd, buff, BUFF_SIZE-1))) // 4 byte씩 읽는다.
{
buff[rd_size] = '\0'; // puts()를 위해 NULL을 대입
puts( buff);
}
close( fd);
}
else
{
printf( "파일 열기 실패.\n");
}
return 0;
}
- puts함수에 대해서 궁금하다면 여기를 참고
- 그러면 아래와 같이 4byte씩을 읽은 결과가 나오게 된다.
$ ./a.out
1234
5
AB
CDE
abcd
e
67
890
$
'프로그래밍 언어 > C++' 카테고리의 다른 글
[C] 포인터로 문자열 선언, 배열 문자열 선언과 차이. (문자열 내부 변경하기) (0) | 2023.09.17 |
---|---|
[C] 문자열(string) 입출력 (puts, fputs, gets, fgets) 사용법 (0) | 2023.09.17 |
[C] 이스케이프 문자(Escape Character) (0) | 2023.09.16 |
[C++] 쓰레기값 거르기(아주 작은값) numeric_limits (0) | 2023.09.16 |
[C] 쓰레기값과 초기화 (0) | 2023.09.16 |