2009-07-04 3 views
8

Системный вызов clone() в Linux принимает параметр, указывающий на стек для нового созданного потока. Очевидным способом сделать это является просто malloc некоторое пространство и передать это, но тогда вы должны быть уверены, что malloc'd столько же пространства стека, сколько этот поток будет использовать (трудно предсказать).Как смонтировать стек для системного вызова clone() на linux?

Я помню, что при использовании pthreads мне не пришлось это делать, поэтому мне было любопытно, что он сделал. Я натолкнулся на this site, в котором объясняется: «Лучшим решением, используемым реализацией pthreads Linux, является использование mmap для выделения памяти с флагами, определяющими область памяти, которая выделяется по мере ее использования. Таким образом, память выделяется для как это необходимо, и произойдет нарушение сегментации, если система не сможет выделить дополнительную память ».

Единственный контекст, который я когда-либо слышал в mmap, используется для сопоставления файлов в памяти и, действительно, чтения man-страницы mmap требуется дескриптор файла. Как это можно использовать для выделения стека динамической длины для предоставления clone()? Этот сайт просто сумасшедший? ;)

В любом случае, ядро ​​не должно знать, как найти свободную связку памяти для нового стека, так как это то, что нужно делать все время, когда пользователь запускает новые процессы? Почему указатель стека даже нужно указать в первую очередь, если ядро ​​уже может это понять?

ответ

2

Джозеф, в ответ на ваш последний вопрос:

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

Если пользователь заменяет выполняющийся в данный момент процесс с помощью exec(), ядро ​​необходимо создать новый стек, но в этом случае это легко, потому что он начинает работать с чистого листа. exec() уничтожает пространство памяти процесса и повторно инициализирует его, поэтому ядро ​​получает сообщение «после exec(), стек всегда живет ЗДЕСЬ».

Если, однако, мы используем clone(), то можем сказать, что новый процесс будет разделять пространство памяти со старым процессом (CLONE_VM). В этой ситуации ядро ​​не может покинуть стек, как это было в вызывающем процессе (например, fork()), потому что тогда наши два процесса будут топать друг другу в стеке. Ядро также не может просто поместить его в местоположение по умолчанию (например, exec()), потому что это место уже занято в этом пространстве памяти. Единственное решение - позволить вызывающему процессу найти место для него, что и есть.

0

mmap - это больше, чем просто отображение файла в память. Фактически, некоторые реализации malloc будут использовать mmap для больших распределений. Если вы прочитаете страницу прекрасного пользователя, вы заметите флаг MAP_ANONYMOUS, и вы увидите, что вам не нужно предоставлять файловый дескриптор вообще.

Что касается того, почему ядро ​​не может просто «найти кучу свободной памяти», хорошо, если вы хотите, чтобы кто-то выполнял эту работу для вас, вместо этого используйте fork или используйте pthreads.

+0

Я хочу сказать, что он должен «найти кучу свободной памяти», потому что, по-видимому, он уже может «найти кучу свободной памяти». Вилка создает новый процесс, который отличается, и я знаю, что я мог бы абстрагировать любую деталь, используя библиотеку. Но я даю разработчикам ядра кредит и полагаю, что есть веские причины для того, чтобы все было так, и я хочу знать, почему. –

+0

fork (exec действительно, поскольку fork просто копирует все) функции «найдите мне кучу свободной памяти». 'clone' - это функция« Я хочу контролировать детали моего процесса создания ». pthread_create - это «создайте мне поток, используйте функцию по умолчанию». Это ваш выбор. Новые потоки нуждаются в собственном стеке, и вы не можете использовать традиционный метод выделения стека (начинать в верхнем/нижнем (адресном) адресном пространстве и расти вниз/вверх в сторону кучи, которая растет другим путем), потому что есть только одна верхняя/нижняя часть адресного пространства. –

+0

Я хочу сказать, что когда пользователь запускает процесс, даже если этот процесс получает свое собственное пространство памяти, на определенном уровне ядро ​​должно выполнять управление памятью * физического * адресного пространства, в которое входят адресные пространства, относящиеся к процессу. Если он может это сделать, он должен иметь логику, чтобы иметь возможность обрабатывать, позволяя мне клонировать, но обрабатывать стек для меня. Возможно, я захочу использовать клон по причинам, которые, например, не имеют никакого отношения к стекам (см. Флаги клонов). –

5

Вам нужен флаг MAP_ANONYMOUS для mmap. И MAP_GROWSDOWN, поскольку вы хотите использовать его как стек.

Что-то вроде:

void *stack = mmap(NULL,initial_stacksize,PROT_WRITE|PROT_READ,MAP_PRIVATE|MAP_GROWSDOWN|MAP_ANONYMOUS,-1,0); 

Смотрите страницу ММАП человек для получения дополнительной информации. И помните, что клон - это концепция низкого уровня, которую вы не собираетесь использовать, если вам действительно не нужна то, что она предлагает. И он предлагает большой контроль - например, настройку собственного стека - на всякий случай, если вы захотите совершить какое-либо вмешательство (например, наличие стека доступно во всех связанных процессах). Если у вас нет оснований для использования клона, придерживайтесь вилки или pthreads.

