Во-первых, создав простую тестовую программу, которая запускает один поток, мы можем увидеть системные вызовы, которые она использовала для создания нового потока.Вот простая тестовая программа:
#include <pthread.h>
#include <stdio.h>
void *test(void *x) { }
int main() {
pthread_t thr;
printf("start\n");
pthread_create(&thr, NULL, test, NULL);
pthread_join(thr, NULL);
printf("end\n");
return 0;
}
И соответствующая часть ее вывода:
write(1, "start\n", 6start
) = 6
mmap2(NULL, 8392704, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0xf6e32000
brk(0) = 0x8915000
brk(0x8936000) = 0x8936000
mprotect(0xf6e32000, 4096, PROT_NONE) = 0
clone(child_stack=0xf7632494, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0xf7632bd8, {entry_number:12, base_addr:0xf7632b70, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}, child_tidptr=0xf7632bd8) = 9181
futex(0xf7632bd8, FUTEX_WAIT, 9181, NULL) = -1 EAGAIN (Resource temporarily unavailable)
write(1, "end\n", 4end
) = 4
exit_group(0) = ?
Мы можем видеть, что она получает стек из mmap с PROT_READ|PROT_WRITE
защитой и MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK
флаги.Затем он защищает первую (т.е. самую нижнюю) страницу стека, чтобы обнаружить переполнение стека.Остальные вызовы не имеют никакого отношения к обсуждаемому обсуждению.
Итак, как же тогда mmap
выделяет стек?Хорошо, давайте начнем с mmap_pgoff
в ядре Linux;точка входа для современного системного вызова mmap2
.Он делегирует do_mmap_pgoff
после взятия некоторых блокировок.Затем он вызывает get_unmapped_area
, чтобы найти соответствующий диапазон неотображенных страниц.
К сожалению, тогда вызывается указатель на функцию, определенный в vma - это, вероятно, так, что 32-битные и 64-битные процессы могут иметь разные представления о том, какие адреса могут быть сопоставлены.В случае x86 это определяется в arch_pick_mmap_layout
, который переключается в зависимости от того, использует ли он 32-битную или 64-битную архитектуру для этого процесса.
Итак, давайте посмотрим нареализация arch_get_unmapped_area
тогда.Сначала он получает некоторые разумные значения по умолчанию для своего поиска из find_start_end
, а затем проверяет, действительна ли переданная подсказка адреса (для стеков потоков подсказка не передается).Затем он начинает сканирование по карте виртуальной памяти, начиная с кэшированного адреса, пока не найдет дыру.Сохраняет конец отверстия для использования в следующем поиске, затем возвращает местоположение этого отверстия.Если он достигает конца адресного пространства, он снова запускается с самого начала, что дает ему еще один шанс найти открытую область.
Так что, как вы можете видеть, обычно он назначает стеки в возрастающей манере(для x86; x86-64 использует arch_get_unmapped_area_topdown
и, скорее всего, назначит их убывающими).Тем не менее, он также хранит кэш того, где начать поиск, поэтому он может оставить пробелы в зависимости от того, когда области освобождены.В частности, когда область mmaped освобождается, она может обновить кэш свободного поиска адресов, так что вы также можете увидеть неупорядоченные распределения там.
Тем не менее, это все детали реализации. Не полагайтесь ни на что из этого в вашей программе .Просто возьмите, какие адреса mmap
раздают и будьте счастливы:)