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


184

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

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

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

위키에 따르면 :

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

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

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

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


1
공간이 부족하면 어떻게합니까? HDD로 교체합니다. 공간을 사용한 경우 다른 종류의 정보를 위해 공간을 해제하십시오.
Igoris Azanovas

28
@Igoris : 실제 메모리 (가상 메모리를 사용하여 필요에 따라 디스크로 스왑 할 수 있음)와 주소 공간을 혼동 하고 있습니다 . 주소 공간을 채우면 스왑 양이 없어도 중간에 해당 주소를 다시 제공하지 않습니다.
Daniel Pryden

7
다시 말해, brk()시스템 호출은 C보다 어셈블리 언어에서 더 유용합니다. C에서는 데이터 할당 목적 malloc()대신 사용해야합니다. brk()그러나 제안 된 질문이 어떤 식 으로든 무효화되지는 않습니다.
alecov

2
@Brian : 힙은 다양한 크기와 정렬, 여유 풀 등의 영역을 처리하기위한 복잡한 데이터 구조입니다. 스레드 스택은 항상 전체 페이지의 연속 (가상 주소 공간에서) 연속입니다. 대부분의 OS에는 페이지 할당 자 기본 스택, 힙 및 메모리 매핑 파일이 있습니다.
Ben Voigt

2
@ 브라이언 : 어떤 "스택"에 의해 조작되고있다 고 말했다 누구 brk()sbrk()? 스택은 페이지 할당 자에 의해 훨씬 낮은 수준으로 관리됩니다.
벤 Voigt

답변:


233

게시 한 다이어그램에서 " 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충돌하지 않도록 영역 에서 멀리 떨어 뜨리려고 시도합니다 .)


7
자세한 설명은 +1입니다. malloc여전히 의존하고 있는지 brk또는 mmap별도의 메모리 블록을 "다시 돌려주는"데 사용 하고 있는지 알고 있습니까?
Anders Abel

18
그것은 특정 구현에 달려 있지만 IIUC는 현재 많은 할당이 작은 할당 영역을 malloc사용하고 큰 brk할당 mmap(> 128K) 할당을 위해 개별 영역을 사용합니다 . 예를 들어 Linux malloc(3)맨 페이지 의 MMAP_THRESHOLD에 대한 설명을 참조하십시오 .
zwol

1
실제로 좋은 설명입니다. 그러나 당신이 말했듯이 스택은 더 이상 가상 주소 공간의 맨 위에 있지 않습니다. 이것은 64 비트 주소 공간에만 해당되거나 32 비트 주소 공간에도 적용됩니다. 스택이 주소 공간의 맨 위에 있으면 익명 메모리 맵은 어디에서 발생합니까? 스택 직전에 가상 주소 공간의 맨 위에 있습니까?
nik

3
@ Nikhil : 복잡합니다. 대부분의 32 비트 시스템은 스택을 사용자 모드 주소 공간 의 맨 위에 배치합니다.이 주소 공간은 전체 주소 공간의 2G 또는 3G에 불과합니다 (나머지 공간은 커널 용으로 예약 됨). 나는 현재 그렇지 않은 것을 생각할 수는 없지만 모두를 모른다. 대부분의 64 비트 CPU에서는 실제로 전체 64 비트 공간을 사용할 수 없습니다. 주소의 상위 10 ~ 16 비트는 모두 0 또는 1이어야합니다. 스택은 일반적으로 사용 가능한 낮은 주소의 상단 근처에 배치됩니다. 나는 당신에게 규칙을 줄 수 없습니다 mmap; 그것은 OS에 매우 의존적입니다.
zwol

3
@RiccardoBestetti 주소 공간을 낭비 하지만 무해합니다. 64 비트 가상 주소 공간이 너무 커서 초당 1 기가 바이트의 기가 바이트를 사용하는 경우 여전히 500 년이 걸립니다. [1] 대부분의 프로세서는 2 ^ 48 ~ 2 ^ 53 비트 이상의 가상 주소를 사용할 수 없습니다 (내가 아는 유일한 예외는 해시 페이지 테이블 모드의 POWER4입니다). 물리적 RAM을 낭비하지 않습니다. 사용되지 않은 주소는 RAM에 할당되지 않습니다.
zwol

