Команда IT специалистов выполнит подготовку инфраструктуры для вашего бизнеса.
Внедрение самых передовых решений и технологий.
Поддержка и сопровождение ваших сервисов.
Выполнение работ под "ключ", от покупки сервера, до настройки автоматизации процессов.
8(977)608-78-62 adm@nixm.ru

Системный вызов fork()

Ответить
Аватара пользователя
nezabudka
Местный говорун
Местный говорун
Сообщения: 618
Зарегистрирован: 18 апр 2015, 06:13
Откуда: Ростов на Дону

Системный вызов fork()

Сообщение nezabudka »

Для линуксоидов Си это не только язык программирования, это еще
удобный уровень абстракции понимания работы компьютера в целом.
Значит даже поверхностные знания этого языка, пусть не позволяющие успешно программирвать,
формируют связанную, правильную картину работы сисетемы и взаимодействия процессов в ней.
Это главная причина уделять внимание программированию на Си
даже если изначально не ставилась задача по овладению им в совершенстве
и время потраченное на его освоение будет в любом случае не напрасным.
На мой взгляд один из самых интересных моментов работы программы это
клонирование процессов и общение между форками через каналы.
В данном случае мы рассматриваем полудуплексный канал пайп. Родственник
вертикальной черточки в баше.
Полудуплексный канал создается в пространстве ядра. Там он имеет определенную
сущьность но пользователю на руки выдаются только дискрипторы от него.
Но сначала по порядку. Скомпилированная программа начинает свой бег.
пока на ее пути не встречается системный вызов fork(). Этот
системыный вызов порождает почти полную копию существующего процесса - программы.
Как же различить в программе кто из них кто и как разделить последующий код
между двумя ветками? Системный вызов fork() возвращает разные значения.
В процесс родитель он возвращает номер дочернего процесса, а в дочернем
процессе возвращает ноль. И если мы в коде предусмотрим переменную например
с именем pid и присвоим ей возваращаемое значение от вызова fork()
то мы легко сможем различить два процесса - две паралельные программы
по ее содержанию.
Поместив в коде переключатель switch или как у меня условный переход
мы можем лекго обазначить какому процессу предназначен тот или иной
участок кода. В родительком процессе переменная получает какое то
число - номер клонированного процесса, во всяком случае больше нуля,
и занчит мы можем отделить его по условию if(pid > 0).
Другое условие определяет другой участок кода для дочернего процесса
по условию if(pid == 0). Теперь нам не составит труда написать нужный
код в программе отдельно для каждого процесса и более того обменятся через
пайп информацией. Системный вызов fork() клонирует так же и переменные
содержащие дискрипторы созданных каналов, занчит
нам надо предусмотреть и создать канал до клонирования программы. У меня
в программе дочерний процесс посылает данные родительскому процессу, поэтому
в дочернем я закрываю выходной дискриптор а в родительском соответственно
закрою входной. Иначе такой канал не сможет определить конец передачи.
Чаще всего такой паре процессов дают устоявшееся название, сервер -
то которое читает из пайпа и клиент - то которое отсылает данные в канал.

Код: Выделить всё

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
        int fd[2]; //массив входного и выходного дискрипторов канала
        char buff[] = "Hello World!";
        char xbuff[strlen(buff)];
        int ret;
        size_t size;
        pid_t pid;
        if(pipe(fd) < 0) //создаем канал и определяем дискрипторы в массиве fd
                perror("pipe!"), exit(EXIT_FAILURE);
        if((pid = fork()) == -1) //с момента вызова fork() мы получим две одинаковых программы.
        {                       //но в переменные pid уже будут возвращены разные значения.
                perror("fork!"), exit(EXIT_FAILURE);
        }
        else if(pid == 0)
        {
                printf("%s текущий процесс: %d родительский: %d\n", "Child", getpid(), getppid());
                close(fd[0]); //закрываем выходной дискриптор
                size = write(fd[1], buff, strlen(buff));
                printf("%s\n", "Выходим из дочернего процесса");
                exit(size); //код возврата
        }
        else
        {
                printf("%s текущий процесс: %d дочерний: %d\n", "Parent", getpid(), pid);
                close(fd[1]); //закрываем входной дискриптор.
                read(fd[0], xbuff, 12); //считаем присланное сообщение в буфер
                waitpid(pid, &ret, 0); //ожидаем код завершения д/п SIGCHILD
                printf("%s %d\n", "Код возврата дочернего процесса:", WEXITSTATUS(ret));
                printf("%s\n", xbuff);
        }
        return 0;
}
[album]438[/album]
[album]439[/album]
"I invented the term Object-Oriented and I can tell you I did not have C++ in mind." - Alan Kay
Olej

Re: Системный вызов fork()

Сообщение Olej »

nezabudka писал(а): Но сначала по порядку. Скомпилированная программа начинает свой бег.
пока на ее пути не встречается системный вызов fork(). Этот
системыный вызов порождает почти полную копию существующего процесса - программы.
Я вам ещё больше скажу :D ...
Так было когда-то. А на сегодня (с MMU и отображением логических страниц на физическую RAM) ничего вообще не создаётся.
И родительский и дочерний процесс - это образ абсолютно одних и тех же страниц физической памяти.
И только при попытке записи хотя бы одного байта в страницу (4Kb для 32 бит системы) - возникает прерывание программы от MMU по нарушению защиты памяти (в общем, грубейшая ошибка сродни SIGSEGV) поскольку все страницы программы отмечены заблаговременно как read-only. И когда система ловит такое заранее предусмотренное прерывание, тогда она отображает эту логическую страницу программы на реальную физическую страницу RAM. (но только одну страницу! ... скажем, из 10Mb адресного пространства двух программ - родитель и потомок - только 4Kb отображаются в разные страницы памяти)

Это тот механизм, который называется COW (Copy On Write). Некоторые ОС (например QNX и другие, требующие особую надёжность) отказались от механизма COW (раньше он был, а потом не стало).

P.S. Если вы стали глубоко копать fork() (а это всё очень интересно), то посмотрите ещё и не-POSIX вызов vfork().

P.P.S. Но логика работы fork(), в связи с таким радикальным изменением с COW, с точки зрения пользователя - нисколько не изменилась, и в точности соответствует тому, что описано в сообщении ТС.
Ответить

Вернуться в «C/C++»