Two types of files

리눅스에서는 파일의 추상화가 두 가지 존재합니다. 첫 번째는 file descriptor(fd) level의 추상화인데, 이러한 추상화는 다음과 같은 함수를 사용할 수 있습니다.
  • open
  • read
  • write
  • close
  • lseek
  • fcntl ..
등등이 있습니다. 리눅스 인터페이스는 매우 강력하지만 때때로 이식성이 필요할 때가 있습니다(mac이나 windows를 위한 프로그램을 작성할 때). 이러한 경우 C abstraction이 사용됩니다. 다른 운영체제에서 C는 저수준 함수를 사용해 어디서든 파일을 다루는 데 사용할 수 있는 wrapper를 만들 수 있습니다. 즉, 리눅스에서 C 도 저수준 함수를 사용할 수 있습니다. C는 다음과 같은 함수를 사용합니다.
  • fopen
  • fread or fgetc/fgets or fscanf
  • fwrite or fprintf
  • fclose
  • fflush
리눅스는 위의 두 가지 파일 추상화 형식을 전환할 수 있는 System call을 제공합니다.
  • int fileno(FILE* stream)
  • FILE* fdopen(int fd)
를 사용해 두 형식 사이의 전환이 가능합니다.

C File의 또 다른 중요한 특징은 버퍼를 가지고 있다는 것입니다. 즉, 기본적으로는 내용이 바로 쓰여지지 않습니다. 이것은 C 옵션을 통해 바꿀 수 있습니다.

How do I tell how large a file is?

long의 크기보다 작은 파일들은 fseek과 ftell을 사용해 간단히 파일의 크기를 알아볼 수 있습니다.
파일의 끝으로 이동한 뒤 현재 위치를 알아내는 방식으로 알아냅니다.
fseek(f, 0, SEEK_END);
long pos = ftell(f);
위와 같은 방식으로 파일 안에 현재 위치를 바이트 단위로 알아낼 수 있습니다 - 즉, 파일의 길이입니다.

fseek은 또한 절대적인 위치로 이동하는 데 사용될 수도 있습니다.

fseek(f, 0, SEEK_SET); // Move to the start of the file 
fseek(f, posn, SEEK_SET);  // Move to 'posn' in the file.


부모 프로세스와 자식 프로세스에서 이후에 호출되는 read와 write는 모두 이 위치를 가지고 수행됩니다. 파일을 읽거나 쓰게 되면 현재 위치가 바뀌는 것에 주의하세요.


fseek이나 ftell에 대한 더 많은 정보는 man page를 참조하세요.


But try not to this

주의: 이 방법은 C언어의 특성상 일반적인 상황에서 추천되지 않습니다. 이 특성은 long이 4Byte이어야 한다는 것입니다. 즉, ftell이 반환할 수 있는 최대 크기는 2GB를 초과할 수 없습니다.(요즘에는 수백기가가 넘는 파일이 존재하고, 심지어 분산파일 시스템에서는 테라바이트가 되기도 합니다) 그렇다면 무엇을 써야 할까요? 이럴 때는 stat을 사용합니다. 나중에 stat에 대해 다시 다루겠지만, 아래와 같은 코드로 파일의 크기를 알아낼 수 있습니다.
struct stat buf;
if(stat(filename, &buf) == -1){
	return -1;
}
return (ssize_t)buf.st_size;

buf.st_size는 엄청 큰 파일을 저장하기에 충분히 큰 type off_t입니다.


What happens if a child process closes a filestream using fclose or close?

파일 스트림을 닫는 것은 가각 프로세스마다 동작합니다. 한 프로세스에서 파일 스트림을 닫아도 다른 프로세스는 여전히 스스로의 file-handle을 통해 사용할 수 있습니다. child가 생성될 때 모든 것이 복사된다는 것을 기억하세요. 즉, 파일의 상대적인 위치도 복사됩니다.

How about mmap for files?

일반적으로 mmap을 사용하는 경우는 file을 메모리에 mapping하기 위해서입니다. 이 말은 파일이 메모리에 즉시 할당된다는 것을 의미하지 않습니다. 

int fd = open(...); //File is 2 Pages
char* addr = mmap(..fd..);
addr[0] = 'l';

위 코드를 살펴보면 커널은 사용자가 파일을 메모리에 mmap하고 싶어하는 것을 알았으니 파일의 길이만큼 메모리 공간에 예약을 합니다. 즉, addr[0]에 쓰는 것은 실제로 파일의 첫번째 바이트에 쓰는 것입니다. 커넣은 실제로 동작할 때 최적화 작업도 수행합니다. 메모리에 파일을 로드하는 대신 한번에 페이지를 로드합니다. 파일은 1024페이지이기 때문입니다.  3 또는 4 페이지에만 접근하고 싶은데 전체를 로드하는 것은 시간 낭비이기 때문입니다.(페이지 폴트가 매우 강력한 이유입니다. 페이지 폴트는 운영체제가 파일을 얼마나 사용하는지 조절해 줍니다)


For every mmap

일단 mmap을 하고 나면 munmap을 사용해 OS에게 더이상 할당된 페이지를 사용하지 않는다고 알려줘야 한다는 것을 잊지 마세요. 이렇게 해주어야 OS는 디스크에게 써주면서 메모리를 돌려주어 나중에 malloc이 필요할 때 사용할 수 있게 해줍니다.


Posted by 몰랑&봉봉
,