Fork (системийн дуудалт)

Чөлөөт нэвтэрхий толь — Википедиагаас
Jump to navigation Jump to search

Форк Системийн Дуудалт - The fork() system call

Fork() системийн дуудалт нь процессийг үүсгэхэд ашиглагддаг. Энэ нь ямар ч аргумент хэрэггүй бөгөөд процессийн ID-г буцаана. Fork() зорилго нь дуудагчийн хүүхдийн процесс болох шинэ процесс үүсгэх явдал юм. Шинэ хүүхдийн процессийг үүсгэсний дараа процессүүд нь Fork() системийн дуудлагын дараа дараагийн зааврыг гүйцэтгэх болно. Тиймээс бид эцэг эхээс эхээс хүүхдээ ялгах хэрэгтэй. Энэ нь Fork() утга буцаах утгыг турших замаар хийж болно:

  • Хэрэв Fork() нь сөрөг утга буцаавал хүүхдийн процесс үүсгэх амжилтгүй болсон.
  • Fork() нь шинээр бий болгосон хүүхдийн процесст тэгийг буцаана.
  • Fork() нь хүүхдийн үйл явцын процессийн ID, эерэг утга буцаана. Буцах процессийн ID нь sys/types.h - д тодорхойлогдсон pid_t төрлийн байна. Ихэнхдээ процессийн ID нь getpid() функцийг ашиглан энэ процессод өгөгдсөн процессийн ID-г авах боломжтой.

Тиймээс системийн дуудлага Fork() хийх үед, энгийн тест нь ямар процесс хүүхэд болохыг хэлж өгдөг. Эцэг эхийн хаягийн зайн хуулбарыг хуулж, хүүхдэдээ өгнө өө гэдгийг анхаарна уу. Тиймээс эцэг эх, хүүхдийн процессүүд нь тусдаа хаягийн зайтай байдаг.

Дээрх цэгүүдийг тодорхой болгохын тулд жишээ авч үзье. Энэ жишээ нь эцэг эх болон хүүхдийн процессыг ялгахгүй.

#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() (Улаан өнгөөр тэмдэглэгдсэн хүртэл явагддаг гэж бодъё: ---Parent--- main() { fork(); pid = ....; .... } Хэрэв Fork()-г дуудах дуудлага амжилттай хэрэгжүүлбэл, Unix-г ажиллуулах болно.

  • Хаягийн талбарын хоёр адил хуулбарыг, нэгийг нь эцэг эх, хоёрыг нь хүүхдэд зориулан хий.
  • Аль аль процессүүд нь Fork() дуудлагаар дамжсан дараагийн илэрхийллээр тэдний execution эхлэх болно. Энэ тохиолдолд хоюул процессүүд нь доор үзүүлсэн шиг даалгаврын мэдэгдэл дээр гүйцэтгэгдэж эхлэх болно.
Parent                            
main()                         
{                              
        fork();                    
---->   pid= ....;                  
         ......                     
}
Child
main()  
{
         fork();
---->    pid=.....;
          ......
}

Аль ч процесс нь системийн дуудлага Fork(). Эдгээр процессүүд нь адилхан боловч тусдаа хаягийн зайтай байдаг тул салаалахаас өмнө эхлүүлсэн хувьсагчууд нь хаягийн талбаруудад хоёуланд нь ижил утгатай байна. Үйл явц бүр өөрийн гэсэн хаягийн зайтай тул аливаа өөрчлөлт нь бусдаас хараат бус байх болно. Өөрөөр хэлбэл, хэрэв эцэг эх хувьсагчийн утгыг өөрчилбөл энэ өөрчлөлт нь зөвхөн эцэг процессийн хаягийн талбар дахь хувьсагчдад нөлөөлөх болно. Fork() дуудлагаар үүсгэгдсэн бусад хаягийн талбарууд нь ижил хувьсагчийн нэртэй байсан ч нөлөөлөхгүй. Хэвлэх дугаар биш, бичиж тэмдэглэх шалтгаан нь юу вэ? Энэ нь printf() нь "буфертай" учраас хэвлэх файлын үр дүнг хамтдаа хэвлэх болно. Эцэг процессын гаралтыг буферээр хийдэг боловч хүүхэд бас зарим мэдээллийг хэвлэхэд зориулж хэвлэнэ. Үүний үр дүнд гаралт нь шууд дэлгэц рүү илгээж чадахгүй тул хүлээгдэж буй үр дүнгийн зөв дарааллыг авч чадахгүй байж магадгүй юм. Илүү муухай, хоёр үйл явцын гаралтыг хачин аргаар хольж болно. Энэ асуудлыг даван туулахын тулд "unbuffered" гэж бичиж болно.

