Linux 커널 [9] 가상 메모리 관련 시스템 호출

#kdt#linux1)가상 메모리 관련 시스템 콜, 이번 기사에서는 가상 메모리 관련 시스템 콜에 대해서 봅시다.다만, 함수를 일일이 따지기보다는 어떤 게 있어서, 그것이 어떤 기능을 하는지를 중심으로 쉽게 봅시다.함수에 대한 부분은 후인 기능을 실장 할 때에 별도의 카테고리를 만들어 주고 보겠습니다.그 전에 Linux상에서 프로세스의 가상 주소 공간을 어떻게 구성하는지 봅시다 아래의 그림을 보면 알 수 있지만 크게 알고 있는 것과 다르지 않습니다.+)만약 32비트 시스템이라면 이 주소 공간의 크기는 4GB죠?

가상 주소 공간을 사용자가 모두 사용하는 것이 아니라 OS관련 코드나 데이터가 들어 있는 커널 영역과 사용자 영역이 구분됩니다.커널 영역 부분을 보면 프로세스마다 부여되는 페이지 탭 les, task andmmstruct(프로세스의 고유 자료 구조), kernelstack등이 할당되어 있는 데이터 영역이 있으며, 기타 모든 프로세스마다 공통되는 커널 메모리 영역과 코드 및 데이터 영역이 있습니다.사용자 공간을 읽어 보면 Code(text), data, heap, stack영역이 나뉘며, 파란 색으로 칠해진 free memory영역이 존재합니다.좀 더 자세히 말씀 드릴게요.0x0번지에서 0x400000까지의 free memory는 프로그램의 실행 코드가 포함되는 부분으로 이 영역에서는 쓰는 동작 등은 할 수 없습니다.그것이 지나 실제 프로그램의 코드가 포함된 텍스트 영역이 시작되고 그 위에 초기화된(. data)/초기화되지 않은 데이터(. bs)이 포함된 영역이 있습니다.실제적인 heap영역은 bs가 끝나고부터 시작되자 이 heap영역이 어느 정도의 크기가 될지는 리눅스 시스템에서 brk()라는 시스템 콜에 제한할 수 있습니다.그리고 CPU내에 rsp 같은 레지스터가 존재하고 스택 포인터의 역할도 하고 주는 대목도 있네요.통상 mmap을 통해서 파일이나 디스크의 내용을 물리 메모리에 매핑 할 경우 그렇게 stackheap사이에 있는 freememory영역을 할당합니다.그럼 실제로 Linux에서는 어떻게 가상 주소 공간을 말하는 거에요?이하의 그림이 일목요연하게 그 과정을 표현하고 있습니다.

참조면 task_struct, mm_struct__area_struct의 3개의 자료 구조가 존재하고 있습니다.무엇인가요, 이것은?task_structtask_struct는 Linux에서 프로세스 관련 정보를 포함하고 있는 매우 중요한 구조체입니다.프로세스의 실행 상태에서, 스케줄링 정보 소유권 등 프로세스에 관한 다양한 정보를 포함한 기본적인 구조체입니다.이 중에는 특히 mm_struct구조체에 대한 포인터이다 mm필드가 있는데, 이것은 또 무엇입니까?2.mm_structmm_struct는 프로세스의 가상 주소 공간에 관한 정보를 보존하는 구조체입니다.이 영역에는 PGd, mmap의 대표적인 2개의 필드가 존재하지만 PGd는 Page table base register의 일입니다.상위 페이지 테이블에 대한 주소를 보존하고 있고, 이 값을 이용하고 문맥 교환이 일어날 때마다 MMU에 각 프로세스의 고유 페이지 테이블의 주소를 설정할 것입니다.아주 중요합니다.mmap은 과정의 가상 주소 공간의 가상 메모리 영역에 포인터를 가리키고 있습니다.이 가상 메모리 영역은__area_struct라는 구조체로 표현되는데, 이것은 또 무엇입니까?3.vm_area_struct이는 가상 메모리의 각 영역을 표현하는 구조체입니다.회상에서도 보시다시피 가상 메모리의 어떤 영역에도 해당하는 범위의 start, end, 권한, 플래그 등에 관한 정보가 있습니다.mmap라고 하는 필드를 통해서 접속 목록의 형태로 관리됩니다.정리하면 task_struct는 실행 중인 프로세스를 표하며 mm_struct은 그 과정의 가상 메모리 정보를 보유하고__area_struct는 가상 메모리 영역의 속성과 범위를 표합니다.실제로 Linux상에서는/proc/PID/maps경로에 이 가상 메모리 매핑 관련 정보가 포함되어 있습니다.

