Назначение стека потоку - PullRequest
3 голосов
/ 21 июля 2011

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

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

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

Моя проблема: что именно представляет собой алгоритм, который назначает пространство стека потокам (на более высоком уровне, чем код), и почему я иногда получаю смежные стеки, а иногда нет, а иногда получаю значения, такие как 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 ]

8 голосов
/ 21 июля 2011

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

#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 раздают и будьте счастливы:)

4 голосов
/ 21 июля 2011

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

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

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

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

...