[c] brk () 시스템 호출은 무엇을합니까?

리눅스 프로그래머 매뉴얼에 따르면 :

brk () 및 sbrk ()는 프로세스 데이터 세그먼트의 끝을 정의하는 프로그램 중단 위치를 변경합니다.

여기서 데이터 세그먼트는 무엇을 의미합니까? 데이터 세그먼트 또는 데이터, BSS 및 힙이 결합 되었습니까?

위키에 따르면 :

때때로 데이터, BSS 및 힙 영역을 통칭하여 “데이터 세그먼트”라고합니다.

데이터 세그먼트의 크기를 변경할 이유가 없습니다. 데이터, BSS 및 힙인 경우 전체적으로 힙이 더 많은 공간을 확보하므로 의미가 있습니다.

두 번째 질문이 나옵니다. 필자는 지금까지 읽은 모든 기사에서 힙이 위로 늘어나고 스택이 아래로 커진다고 말합니다. 그러나 그들이 설명하지 않는 것은 힙이 힙과 스택 사이의 모든 공간을 차지할 때 발생하는 것입니다.

여기에 이미지 설명을 입력하십시오



답변

게시 한 다이어그램에서 ” brk및 “으로 조작 된 주소 인 “중단” sbrk은 힙 상단의 점선입니다.

가상 메모리 레이아웃의 단순화 된 이미지

읽은 문서는 이것을 기존의 (공유 이전 라이브러리, pre- mmap) 유닉스에서 데이터 세그먼트가 힙과 연속적 이기 때문에 이것을 “데이터 세그먼트”의 끝이라고 설명합니다 . 프로그램 시작 전에 커널은 “텍스트”및 “데이터”블록을 주소 0에서 시작하여 RAM에 실제로로드하고 (실제로는 주소 0보다 약간 높으므로 NULL 포인터가 실제로 아무 것도 가리 키지 않도록) 중단 주소를 데이터 세그먼트의 끝 malloc그런 다음 첫 번째 호출 은 분할을 이동하고 다이어그램에 표시된 것처럼 데이터 세그먼트의 맨 위와 새로운 더 높은 중단 주소 사이에sbrk 작성하는 데 사용되며 후속 사용은 malloc힙을 더 크게 만드는 데 사용됩니다. 필요에 따라.

그 동안 스택은 메모리 맨 위에서 시작하여 커집니다. 스택은 더 큰 시스템 호출을 필요로하지 않습니다. 어느 때보 다 많은 RAM이 할당되어 시작되거나 (이것은 기존의 접근 방식 임) 스택 아래에 예약 된 주소 영역이있어 커널이 RAM을 쓰려고 시도 할 때 자동으로 RAM을 할당합니다. (이것은 현대적인 접근법입니다). 어느 쪽이든, 주소 공간의 맨 아래에 스택에 사용될 수있는 “가드”영역이있을 수도 있고 없을 수도 있습니다. 이 영역이 존재하면 (모든 현대 시스템에서)이 영역은 영구적으로 매핑 해제됩니다. 경우 중 하나스택 또는 힙이 스택으로 증가하려고하면 분할 오류가 발생합니다. 그러나 전통적으로 커널은 경계를 강제하지 않았습니다. 스택이 힙으로 커지거나 힙이 스택으로 커져서 서로의 데이터를 뒤섞 으면 프로그램이 중단 될 수 있습니다. 운이 좋으면 즉시 충돌합니다.

이 다이어그램에서 512GB가 어디에서 왔는지 잘 모르겠습니다. 64 비트 가상 주소 공간을 의미하며, 이는 매우 간단한 메모리 맵과 일치하지 않습니다. 실제 64 비트 주소 공간은 다음과 같습니다.

덜 단순화 된 주소 공간

              Legend:  t: text, d: data, b: BSS