Хэрэв та энэ програмыг ажиллуулж байгаа бол дэлгэц дээр дараах зүйлсийг харж болно:

              * .....................*
       *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 нь эцэг эх эсвэл хүүхдэд олгосон байж болно. Эдгээр процессууд нь нэг зэрэг ажилласнаар тэдгээрийн гаралтын шугамууд нь урьдчилан таамаглах аргагүй арга замаар хоорондоо холилдсон байдаг. Үүнээс гадна эдгээр мөрүүдийн дарааллыг CPU хуваарьар тодорхойлно. Тиймээс хэрэв та энэ програмыг дахин ажиллуулбал, та өөр өөр үр дүнд хүрч болно.

Эцэг эхээс хүүхдээс ялгагдах энгийн хялбар жишээг авч үзье.

#include  <stdio.h>
#include  <sys/types.h>

#define   MAX_COUNT  200

void  ChildProcess(void);                /* child process prototype  */
void  ParentProcess(void);               /* parent process prototype */

void  main(void)
{
     pid_t  pid;

     pid = fork();
     if (pid == 0) 
          ChildProcess();
     else 
          ParentProcess();
}

void  ChildProcess(void)
{
     int   i;

     for (i = 1; i <= MAX_COUNT; i++)
          printf("   This line is from child, value = %d\n", i);
     printf("   *** Child process is done ***\n");
}

void  ParentProcess(void)
{
     int   i;

     for (i = 1; i <= MAX_COUNT; i++)
          printf("This line is from parent, value = %d\n", i);
     printf("*** Parent is done ***\n");
}

Энэ програмд хоёулаа (1) хүүхдэд эсвэл эцэг эхийн процессоор хэвлэсэн эсэх, (2) i хувьсагчийн утга Энгийн байдлаар printf() ашиглагддаг.

Үндсэн програм нь Fork() ажиллаж байгаа үед түүний хаягийн талбар, түүний дотор програм ба бүх өгөгдөлийн адил хуулбар үүсгэгддэг. Системийн дуудлагын салаа () нь хүүхдийн процесс ID-ыг эцэг эх рүү нь буцааж 0-ийг буцаана. Дараах зураг дээр хаягийн талбарт хоёулаа хувьсагч pid байна. Эцэг эх нь хүүхдийн процессийн дугаарыг 3456 хүлээн авдаг ба хүүхдийн нэгийг нь 0 хүлээн авдаг.

main()     "pid = 3456"
{
     pid=fork();
     if(pid == 0)
        ChildProcess();
     else
        ParentProcess();
}
void ChildProcess()
{
   .....
}
void ParentProcess()
{
   .....
}
main() "pid = 0 "
{
    pid=fork();
    if(pid == 0)
      ChildProcess();
    else
      ParentProcess();
}
void ChildProcess()
{
   .....
}
void ParentProcess()
{
   .....
}

Одоо хоёр программ хоёулаа(өөрөөр хэлбэл Эцэг эх, хүүхэд) бие биенээсээ биедаан хэрэгжих болно. Үүнд:

Parent
main()   pid=3456
{ 
    pid=fork();
--->if(pid==0)
      ChildProcess();
    else
      ParentProcess();
}
void ChildProcess()
{
   .....
}
void ParentProcess()
{
   .....
}
Child
main()  pid=0
{
    pid=fork();
--->if(pid==0)
      ChildProcess();
    else
      ParentProcess();
}
void ChildProcess()
{
  .....
}
void ParentProcess()
{
  .....
}

Эцэг эхийн хувьд, pid тэг биш болохоор ParentProcess() функцийг дууддаг. Нөгөө талаас, хүүхэд null pid байдаг бөгөөд ChildProcess() нь доор үзүүлсэн шиг:

Parent
main() "pid=3456"
{
    pid=fork();
    if(pid==0)
      ChildProcess();
    else 
      ParentProcess();
}
void ChildProcess()
{
    .....
}
--->void ParentProcess()
{
    .....
}
child
main()    "pid=0"
{
    pid=fork();
    if(pid==0)
       ChildProcess();
    else
       ParentProcess();
}
--->void ChildProcess()
{
    .....
}
void ParentProcess()
{
    .....
}

CPU хуваарилагч нь процесс бүрт зориулсан цагны зүсмэлийг үүсгэдэг тул удирдлага нь нөгөө рүү шилжиж эхлэхээс өмнө эцэг эх эсвэл хүүхдийн процесс нь хэд хэдэн мөрүүдийг хэвлэх болно. өөр үйл явц хэвлэсэн. Тиймээс MAX_COUNT-ийн утга нь хоёулаа хоѐр буюу түүнээс дээш хугацааны зүсмэлийг ажиллуулахад хоёр процесст хангалттай байх ёстой. Хэрэв MAX_COUNT-ийн утга нь маш бага бол процессыг нэг дор квантаар гүйцэтгэж дууссаны дараа та ижил төстэй процессуудаар хэвлэгдсэн бүх мөрүүдийг агуулсан хоёр мөрийг харах болно.