Subject : IPC 개념은 ?

Description :



   <  프로세스간 통신 >

o  개 요
   - 전산망 프로그램은 둘 또는 그 이상의 프로세스들 사이에서 일어나는 상호 작용을 포함하므로,
     통신을 하고자 하는 서로 다른 프로세스들이 사용가능한 여러 가지 방법들에 대하여 조심스럽게 살펴
     보아야 한다. 기존의 단일 프로세스 프로그램에서는 한 프로세스 내에 여러 단편들이 전역 변수나 
     함수 호출, 그리고 이 함수와 그 호출자 사이에서 주고 받는 독립 변수와 함수의 결과를 이용하여
     서로 통신할 수 있다. 그러나 각 프로세스가 고유한 자신의 주소 영역내에서 실행되는 , 서로 독립되어
     있는 프로세스들을 다루고자 할 때에는 좀다 자세히 고려해야 할 것들이다.
     약 20여년전, 시분할-다중 프로그램 운영 체제가 개발되었을 때 그 설계 목적 중의 하나는 독립된
     프로세스들이 서로 간섭하지 않음을 보장해 주는 것이었다.
     두 프로세스가 서로 통신하기 위해서는, 양자가 모두 이 통신에 동의해야 하고, 또 운영 체제가 
     프로세스간 통신(Interprocess Communication,IPC)을 위한 편의를 제공해야 한다.
          page 85 그림 참조
     여기서 IPC의 여러 가지 방법들을 살펴보기로 한다.

        . 파이프(pipes)
        . FIFOs(지명 파이프:named pipes)
        . 소식대롱(message queues)
        . 차단 표시기(semaphores)
        . 공유기억장소(shared memory)

o  잠금과 레코드 화일
   - 다수의 프로세스가 특정 자원을 공유하고자 할 경우가 있다. 이때 어느 한순간에는 한 프로세스만이
     그 자원을 사용할 수 있도록, 그 자원 사용에 있어 어떤 형태의 상호 배제가 필수적으로 제공되어야 한다.
     다음과 같은 프로그램을 생각해보자
        . 일련번호 화일을 읽는다.
        . 그 번호를 사용한다.
        . 숫자를 증가시키고 그것을 다시 기록한다.
     한 프로세스가 이 세단계를 실행하고 있는 동안 일어날 수 있는 문제점은 또 다른 프로세스가 같은 세 단계를
     수행할 수도 있다는 것이다. 결국 혼란이 되는데 , 따라서 한 프로세스가 일을 모두 끝낼 때까지
     다른 어떤 프로세스도 그 화일에 접근할 수 없도록 잠금을 설정하는 것이 필요하다.
     다음은 위의 세 단계를 수행하는 간단한 프로그램이다. 
     함수 my_lock my_unlock은 시작할 때 화일을 잠그고 일련 번호와 관계된 작업이 끝나면 화일의 잠금을
     풀기위해 호출한다.

          #include
          #define SEQFILE "ljs"
          #define MAXBUFF 100
          
          main()
          {
             int  fd,i,n,pid,seqno;
             char buff[MAXBUFF+1];
          
             pid=getpid();
             if ( (fd =open(SEQFILE,2))<0)
                  printf("can't open %s\n",SEQFILE);
          
             for (i=0;i<20;i++) {
                  my_lock(fd);
                  lseek(fd,0L,0);   /* rewind before read */
                  if ( (n=read(fd,buff,MAXBUFF)) <=0)
                       printf("read error\n");
                  buff[n]='\0';

                  if ( (n=sscanf(buff,"%d\n",&seqno)) != 1)
                     printf("sscanf error");
                  printf("pid=%d, seq#=%d\n",pid,seqno);
                  seqno++;
                  sprintf(buff,"%03d\n",seqno);
                  n=strlen(buff);
                  lseek(fd,0L,0);
                  if (write(fd,buff,n)!=n)
                      printf("write error");
                  my_unlock(fd);
             }
          }

          my_lock(fd)
          int fd;
          {
              return;
          }
          
          my_unlock(fd)
          int fd;
          {
              return;
          }
      
     위의 my_lock와 my_unlock은 전혀 잠금을 제공하지 않는다.

o  화일 잠금과 레코드 잠금
   - 화일 잠금은 화일 전체를 잠그는데 비하여 레코드 잠금은 프로세스로 하여금 한 화일의 특정 부분을
     잠글 수 있도록 한다. 유닉스 레코드 잠금에서 사용하는 레코드는 화일내에서 시작 바이트 위치와
     그 지점으로부터의 바이트 수를 지정함으로써 정의한다.
     레코드 잠금이라는 용어는 다른 운영 체제에서 디스크 화일을 레코드 자료 구조로 
     구성하는데에서 유래되었다.
     유닉스 알맹이가 레코드의 개념을 제공하지 않으므로 유닉스의 특징을 고려한다면 영역 잠금(range locking)
     이라는 용어가 더 좋을 것이다.
     즉 잠금의 대상이 화일의 영역이 되는 것이다. 이 영역이 레코드와 어떤 유사성을
     갖는지 여부는 응용 프로그램에 달려 있다.
     System V에서 lockf 함수는 다음과 같다.

         #include
         int lockf(int fd, int function, long size);

     여기서 function은 다음 값중의 하나를 갖는다.

         F_ULOCK     전에 잠근 영역을 잠금 해제한다.
         F_LOCK      한 영역을 잠근다(봉쇄)
         F_TLOCK     한 영역을 검사하고 잠근다(비봉쇄)
         F_TEST      한 영역의 잠금 여부를 검사한다..

      lockf 함수는 현재 화일의 위치와 "레코드"를 정의하기 위한 size독립 변수를 사용한다.
      레코드는 현재의 위치에서 시작하여 양수의 size만큼 앞쪽으로 확장되거나 음수의 size 만큼 뒷쪽으로 
      확장된다. 
      size가 0인 경우 영향을 받는 레코드는 현재의 위치로부터 가장 큰 화일 위치(화일의 끝)까지 확장된다.
      화일의 시작점으로 lseek을 행한후 size0의 lockf를 수행하면 전체 화일이 잠긴다.
      lockf 함수는 잠금을 설정하는 기능과 잠금 설정 여부를 검사하는 기능 두가지를 모두 제공한다.
      function값이 F_LOCK이고 그 영역이 이미 다른 프로세스에 의해 잠겨져 있으면 , 호출 프로세스는
      그 영역이 사용 가능해질 때까지 대기 상태가 된다. 이를 봉쇄라 한다.
      그러나 F_TLOCK연산은 비봉쇄라 칭하는데 - 만약 그 영역이 사용가능하지 않은 경우, lockf함수는
      즉시 -1의 값으로 복귀하고 errno을 EAGAIN 또는 EACCES로 만든다.
      또한 F_TEST연산은 프로세스로 하여금 잠금설정 없이 잠금 여부만을 검사할 수 있도록 해준다.
      비봉쇄 잠금을 하기 위해서는 F_TLOCK연산을 사용해야만 한다.
      다음과 같은 경우를 생각해 보자.

          if (lockf(fd, F_TEST, size) ==0) {
                rc = lockf(fd, F_LOCK, size);
               ...
          }

      위의경우, F_TEST와 F_LOCK사이에서 다른 프로세스가 lockf 함수를 호출하면 F_LOCK을 봉쇄하는
      경우가 생길 수 있다. F_TLOCK은 이 경우에 필요한 단일 작업 방식으로 잠금을 시험하고 설정할 수
      있도록 해준다.
      비봉쇄 잠금의 한 예로, 만약 어느 파수꾼 프로그램이 시작하고 수행중인 파수꾼이 오직 하나이기를
      원하는 경우를 생각해 보자. 
      파수꾼 프로그램이 시작되면, 특정 화일에 비봉쇄 잠금을 실행한다.
      잠금이 성공적으로 수행되면, 파수꾼 프로그램은 오직 하나만이 수행중일 것이고, 실패한다면 이미
      그의 복제가 하나 더 존재한다는 것으로 인식하고, 자신은 종료할 수 있다.

