Programing

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

lottogame 2020. 5. 27. 07:31
반응형

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처럼 보이지 않습니다. 스택은 더 이상 가상 주소 공간의 상단에 고정되지 않으며 힙과 스택 사이의 거리가 너무 커서 스택을 넘길 염려가 없습니다.

The break is still the upper limit of the heap. However, what I didn't show is that there could be dozens of independent allocations of memory off there in the black somewhere, made with mmap instead of brk. (The OS will try to keep these far away from the brk area so they don't collide.)


Minimal runnable example

What does brk( ) system call do?

Asks the kernel to let you you read and write to a contiguous chunk of memory called the heap.

If you don't ask, it might segfault you.

Without 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;
}

With 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 upstream.

The above might not hit a new page and not segfault even without the brk, so here is a more aggressive version that allocates 16MiB and is very likely to segfault without the 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;
}

Tested on Ubuntu 18.04.

Virtual address space visualization

Before brk:

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

After brk(p + 2):

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

After brk(b):

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

To better understand address spaces, you should make yourself familiar with paging: How does x86 paging work?.

Why do we need both brk and sbrk?

brk could of course be implemented with sbrk + offset calculations, both exist just for convenience.

In the backend, the Linux kernel v5.0 has a single system call brk that is used to implement both: https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64.tbl#L23

12  common  brk         __x64_sys_brk

Is brk POSIX?

brk used to be POSIX, but it was removed in POSIX 2001, thus the need for _GNU_SOURCE to access the glibc wrapper.

The removal is likely due to the introduction mmap, which is a superset that allows multiple range to be allocated and more allocation options.

I think there is no valid case where you should to use brk instead of malloc or mmap nowadays.

brk vs malloc

brk is one old possibility of implementing malloc.

mmap is the newer stricly more powerful mechanism which likely all POSIX systems currently use to implement malloc. Here is a minimal runnable mmap memory allocation example.

Can I mix brk and malloc?

If your malloc is implemented with brk, I have no idea how that can possibly not blow up things, since brk only manages a single range of memory.

I could not however find anything about it on the glibc docs, e.g.:

Things will likely just work there I suppose since mmap is likely used for malloc.

See also:

More info

Internally, the kernel decides if the process can have that much memory, and earmarks memory pages for that usage.

This explains how the stack compares to the heap: What is the function of the push / pop instructions used on registers in x86 assembly?


You can use brk and sbrk yourself to avoid the "malloc overhead" everyone's always complaining about. But you can't easily use this method in conjuction with malloc so it's only appropriate when you don't have to free anything. Because you can't. Also, you should avoid any library calls which may use malloc internally. Ie. strlen is probably safe, but fopen probably isn't.

Call sbrk just like you would call malloc. It returns a pointer to the current break and increments the break by that amount.

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

While you can't free individual allocations (because there's no malloc-overhead, remember), you can free the entire space by calling brk with the value returned by the first call to sbrk, thus rewinding the brk.

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

You could even stack these regions, discarding the most recent region by rewinding the break to the region's start.


One more thing ...

sbrk is also useful in code golf because it's 2 characters shorter than malloc.


There is a special designated anonymous private memory mapping (traditionally located just beyond the data/bss, but modern Linux will actually adjust the location with ASLR). In principle it's no better than any other mapping you could create with mmap, but Linux has some optimizations that make it possible to expand the end of this mapping (using the brk syscall) upwards with reduced locking cost relative to what mmap or mremap would incur. This makes it attractive for malloc implementations to use when implementing the main heap.


I can answer your second question. Malloc will fail and return a null pointer. That's why you always check for a null pointer when dynamically allocating memory.


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


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


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

포함

int main(void){

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

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

참고 URL : https://stackoverflow.com/questions/6988487/what-does-the-brk-system-call-do

반응형