Назначение стека потоку


Я пытался собрать воедино, как память стека передается потокам. Я не смог собрать все это воедино. Я пытался перейти к коду, но запутался еще больше, поэтому прошу вас о помощи.

Я задал этот вопрос некоторое время назад. Поэтому предположим, что конкретная программа (следовательно, все потоки находятся в пределах одного процесса). Если я напишу printfs для каждого начала указателя стека, а затем сколько выделено для них, то я получу такие вещи, как таблица в конце этого сообщения, где первый столбец-это time_t usec, второй не имеет значения, третий-это tid потока, четвертый-размер защиты, затем начало стека, конец стека (сортируется по началу стека), предпоследний-это выделенный стек (по умолчанию 8 мегабайт), а последний столбец-это разница между концом первого выделенного стека и началом следующего стека.

Это означает ,что (я думаю), если 0, то стеки непрерывны, если положительны, поскольку стек растет в памяти, то это означает, что между tid и следующим (в памяти) есть "свободное пространство" из любого количества Мб. Если отрицательный, это означает, что память используется повторно. Таким образом, это может означать, что что пространство стека было освобождено до создания этого потока.

Моя проблема заключается в следующем: что именно является алгоритмом, который назначает пространство стека потокам (на более высоком уровне, чем код), и почему я иногда получаю непрерывные стеки, а иногда нет, а иногда получаю такие значения, как 7,94140625 и 0,0625 в последнем столбце?

Это все Linux 2.6, C и pthreads.

Это может быть вопрос, который нам придется повторить, чтобы получить его правильно, и за это я прошу прощения, но я говорю вам то, что знаю прямо сейчас. Не стесняйтесь обращаться за разъяснениями.

Спасибо за это. Далее следует таблица.

52815   14  14786   4096    92549120    100941824   8392704 0
52481   14  14784   4096    100941824   109334528   8392704 0
51700   14  14777   4096    109334528   117727232   8392704 0
70747   14  14806   4096    117727232   126119936   8392704 8.00390625
75813   14  14824   4096    117727232   126119936   8392704 0
51464   14  14776   4096    126119936   134512640   8392704 8.00390625
76679   14  14833   4096    126119936   134512640   8392704 -4.51953125
53799   14  14791   4096    139251712   147644416   8392704 -4.90234375
52708   14  14785   4096    152784896   161177600   8392704 0
50912   14  14773   4096    161177600   169570304   8392704 0
51617   14  14775   4096    169570304   177963008   8392704 0
70028   14  14793   4096    177963008   186355712   8392704 0
51048   14  14774   4096    186355712   194748416   8392704 0
50596   14  14771   4096    194748416   203141120   8392704 8.00390625
2 3

2 ответа:

Во-первых, при построении простой тестовой программы, которая запускает один поток, мы можем увидеть syscalls, которые она использовала для создания нового потока. Вот простая тестовая программа:

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

И соответствующая часть его вывода strace:

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 syscall. Он делегирует на 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 руки и будьте счастливы:)

Glibc обрабатывает это в nptl/allocatestack.c .

Ключевая строка:

mem = mmap (NULL, size, prot,
            MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);

Поэтому он просто запрашивает у ядра некоторую анонимную память, в отличие от malloc для больших блоков. Какой блок он на самом деле получает, зависит от ядра...