왼쪽부터 형식은 VMA(start-end), permission, offset, device, i-node, mapped file name입니다.현재 cat이라는 유틸리티를 실행하고 그것을 읽어서 가상 메모리 상에 cat이 그렇게 비교되고 있어요.텍스트 영역에 해당합니다.그 아래에는 heap, stack와 같이 계속 이어가고 있습니다.마지막에 해당하는 부분은 커널 메모리의 편인데 이 부분은 나중에 보겠습니다.또 여기서 하나 더 주의 깊게 봐야 할 점은 시스템 라이브러리도 역시 공유 라이브러리 형태로 메모리에 실리고 있다는 점입니다.그럼 이제 본격적으로 시스템 콜을 봅시다.-fork, exec계열 함수(Memory mapping)

유닉스 계열 운영시스템에서는 fork를 통해서 새로운 프로세스를 생성합니다.여러분도 아시겠지만, 이는 부모 프로세스의 정보를 그대로 복사해서 생성하는 부분입니다.내부적으로 우리가 쓰는 리눅스상의 터미널도

위와 같이 실행했을 때, fork를 사용합니다.새로운 프로세스를 만들어 거기서 echo이라는 유틸리티를 hi라는 인자와 함께 실행할 것입니다.자식 프로세스가 종료했을 때 이에 대한 리소스 반납 처리를 부모가 반드시 해야 하는데 관련되는 시스템 콜로는 wait계열의 함수가 있습니다.이것을 하고 주지 않으면 좀비 프로세스가 되어 문제가 발생하므로, 언제나 하고 주어야 합니다.exec시스템 함수는 현재 실행되는 프로세스의 화상을 새 프로그램에 바꾸는 시스템 콜입니다.이를 사용하면 아이의 프로세스가 부모 프로세스에서 독립하고 새로운 프로그램을 실행할 수 있게 됩니다.Windows프로그래밍의 경우 새 프로세스를 만드는 함수가 CreateProcess다고 하는데, 이는 내부적인 방법이 fork()+exec()와 비슷한 방법으로 구현되고 있습니다.-mlock계열 함수(Memorylocking)이 함수는 가상 메모리 페이지를 물리 메모리상에 pinning 하고 퇴거하지 않도록 잠그는 역할의 함수입니다.그래서, 문맥 교환이나 페이지 교환에도 영향을 받지 않고 그 프레임은 그 페이지와 계속 이어지고 있습니다.

이 함수는 주로 보안 및 성능 요건의 있는 어플리케이션에서 사용됩니다.예를 들어 데이터베이스 시스템과 실시간 처리 시스템에서 중요한 데이터나 코드를 메모리에 고정하고 접속 속도를 향상시키고 외부의 악의에 대한 접속을 막는 방법으로 사용됩니다.그만큼 이 함수는 누구도 실행하지 못하고 Superuser의 권한을 갖고 있는 유저만 사용할 수 있습니다.-madvise(Memoryadvise)메모리 액세스 관련 정보를 시스템에 “조언” 하고 그렇게 동작하는 madvise라는 시스템 콜도 존재합니다.

이것들을 통해서 얻을 수 있는 다양한 정보가 있습니다. 예를 들면

아래와 같은 리턴을 받으면 페이지 참조가 어차피 랜덤이 되므로 파이프라인 구조에서 미리 가져오는 prefetch 작업 등은 하지 않습니다. 반대로 Sequential이라는 정보를 얻으면 prefetch를 하여 성능을 향상시킬 수 있습니다.이외에도 MADV_DONTNEED 같은 것을 주어 그 가상 메모리 영역이 필요 없음을 알려주는 어드밴스도 있습니다.