o  BSD 권고 잠금
   - 4.3BSD가 제공하는 권고 화일 잠금을 이용하는 앞의 일련 번호의 예를 계속 설명한다.
     flock시스템 호출은 한 화일의 잠금과 잠금 해제를 제공한다.

         #include
         int flock(int fd, int operation);

     fd는 열려 있는 화일의 화일 지정 번호이고, operation은 다음 상수들로부터 형성된다.
        
         LOCK_SH  공유 잠금
         LOCK_EX  상호 배제 잠금
         LOCK_UN  잠금 해제
         LOCK_NB  잠금에서 봉쇄하지 않음

     하나 이상의 공유 잠금은 한 화일에 대하여 항상 적용할 수 있다.
     그러나 하나의 화일에 대해 공유잠금과 상호 배제 잠금을 동시에 제공하거나 다수의 상호 배제 잠금을
     제공하는 것은 어떤 경우에도 불가능하다. 허용되는 잠금 연산은 아래와 같다.
 
         LOCK_SH            공유 잠금(봉쇄)
         LOCK_EX            상호 배제 잠금(봉쇄)
         LOCK_SH | LOCK_NB  공유 잠금(비봉쇄)
         LOCK_EX | LOCK_NB  상호 배제 잠금(비봉쇄)
         LOCK_UN            잠금 해제

      잠금이 성공적으로 수행되면 flock이 0을 돌려주고, 그렇지 않으면 -1을 돌려준다.
      만약 화일이 이미 잠기어 있고, 비봉쇄 잠금이 요구되어(LOCK_NB), 잠금에 실패한다면 , 전역 변수
      errno는 EWOULDBLOCK이 된다.
      우리가 다음과 같이 잠금 함수를 만들어야 함을 의미한다.

          /*  Locking routines for 4.3BSD  */
          #include
          my_lock(fd)
          int fd;
          {
              if(flock(fd,LOCK_EX) == -1)
                   printf("Can't LOCK_EX);
          }

          my_unlock(fd)
          int fd;
          {
                   if(flock(fd,LOCK_UN) == -1)
                   printf("Can't LOCK_UN);
          }

o  단순한 고객-일꾼의 예
   - 아래그림의 고객-일꾼의 예는 IPC의 다양한 방법들을 설명하기 위하여 이 장 전체에서 사용한다.
     고객은 표준입력으로부터 화일 이름을 읽고 그것을 IPC 채널에 쓴다.
     일꾼은 이 화일이름을 IPC 채널로부터 읽고, 이 화일을 읽기 위해 화일을 열려고 한다.
     일꾼이 화일을 열게되면 그 화일을 읽어서 그 내용을 IPC 채널에 씀으로써 응답하게 되고, 그렇지 못하면
     화일을 열수 없다는 ASCII로 된 에러 소식으로 응답하게 된다.
     그러면 , 고객은 IPC채널로부터 읽고, 자신이 읽은 것을 표준 출력에 쓴다.
     일꾼이 화일을 읽지 못하면, 고객은 IPC채널로부터 에러 소식을 읽는다.
     그렇지 않은 경우 그 화일의 내용을 읽는다.

       filename   stdin  -----------      filename      -------------
                 ------> |         | ---------------->  |           |  <--- file
                         | client  |   file contents    |  server   |
  file contents  <-----  |         | <---------------   |           |
  or error 메시지 stdout -----------  or error messages -------------  

o  파이프
   - 유닉스는 여러 흥미있는 것들과 함께 파이프를 제공한다. 하나의 파이프는 데이타의 일방 흐름을
     제공하며 시스템 호출 pipe를 이용하여 만든다.

         int pipe(int *filedes);

     두개의 화일 지정 번호 -읽기를 위해 열리는 filedes[0]와 쓰기를 위한 filedes[1]- 를 돌려준다.
     파이프는 하나의 프로세스내에서는 잘 사용하지 않지만, 여기서는 이 파이프들이 어떻게 만들어지고
     사용되는지를 설명하기 위해 간단한 예를 든다.

         main()
         {
            int  pipefd[2],n;
            char buff[100];

            if (pipe(pipefd) <0)
                 printf("pipe error");

            printf("read fd= %d, write fd= %d\n", pipefd[0],pipefd[1]);
            if (write(pipefd[1], "hello world\n",12) != 12)
                    printf("write error");
            if ( (n=read(pipefd[0], buff, sizeof(buff))) <= 0)   ==> pipefd[0]에서 sizeof(buff)만큼 읽어서 
                    printf("read error");                            buff에 저장
            write(1,buff,n);   /*  fd 1 = stdou */
            exit(0);
          }   

     단일 프로세스 내에서 파이프가 어떻게 동작하는지는 page 99 그림 3.4에 있다. 하나의 파이프는
     항상 적어도 4096 바이트의 한정된 크기를 갖는다. 파이프를 읽고 쓰기 위한 규칙 - 파이프 내에
     더이상의 데이타가 없을 때 혹은 파이프가 가득 찾을 때 -은 FIFO에 관한 다음 절에서 설명할 것이다.
     서로 다른 두 프로세스 사이에서 통신하기 위해 파이프를 사용하는 전형적인 방법은 다음과 같다.
     우선 한 프로세스가 파이프를 만들고 그리고나서 fork를 이용, 자기자신을 복제한다.
     그리고 나서 어버이 프로세스가 파이프 읽기쪽을 닫고, 자식 프로세스가 쓰기쪽을 닫는다.
     이렇게 하여 두 프로세스 사이에서 데이타의 일방 흐름이 만들어진다.
   - 유닉스 껍데기에 사용자가
     
            who | sort | lpr

     의 명령어를 입력하면 껍데기는 세개의 프로세스와 그들 사이에 두개의 파이프를 만들기 위해서 
     위에서 언급한 것을 단계적으로 행한다.
     지금까지 보았던 모든 파이프는 단일 방향을 가지며, 오직 한 방향의 데이타 흐름을 제공하는 것이었다.
     양방향의 데이타 흐름이 필요한 경우, 두개의 파이프를 만들어서 각 방향의 전송을 위해 각각 하나씩
     사용한다. 실제로 이를 구현하기 위한 단계는 다음과 같다.

         . 파이프 1을 만들고, 파이프 2를 만든다.
         . fork를 한다.
         . 어버이는 파이프1의 읽기쪽을 닫는다.
         . 어버이는 파이프2의 쓰기쪽을 닫는다.
         . 자식은 파이프 1의 쓰기쪽을 닫는다.
         . 자식은 파이프 2의 읽기쪽을 닫는다.
    - 표준 입출력 모음은 파이프를 만들고 그 파이프로부터 읽거나 그 파이프에 쓰는 다른 프로세스를
      초기화해 주는 기능을 제공한다.

          #include
          FILE *popen(char *command, char *type);

      command는 껍데기 명령어 줄이다. 이것은 Bourne Shell에서 구동되며, PATH 환경변수를 command를 찾는데
      사용한다. 호출 프로세스와 지정된 명령어 사이에 파이프가 하나 만들어진다.
      popen에서 돌아오는 값은 문자열 type에 따라 입력 또는 출력에 사용되는 표준 입출력 FILE 지시자가
      된다. 이 type의 값이 r이면 호출 프로세스는 command의 표준 출력을 읽고 w 이면 호출 프로세스는
      그 명령어의 표준입력에 쓴다. popen 시스템 호출이 실패하면 NULL값이 돌아온다.
      아래의 함수는

          #include
          int pclose(FILE *stream);

      popen에 의해 만들어진 입출력 연속 처리(stream)를 닫고, command의 exit 상태를 돌려준다.
      만약 이 popen에 의해 연속 처리가 만들어지지 않았다면 -1이 복귀되다.
 
           #include 
           #define MAXLINE 10
           main()
           {
              int n;
              char line[MAXLINE], command[MAXLINE+10];
              FILE *fp;
           
              if (fgets(line,MAXLINE,stdin) == NULL)     ---> MAXLINE은 읽혀질 크기를 제한,line은 저장장소
                    printf("filename read error");
           
              sprintf(command, "cat %s", line);     ---> line에는 cat다음에 오는 인자가 저장된다
              if ( (fp=popen(command,"r")) ==NULL)
                    printf("popen error");
           
              while ((fgets(line,MAXLINE,fp)) != NULL) {
                                 n = strlen(line);
                      if (write(1,line,n) != n)   ---> 1은 fd로서 표준출력을 의미함
                         printf("data write error");
              }
           
              if (ferror(fp))
                  printf("fgets error");
              pclose(fp);
              exit(0);
           }
      
           # a.out
             정리
             하면 cat 정리가 실행이 된다.

       popen함수는 또 다른 용도로 ,프로세스의 현재 작업 화일 명부를 알아내는데 사용할 수 있다.
       시스템 호출 chdir이 프로세스의 현 작업 화일 명부를 변화시킬 수 있음을 상기하고, 한편으로
       현재의 값을 얻을 수 있는 시스템 호출은 없다는 것에 유의하자.
       System V에서는 getcwd함수로 이 작업을 할 수 있고, 4.3BSD에서는 getwd가 비슷한 작업을 해 주지만
       똑같지는 않다.
       다음은 유닉스의 pwd 명령어를 사용하여 현재의 작업 화일 명부를 알아내고, 
       그것을 인쇄하는 프로그램이다.

           #include
           #define MAXLINE 255
           main()
           {
              FILE *fp;
              char line[MAXLINE];
              
              if ( (fp=popen("/bin/pwd","r"))==NULL)
                     printf("popen error");
              if (fgets(line,MAXLINE,fp) == NULL)  --> 여기서 line은 pwd의 실행결과가 저장됨 
                     printf("fgets error");
              printf("%s",line);
              pclose(fp);
              exit(0);
            }
 
       파이프의 사용에 있어 가장 큰 단점은 같은 어버이 프로세스를 가진 프로세스들 사이에서만 파이프를
       사용할 수 있다는 것이다. 파이프가 한 프로세스에서, 시스템 호출 fork로 만들어진 다른 
       프로세스들에게만 전달이 되기때문이며, 어버이 프로세스와 시스템 호출 fork후에 만들어진 
       자식프로세스들 사이에서만 열린 화일이 공유되기 때문이다.
       전혀 관계가 없는 두 프로세스를 대상으로 그 사이에 파이프를 만들고 그것을 IPC를 위해 
       사용할 수 있는 방법은 없다. 

o  FIFOs
   - FIFO는 First In First Out. 즉 먼저 들어온 것이 먼저 나간다는 뜻의 영문 약자이다.
     유닉스 FIFO는 파이프와 비슷한데 , 데이타의 일방 흐름을 제공하고, 그곳 FIFO에 첫번째 쓰인 바이트가
     그것으로부터 읽어 낼수 있는 첫번째 바이트이다.
     그러나 파이프와는 달리 FIFO는 그것 자체에 이름이 붙어 있어 서로 무관한 프로세스들이 하나의
     FIFO를 사용할 수 있도록 해준다.
     따라서 지명 파이프(named pipe)라고 부르기도 한다.
     FIFO는 System V에서 제공하고, 4.3BSD에서는 제공하지 않는다.
     
o  연속처리와 소식(Streams and Messages)
   - 지금까지 보아왔던 파이프와 FIFO들의 예는 유닉스에 적합한 연속 처리 입출력 모델을 사용하고 있다.
     여기에는 레코드의 경계가 없으며, 따라서 읽기와 쓰기 과정에서 데이타를 검사할 필요가 없다.
     예를 들어 파이프로부터 100바이트를 읽는 프로세스는 그 파이프에 데이타를 적었던 프로세스가 한번에
     100 바이트를 적었는지, 아니면 20 바이트씩 다섯번을 썼는지, 혹은 50바이트씩 두번을 썼는 지 
     알수 없다. 또한 한 프로세스가 파이프에 55 바이트를 적고 다른 프로세스가 45바이트를 쓴 경우도
     있을 수 있다. 여기에서 데이타는 시스템에 의해 아무런 해석이 가해지지 않는 단순한 바이트의 연속일
     뿐이다. 만약 어떤 해석이 요구된다면, 쓰기 프로세스와 읽기 프로세스는 그 해석에 동의해야 하고, 자기 
     자기 자신들이 알아서 해야 한다.
     그러나 프로세스가 전달하는 데이타에 어떤 구조를 부여하고자 할 때가 있다. 
     데이타가 가변 길이의 소식으로 이루어진 경우가 대표적인 예로, 이 경우 읽는자로 하여금, 소식의 경계가
     어느 곳인지를 알게 하여 하나의 단위 소식만을 읽어 가도록 해야 할 필요가 있다.
     IPC 기능을 바탕으로 한 연속 처리 위에 소식 구조를 부여할 필요가 있는 많은 유닉스 프로세스들은 각
     소식을 분리하기 위해 줄바꿈 문자(newline character)를 사용하고 있다.
     쓰기 프로세스는 각 소식에 줄바꿈 문자를 첨가하고 읽기 프로세스는 IPC의 채널로부터 한번에 한 줄씩
     읽어 온다. 
     또한 파이프나 FIFO의 읽기 쓰기를 위해 표준 입출력 모음을 사용할 수 있다.
     파이프를 열기 위해서는 시스템 호출 pipe를 사용하는 방법뿐이고, 이때 화일 지정 번호가 들아오므로
     표준 입출력함수 fdopen은 반드시 표준 입출력 연속 처리와 열린 화일 지정번호와 결부시켜 
     사용하여야만 한다. 
     FIFO는 이름을 자지므로 표준 입출력 fopen을 사용하여 열 수 있다.
     
o  이름 영역 (Name Spaces)
   - 파이프는 이름을 갖고 있지 않지만 ,FIFO는 자신을 식별할 수 있도록 유닉스 경로명을 갖는다.
     다음절에서 IPC의 다른 형태를 다룰 때는, 추가로 이름을 붙이는 여러가지 관례를 사용한다.
     IPC의 주어진 형태에 대해 사용가능한 이름의 집합을 이름 영역이라 한다.
     평이한 파이프가 아닌 IPC의 모든 형태에서 소식을 "교환"하기 위해 어떻게 고객과 일꾼이 연결되는 지를
     이름이 규정하므로 이름 영역은 중요하다. 
     이 장에서 우리가 다룰 시스템의 프로세스사이에 대한 IPC방법을 기술할 때는 사용 가능한 이름 영역에서
     이름을 선택하는 것이 중요해 진다.
     다음은 IPC의 여러 가지 형태에서 사용하는 이름의 관례를 요약한 것이다. 마지막 열은 프로세스가 어떻게
     특정 형태의 IPC에 접근하는가를 규정하고 있다.

                   IPC를 위한 이름 공간
          -------------------------------------------------------------------- 
          |    IPC type            |  Name  space      |  Identification     | 
          -------------------------------------------------------------------- 
          |  pipe                  |  (no name)        |  file descriptor    |
          |  FIFO                  |  pathname         |  file descriptor    |
          |  message queue         |  key_t key        |     identifier      |
          |  shared memory         |  key_t key        |     identifier      |
          |  semaphore             |  key_t key        |     identifier      |
          |  socket-Unix domain    |  pathname         |  file descriptor    |
          |  socket-other domains  |(domain dependent) |  file descriptor    |
          --------------------------------------------------------------------
        
o  key_t 열쇠(keys)
   - System V 표준 C모듬에서 제공하는 함수 ftok은 경로명과 과제 식별자를 System V IPC열쇠로 바꾸어 준다.
     (어떤 이유인지는 모르지만, 이 함수는 실제 자신의 이름보다는 stdipc라는 이름으로 System V의 사용
     설명서에 표시되어 있다) 이 System V IPC열쇠들은 다음 절들에서 설명할 소식 대롱, 공유기억 장소
     그리고 차단 표시기를 식별하는데에 사용한다.
     
            #include
            #include

            key_t ftok(char *pathname, char proj);

     화일 에서 전형적인 32비트의 정수형인 key_t의 데이타형을 정의하고 있다.
     IPC 응용 프로그램에서 일꾼과 고객은 모두 그 응용 프로그램에 특별한 의미를 지니는 단일 경로명에
     일치해야 한다. 그 경로명은 일꾼 파수꾼의 경로명일수도 있고, 일꾼이 사용하는 일반 데이타화일의 
     경로명 또는 시스템 내의 어떤 경로명일수도 있다.
     만약 고객과 일꾼이 그들 사이에 하나의 IPC 채널만을 사용한다면, proj=1을 사용할 수 있다.
     다수의 IPC채널이 필요하면, 예를 들어 고객으로부터 일꾼으로 가는 채널 하나와 일꾼으로부터 고객으로 가는
     다른 하나의 채널이 필요한 경우, 한 채널은 proj=1, 다른 채널은 proj=2를 사용할 수 있다.
     한번 pathname과 proj가 고객과 일꾼에 의해 일치되면 이들은 같은 IPC 열쇠로 변환하기 위해 함수
     ftok을 호출할 수 있다.
     pathname이 존재하지 않거나, 호출 프로세스가 그 pathname에 접근할 수 없을 때, ftok은 -1을 돌려 준다.
     자신의 pathname이 열쇠를 만드는데 사용된 화일은 존재하는 동안에는 일꾼에 의해서 만들어지거나
     제거될 수 없는데, 이는 화일이 만들어질때마다 새로운 i-node번호를 가정하므로 다음 호출자에게
     ftok이 돌려 주는 열쇠가 다를 수 있기 때문이다.
     ftok에 대한 독립 변수 proj는 8비트의 문자이지, 문자열에 대한 지시자가 아님을 명심하라.
     일반적으로 함수 ftok은 다음과 같이 구현한다. 함수 ftok은 8비트의 proj값을 지정된 pathname에 
     해당하는 디스크 화일의 i-node번호 값과 디스크 화일이 위치하는 화일 시스템의 부기기 번호를 조합한다.
     이 세 값의 조합이 32비트의 열쇠를 만들어내는 것이다.
     화일이 존재하는 화일 시스템 내에서의 화일의 i-node 번호를 (단일 디스크 제어기의 디스크 분할에 
     해당하는)화일 시스템의 부기기 번호와 같이 사용하므로써 이 함수는 고유한 열쇠 값을 만들어낼려고 한다.
     시스템내의 가능한 모든 경로명에 대해 고유한 열쇠값을 보장하기 위해 32비트의 i-node 번호와 8비트의
     주기기 번호, 그리고 8비트의 부기기 번호를 조합할 필요가 있다.
     ftok이 사용하는 알고리즘은 서로 다른 두 경로명이 같은 키이 값을 만들어 내는 확률은 0은 아니지만
     매우 작은 타협안이다. 실제로 i-node번호가 16비트보다 큰 경우는 거의 없으므로 서로 다른 두 경로명이
     같은 열쇠를 만들어낼려면 두 화일이 같은 i-node번호와 같은 부기기 번호를 가지면 되고 주기기 번호는
     관계가 없다.

o  System V IPC
   - 다음 세가지 형태의 IPC를 총칭하여 "System V IPC"라고 한다.
       . 소식 대롱(message queues)
       . 차단 표시기(semaphores)
       . 공유기억 장소(shared memory)
     이들은 이들을 이용하는 시스템 호출 관점에서 많은 유사점을 가지고 있고, 또 알맹이가 유지하는 정보의
     관점에서도 매우 비슷하다. 이들의 시스템 호출은 다음과 같이 요약할 수 있다.

         ------------------------------------------------------------------------------------------
         |                                      |  Message queue   |  Semaphore  |  Shared memory | 
         ------------------------------------------------------------------------------------------
         |  include file                        |       |  |     |
         |  system call to create or open       |     msgget       |   semget    |    shmget      |
         |  system call for control operations  |     msgctl       |   semctl    |    shmctl      |
         |  system calls for IPC operations     |     msgsnd       |   semop     |    shmat       |
         |                                      |     msgrcv       |             |    shmdt       | 
         ------------------------------------------------------------------------------------------
    
     알맹이는 각 IPC채널에 대해서도 화일을 다루기 위한 정보와 비슷한 정보의 구조를 갖는다.
              struct ipc_perm {
                 ushort  uid;    /* owner's user id */
                 ushort  gid;    /* owner's group id */
                 ushort  cuid;   /* creator's user id */
                 ushort  cgid;   /* creator's group id */
                 ushort  mode;   /* access modes */
                 ushort  seq;    /* slot usage sequence number */
                 key_t   key;    /* key */
              };

     이 구조는 System V IPC 시스템 호출에서 사용하는 명백한 상수들과 함께 에 정의되어 있다.
     이 구조를 사용하고 또 변경하기 위해서는 세가지의 ctl 시스템 호출을 사용한다.
     세개의 시스템 호출 get은 하나의 IPC 채널을 만들거나 열기위해 사용되며 모두 key_t형의 열쇠 값을
     취하고 정수형 식별자를 되돌려 준다. 이의 단계별 순서는 아래에 표시하였다.

                 < ftok를 이용한 IPC id 를 만드는 과정 >
                     ------------              ------------
        char *path   |  ftok()  |   key_t key  | msgget() |   int id
      -------------> |          |  ----------> | semget() | --------->
        char proj    ------------              | shmget() |
                                               ------------
                 
     이 세개의 get 시스템 호출은 모두가 IPC채널에 대한 mode의 하위 순서 9비트를 지정하고, 새로운 IPC
     채널을 만들지, 또는 이미 존재하는 채널을 참조할 것인지를 지정하기 위해 독립 변수 flag를 취한다.
     새로운 IPC채널을 만들지, 또는 이미 존재하는 채널을 참조하고 있는지에 대한 규칙은 다음과 같다.

       . IPC_PRIVATE의 열쇠 값을 지정하면, 고유한 IPC채널이 만들어짐이 보장된다.
         ftok으로 하여금 IPC_PRIVATE의 열쇠 값을 만들어 내도록 할 수 있는 pathname과 proj의 조합은 없다.
       . flag낱말의 IPC_CREAT비트를 1로 하면 지정된 열쇠에 대하여 항목이 존재하지 않는경우 
         새로운 항목을 만들어 준다. 이미 항목이 존재하고 있다면, 이 항목은 되돌아 온다.
       . flag낱말의 IPC_CREAT와 IPC_EXCL 비트를 모두 1로하면 만들고자 하는 항목이 존재하지 않는 경우에
         한해 지정된 열쇠에 대한 새로운 항목을 만들어 준다. 만약, IPC채널이 이미 존재하고 있으면
         에러가 발생한다.
         IPC_EXCL비트는 호출자가 IPC채널을 배타적으로 접근하도록 보장해주지는 않는다는데에 유의해야 한다.
         이것의 기능은 새로운 IPC채널이 만들어지거나, 또는 그렇지 않은 경우 에러가 돌려짐을 보장할 뿐이다.
         심지어는 IPC_CREAT와 IPC_EXCL 모두를 지정하고 새로운 IPC 채널을 만드는 경우에도 만약 다른
         사용자들이 그 채널의 mode 낱말의 하위 9비트에 적절한 허가를 갖고 있으면, 역시 그 채널에 
         접근할 수 있다.
       . IPC_CREAT비트를 1로 하지 않고 IPC_EXCL만을1로 하는 경우는 아무런 의미가 없다.
 
                  < IPC 채널을 열거나 만들 때의 논리 >

           --------------------------------------------------------------------------
           |  flag  argument        |  key 존재안할때        |  key가 이미 존재할때 |
           --------------------------------------------------------------------------
           |  no special flags      | error,errno=ENOENT     |         OK           |
           |     IPC_CREAT          | OK. creates new entry  |         OK           |
           |  IPC_CREAT | IPC_EXCL  | OK. creates new entry  |  error, errno=EEXIST | 
           --------------------------------------------------------------------------

      위에서 가운데 줄에서 IPC_CREAT표시기가 IPC_EXCL없이 사용되는 경우, 새로운 항목이 만들어 졌는지 또는
      현재 존재하고 있는 항목을 참조하고 있는지를 알 수 있는 방법은 없다는 것에 주의하자.
      IPC_CREAT표시기와 함께 시스템 호출 get중 하나를 사용하여 새로운 IPC 채널을 만들 때마다 독립변수
      flag의 하위 9비트는 ipc_perm구조내의 mode 낱말을 초기화한다.
      부가적으로 cuid와 cgid부분도 호출 프로세스의 유효 사용자 ID와 유효 집단 ID를 각각 갖게 된다.
      ipc_perm구조내의 uid와 gid또한 호출 프로세스의 유효 사용자 ID와 유효 집단 ID를 각각 갖게 된다.
      이 두가지 ID는 "소유자 ID(owner ID)"라고 하는 반면 cuid와 cgid는 "생성자 ID(creator ID)"라고
      한다. 이 생성자 ID는 절대로 변하지 않지만, 프로세스가 소유자 ID를 변경하는 것은 IPC장치에 
      대한 ctl시스템 호출(msgctl, semctl, 또는 shmctl)을 호출함으로써 가능하다.
      이 세가지 시스템 호출 ctl은 한 프로세스가 IPC 채널의 mode 낱말의 하위 9비트를 바꾸는 것에 허용한다.
      어떤 프로세스가 IPC채널을 접근할 때마다 다음 두가지 수준의 검사가 이루어진다.

      1. 한 프로세스가 get 시스템 호출 중의 하나를 이용하여 현재 존재하는 IPC채널에 접근할 때마다
         호출자의 flag 독립 변수가 mode낱말에 없는 접근 비트를 지정하지 않았는가를 초기에 검사한다.
         예를 들어 한 일꾼 프로세스가 집단 읽기와 기타 읽기 허가 비트를 0으로 입력 소식 대롱에 대한
         mode낱말을 정할 수가 있다. 이러한 비트들을 1로 하는 flag독립변수를 가진 프로세스들에게는
         시스템 호출 msgget으로부터 에러가 돌아 온다. 
         그러나 시스템 호출 get에 의해서 이루어지는 이러한 검사들은 별로 사용되지 않는다.
         즉 호출자가 자신이 어느 허가 범위 - 소유자,집단, 기타 - 에 속하는지 알고 있음을 의미한다.
         만약 생성자가 특정 허가 비트를 끄고 , 호출자가 이 비트를 지정하고 있다면 시스템 호출 get에 의해
         에러가 감지된다. 그러나 어떤 프로세스든지 IPC 채널이 이미 존재하고 있음을 알고 있다면 0의
         flag 독립변수를 지정해 놓음으로써 이러한 검사를 전적으로 피할 수 있다.
      2. 모든 IPC 연산은 그 연산을 사용하는 프로세스에 대해서 허가 사항 검사를 한다.
         예를 들어 프로세스가 시스템 호출 msgsnd로 소식 대롱에 소식을 넣고자 할 때마다 화일 시스템 접근에서
         수행하는 것과 같은 검사를 하게 된다.
            . 책임 사용자는 항상 접근이 허가된다.
            . 만약 유효 사용자 ID가 그 IPC채널에 대한 uid값 또는 cuid 값과 같고 그 IPC채널에 대한 
              mode 낱말이 적절한 접근 허가 비트를 갖고 있다면, 접근은 허용된다.
              "적절한 접근허가 비트"라 함은, 호출자가 IPC채널에 읽기 연산의 수행을 원하는 경우
              (예를 들어 소식 대롱으로부터 소식을 받고 싶을 때)읽기 비트가 1로 되어 있어야 하고,
               쓰기 연산을 위해서는 쓰기 비트가 1로 되어 있어야 함을 의미한다.
            . 유효 집단 ID가 그 IPC채널의 gid 값 또는 cgid값 중의 하나와 같고 적절한 접근 허가 비트가
              그 IPC채널의 mode낱말에 표시되어 있으면, 접근이 허가된다.
            . 위의 모든 검사에서 '성공'이 아닌 경우에는 적절한 허가가 가능하려면 IPC 채널의 mode 낱말내의
              기타 접근 허가 비트가 1이어야만 한다.

      ipc_perm 구조에서는 또한 홈 사용 일련 번호(slot usage sequence number)인 seq라는 이름의 변수를 
      포함하고 있다.이것은 시스템 내에서 가능한 모든 IPC채널에 대해 알맹이가 유지하는 계수기이다.
      IPC채널이 닫힐 때마다 알맹이는 이 홈 번호를 증가시키고 이것이 넘치면 0으로 돌아가게 한다.
      이 계수기가 필요한 이유는 여러가지인데 우선 열린 화일에 대해 알맹이가 유지하는 화일 지정 번호들을
      생각해 보자. 
      이들은 작은 정수이고, 한 프로세스 내에서만 의미를 가지므로 , 이들은 프로세스에 한정된 번호이다.
      예를 들어 어떤 프로세스에서 화일 지정 번호 4로부터 읽기를 원한다면 이 연산은 그 프로세스가 이 화일
      지정 번호로 열어 놓은 화일이 있는 경우에만 허용된다.
      다른 무관한 프로세스가 화일 지정 번호4로 열어 놓은 화일에 대해서는 아무런 영향도 미치지 못한다.
      그러나 IPC 채널은 전시스템적(systemwide)이고 특정 프로세스에 한정된 것이 아니다.
      시스템 호출 get(msgget,semget,shmget)중의 하나로부터(화일 지정번호와 비슷한)IPC 식별자를 
      얻을 수 있다.
      그러나, 정수형인 이 식별자들은 시스템 내의 모든 프로세스들에 대해서 의미를 갖는다.
      예를 들어 고객과 일꾼 같이 서로 연계되어 있지 않은 두 프로세스가 하나의 소식 대롱을 사용할 때에
      시스템 호출 msgget에서 돌아오는 소식 대롱 식별자는 두 프로세스에서 같은 정수값을 갖는다. 
      이것은 현재 기타-읽기 접근이 허가된 채 사용중인 소식대롱에 대해서, 비합리적인 프로세스들이 작은
      정수형 식별자를 사용하여 소식을 읽을 수도 있음을 의미한다. 이러한 식별자들에 대해서 사용 가능한 
      값들이 화일 지정 번호들처럼 작은 값들이라면 해당 식별자를 발견할 확률이 1/50정도가 될 것이다.
      이러한 잠재적인 문제를 피하기위해 System V IPC기능의 설계자들은 식별자 값의 가능한 범위를
      작은 정수가 아니라 모든 정수를 포함하도록 확장시켰다. 이것은 IPC 테이블 항목이 다시 사용될
      때마다 호출 프로세스로 돌아오는 식별자 값을 IPC표 항목 수만큼 증가시킴으로써 구현한다.
      예를 들어 만약 시스템이 최대 50개의 소식대롱을 가질수 있도록 구성되어 있다면 처음에는 알맹이의
      첫번째 소식 대롱 표 항목이 사용되고 , 그 프로세스로 돌아오는 식별자 값은 0이다.
      이 소식 대롱이 제거된후 이 첫번째 표 항목을 다시 사용하면 돌아 오는 식별자는 50이다.
      그 후에는 식별자가 100이 되고, 계속 이런 식으로 증가한다. seq는 unsigned short 정수형이므로
      이 seq는 테이블 엔트리가 65,535번 사용된 후 순환을 형성한다.
      이러한 유형의 예로 다음 프로그램을 살펴보면 이 프로그램은 msgget에서 돌아오는 처음 10개의
      식별자값을 인쇄한다.

           include 
           #include 
           #include 
           #define KEY ((key_t) 98765L)
           #define PERMS 0666
           
           main()
           {
               int i,msqid;
               for (i=0;i<10;i++) {
                   if ((msqid=msgget(KEY,PERMS|IPC_CREAT)) < 0)
                         printf("cat't create message queue\n");
                   printf("msqid = %d\n",msqid);
                   if (msgctl(msqid,IPC_RMID, (struct msqid_ds *) 0)<0)
                        printf("can't remove message queue");
               }
           }
 
           # a.out
             msqid =0 
             msqid =50 
             msqid =100 
             msqid =150 
             msqid =200 
             msqid =250 
             msqid =300 
             msqid =350 
             msqid =400 
             msqid =450 
 

o  소식 대롱
   - 프로세스들간의 소식 전달의 몇가지 형태는 현재 많은 운영체제의 일부분이 되었다. 
     몇몇 운영체제에서는 한 프로세스가 다른 특정한 프로세스에게만 소식을 보낼 수 있도록 소식 전달을
     제어한다. 그러나 System V에는 이러한 제한이 없다.
     System V의 소식 구현에서는 모든 소식은 알맹이 속에 저장되며, 이와 관련된 소식 대롱 식별자
     (message queue identifier)를 가진다. 이 식별자를 msqid라 부르며 소식의 특정한 대롱을 가리킨다.
     프로세스들은 임의의 대롱에 소식을 읽고 쓸 수 있다.
     어떤 프로세스가 대롱에 소식을 쓰기전에 이 소식이 도착하기를 기다리는 프로세스가 존재해야 할
     필요는 없다. 이러한 점은 파이프나 FIFO와는 대조적이라 할 수 있는데, 이러한 것들은 읽는 프로세스가
     존재하지 않는 경우에는 쓰는 프로세스는 의미가 없기 때문이다.         
     어떤 프로세스가 대롱에 몇가지 소식을 쓰고 난 후에 종료해 버리고, 나중에 다른 프로세스가 그 소식을 
     읽는 것이 가능하다.
     대롱내의 모든 소식은 다음과 같은 속성을 가진다.

       . 긴정수형(long integertype)
       . 소식중의 데이타 부분의 길이(0이 될수도 있음)
       . 데이타(만약 길이가 0보다 크다면)

     시스템내의 각 소식 대롱에 대해서 알맹이는 다음과 같은 정보 구조를 유지한다.

       #include
       #include 
       struct msqid_ds {
               struct ipc_perm msg_perm;       /* operation permission struct */
               struct msg      *msg_first;     /* ptr to first message on q */
               struct msg      *msg_last;      /* ptr to last message on q */
               ushort          msg_cbytes;     /* current # bytes on q */
               ushort          msg_qnum;       /* # of messages on q */
               ushort          msg_qbytes;     /* max # of bytes on q */
               ushort          msg_lspid;      /* pid of last msgsnd */
               ushort          msg_lrpid;      /* pid of last msgrcv */
               time_t          msg_stime;      /* last msgsnd time */
               time_t          msg_rtime;      /* last msgrcv time */
               time_t          msg_ctime;      /* last change time */
       };

     ipc_perm은 특정한 소식 대롱에 대한 접근 허가 사항들을 가지고 있다.
     msg 구조는 알맹이에 의해 사용되는 내부 데이타 구조로서 특정한 대롱에 대해 소식들의 
     연결 목록(linked list)을 유지한다.
     
             page 123 그림 3.15 참조

     그림처럼 알맹이 속의 특정 소식 대롱을 소식들의 연결 목록으로서 그릴 수 있다.
     대롱에는 각각의 길이가 1,2,그리고 3바이트인 3개의 소식이 있고 소식들이 열거된 순서대로
     쓰여진다고 하자.
     또 , 이러한 세개의 소식들은 각각 100,200,그리고 300의 type을 갖고 있다고 가정한다.
     msgget 시스템 호출로 새로운 소식 대롱을 만들거나 기존의 소식 대로에 접근할 수 있다.

         #include 
         #include 
         #include 
         
         int msgget(key_t key, int msgflag);

     msgflag의 값은 다음에 있는 상수들의 조합이 된다.

             -------------------------------------------------------
             | Numeric  |  Symbolic        |  Description          |
             -------------------------------------------------------
             |  0400    |  MSG_R           | Read by owner         |
             |  0200    |  MSG_W           | Write by owner        |
             |  0040    |  MSG_R >> 3      | Read by group         |
             |  0020    |  MSG_W >> 3      | Write by group        |
             |  0004    |  MSG_R >> 6      | Read by world         |
             |  0002    |  MSG_W >> 6      | Write by world        |
             |          |  IPC_CREAT       |  앞장 참조            |
             |          |  IPC_EXCL        |  앞장 참조            |
             -------------------------------------------------------
     msgget에 돌아오는 값은 소식 대롱 식별자인 msqid이지만, 만약 에러가 발생했다면 -1 값이 돌아온다.
     msgget으로 소식 대롱을 한번 열면, msgsnd 시스템 호출을 사용하여 대롱에 소식 하나를 넣을 수 있다.
     
          #include 
          #include 
          #include 

          int msgsnd(int msqid, struct msgbuf *ptr, int length, int flag );

     독립변수 prt은 다음과 같은 모양을 갖는 구조에 대한 지시자이다.(이것은 에 정의되어 있다.)
     
          struct msgbuf {
            long mtype;    /* message type, must be > 0 */
            char mtext[1]; /* message data */

     mtext는 소식의 데이타 부분이다. 이 부분은 문자만을 위한 것이 아니므로 이름이 잘못되었다.
     데이타의 형태로는 이진 데이타나 문자 어느 것이나 가능하다. 0의 소식형은 msgrcv 시스템호출에서
     특별한 표식으로 사용되므로 소식 형은 0보다 커야 한다.  
     msgsnd의 length 독립 변수는 바이트 단위로 소식의 길이를 나타낸다. 이것은 long integer 소식형
     뒤에 오는 사용자 정의 데이타의 길이다. 이 길이는 0이 될 수 있다.
     flag 독립 변수는 IPC_NOWAIT나 0으로 지정할 수 있다. IPC_NOWAIT값은 소식 대롱에 새로운 소식을
     위한 여유 공간이 없을 경우 즉시 시스템 호출에서 돌아오도록 한다. 이러한 상항은 특정한 대롱에
     너무 많은 소식이 있거나 시스템 전체에 너무 많은 소식이 있을 경우에 일어난다.
     만약에 소식을 위해 필요한 공간이 없고 IPC_NOWAIT가 명시되어 있다면 msgsnd는 -1을 돌려주고
     errno는 EAGAIN을 갖게 된다. 만약 시스템 호출이 성공한다면 msgsnd는 0을 돌려 준다.
     msgrcv 시스템 호출을 사용하여 소식 대롱에서 소식을 읽을 수 있다. 

           #include 
           #include 
           #include 

           int msgrcv(int msqid, struct msgbuf *ptr, int length, long msgtype, int flag);

     ptr 독립 변수는 msgsnd에서와 비슷하며 받은 소식을 저장할 장소를 명시한다.
     len는 ptr이 지시하는 구조의 데이타 부분의 크기를 명시한다.  
     이것은 시스템 호출에서 복귀하는 데이타의 최대양이다. 만약 flag 독립 변수에서 MSG_NOERROR
     비트가 설정되면 이것은 받은 소식의 실제 데이타 부분이 length보다 크다면 즉시 데이타 부분을
     자르고 에러없이 복귀한다는 것을 나타낸다.
     반대로 MSG_NOERROR표시기를 명시하지 않고 length가 전체 소식을 받을 수 있을 정도로 충분히 크지
     않다면 에러가 일어나는 원인이 된다.
     긴정수인 msgtype 독립변수는 대롱으로부터 어떤 소식을 요구하는지를 명시한다. 
     
          . msgtype이 0이라면, 대롱의 첫번째 소식을 받는다. 각 소식 대롱은 먼저 들어오는 것이 먼저
            나가도록 유지되므로 msgtype이 0이라는 것은 대롱에서 가장 오래된 소식이 주어진다는 것을 
            뜻한다.
          . msgtype이 0보다 크다면, msgtype과 동일한 형을 갖는 첫번째 소식을 받는다.
          . msgtype이 0보다 작다면, msgtype의 절대치보다 같거나 작은 형중에서 가장 작은 형을 갖는
            첫번째 소식을 받는다.

     msgtype의 여러가지 값에 대해 받을 소식을 보여준다.

             ------------------------------------------
             | msgtype  |   Type of message returned  |
             ------------------------------------------
             |     0L   |           100               |
             |   100L   |           100               |
             |   200L   |           200               |
             |   300L   |           300               |
             |  -100L   |           100               |
             |  -200L   |           100               |
             |  -300L   |           100               |
             ------------------------------------------   

     flag 독립 변수는 요구된 형의 소식이 대롱에 없을 경우에 어떻게 할 것인지를 명시한다.
     만약 IPC_NOWAIT비트가 설정되었다면, msgrcv 시스템 호출은 소식이 없을 경우에 즉시 돌아온다.
     이 경우에 시스템 호출은 errno를 ENOMSG로 설정하고 -1을 돌려 준다.
     그렇지 않다면, 호출한 프로세스는 다음중의 어떤 하나가 일어날 때까지 지연된다.

          . 요구된 형의 소식이 도착한다.
          . 소식 대롱이 시스템에서 제거된다.
          . 프로세스가 신호를 받아 포착한다.

     msgrcv는 성공적인 호출에 대해서는 받은 소식의 데이타 바이트 수를 돌려준다. 이것은 ptr독립 변수를
     통해 복귀하는 소식의 크기(long integer)를 포함하지 않는다.

     msgctl 시스템 호출은 소식 대롱에 대한 다양한 제어 기능을 제공한다.

          #include 
          #include 
          #include 

          int msgctl(int msqid, int cmd, struct msqid_ds *buff);

     IPC_RMID를 갖는 cmd는 단지 시스템에서 소식 대롱을 제거하는 것으로 여기서는 이것만 사용한다.

o  공유 기억 장소 (Shared Memory)
   - 고객-일꾼 화일 복제 프로그램에 사용되는 통상적인 몇가지 단계를 고려해 보자.
     . 일꾼이 입력 화일에서 읽는다. 이러한 동작은 보통 알맹이가 데이타를 자신의 내부 덩이 완충영역
       (block buffer)중의 하나로 읽어들이고 이것을 일꾼의 완충 영역(시스템 호출 read의 두번째 독립 변수)
       으로 복제하는 두가지 동작으로 이루어진다. 대부분의 유닉스 시스템은 일꾼에 의해 행해지는 것과
       같은 순차 읽기를 감지하면, read 요구에 앞서 한덩이를 미리 읽으려고 한다.
       이것을 화일을 복제하는데 걸리는 시간을 단축시키지만 모든 read에 대한 데이타가 알맹이에 의해
       덩이 완충 영역에서 호출한 프로세스의 완충 영역으로 복제되는 것과 관계가 없다.
     . 일꾼은 이 장에서 설명된 파이프, FIFO, 소식 대롱등의 기법중에 하나를 사용하여 이러한 데이타를 
       소식 안에 쓸 수 있다. 이러한 세가지 IPC 형태의 어는 것이나 데이타가 사용자의 완충 영역에서
       알맹이로 복제되어야 한다.
     . 고객은 IPC채널에서 데이타를 읽는데 이때, 다시 데이타를 알맹이의 IPC 완충 영역에서 고객의
       완충 영역으로 복제하여야 한다.
     . 마지막으로 데이타는 시스템 호출 write의 두번째 독립 변수인 고객의 완충 영역에서 출력화일로
       복제된다. 이 시스템 호출은 데이타를 바로 알맹이 완충 영역으로 복제하고 복귀하며,
       이후 아무때나 알맹이는 기기에 실제적인 쓰기를 한다.
     전체적으로 데이타를 네번 복제하는 것이 필요하다. 이러한 네번의 복제는 알맹이와 사용자 프로세스간에서
     행해진다. 이러한 복제를 문맥간 복제(intercontext copy)라고 한다.
     대부분의 유닉스 구현에는 가능한한 복제를 빠르게 하고자 하지만 여전히 많은 비용이 요구된다.
     다음은 두 프로세스사이에서 이러한 데이타의 이동을 그림으로 보여준다.

                   ---------------                 ---------------
                   |   client    |                 |  server     |  
                   ---------------                 ---------------
                       \    ↖                             /  ↗ 
                        \     \                           /  /
                         \ ----------------------------------
                          \|   \    -----------------   /  / |
                           |\   \   |  FIFO, pipe   |  /  /  |
        output             | \   -- |  or message   |<-   ---|--------   input
        file   <-----------|--      -----------------        |           file
                           -----------------------------------
                                         kernel

                        < 고객과 일꾼사이에서의 전형적인 데이타 이동 >

      이러한 IPC형태(파이프, FIFO, 소식대롱)와 관련된 문제는 두 프로세스가 정보를 교환하기 위해서
      정보가 알맹이를 거쳐가야 한다는 것이다. 
      공유 기억 장소는 두개 이상의 프로세스가 기억 장소 조각을 공유하도록 해서 이러한 문제를 해결해 준다.
      물론 다수의 프로세스가 기억 장소의 일부를 공유하기 위해서는 다수의 프로세스들이 그들 사이에서
      기억 장소의 사용에 대해 협조해야 하는 문제가 있다.
      (기억 장소의 일부분을 공유하는 것은 화일 잠금 예에서 사용되었던 순차 번호 화일처럼 디스크 화일을
       공유하는 것과 비슷하다)
      예를 들면 , 한 프로세스가 공유 기억 장소를 읽는다면 , 다른 프로세스는 데이타를 처리하기 전에
      읽기가 끝나기를 기다려야 한다. 다행히도, 이것은 이것은 동기화를 위한 차단 표시기를 사용하면
      쉽게 해결할 수 있다.
      이제 고객-일꾼 예에서의 단계들은 다음과 같이 된다.
        . 일꾼은 차단 표시기를 사용하여 공유 기억 장소 조각으로 접근한다.
        . 일꾼은 입력 화일을 공유 기억 장소 조각으로 읽어들인다. 읽어들일 주소(시스템 호출 read의
          두번째 독립 변수)는 공유 기억 장소를 가리킨다.
        . 읽기가 완료되면 일꾼은 다시 차단 표시기를 사용하여 고객에게 알린다.
        . 고객은 공유 기억 장소 조각에서 출력 화일로 데이타를 쓴다.
 
                   ────────       --------------------       ─────────
                  ┃  client      ┃<---> |  shared memory   |<---> ┃   server       ┃
                   ────────       --------------------       ─────────
                                              /          ↖ 
                                             /             \
                                   ───────────────── 
                     output       ┃       /                 \      ┃       input 
                      file <------┃-------                   ------┃-----   file 
                                   ───────────────── 
                                                 kernel 
                             
                          < 공유 기억 장소를 이용한 고객과 일꾼간의 데이타 이동 > 

        이 그림에서 데이타는 단지 두번만 복제되면 된다. 즉, 입력 화일에서 공유 기억 장소로 그리고
        공유 기억 장소에서 출력 화일로 복제된다. 이러한 두번의 복제는 앞에서 언급된 것처럼 알맹이의
        덩이 완충 영역도 포함된다.
        모든 공유 기억 장소 조각에 대해서 알맹이는 다음과 같은 정보 구조를 유지한다.

            #include 
            #include 

            struct shmid_ds {
            struct ipc_perm shm_perm;       /* operation permission struct */
            uint            shm_segsz;      /* size of segment in bytes */
            struct XXX     shm_YYY;        /* implementation dependent info */      
            ushort          shm_lpid;       /* pid of last shmop */
            ushort          shm_cpid;       /* pid of creator */
            ushort          shm_nattch;     /* number of current attaches */
            time_t          shm_atime;      /* last shmat time */
            time_t          shm_dtime;      /* last shmdt time */
            time_t          shm_ctime;      /* last change time */
             };

         ipc_perm 구조는 앞에서 설명한 것처럼 공유 기억 장소 조각에 대한 접근 허가사항을 담고 있다.
         소식대롱이나 차단 표시기와는 다르게, 굳은모와 구현에 직접적으로 관계가 있으므로, 공유 기억 장소
         조각을 가리키기 위해 알맹이가 사용하는 실제 데이타 구조를 설명할 수 없다.
         위의 구조에서 표기 XXX 와 YYY는 이 사실을 나타내기 위하여 사용된 것이다.
         시스템 호출 shmget을 사용하여 공유 기억장소 조각을 생성하거나 기존의 것에 접근할 수 있다.
      
             #include 
             #include 
             #incldue 

             int shmget (key_t key, int size, int shmflag); 

          shmget이 돌려주는 값은 공유 기억 장소 식별자인 shmid이고 만약 에러가 일어나게 되면 -1을 돌려준다.
          size 독립 변수는 바이트 단위로 조각의 크기를 나타낸다. 
          shmflag 독립변수는 다음 그림과 같은 상수들의 조합이다.

             -------------------------------------------------------
             | Numeric  |  Symbolic        |  Description          |
             -------------------------------------------------------
             |  0400    |  SHM_R           | Read by owner         |
             |  0200    |  SHM_W           | Write by owner        |
             |  0040    |  SHM_R >> 3      | Read by group         |
             |  0020    |  SHM_W >> 3      | Write by group        |
             |  0004    |  SHM_R >> 6      | Read by world         |
             |  0002    |  SHM_W >> 6      | Write by world        |
             |          |  IPC_CREAT       |  앞장 참조            |
             |          |  IPC_EXCL        |  앞장 참조            |
             -------------------------------------------------------
                      < shmget 시스템 호출을 위한 shmflag값 > 

          shmget은 공유 기억 장소 조각을 생성하거나 열수는 있지만 호출 프로세스가 그조각으로 접근 할 수
          있도록 해주지는 않는다. 시스템 호출 shmat을 호출하여 공유 기억 장소 조각을 붙여야 한다.

              #include 
              #include 
              #include 
  
              char *shmat (int shmid, char *shmaddr, int shmflag);

          이 시스템 호출은 공유 기억 장소 조각의 시작 주소를 돌려 준다. 이 주소를 결정하는 규칙은 
          다음과 같다.
            . shmaddr 독립변수가 0이면, 시스템은 호출한 프로세스를 위한 주소를 선정한다.
            . shmaddr 독립변수가 0이 아니면, 돌려주는 주소를 호출 프로세스가 shmflag 독립변수로
              SHM_RND값을 명시했는지 여부에 따라 다르다.
              - 만약 SHM_RND값이 명시되지 않았다면, 공유 기억 장소 조각은 shmaddr 독립변수에 의해
                명시된 주소에 붙인다.
              - 만약 SHM_RND값이 명시되었다면, 공유기억 장소 조각은 shmaddr 독립 변수에 의해 명시된
                주소를 상수 SHMLBA(LBA sms  "lower bound address"의 약자이다)만큼 감소시킨 주소에
                붙인다.
          실제적인 목적에서는 이식 가능한 shmat에 대한 호출로써 shmaddr을 0으로 명시하고, 알맹이가
          주소를 선택하도록 한다.
          기본적으로는 공유 기억 장소 조각은 호출 프로세스가 읽고 쓸 수 있도록 부착한다.
          또한, "읽기 전용"접근을 명시하면서 SHM_RDONLY 값을 shmflag 독립변수에 명시할 수 있다.
          프로세스가 공유 기억 장소 조각의 사용을 끝나게 되면 시스템 호출 shmdt을 사용하여 조각을 
          분리한다. 
   
               #include 
               #include 
               #include 

               int shmdt (char *shmaddr);

          이 호출은 공유 기억 장소 조각을 없애지는 않는다. 공유 기억 장소 조각을 제거하기 위해서는 
          시스템 호출 shmctl을 사용한다.

               #include 
               #include 
               #include 

               int shmctl (int shmid, int cmd, struct shmid_ds *buf);

          cmd값으로 IPC_RMID를 명시하면 시스템에서 공유 기억 장소 조각을 제거한다.
 



Revision History

작성일자 : 96.06.17
작성자 : 이진수

수정일자 : 
수정자