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;
}
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
'Programing' 카테고리의 다른 글
SQL에서 범위를 어떻게 "그룹화"할 수 있습니까? (0) | 2020.05.27 |
---|---|
Eclipse의 기존 소스에서 프로젝트를 작성하고 찾는 방법은 무엇입니까? (0) | 2020.05.27 |
컬렉션을 비교하는 기본 제공 방법이 있습니까? (0) | 2020.05.27 |
TypeScript에서 추상 메서드 선언 (0) | 2020.05.27 |
쉘 기능을 어떻게 삭제합니까? (0) | 2020.05.27 |