-NUMA(non-uniform memory access)계열의 시스템콜, 마지막으로 살펴볼 것은 NUMA계열의 시스템콜입니다. NUMA는 위와 같이 균일하고 메모리 접근이라는 뜻인데, 이게 무슨 뜻인지… 이 말을 의미하기 위해서는 UMA와 NUMA의 차이점부터 알아야 합니다.아래 그림을 보세요.UMA는 자주 우리가 사용 PC환경에서 사용하는 것을 말한다, NUMA는 서버나 가속기 같은 환경에서 사용되는 경우입니다.UMA는 문자 그대로 각 CPU내 핵심에서 메모리에 같은 거리를 가지고 Uniform에 접속 가능한 환경을 의미하며 NUMA는 그 반대로 있는 CPU는 메모리에 가깝지만 한 CPU는 메모리는 먼 이런 구조를 합니다.NUMA구조의 경우 어떤 메모리에 접근하느냐에 의해서 접속 시간이 다르기 때문에 non-uniform이라는 것입니다.이런 환경에서 프로세스가 자신의 메모리를 어떤 메모리에 두느냐에 의해서 그 성능이 바뀔 수 있지요???그것에 관련되는 시스템 콜이 get_mempolicy(2)mbind(2)migrate_pages(2)move_pages(2), set_mempolicy(2)상처럼 존재하고 있습니다.그럼 실제 예를 봅시다.Linux명령어에서는 free라는 명령을 현재의 메모리 상태를 나타냅니다.읽어 보자 free영역이 630MB정도 나타납니다.우선 단순히 코드를 아래와 같이 구성하여 실행시켜 보겠습니다. 500MB를 할당할 수 있는 코드입니다.int main(){ int *ptr, i; ptr = mmap(NULL, 1024 * 1024 * 500, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMIC, -1, 0);if (ptr == MAP_FAILED)perror(“mmap”); sleep(100); return 0;}해당 코드를 실행시킨 후 프리 영역을 확인했는데 크기가 거의 변하지 않았습니다. 왜 그럴까요?정답은 demand paging입니다. 그 가상 메모리 영역이 실제로 사용할 수 없게 될 때까지는 페이지를 할당하지 않기 때문입니다. 그러면 저기 페이지 폴트를 의도적으로 발생시켜 보겠습니다. 250MB 영역에 대해 이하의 연산을 실시해 주세요.For(i = 0; i <1024*250/sizeof(int), i++) ptr[i] = 0;그 결과 보시다시피 250MB는 아니지만 약 300MB 정도가 할당되어 프리 영역이 감소하는 것을 알 수 있습니다.다시 아래의 프리 메모리 용량이 있는 상태에서 다른 시스템 콜을 실행해 보겠습니다.mlock을 걸고 페이지 프레임을 물리 메모리로 pinning 해 봅니다.//for(i = 0; i< 1024*165*250 / sizeof(int); i++)// ptr[i] = 0;if (mlock(mlock(mlock, 1024*165*250) == -1)perror(mlock”);역시 같은 효과를 볼 수 있습니다.그러면 이렇게 mlock을 걸고 pinning을 시킨 후, munlock으로 pinning를 해제한 뒤 250MB반환 시에 madvise를 이용하고 메모리에 “조언”을 주는 기능을 찾아뵙겠다고 생각합니다.250MB를 망치 대가리 한 뒤 다시 해제하면 250MB를 받지 않으면 안 되는데 200MB영역은 필요 없으면 시스템에 madvise를 이용해서 알렸어요.그렇다면 나머지 50MB는 돌려받지 않을 겁니다.if (mlock(ptr, 1024*prelic*250) == == -1)perror(mlock”); munlock(ptr, prelic*prelic*250);if (madvise(ptr, 1024*prelic*200, MADV_DONTNEED) == == -1)perror(madvise”);아래와 같이 50MB 정도를 반납하지 못했습니다. madvise를 이용하여 200MB 정도가 필요 없으므로 비워도 된다는 힌트를 주었고, 그 결과 200MB에 해당하는 페이지만 해제되었습니다.지금까지 가상 메모리에 대해 알아봤습니다. 다음문장에서는메모리관련기술에대해서자세히살펴보도록하겠습니다.

error: Content is protected !!