Вилка и возвращаясь дважды - PullRequest
3 голосов
/ 04 марта 2012

Я работаю над проектом, который требует реализации fork () в Unix.Я читаю исходный код FreeBSD и OpenBSD, но это действительно трудно понять.Может кто-нибудь объяснить, пожалуйста, концепция возвращения дважды?Я понимаю, что одно возвращение является pid дочернего, и это возвращается родительскому, а другое - ноль, и оно возвращается дочернему процессу.Но я не могу обернуть голову, как реализовать это понятие возвращения дважды ... как я могу вернуться дважды?Спасибо всем заранее.

Ответы [ 3 ]

2 голосов
/ 04 марта 2012

Когда вы вызываете fork, он возвращает «дважды», так как форк порождает два процесса, каждый из которых возвращает.

Итак, если вы реализуете fork, вам нужно создать второй процесс, не заканчивая первый. Тогда поведение возврата дважды будет происходить естественным образом: каждый из двух отдельных процессов продолжит выполнение, отличаясь только возвращаемым значением (дочерний элемент дает ноль, а родительский - PID ребенка).

1 голос
/ 04 марта 2012

Когда вы думаете о возвращении функции, вы имеете в виду обычный поток кода, который начинается с точки входа (обычно main), а затем выполняется построчно, строго детерминистическим и линейным образом.

Однако в реальной системе возможно иметь несколько контекстов исполнения , каждый из которых имеет свой собственный поток управления (и новый стандарт C ++ фактически включает это понятие).Каждый отдельный процесс представляет собой контекст выполнения, который начинается с main, но вы также можете создать новый контекст выполнения из существующего (на самом деле, все операционные системы должны быть в состоянии сделать это!).fork - это один из способов создания нового контекста выполнения, а точка входа нового контекста - это точка, в которую возвращается fork.Тем не менее, исходный контекст также продолжает работать, и он продолжается как обычно после вызова fork.Новый контекст - это отдельный процесс , поэтому fork возвращает (один раз) в обоих контекстах.

Существуют другие способы создания новых контекстов выполнения;Одним из них является создание нового потока (в том же процессе) путем создания экземпляра объекта std::thread или использования функции, специфичной для платформы;другая функция Linux clone(), которая лежит в основе реализации потока Posix и fork в Linux (путем создания нового пути выполнения для планировщика ядра и либо копирования всей виртуальной памяти (новый процесс), либо нет (новый поток).

0 голосов
/ 01 августа 2012

Далее я попытаюсь объяснить, как дважды вернуться из функции. Я предупреждаю вас с самого начала, что это все взломать. Но есть много мест, где используются подобные хаки.

Сначала скажем, у нас есть следующая программа на Си.

#include <stdio.h>

uint64_t saved_ret;

int main(int argc, char *argv[])
{
        if (saveesp()) {
                printf("here! esp = %llX\n", saved_ret);
                jmpback();
        } else {
                printf("there! esp = %llX\n", saved_ret);
        }

        return 0;
}

Теперь мы хотим, чтобы saveesp () возвращал дважды, чтобы мы могли достичь обоих printf. Итак, вот как реализована функция saveesp ():

#define _ENTRY(x) \
        .text; .globl x; .type x,@function; x:
#define NENTRY(y)       _ENTRY(y)

NENTRY(saveesp)
        movq    (%rsp), %rax
        movq    %rax, saved_ret

        movl    $1, %eax
        ret

NENTRY(jmpback)
        xorq    %rax, %rax
        pushq   saved_ret
        ret

Это ни в коем случае не переносимый код. Но вы можете написать одинаковые сборочные заглушки для всех платформ, которые вы хотите поддерживать.

Что делает saveesp (), так это то, что он берет адрес возврата, сохраненный в стеке, и сохраняет его в локальной переменной. После этого возвращается 1. Это ненулевое возвращение, которое приводит нас к первому printf.

После printf () мы вызываем jmpback (). Что является настоящим взломом. Эта функция делает так, что saveesp () возвращает второй раз.

Он делает это, помещая сохраненный адрес возврата в стек и выполняя команду ret. Ret вытолкнет адрес из стека и перейдет к нему. На этот раз код возврата устанавливается равным нулю. Поэтому, когда мы «возвращаемся» к нашей подпрограмме C, кажется, что мы только что вернулись из saveesp () с нулевым возвращаемым значением. Таким образом достигается вторая печать.

Если вас интересуют подобные хаки, вам следует прочитать немного больше о setjmp и longjmp из стандарта C, которые используются для реализации обработки исключений.

Кроме того, мы фактически используем это в ядре OpenBSD в пути кода приостановки / возобновления. Взгляните здесь на строки 231 и 250, это в значительной степени тот же код C, что и выше. А затем взгляните на ассемблерный код здесь в строке 542 - это функция savecpu, которая возвращает первый раз при приостановке, а в строке 375 мы возвращаемся второй раз, когда возвращаемся к возобновлению.

...