Почему эта арифметика указателя необходима для использования функции клона в C? - PullRequest
0 голосов
/ 25 декабря 2011

Я пытаюсь использовать функцию clone() в C, и я не уверен, как работает второй аргумент. По справочной странице clone():

   The child_stack argument specifies the location of the stack used by  the
   child  process.  Since the child and calling process may share memory, it
   is not possible for the child process to execute in the same stack as the
   calling  process.  The calling process must therefore set up memory space
   for the child stack and pass a pointer to this space to clone().   Stacks
   grow downwards on all processors that run Linux (except the HP PA proces‐
   sors), so child_stack usually points to the topmost address of the memory
   space set up for the child stack.

После следующих предложений в комментариях к этой статье я смог получить простой пример работы с этой программой на C:

#include <stdio.h>
#include <sched.h>
#include <stdlib.h>
#include <assert.h>

#define SIZE 65536

int v1;

int run(void *arg) {
  v1 = 42;
  return 0;
}

int main(int argc, char **argv) {
  void **child_stack;
  int pid, rc, status;
  v1 = 10;
  child_stack = (void **) malloc(SIZE);
  assert(child_stack != NULL);
  printf("v1 before: %d\n", v1);

  pid = clone(run, child_stack + SIZE/sizeof(void **), CLONE_VM, NULL);
  //pid = clone(run, child_stack + SIZE, CLONE_VM, NULL);

  assert(pid != -1);
  status = 0;
  rc = waitpid(pid, &status, __WALL);
  assert(rc != -1);
  assert(WEXITSTATUS(status) == 0);
  printf("v1 after:  %d\n", v1);
  return 0;
}

Но я не совсем понимаю, зачем нужна конкретная арифметика указателей в строке clone. Учитывая, что согласно документам clone стек должен расти вниз, я понимаю, почему вы должны добавить значение к указателю, возвращаемому malloc, перед его передачей. Но я ожидаю, что вы добавите число байт malloc'd вместо этого значения, разделенного на 8 (в 64-битной системе), что, по-видимому, действительно работает. Код выше, кажется, работает нормально, независимо от того, что я определяю SIZE как, но если я использую закомментированную версию вместо этого, что я и ожидал бы работать, я получаю ошибку сегментации для всех значений SIZE выше определенного порога.

Итак, кто-нибудь понимает, почему данная строка clone работает, а прокомментированная - нет?

Что касается того, почему я для начала использую clone, вместо fork или pthreads, я пытаюсь использовать некоторые из его расширенных функций песочницы, чтобы не дать ненадежному процессу вырваться из тюрьмы chroot, как описано здесь .

Ответы [ 4 ]

3 голосов
/ 26 декабря 2011

Проблема в том, что вы объявили child_stack как void ** (указатель на пустые указатели), когда это действительно указатель на необработанную память, которая будет использоваться для стека (который не имеет типа C)).Поэтому было бы более разумно, если бы вы просто объявили его как char * или intptr_t, в этом случае вы могли бы выполнять арифметику указателя напрямую (передавая child_stack + SIZE), а не исправлять неправильный тип.

Обратите внимание, что написанное исправление является неправильным (должно быть / sizeof(void *), а не / sizeof(void **)), но оно работает нормально, как sizeof(void **) == sizeof(void *) на большинстве машин

2 голосов
/ 25 декабря 2011

Добавление целочисленного значения V к указателю типа T * увеличивает адрес памяти на V * sizeof (T).Поскольку child_stack в вашем коде имеет тип void**, child_stack+SIZE в действительности означает, что адрес памяти увеличивается на SIZE*sizeof(void*) байт.

1 голос
/ 25 декабря 2011

При использовании арифметики с указателем размер указанного типа включается при определении фактического смещения памяти, например:

int a[2] = {1, 2};
int* p = a;

printf("%x: %x\n", &a[0], p);
printf("%x: %x\n", &a[1], p + 1);

В этом случае значение p - это не просто адрес p + 1, это значение p + 1 * sizeof(int) (размер указанного типа). Чтобы учесть это, когда вы хотите сместить некоторое количество байтов, вам нужно разделить смещение на размер изменяемого типа указателя. В вашем случае тип, на который вы указываете, - void*, поэтому, возможно, будет более точным сказать:

pid = clone(run, child_stack + SIZE/sizeof(void *), CLONE_VM, NULL);

Вы можете визуализировать это поведение с помощью чего-то вроде:

int SIZE = 65536;
void** child_stack = (void **) malloc(SIZE);

void** child_stack_end = child_stack + SIZE;
void** child_stack_end2 = child_stack + SIZE / sizeof(*child_stack);

printf("%d\n", (intptr_t)child_stack_end - (intptr_t)child_stack); // "262144"
printf("%d\n", (intptr_t)child_stack_end2 - (intptr_t)child_stack); // "65536"
0 голосов
/ 25 декабря 2011

child_stack + SIZE указывает на конец за концом выделенных вами данных, поэтому ошибка сегментации при использовании этого расположения в качестве начала стека не вызывает удивления. Вы пробовали child_stack + SIZE - 1?

...