+0

Как это может получить динамически растущий стек? Вам не нужно указывать длину? Или реализации, подобные pthreads, передают гигантскую длину и полагаются на копию при записи? –

+0

Да, они полагаются на копию при записи. Я не уверен, насколько велик размер стека pthread, он по умолчанию был 2Mb - вы можете изменить его с помощью команды ulimit -s. – nos

+0

Хорошо, тестирование с помощью pthread_attr_getstacksize предполагает, что размер стека по умолчанию составляет 10485760 байт в настоящее время, и – nos

1

Обратите внимание, что системный вызов cloneне содержит аргумент для расположения стека. Он фактически работает так же, как fork. Это только оболочка glibc, которая принимает этот аргумент.

+0

Вы уверены? Каждая подпись, которую я могу найти в Интернете, включает в себя дочерний стек. Если системный вызов не нужен, то почему glibc? –

+0

В противном случае, как «glibc» вернется к вам? –

0

Вот код, который mmaps в области стека & инструктирует системный вызов клона использовать этот регион в качестве стека.

#include sys/mman.h> 
#include stdio.h> 
#include string.h> 
#include sched.h> 
int execute_clone(void *arg) 
{ 
    printf("\nclone function Executed....Sleeping\n"); 
    fflush(stdout); 
    return 0; 
} 

int main() 
{ 
    void *ptr; 

    int rc; 
    void *start =(void *) 0x0000010000000000; 
    size_t len = 0x0000000000200000; 

    ptr = mmap(start, len, PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED|MAP_GROWSDOWN, 0, 0); 
    if(ptr == (void *)-1) 
    { 
     perror("\nmmap failed"); 
    } 

    rc = clone(&execute_clone, ptr + len, CLONE_VM, NULL); 

    if(rc <= 0) 
    { 
     perror("\nClone() failed"); 
    } 
} 
5

Стеки не могут быть и не могут быть неограниченными в своем пространстве для роста. Как и все остальное, они живут в виртуальном адресном пространстве процесса, и количество, на которое они могут расти, всегда ограничено расстоянием до смежной области памяти.

Когда люди говорят о стек растет динамически, что они могут означать одно из двух:

  • Страницы стека может быть от копирования при записи нулевых страниц, которые не получают частные копии, сделанные пока не будет выполнена первая запись.
  • Нижние части области стека еще не могут быть зарезервированы (и, следовательно, не учитываются плата за совершение процесса, то есть объем физической памяти/своп ядра учитывается как зарезервированный для процесса), пока не пострадает страница защиты , и в этом случае ядро ​​совершает больше и перемещает страницу защиты, или убивает процесс, если нет памяти для фиксации.

Пытаясь опереться на MAP_GROWSDOWN флага является ненадежным и опасным , потому что он не может защитить вас от mmap создания нового отображения только прилегающего к вашей стеке, который будет затерт. (См. http://lwn.net/Articles/294001/). Для основного потока ядро ​​автоматически резервирует размер стека ulimitадресное пространство (не памяти) ниже стека и не позволяет mmap выделить его. (Но будьте осторожны! Некоторые сломанные ядра, обработанные поставщиком, отключают это поведение, приводящее к случайному повреждению памяти!) Для других потоков вы просто должны mmap весь диапазон адресного пространства, которое может понадобиться потоку для стека при его создании. Другого пути нет. Вы могли бы сделать большую часть изначально незаписываемой/нечитаемой и изменить это на ошибках, но тогда вам понадобятся обработчики сигналов, и это решение неприемлемо для реализации потоков POSIX, поскольку это будет мешать сигналу приложения обработчики. (Обратите внимание, что в качестве расширения ядро ​​может использовать специальные флаги MAP_ для доставки другого сигнала вместо SIGSEGV на незаконный доступ к сопоставлению, а затем реализация потоков может захватывать и действовать по этому сигналу. нет такой функции.)

Наконец, обратите внимание, что в системном вызове clone не требуется аргумент указателя стека, поскольку он ему не нужен. Syscall должен выполняться из кода сборки, потому что оболочка пользовательского пространства требуется для изменения указателя стека в потоке «ребенок», чтобы указать на нужный стек, и не записывать ничего в стек родителя.

Фактически, clone действительно принимает аргумент указателя стека, потому что небезопасно ожидать изменения указателя стека в «дочернем» после возвращения в пользовательское пространство. Если сигналы не заблокированы, обработчик сигналов может работать сразу же в неправильном стеке, а на некоторых архитектурах указатель стека должен быть действительным и указывать на безопасную зону для записи в любое время.

Невозможно изменить указатель стека невозможным с C, но вы также не могли избежать возможности того, что компилятор будет сжимать стек родителя после syscall, но до изменения указателя стека.

0

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