Назначение стека потоку
Я пытался собрать воедино, как память стека передается потокам. Я не смог собрать все это воедино. Я пытался перейти к коду, но запутался еще больше, поэтому прошу вас о помощи.
Я задал этот вопрос некоторое время назад. Поэтому предположим, что конкретная программа (следовательно, все потоки находятся в пределах одного процесса). Если я напишу printf
s для каждого начала указателя стека, а затем сколько выделено для них, то я получу такие вещи, как таблица в конце этого сообщения, где первый столбец-это 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 ответа:
Во-первых, при построении простой тестовой программы, которая запускает один поток, мы можем увидеть 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
для больших блоков. Какой блок он на самом деле получает, зависит от ядра...