이것은 원격으로 확장 할 수 없으며 주어진 OS가 어떻게 작동하는지 정확하게 해석해서는 안됩니다 (그린 후 Linux가 실제로 생각했던 것보다 0을 주소에 훨씬 가깝게 실행 파일을 배치한다는 것을 발견했습니다. 놀랍게도 높은 주소에서). 이 그림의 검은 색 영역 매핑되지 않은 있습니다 – 모든 액세스는 즉시 segfault의 원인 – 그리고 그들은는 거대한 회색 지역에 상대적인. 밝은 회색 영역은 프로그램 및 공유 라이브러리입니다 (수십 개의 공유 라이브러리가있을 수 있음). 각각 독립적으로텍스트 및 데이터 세그먼트 (및 글로벌 데이터도 포함하지만 디스크의 실행 파일 또는 라이브러리의 공간을 차지하지 않고 모든 비트 0으로 초기화되는 “bss”세그먼트) 힙은 더 이상 실행 파일의 데이터 세그먼트와 연속적 일 필요는 없습니다. 그런 식으로 그렸지만, 적어도 Linux처럼 보이지 않습니다. 스택은 더 이상 가상 주소 공간의 상단에 고정되지 않으며 힙과 스택 사이의 거리가 너무 커서 스택을 넘길 염려가 없습니다.

중단은 여전히 ​​힙의 상한입니다. 그러나 내가 보여주지 않은 것은 검은 mmap대신 어딘가에 수십 개의 독립적 인 메모리 할당이있을 수 있다는 것입니다 brk. (OS는 brk충돌하지 않도록 영역 에서 멀리 떨어 뜨리려고 시도합니다 .)


답변

최소 실행 가능 예

brk () 시스템 호출은 무엇을합니까?

커널에게 힙이라는 연속 된 메모리 덩어리를 읽고 쓸 수 있도록 요청합니다.

요청하지 않으면 segfault가 될 수 있습니다.

없이 brk:

#define _GNU_SOURCE
#include <unistd.h>

int main(void) {
    /* Get the first address beyond the end of the heap. */
    void *b = sbrk(0);
    int *p = (int *)b;
    /* May segfault because it is outside of the heap. */
    *p = 1;
    return 0;
}

brk:

#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>

int main(void) {
    void *b = sbrk(0);
    int *p = (int *)b;

    /* Move it 2 ints forward */
    brk(p + 2);

    /* Use the ints. */
    *p = 1;
    *(p + 1) = 2;
    assert(*p == 1);
    assert(*(p + 1) == 2);

    /* Deallocate back. */
    brk(b);

    return 0;
}

GitHub의 상류 .

위의 내용은 새로운 페이지에 도달 brk하지 않고 brk.

#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>

int main(void) {
    void *b;
    char *p, *end;

    b = sbrk(0);
    p = (char *)b;
    end = p + 0x1000000;
    brk(end);
    while (p < end) {
        *(p++) = 1;
    }
    brk(b);
    return 0;
}

우분투 18.04에서 테스트되었습니다.

가상 주소 공간 시각화

brk:

+------+ <-- Heap Start == Heap End

brk(p + 2):

+------+ <-- Heap Start + 2 * sizof(int) == Heap End
|      |
| You can now write your ints
| in this memory area.
|      |
+------+ <-- Heap Start

brk(b):

+------+ <-- Heap Start == Heap End

주소 공간을 더 잘 이해하려면 페이징에 익숙해 져야합니다. x86 페이징은 어떻게 작동합니까? .

우리는 왜 필요 둘 다 않습니다 brksbrk?

brk물론 구현 될 수 sbrk+는 편의를 위해 양국을 계산 오프셋.

백엔드에서 Linux 커널 v5.0에는 https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64brk 를 구현하는 데 사용되는 단일 시스템 호출 이 있습니다. tbl # L23

12  common  brk         __x64_sys_brk

brkPOSIX는?

brkPOSIX 였지만 POSIX 2001에서 제거되었으므로 _GNU_SOURCEglibc 래퍼에 액세스 해야합니다 .

소개 mmap는 다중 범위를 할당하고 더 많은 할당 옵션을 허용하는 수퍼 셋 으로 인해 제거 될 수 있습니다.

