Как определяется дно стека? - PullRequest
9 голосов
/ 30 марта 2010

Стек представляет собой непрерывный блок память, содержащая данные. Регистр называется указателем стека (SP) точек на вершину стека. Дно стека находится по фиксированному адресу.

Как ядро ​​стека фиксируется ядром?

Ответы [ 4 ]

6 голосов
/ 30 марта 2010

Я набрал более длинный ответ, но он неуклюжий, поэтому вот более короткий ...

Когда процесс запускается, ему нужен стек для хранения временных значений (то есть автоматического выделения) или стековых кадров при вызове функций. Память для этого стека должна откуда-то приходить.

Итак, что делает ОС, так это создает отображение в виртуальной памяти для стека и присваивает указателю стека высокий адрес этого блока. В стеке предварительной обработки, где указатель стека уменьшается перед разыменованием, указатель начального стека фактически является адресом после последнего адреса в отображаемом пространстве.

Когда процесс пытается поместить что-либо в стек, это приводит к доступу к этой области памяти, которая не имеет привязанной к нему физической памяти. Это вызывает сбой страницы, в результате чего ОС вставляет наименее используемую (или близкую к ней) страницу ОЗУ в диск подкачки или файл подкачки и переназначает физическую страницу ОЗУ на страницу стека, к которой осуществляется доступ. Теперь, когда есть физическая память, ОС возвращается, и процесс продолжается, помещая помещенные данные в память стека.

Так что же произойдет, если вы вытолкнете все из стека, а затем попробуете снова? Последнее всплывающее окно, в котором указатель стека имеет начальное значение, приводит к доступу к адресу виртуальной памяти, который не сопоставлен со стеком. Это вызывает ошибку сегментации, что означает, что процесс пытался получить доступ к памяти, которую он никогда не выделял. ОС отвечает, завершая процесс и убирая все, что может.

Почему бы не отобразить страницу за концом стека? потому что это приведет к чтению неинициализированной ОЗУ, которая будет содержать все, что раньше использовало эту страницу физической ОЗУ. Просто нет способа , это может привести к правильно работающей программе (не говоря уже о том, что это представляет огромную угрозу безопасности), поэтому все же лучше убить программу.

1 голос
/ 30 марта 2010

Это будет частью реализации самого стека. Если вы реализуете стек в (например) C, вы можете хранить указатель стека и текущего количества элементов. Или указатель стека вместе с основанием стека, что-то вроде:

typedef struct {
    int *sp_empty;
    int *sp;
    int *sp_full;
} tIntStack;

tIntStack stk;

// Initialise 20-element stack.
stk.sp = stk.sp_empty = malloc (sizeof(int) * 20);
stk.sp_full = &(stack[20]);

// Push a value x, detecting overflow.    
if (stk.sp == stk.sp_full) { error here}
*(stk.sp) = x;
stk.sp++;

// Pop a value x, detecting underflow.    
if (stk.sp == stk.sp_empty) { error here}
stk.sp--;
x = *(stk.sp);

Если вы говорите о стеке ЦП (адресах возврата и т. Д.), Вы можете обнаружить переполнение стека в силу того, что у вас произошел сбой. Нехорошо.


Например, в старые времена, когда процессоры были ограничены адресным пространством 64 КБ, ЦП обычно проверяли память, пока не нашли первый байт, который не читал то, что было только что записано (в процессе загрузки). Это был первый байт за пределами физической памяти, поэтому они установили SP на единицу ниже. Конечно, некоторые (небольшое количество) машин имели 64 КБ физической памяти, поэтому просто установите SP в верхнюю часть адресного пространства.

В настоящее время этот процесс в многоадресных операционных системах с огромным адресным пространством, виртуальной памятью немного сложнее, но все же сводится к (в большинстве случаев):

  • Адресное пространство выделено для процесса.
  • SP установлен где-то в этом адресном пространстве.
  • Процесс запущен.

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

Ядро может проверить ваш SP на переключателе задач, чтобы убедиться, что вы сделали что-то не так, но это отнюдь не гарантировано. Он также может использовать аппаратную защиту памяти для обнаружения недостаточного количества памяти (если стек находится на вершине выделенного вами адресного пространства). Но опять же, это полностью зависит от используемого вами ядра.

0 голосов
/ 05 октября 2011

На самом деле, есть что-то после дна стека. После стека идут элементы массива argv, затем элементы массива env. После этого стоит барьер, затем код libc.

0 голосов
/ 30 марта 2010

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

bfbce000-bfbe3000 rw-p bffeb000 00:00 0          [stack]

на выходе cat /proc/<pid-here>/maps. Дно стека в этом случае составляет 0xbffeb000. В моей системе все днища стека, по-видимому, попадают в одно из следующих значений: bffca000 bffcb000 bffdd000 bffe0000 bffe4000 bffe6000 bffeb000 (после цикла ~ 200 процессов).

Полагаю, что эти значения присваиваются глубоко в ядре, где бы ни создавались процессы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...