У багатьох програмах та на сторінках інструкцій Linux я бачив використання коду fork()
. Навіщо нам потрібно користуватися fork()
і яке його призначення?
Відповіді:
fork()
це те, як ви створюєте нові процеси в Unix. Під час дзвінка fork
ви створюєте копію власного процесу, який має власний адресний простір . Це дозволяє виконувати декілька завдань незалежно одне від одного, як ніби кожна з них має повну пам’ять машини для себе.
Ось кілька прикладів використання fork
:
fork
для запуску програм, які ви викликаєте з командного рядка.fork
для створення декількох серверних процесів, кожен з яких обробляє запити у своєму адресному просторі. Якщо одна людина помирає або втрачає пам’ять, інші не зазнають змін, тому це функціонує як механізм стійкості до несправностей.fork
для обробки кожної сторінки в рамках окремого процесу. Це не дозволить коду на стороні клієнта на одній сторінці не зруйнувати весь ваш браузер.fork
використовується для створення процесів у деяких паралельних програмах (наприклад, тих, що написані за допомогою MPI ). Зауважте, це відрізняється від використання потоків , які не мають власного адресного простору і існують у процесі.fork
опосередковано для запуску дочірніх процесів. Наприклад, кожного разу, коли ви використовуєте команду, як subprocess.Popen
у Python, ви fork
дочірній процес і читаєте її результати. Це дозволяє програмам працювати разом.Типове використання fork
оболонки може виглядати приблизно так:
int child_process_id = fork();
if (child_process_id) {
// Fork returns a valid pid in the parent process. Parent executes this.
// wait for the child process to complete
waitpid(child_process_id, ...); // omitted extra args for brevity
// child process finished!
} else {
// Fork returns 0 in the child process. Child executes this.
// new argv array for the child process
const char *argv[] = {"arg1", "arg2", "arg3", NULL};
// now start executing some other program
exec("/path/to/a/program", argv);
}
Оболонка породжує дочірній процес за допомогою exec
і чекає його завершення, а потім продовжує власне виконання. Зверніть увагу, що вам не потрібно використовувати вилку таким чином. Ви завжди можете породити безліч дочірніх процесів, як це може робити паралельна програма, і кожна може запускати програму одночасно. В основному, ви використовуєте будь-який час, коли ви створюєте нові процеси в системі Unix fork()
. Для еквівалента Windows подивіться наCreateProcess
.
Якщо ви хочете більше прикладів і більш довгих пояснень, Вікіпедія має гідний резюме. І ось кілька слайдів про те, як процеси, потоки та паралельність працюють у сучасних операційних системах.
fork()
це шлях , щоб створити новий процес в UNIX, але бути педантичним, є принаймні один інший: posix_spawn()
.
fork () - це те, як Unix створює нові процеси. У той момент, коли ви викликали fork (), ваш процес клонується, і два різні процеси продовжують виконання звідти. Один з них, дитина, отримає fork () return 0. Інший, батько, буде fork () повернути PID (ідентифікатор процесу) дитини.
Наприклад, якщо ви введете в оболонку наступне, програма оболонки викличе fork (), а потім виконає команду, яку ви передали (telnetd, в даному випадку) в дочірній матері, тоді як батько також знову відобразить підказку як повідомлення із зазначенням PID фонового процесу.
$ telnetd &
Що стосується причини створення нових процесів, саме так ваша операційна система може робити багато речей одночасно. Ось чому ви можете запустити програму і, поки вона працює, перейти в інше вікно і зробити щось інше.
fork () використовується для створення дочірнього процесу. Коли викликається функція fork (), буде створено новий процес, і виклик функції fork () поверне інше значення для дочірнього та батьківського елементів.
Якщо значення, яке повертається, дорівнює 0, ви знаєте, що ви є дочірнім процесом, а якщо значення, що повертається, є числом (яке є ідентифікатором дочірнього процесу), ви знаєте, що ви батьки. (а якщо це від’ємне число, вилка не вдалася, і дочірній процес не створювався)
fork () в основному використовується для створення дочірнього процесу для процесу, в якому ви викликаєте цю функцію. Кожного разу, коли ви викликаєте fork (), він повертає нуль для дочірнього ідентифікатора.
pid=fork()
if pid==0
//this is the child process
else if pid!=0
//this is the parent process
за допомогою цього ви можете надавати різні дії для батьків та дитини та використовувати функцію багатопоточності.
fork () створить новий дочірній процес, ідентичний батьківському. Отже, все, що ви запускаєте в коді після цього, буде виконуватися обома процесами - дуже корисно, якщо у вас є, наприклад, сервер, і ви хочете обробляти кілька запитів.
Ймовірно, вам не потрібно використовувати fork у повсякденному програмуванні, якщо ви пишете програми.
Навіть якщо ви хочете, щоб ваша програма запустила іншу програму, щоб виконати якесь завдання, існують інші простіші інтерфейси, які використовують форк за кадром, наприклад, "система" на C та perl.
Наприклад, якщо ви хотіли, щоб ваш додаток запустив іншу програму, таку як bc, щоб зробити для вас якийсь розрахунок, ви можете використовувати "систему" для її запуску. Система робить "форк", щоб створити новий процес, а потім "exec", щоб перетворити цей процес на bc. Після завершення bc система повертає управління вашою програмою.
Ви також можете запускати інші програми асинхронно, але я не пам’ятаю, як.
Якщо ви пишете сервери, оболонки, віруси чи операційні системи, ви, швидше за все, захочете використовувати fork.
system()
. Я читав про те, fork()
що хочу, щоб мій код C запускав сценарій python.
Форк системного виклику () використовується для створення процесів. Він не бере аргументів і повертає ідентифікатор процесу. Призначення fork () - створити новий процес, який стає дочірнім процесом абонента. Після створення нового дочірнього процесу обидва процеси виконуватимуть наступну інструкцію після системного виклику fork (). Тому ми повинні відрізняти батьків від дитини. Це можна зробити, протестувавши повернене значення fork ():
Якщо fork () повертає від'ємне значення, створення дочірнього процесу не вдалося. fork () повертає нуль новоствореному дочірньому процесу. fork () повертає позитивне значення, ідентифікатор процесу дочірнього процесу, батьківському елементу. Повернений ідентифікатор процесу має тип pid_t, визначений у sys / types.h. Зазвичай ідентифікатор процесу є цілим числом. Більше того, процес може використовувати функцію getpid () для отримання ідентифікатора процесу, призначеного цьому процесу. Отже, після системного виклику fork (), простий тест може визначити, який процес є дитиною. Зверніть увагу, що Unix зробить точну копію адресного простору батьків та передасть її дитині. Отже, батьківський та дочірній процеси мають окремі адресні простори.
Давайте розберемося на цьому на прикладі, щоб чітко пояснити наведені вище моменти. Цей приклад не відрізняє батьківський та дочірній процеси.
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#define MAX_COUNT 200
#define BUF_SIZE 100
void main(void)
{
pid_t pid;
int i;
char buf[BUF_SIZE];
fork();
pid = getpid();
for (i = 1; i <= MAX_COUNT; i++) {
sprintf(buf, "This line is from pid %d, value = %d\n", pid, i);
write(1, buf, strlen(buf));
}
}
Припустимо, що вищевказана програма виконується до точки виклику fork ().
Якщо виклик fork () виконано успішно, Unix зробить дві однакові копії адресних просторів, одну для батьківської, а іншу для дочірньої. Обидва процеси розпочнуть своє виконання з наступного оператора після виклику fork (). У цьому випадку обидва процеси розпочнуть своє виконання з призначення
pid = .....;
Обидва процеси починають своє виконання відразу після системного виклику fork (). Оскільки обидва процеси мають однакові, але окремі адресні простори, ці змінні, ініціалізовані перед викликом fork (), мають однакові значення в обох адресних просторах. Оскільки кожен процес має власний адресний простір, будь-які модифікації будуть незалежними від інших. Іншими словами, якщо батько змінює значення своєї змінної, модифікація вплине лише на змінну в адресному просторі батьківського процесу. Інші адресні простори, створені викликами fork (), не постраждають, навіть якщо вони мають однакові імена змінних.
У чому причина використання write, а не printf? Це тому, що printf () є "буферизованим", тобто printf () буде групувати результати процесу разом. Буферуючи вихідні дані для батьківського процесу, дитина може також використовувати printf, щоб роздрукувати деяку інформацію, яка також буде буферизована. Як результат, оскільки вихідні дані не будуть негайно відправлені на екран, можливо, ви не отримаєте правильний порядок очікуваного результату. Гірше того, результати двох процесів можуть змішуватися дивними способами. Щоб подолати цю проблему, ви можете розглянути можливість використання "небуферованого" запису.
Якщо ви запускаєте цю програму, на екрані може з’явитися таке:
................
This line is from pid 3456, value 13
This line is from pid 3456, value 14
................
This line is from pid 3456, value 20
This line is from pid 4617, value 100
This line is from pid 4617, value 101
................
This line is from pid 3456, value 21
This line is from pid 3456, value 22
................
Ідентифікатор процесу 3456 може бути таким, який присвоєний батькові або дочірній організації. Через те, що ці процеси виконуються одночасно, їх вихідні лінії змішуються досить непередбачувано. Більше того, порядок цих рядків визначається планувальником процесора. Отже, якщо ви знову запустите цю програму, ви можете отримати зовсім інший результат.
Багатопроцесорна обробка є основною для обчислювальної техніки. Наприклад, ваш IE або Firefox можуть створити процес завантаження файлу для вас, поки ви все ще переглядаєте Інтернет. Або, поки ви роздруковуєте документ у текстовому процесорі, ви все ще можете переглядати різні сторінки та все одно редагувати разом із ним.
Форк () використовується для створення нових процесів, як писало кожне тіло.
Ось мій код, який створює процеси у вигляді двійкового дерева ....... Він попросить відсканувати кількість рівнів, до яких ви хочете створити процеси в двійковому дереві
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
int main()
{
int t1,t2,p,i,n,ab;
p=getpid();
printf("enter the number of levels\n");fflush(stdout);
scanf("%d",&n);
printf("root %d\n",p);fflush(stdout);
for(i=1;i<n;i++)
{
t1=fork();
if(t1!=0)
t2=fork();
if(t1!=0 && t2!=0)
break;
printf("child pid %d parent pid %d\n",getpid(),getppid());fflush(stdout);
}
waitpid(t1,&ab,0);
waitpid(t2,&ab,0);
return 0;
}
ВИХІД
enter the number of levels
3
root 20665
child pid 20670 parent pid 20665
child pid 20669 parent pid 20665
child pid 20672 parent pid 20670
child pid 20671 parent pid 20670
child pid 20674 parent pid 20669
child pid 20673 parent pid 20669
Спочатку потрібно зрозуміти, що таке системний виклик fork (). Дозволь пояснити
fork () Системний виклик створює точний дублікат батьківського процесу, робить дублікат батьківського стеку, купи, ініціалізованих даних, неініціалізованих даних та надає код у режимі лише для читання батьківському процесу.
Системний виклик Fork копіює пам’ять на основі копіювання на запис, означає, що дитина робить сторінку віртуальної пам’яті, коли є необхідність копіювання.
Тепер Призначення fork ():
fork()
використовується для породження дочірнього процесу. Зазвичай він використовується в подібних ситуаціях, як різьбонарізка, але є відмінності. На відміну від потоків, fork()
створює цілі окремі процеси, а це означає, що дитина та батько, хоча вони є прямими копіями один одного в пункті, що fork()
викликається, повністю відокремлені, і не можуть отримати доступ до простору пам'яті іншого (не переходячи до звичайних проблем Ви хочете отримати доступ до пам'яті іншої програми).
fork()
як і раніше використовується деякими серверними програмами, переважно тими, що працюють як root на машині * NIX, що скидає дозволи перед обробкою запитів користувачів. Є ще деякі варіанти використання, але в основному люди зараз перейшли до багатопоточності.
Обґрунтування причини fork () проти простої функції exec () для ініціювання нового процесу пояснюється у відповіді на подібне запитання про біржу стеків unix .
По суті, оскільки форк копіює поточний процес, всі різні можливі параметри процесу встановлюються за замовчуванням, тому програміст не має їх постачати.
Навпаки, в операційній системі Windows програмістам доводиться використовувати функцію CreateProcess, яка набагато складніша і вимагає заповнення різноманітної структури для визначення параметрів нового процесу.
Отже, підсумовуючи, причиною форкінгу (проти виконання) є простота створення нових процесів.
Системний виклик Fork () використовується для створення дочірнього процесу. Це точна копія батьківського процесу. Форка копіює розділ стеку, розділ купи, розділ даних, змінну середовища, аргументи командного рядка від батьківського.
зверніться: http://man7.org/linux/man-pages/man2/fork.2.html
Функція fork () використовується для створення нового процесу шляхом дублювання існуючого процесу, з якого він викликається. Існуючий процес, з якого викликається ця функція, стає батьківським, а новостворений - дочірнім. Як уже зазначалося, дитина є копією батьків, але є деякі винятки.
У дитини є унікальний PID, як і будь-який інший процес, що працює в операційній системі.
У дитини є батьківський ідентифікатор процесу, який збігається з PID
процесу, який його створив.
У дочірньому процесі використання ресурсів та лічильники часу процесора скидаються до нуля.
Набір сигналів, що очікують на розгляд, у дитини порожній.
Дочка не успадковує таймерів від батьків
Приклад:
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int var_glb; /* A global variable*/
int main(void)
{
pid_t childPID;
int var_lcl = 0;
childPID = fork();
if(childPID >= 0) // fork was successful
{
if(childPID == 0) // child process
{
var_lcl++;
var_glb++;
printf("\n Child Process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
else //Parent process
{
var_lcl = 10;
var_glb = 20;
printf("\n Parent process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
}
else // fork failed
{
printf("\n Fork failed, quitting!!!!!!\n");
return 1;
}
return 0;
}
Тепер, коли вищезгаданий код скомпільовано та запущено:
$ ./fork
Parent process :: var_lcl = [10], var_glb[20]
Child Process :: var_lcl = [1], var_glb[1]