나는 당신이 지금 또는 brk대신에 사용해야하는 유효한 경우가 없다고 생각합니다 .mallocmmap

brk vs malloc

brk구현의 하나의 오래된 가능성입니다 malloc.

mmap모든 POSIX 시스템이 현재 구현하는 데 사용하는 최신의보다 강력한 메커니즘입니다 malloc. 다음은 실행 가능한 최소 mmap메모리 할당 예 입니다.

섞고 brkmalloc 할 수 있습니까 ?

malloc로 구현 된 경우 단일 범위의 메모리 만 관리하기 brk때문에 어떻게 그렇게 할 수 없을지 모르겠습니다 brk.

그러나 glibc 문서에서 그것에 대해 아무것도 찾을 수 없었습니다.

내가 mmap사용 된 것 같아서 물건이 거기에서 효과 가있을 것입니다 malloc.

또한보십시오:

더 많은 정보

내부적으로 커널은 프로세스가 많은 메모리를 가질 수 있는지 결정 하고 해당 사용법에 대한 메모리 페이지 를 표시합니다.

스택과 힙을 비교하는 방법을 설명합니다. x86 어셈블리의 레지스터에 사용되는 푸시 / 팝 명령어의 기능은 무엇입니까?


답변

모든 사람이 항상 불평하는 “멀록 오버 헤드”를 피하기 위해 자신 brksbrk자신을 사용할 수 있습니다 . 그러나이 방법을 결합하여 쉽게 사용할 수 없으므로 아무것도 malloc필요없는 경우에만 적합합니다 free. 당신이 할 수 없기 때문에. 또한 malloc내부적으로 사용할 수있는 라이브러리 호출을 피해야합니다 . 즉. strlen아마 안전하지만 fopen아마 아닐 것입니다.

전화하는 sbrk것처럼 전화하십시오 malloc. 현재 나누기에 대한 포인터를 반환하고 그 정도만큼 나누기를 증가시킵니다.

void *myallocate(int n){
    return sbrk(n);
}

malloc-overhead 가 없기 때문에 개별 할당을 해제 할 수는 없지만 첫 번째 호출에서 반환 된 값 을 호출 하여 전체 공간 을 해제 할 있으므로 brk되감습니다 .brksbrk

void *memorypool;
void initmemorypool(void){
    memorypool = sbrk(0);
}
void resetmemorypool(void){
    brk(memorypool);
}

이 지역을 쌓아서 지역의 시작 부분으로 나누기를 되 감아 가장 최근 지역을 버릴 수도 있습니다.


하나 더 …

sbrk코드 골프 에서는 2 자보다 짧기 때문에 유용합니다 malloc.


답변

특수하게 지정된 익명의 개인 메모리 매핑이 있습니다 (전통적으로 데이터 / BS를 넘어 위치하지만 현대 Linux는 실제로 ASLR을 사용하여 위치를 조정합니다). 원칙적으로으로 만들 수있는 다른 매핑보다 낫지는 mmap않지만 Linux에는 brksyscall을 사용 하여이 매핑의 끝을 위쪽으로 확장하여 잠금 비용을 줄이거 mmapmremap발생 하는 항목 과 비교하여 줄일 수있는 최적화 가 있습니다. 이것은 malloc메인 힙을 구현할 때 구현에서 사용 하기에 매력적입니다 .


답변

두 번째 질문에 대답 할 수 있습니다. Malloc은 실패하고 널 포인터를 리턴합니다. 따라서 동적으로 메모리를 할당 할 때 항상 널 포인터를 확인해야합니다.


답변

힙은 프로그램의 데이터 세그먼트에서 마지막에 배치됩니다. brk()힙 크기를 변경 (확장)하는 데 사용됩니다. 힙이 더 이상 커지지 않으면 malloc호출이 실패합니다.


답변

데이터 세그먼트는 모든 정적 데이터를 보유하고 시작시 실행 파일에서 읽고 일반적으로 0으로 채워지는 메모리 부분입니다.