26

최소 실행 가능 예

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 어셈블리의 레지스터에 사용되는 푸시 / 팝 명령어의 기능은 무엇입니까?


4
ptype에 대한 포인터 이기 때문에 int이것이 아니어야 brk(p + 2);합니까?
Johan Boulé

작은 참고 : 공격적인 버전의 대한 루프의 표현은 아마이어야한다*(p + i) = 1;
lima.sierra

그런데 왜 우리 brk(p + 2)는 단순히 그것을 증가 시키는 대신 대신 을 사용해야 sbrk(2)합니까? brk가 정말로 필요합니까?
Yi Lin Liu

1
@YiLinLiu 단일 커널 백엔드 ( brksyscall)에 대해 두 개의 매우 유사한 C 프론트 엔드라고 생각합니다 . brk이전에 할당 된 스택을 복원하는 것이 약간 더 편리합니다.
Ciro Santilli 님이 새로운 사진을 추가

1
int의 크기를 4 바이트로 고려하고 int의 크기를 4 바이트로 고려하면 (32 비트 시스템에서) 4 바이트 단위로 증가해서는 안됩니다. 8-(2 * sizeof int)). 사용 가능한 다음 힙 스토리지를 가리켜서는 안됩니다. 8 바이트가 아닌 4 바이트입니다. 여기에 뭔가 빠진 것이 있으면 정정하십시오.
Saket Sharad

10

모든 사람이 항상 불평하는 "멀록 오버 헤드"를 피하기 위해 자신 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.


7
-1 이유 : malloc/ free가장 확실하게 OS에 메모리를 되돌려 줄 수 있습니다 . 그들은 원할 때 항상 그렇게 할 수는 없지만 휴리스틱이 사용 사례에 맞게 완벽하게 조정되지 않은 문제입니다. 더 중요한 것은, 호출sbrk 할 수있는 모든 프로그램에서 0이 아닌 인수 로 호출하는 것이 안전하지malloc 않으며 거의 ​​모든 C 라이브러리 함수가 malloc내부적 으로 호출 될 수 있다는 것 입니다. 확실히 비동기 신호 안전 기능 은 아닙니다 .
zwol

그리고 "안전하지 않다"는 말은 "프로그램이 중단 될 것"을 의미합니다.
zwol

반환 메모리 자랑 을 제거하기 위해 편집했으며 내부를 사용하여 라이브러리 함수의 위험에 대해 언급했습니다 malloc.
luser droog

1
멋진 메모리 할당을 원한다면 malloc 상단 또는 mmap 상단을 기준으로하십시오. BRK과는 sbrk를 만지지 마십시오, 그들은 좋은보다 더 많은 해를 과거의 유물이다 (심지어 맨 당신이 그들의 명확한 유지하는 말은!)
Eloff

3
이것은 바보입니다. 많은 작은 할당에 대한 malloc 오버 헤드를 피하려면 큰 할당을 한 번 수행하십시오 ( sbrk가 아닌 malloc 또는 mmap 사용 ). 이진 트리의 노드를 배열로 유지하면 64b 포인터 대신 8b 또는 16b 인덱스를 사용할 수 있습니다. 삭제 준비가 될 때까지 어떤 노드를 삭제하지 않을 때 잘 작동 모든 노드를. (예를 들면 즉석에서 정렬 된 사전을 구축 할 수 있습니다.) 사용 sbrk이 때문이다 에만 코드 골프 유용 수동으로 사용이 때문에 mmap(MAP_ANONYMOUS)더 나은 소스 코드의 크기를 제외하고 모든면에서입니다.
Peter Cordes

3

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


