Superkkt Blog

기존에 내가 프로그래밍한 소켓 파일 전송 프로그램은 전부 다 read/write/lseek 함수의 조합으로 이루어져 있다. 그런데 이번에 Pure-FTPd를 분석하면서 알게 된 사실인데 sendfile이라는 함수가 있다.

sendfile 함수는 두개의 파일 디스크립터 사이에서 데이터를 교환하는 함수이다. in_fd와 out_fd가 있는데 두개의 fd 모두 소켓을 사용할 수 있다고 한다. 그러나 리눅스 맨 페이지에서는 아직 in_fd에는 소켓을 사용할 수 없다고 되어 있다. 이 함수는 커널 레벨에서 구현되기 때문에 사용자 공간으로 데이터를 복사할 필요가 없어서 파일 전송에 불필요한 오버헤드를 줄여준다.

비록 sendfile 함수가 POSIX 표준 문서에는 나오지 않지만 리눅스. HP-UX, FreeBSD, Solaris에서 모두 구현되어 있다. 그러나 프로토타입이 다르기 때문에 멀티플랫폼을 지원하는 프로그램에서는 사용에 주의를 요한다.

그리고 Pure-FTPd에서는 sendfile 함수가 구현되지 않은 시스템에서도 read/write/lseek 함수 조합을 사용하지 않고 mmap을 사용해서 대상 파일을 메모리에 매핑시키고 write 함수를 사용해서 메모리에서 내용을 읽어서 전송하도록 되어있다. mmap에 대한 간단한 설명은 여기를 참조한다. 일반적으로 mmap이 read/write/lseek보다 성능이 좋다고 한다.

그런데 한가지 궁금한 점이 있는데.. 대상 파일의 사이즈가 엄청 커서 이 파일을 매핑시키는데 필요한 메모리가 모자른 경우 어떻게 되나? mmap은 한번에 파일 전체를 메모리에 매핑시키는것이 아니라 작은 부분으로 나눠서 하도록 구현되어 있나?

- 추가 -

http://fscked.org/writings/SHM/shm-2.html 내용 중,

The kernel then instructs the MMU of the CPU to generate a "page fault" when a section of this region of memory is accessed. When the kernel gets this page fault, it gets a page (4K) of data from the corresponding location in the file, and reads it into memory. The kernel only does this once per page of data in your mapped segment. This also means that no physical memory is allocated untill you start accessing the pages of the mapped region. The one exception to this is when mapping /dev/zero. Maps to /dev/zero are all prefaulted (zeroed), and the memory thus actually allocated.


-추가-

http://kldp.org/node/50774 에서 cinsk님의 답변 중,

mmap은 파일의 내용을 memory로 mapping시켜주는 함수입니다. mmap()으로 파일에 내용을 덧붙이는 작업은 할 수 없습니다. 즉, mmap()을 써서 파일 크기가 0인 파일에 어떤 내용을 써 넣는 것은 불가능합니다.

실제 써 넣는 내용과 파일을 항상 동기화하는 것이 목적이라면 꼭 mmap()을 쓸 필요가 없습니다. 원하는 파일에 fsync()를 불러주면 됩니다.

mmap()을 잘못썼을 때 흔히 SIGSEGV와 SIGBUS가 발생합니다. 가장 흔한 예는 read only로 mapping한 memory등, 접근이 불가능한 곳에 access했을 경우 SIGSEGV, 현재 파일의 내용과 범위가 맞지 않는 곳을 접근하려 했을 때 SIGBUS가 발생합니다. 즉, 파일 크기가 0인데, 쓰려한다면 대개 SIGBUS가 발생합니다.

배열의 범위가 벗어나는 것을 잡아주는 debugging library인 Electric Fence(efence)는 바로 read-only로 mmap()했을 때 SIGSEGV가 발생하는 것에 힌트를 얻었습니다.

또한 실제 파일 크기보다 큰 공간을 mmap()으로 할당하면 (예: 파일 크기는 100byte이고, mmap에서 1024 byte를 할당한 경우), 나머지 1024 - 100 byte의 위치에는 0으로 채워지고, 이 곳에 쓰는 데이터는 나중에 무시됩니다. 즉 파일의 내용에 반영되지 않습니다. 앞에서 mmap()으로 파일의 크기를 변경하는 연산을 수행할 수 없다고 말한 것을 기억하기 바랍니다.

한가지 더, mmap()의 두번째 인자에 지정하는 길이는 보통 system page size의 배수가 되어야 합니다. 주어진 예제에서 "sizeof(int)*100"의 크기로 했는데, 바람직하지 않습니다. mmap()이 에러를 내고 errno를 EINVAL로 setting할 확률이 높습니다. 따라서 이 크기는 항상 page size의 배수가 되도록 하기 바랍니다. 앞에서도 말했듯이, file size보다 같거나 크며 page size의 배수가 되도록 하고, 이 때 남은 공간에는 0이 채워지고, 여기에 쓴 내용은 무시됩니다.

System의 page size는 sysconf(3)을 써서 얻을 수 있습니다.

2008/02/05 20:11 2008/02/05 20:11

trackbacks

trackbacks rss

이 글에는 트랙백을 보낼 수 없습니다

  1. M/D R
    sendfilev 가 웹 서버에서 많이 쓰이던데(솔라리스에서 truss로 해보면 나옴) 어케 쓰는지는 당근 잘 모름 ^^

Leave a Comment