리눅스 시스템 프로그래밍 [2] : 파일 입출력

이번 장에서는 리눅스 시스템 프로그래밍에 있어 가장 기초적이면서도 중요한
파일 입출력에 대해 정리하고자 한다.

모든 소스코드는 우분트 32비트 환경에서 컴파일 하였으며, 시큐리티 옵션을 따로 주지 않았다.

 

1. 파일 디스크립터 (File Descriptor)

리눅스에서 파일을 읽고 쓰기 위해서는 반드시 파일을 Open 해야만 한다.
파일이 오픈되고 나면 파일 디스크립터라는 일종의 Index 번호가 반환되며,
이 값은 파일을 오픈한 프로세스의  고유 번호라 생각하면 이해하기 쉬울 것이다.

파일 디스크립터는 C int 타입으로 표현되며, 최대값은 1,024이지만 1,048,576번까지 설정할 수 있다.

또한, 프로세스마다 관례적으로 0,1,2 번은 사전 배정되어 있는데 각각 번호별 의미는 다음과 같다.

0 : 표준 입력 (stdin) / 1 : 표준 출력 (stdout) / 2 : 표준 오류 (stderr)

따라서 실제 하나의 파일을 생성하게 되면 “3번” 부터 File Descriptor가 부여된다.

결과 : FD Success…!!! FD is : 3

 

2. 파일 열기 (File Open)

리눅스 시스템에서는 파일을 읽거나 쓰기 전에,  파일에 접근하기 위한 준비 과정을 거친다.
이 때 사용되는 함수가 바로 open() 또는 creat() 함수이다.

open용 함수에서는 flags를 통해 파일의 쓰임새를 정의하게 되는데
O_RDONLY, O_WRONLY, O_RDWR 중 하나를 사용해야 한다.

파일 사용이 끝나면 close()함수를 통해 파일을 닫아주어야 한다.

지정한 파일명이 존재하지 않을때 생성하고 싶을 때는 O_CREAT flag를 사용한다.
새롭게 생성된 파일의 uid와 gid는 파일을 생성한 프로세스의 그것을 따라가게 된다. (매우 중요함)

파일을 생성하기 위한 더 간단한 방법으로 아래 creat()함수가 있다.

File Creat

open()함수에 비해 훨씬 간단하게 파일을 생성할 수 있는 것을 확인할 수 있다.

 

3. File Read를 통해 파일 내용 읽기

read() 시스템 콜은 다음과 같이 정의되어 있다.

“fd 가 바라보고 있는 파일에서 len길이 만큼을 buf에 읽어 들인다” 고 해석하면 이해가 쉬울 것이다.
일단 예제를 통해 사용법을 익혀보자.

실행결과 정상적으로 파일을 읽은 후 버퍼 내용을 출력하였다.

상황에 따라 가변적으로 파일 크기를 읽어야 되는 상황이 발생할 수도 있을 듯 한데…
이런 부분은 프로그래밍을 하면서 그때그때 적용을 하며 시행착오를 격어보는게 좋을 것 같다.

 

4. File Write를 통해 내용 쓰기

write() 시스템 콜은 read() 와 반대로 동작한다고 생각하면 이해가 쉽다.
write()는 다음과 같이 정의되어 있다.

“count 바이트만큼 fd가 참조하는 파일의 현재 위치에 buf를 기록한다” 고 일단 이해하자.
쓰고 난 후 파일의 위치는 쓴 크기 만큼 “전진”하게 된다.
즉 10바이트를 썼다면 파일의 위치는 11바이트를 가리키는 상태가 된다.

예제를 통해 일단 위에서 미리 만들어 둔 test.txt 파일 뒷 부분에 내용을 추가해 보도록 하자.

실행 결과 첫번째 행의 “AAAAAAAAAA” 가  “XXXXXXXXXX”로 write 되었다.
이는 파일을 wrtie() 할 때 파일의 위치가 첫번째 바이트를 가리키고 있기 때문이다.

만약 파일을 close() 하기 전에 다시 한번 write()를 호출하게 되면
파일 가장 아랫 행에 “XXXXXXXXXX” 가 추가되는 것을 볼 수 있다.

만약 파일 뒷부분에 내용을 추가해서 쓰고 싶다면 파일을 오픈할 때 flag 값으로
“O_APPEND”를 설정하면 기존 내용을 덮어쓰지 않고, 가장 뒷 부분에 내용이 추가된다.

 

5. lseek() 을 통해 파일 위치 조정하기

파일을 읽어들일 때, 기본 flag를 설정하지 않으면, 읽은 파일의 가장 첫 바이트를 기준점으로 잡게 된다.
예를 들어 “ABCDEFG” 라는 내용의 파일을 아무런 옵션 없이 open() 하게 되면
파일의 기준점은 가장 첫 글자인 “A”에 위치하게 된다.

lseek() 함수를 이용하면, 파일 객체에서 읽어들일 위치를 조정할 수 있게 된다.
먼저 아래 예제를 실행해보도록 하자.

실행결과

첫번째,  lseek(fd, 0, SEEK_CUR)에서는 파일의 현재 위치(SEEK_CUR)에서 0만큼 움직이므로
lseek location은 0 값이 반환된다.

두번째, lseek(fd, 11, SEEK_CUR)에서는 파일의 현재위치에서 11만큼 뒤로 움직이므로
XXXXXXXXXX 가 아닌 BBBBBBBBBB 부터 출력 된것을 확인할 수 있다.

세번째에서는 lseek(fd, 0, SEEK_SET)을 통해 11바이트 뒤로 이동된 현재 값을
초기 값 ‘0’으로 셋팅하게 된다. 즉, 파일의 가장 첫 글자를 가리키게 되므로
출력 결과는 파일의 처음부터 끝까지를 표시하게 된다.

 

일단 여기까지 기본적인 리눅스 파일 I/O 관련 함수들을 알아보았다.

select(), poll() 함수 등 고급 내용은 다음번에 다루도록 하고,
일단 충분한 실습을 통해 파일을 자유자재로 다룰 수 있는 수준까지 오르는 것이 중요하겠다.

 

 

Site Footer

Sliding Sidebar

About Me

About Me

June Park