이 매핑의 끝을 위로 확장 수 있다는 것을 의미했습니다 .
zwol

예, 고정되었습니다. 미안합니다!
R .. GitHub 중지 지원 얼음

0

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


그렇다면 brk와 sbrk의 사용법은 무엇입니까?
nik

3
@NikhilRathod은 : malloc()사용 brk()및 / 또는 sbrk()- 당신은 당신의 자신의 사용자 정의 버전을 구현하려는 경우, 너무, 그리고 당신이 할 수있는 후드 malloc().
Daniel Pryden

@Daniel Pryden : 스택과 데이터 세그먼트 사이에있을 때 brk와 sbrk가 어떻게 위의 다이어그램에서 보여지는 것처럼 힙에서 작동합니까? 이것이 작동하려면 힙이 끝나야합니다. 내가 맞아?
nik

2
@Brian : Daniel은 OS 가 스택 포인터가 아닌 스택 세그먼트를 관리한다고 말했다 . 요점은 스택 세그먼트에 대한 sbrk / brk syscall이 없다는 것입니다 .Linux는 스택 세그먼트의 끝에 쓰려고 할 때 자동으로 페이지를 할당합니다.
짐 발터

1
브라이언, 당신은 질문의 절반 만 대답했습니다. 나머지 절반은 사용 가능한 공간이 없을 때 스택에 밀어 넣으려고하면 발생합니다. 세그먼트 오류가 발생합니다.
짐 발터

0

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


그래서 당신은 내 질문에있는 것과 같은 인터넷의 모든 다이어그램이 잘못되었다고 말합니다. 가능하면 정확한 다이어그램을 알려주십시오.
nik

2
@Nikkhil 그 다이어그램의 상단은 메모리 의 이라는 것을 명심하십시오 . 스택이 커짐에 따라 스택의 상단이 다이어그램에서 아래쪽 으로 이동 합니다. 힙 이 확장되면 다이어그램에서 위로 이동 합니다.
브라이언 고든

0

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


또한 가비지 일 수있는 초기화되지 않은 정적 데이터 (실행 파일에 존재하지 않음)를 보유합니다.
luser droog

초기화되지 않은 정적 데이터 ( .bss)는 프로그램 시작 전에 OS에 의해 모든 비트 0으로 초기화됩니다. 이것은 실제로 C 표준에 의해 보장됩니다. 일부 임베디드 시스템은 귀찮게하지 않을 수도 있습니다 (내가 본 적이 없지만 임베디드 시스템을 모두 작동하지는 않습니다)
zwol

@zwol : Linux에는으로 반환 된 페이지를 0으로하지 않는 컴파일 타임 옵션이 mmap있지만 .bss여전히 0으로 가정합니다 . BSS 공간은 아마도 프로그램이 제로 배열을 원한다는 사실을 표현하는 가장 간단한 방법 일 것입니다.
Peter Cordes

1
@PeterCordes C 표준이 말하는 것은 이니셜 라이저없이 선언 된 전역 변수는 0으로 초기화 된 것처럼 취급된다는 것입니다. 따라서 이러한 변수를 넣고 .bss0 .bss이 아닌 AC 구현은 부적합합니다. 그러나 C 구현이 전혀 사용하지 .bss않거나 그러한 것을 갖도록 강요 하는 것은 없습니다.
zwol

@PeterCordes 또한, "C 구현"과 프로그램 사이의 경계는 매우 애매 모호 할 수 있습니다. 예를 들어 이전에 실행되는 각 실행 파일에 정적으로 링크 된 구현의 코드가 일반적으로 적습니다 main. 이 코드 .bss는 커널이 수행하지 않고 영역을 제로화 할 수 있으며 여전히 적합합니다.
zwol

0

malloc은 brk 시스템 호출을 사용하여 메모리를 할당합니다.

포함

int main(void){

char *a = malloc(10); 
return 0;
}

strace 로이 간단한 프로그램을 실행하면 brk 시스템이 호출됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.