Shell: 두 판 사이의 차이

youngwiki
편집 요약 없음
 
(같은 사용자의 중간 판 2개는 보이지 않습니다)
1번째 줄: 1번째 줄:
상위 문서: [[Signals and Nonlocal jumps]]
상위 문서: [[Exceptional Control Flow]]


==개요==
==개요==
6번째 줄: 6번째 줄:
* csh/tcsh : BSD Unix C 쉘
* csh/tcsh : BSD Unix C 쉘
* bash : Bourne-Again Shell (리눅스 기본 쉘)
* bash : Bourne-Again Shell (리눅스 기본 쉘)
Shell은 아용자의 명령어를 입력받고, 이를 실행시키는 식으로 작동한다.
Shell은 이용자의 명령어를 입력받고, 이를 실행시키는 식으로 작동한다.


==예시==
==예시==
40번째 줄: 40번째 줄:
      
      
     /* argv가 내장된 명령어인지 확인하고 내장된 명령어라면 내부에서 실행한다. */
     /* argv가 내장된 명령어인지 확인하고 내장된 명령어라면 내부에서 실행한다. */
     if (!builtin_command(argv)) { /* 내장된 명령어가 아닌 경우 */
     if (!builtin_command(argv)) { /* 내장된 명령어가 아닌 경우, 내장된 명령어면 builtin_command() 함수가 실행한다. */
         if ((pid = fork()) == 0) { /* 자식 프로세스를 생성한다. */
         if ((pid = fork()) == 0) { /* 자식 프로세스를 생성한다. */
             if (execve(argv[0], argv, environ) < 0) { /* 자식 프로세스 내에서 실제 명령어를 실행시킨다. */
             if (execve(argv[0], argv, environ) < 0) { /* 자식 프로세스 내에서 실제 명령어를 실행시킨다. */

2025년 4월 10일 (목) 16:42 기준 최신판

상위 문서: Exceptional Control Flow

개요

Shell이란 사용자 명령을 실행하는 애플리케이션 프로그램이다. 다음은 shell의 대표적인 예시이다.

  • sh : 오리지널 Unix 쉘 (Stephen Bourne, AT&T Bell Labs, 1977)
  • csh/tcsh : BSD Unix C 쉘
  • bash : Bourne-Again Shell (리눅스 기본 쉘)

Shell은 이용자의 명령어를 입력받고, 이를 실행시키는 식으로 작동한다.

예시

int main() {
    char cmdline[MAXLINE]; /* 명령어를 입력받을 버퍼이다. */
    while (1) {
        /* read */
        printf("> ");
        Fgets(cmdline, MAXLINE, stdin); /* 명령어를 입력받는다. */
        if (feof(stdin))
            exit(0);
        
        /* evaluate */
        eval(cmdline); /* 명령어를 실행시킨다 */
    }
}
void eval(char *cmdline) { /* cmdline은 사용자가 입력한 명령어 문자열이다. */
    char *argv[MAXARGS]; /* cmdline을 공백을 기준으로 쪼개어 저장하는 문자열 배열이다. */
    char buf[MAXLINE]; /* cmdline을 그대로 저장하는 버퍼 */
    int bg; /* 실행할 명령이 백그라운드 실행인지 여부를 나타내는 플래그이다. */
    pid_t pid; /* 새로 생성한 자식 프로세스의 PID를 저장 */
    
    strcpy(buf, cmdline); /* cmdline을 buf에 복사한다. */
    /* cmdline을 공백단위로 분리하여 이를 argv에 저장한다. 
    명령어 뒤에 &가 붙어있다면 백그라운드 실행이라는 뜻이므로 bg의 플래그를 1로 설정한다. */
    bg = parseline(buf, argv); 

    if (argv[0] == NULL) /* 엔터만 입력된 경우이면 아무런 작업도 하지 않는다. */
        return; /* Ignore empty lines */
    
    /* argv가 내장된 명령어인지 확인하고 내장된 명령어라면 내부에서 실행한다. */
    if (!builtin_command(argv)) { /* 내장된 명령어가 아닌 경우, 내장된 명령어면 builtin_command() 함수가 실행한다. */
        if ((pid = fork()) == 0) { /* 자식 프로세스를 생성한다. */
            if (execve(argv[0], argv, environ) < 0) { /* 자식 프로세스 내에서 실제 명령어를 실행시킨다. */
                printf("%s: Command not found.\n", argv[0]);
                exit(0);
            }
        }
       
        if (!bg) { /* foreground 실행 */
            int status;
            if (waitpid(pid, &status, 0) < 0) /* foreground 작업인 경우 부모 프로세스는 해당 작업의 종료를 기다린다. */
                unix_error("waitfg: waitpid error");
        } else { /* background 실행 */
            printf("%d %s\n", pid, cmdline);  /* waitpid()를 호출하지 않고 PID만 출력 후 즉시 다음 명령을 받을 준비를 한다.*/
        }
    }
    return;
}

해당 shell 프로그램은 foreground job에 대하여 종료를 기다리고 종료되는 즉시 이를 reap한다. 하지만 background job에 대해서는 다음과 같은 문제가 발생한다.

  • 자식 프로세스가 종료될 경우 zombie 상태가 된다.
  • shell이 종료되지 않을 경우 이를 reap하지 않고, 이로 인해 memory leak이 발생한다.

이를 해결할 수 있는 방법이 바로 Exceptional Control Flow이다. 커널은 background 프로세스가 종료될 때 signal을 보내서 이를 shell에 알릴 수 있다. Unix에서는 이를 이용하여 zombie들을 적절히 reap할 수 있다.

각주