ostep project - process shell(쉘 구현)

2022. 4. 30. 15:21컴퓨터 공학/운영체제

반응형

 

본 티스토리 블로그는 PC 환경에 최적화되어 있습니다.

모바일 유저분들은 아래 네이버 블로그를 이용해 주세요.

 

안녕하세요, ICMP입니다.

이번에 제가 작성한 프로그램은 Unix의 가장 핵심 기능인 명령어 해석기 쉘입니다.

코딩을 잘 못해서 상당히 어려웠으나 Ostep 과제 제작자가 요구 사항과 기술 명세서를 상세히

잘 작성해 놓아서 해당 내용을 잘 읽고 따라간다면 충분히 만들 수 있을 것이라고 생각합니다.

 

 

GitHub - remzi-arpacidusseau/ostep-projects: Projects for an undergraduate OS course

Projects for an undergraduate OS course. Contribute to remzi-arpacidusseau/ostep-projects development by creating an account on GitHub.

github.com

 

 

 

 

 

일단 구현해야 할 부분을 생각해 보도록 하겠습니다.

 

1. 명령어 구문 해석 부분

 

  • command, option, argument

입력된 문자열을 명령어, 옵션, 인자로 분리시킬 수 있어야 하며, 병렬 실행을 위한 &나 리다이렉션을 위해 >와 같은 특수 문자를 사용한다면 이에 따른 처리 루틴도 따로 만들어 줘야 합니다.

 

제가 생각한 가장 복잡한 입력 루틴의 처리 과정을 아래와 같이 처리했습니다.

ls -al ./ > test1.txt & cat asdf.c > test1.txt

 

위와 같이 입력이 들어오면 상당히 복잡하지만 작은 문제로 분리해 보도록 해보겠습니다.

우선 & 문자를 기준으로 분리를 진행하면 다음과 같은 결과를 얻을 수 있습니다.

ls -al ./ > test1.txt
cat asdf.c > test1.txt

 

 

즉, & 개수 + 1 만큼의 명령어가 존재한다고 가정하고 반복문으로 돌려서 처리해 주면 되겠죠?

그럼 각각의 분리된 문자열들을 strsep 함수로 '>' 한번, ' ' 공백으로 한번 분리해 주면 명령어, 옵션, 인자, 리다이렉션 경로로 분리가 가능합니다.

 

다음으로 병렬 처리를 위해 스레드를 사용해야 하는가? 그건 아닙니다.

fork와 wait 함수 호출 시점을 적절히 조절해 줄 수 있다면 충분히 병렬처리처럼 동작을 시킬 수 있습니다.

 

즉, 반복문에 fork 부분을 넣고 main 문에 반복문이 완전히 끝나고 직후에 실행되는 코드 부분에 wait를 넣으면 부모 프로세스가 생성한 모든 명령어 실행 프로세스(자식 프로세스)가 끝날 때까지 기다리도록 동작을 설정할 수 있습니다.

 

 

2. 명령어 실행 부분

 

  • 내장 명령어(cd, path, exit)

내장 명령어는 운영체제에서 제공하는 쉘에서 동작하는 게 아니라 지금 제가 만든 쉘 프로그램이 동작해야 하므로 따로 만들어 줘애 합니다.

단순히 exec("cd", "./", "NULL")와? 같은 구문을 작성해서 구현하게 되면 운영체제단의 쉘 동작 경로만

변할 뿐, 앞서 우리가 제작할 wish 쉘은 경로가 변경되지 않습니다.

 

이런 부분을 해결하려면 우리가 제작하는 쉘 자체에서 이런 기능을 제공하는 명령어를 구현해야겠죠?

 

  • 운영체제 지원 명령어

명령어가 어떻게 실행되는지를 알고 있어야 합니다.(여기서 환경 변수라는 개념이 상당히 중요합니다.)

정말 간단하게 설명하자면 아래와 같습니다.

 

user(cat 명령어 입력) ->

쉘(명령어를 받고 환경 변수 참조) ->

환경 변수에 cat으로 끝나는 경로가 존재하는지 검색 ->

해당 경로의 실행파일에 인자, 옵션과 함께 실행 후 결과를 표준 출력으로 전달

(리다이렉션이 존재한다면 따로 입출력을 재지정해 줘야겠지요?)

 

그럼 지원 명령어인지 아닌지 어떻게 환경 변수를 참고해서 확인하느냐?

아래 코드에 그 해답을 작성했습니다.

int check_builtIn(char *command){
    // 1: /bin/ in found
    // 2: /usr/bin/ in found
    // 3: path
    // -1 : not found!!!
    
    char base1[100] = "/bin/";
    char base2[100] = "/usr/bin/";

    
    if(strstr(PATH[0], "/"))
        return 3;
    
    strcat(base1, command);
    if(!access(base1, X_OK))
        return 1;

    strcat(base2, command);
    if(!access(base2, X_OK))
        return 2;
    
    
    return -1;
}
 

입력된 명령어가 path 명령어를 통해 지정된 경로에 존재하는지를 우선적으로 확인한 후 없으면 /bin 경로에 해당 명령어가 실행 가능한지 확인해 주는 함수입니다.

 

 

3. 입출력 리다이렉션

 

말 그대로 입력 또는 출력을 재지정하겠다는 말입니다.

일반적으로 표준 입출력이 디폴트로 설정되어 있으므로 표준 출력을 닫고 리다이렉션할 파일을 열어 해당 포인터로 대체해 줘야 합니다.

 

if(fork() == 0){
            if(re_Dir != NULL){
                fd1 = open(re_Dir, O_RDWR | O_CREAT);

                if (fd1 < 0)
                    ERROR();

                dup2(fd1, STDOUT_FILENO);
                close(fd1);
            }
            //자식코드
            execv(direction, argv);
            exit(0);
        }
 

여기서 주의해야 할 점은 표준 입출력을 닫았다가 다시 복구하는 루틴을 자식 프로세스가 진행하도록 하여 추가적인 연산 코드를 제거하는 것입니다.

 

아래는 다른 프로그래머 분이 리눅스 입출력 리다이렉션을 구현하는 과정을 담은 블로그 포스팅 링크입니다.

 

 

쉘 구현 3차

3차 과제 1. 리다이렉션 구현 (< , >만 구현해도 됩니다.) 2. 파이프 구현( | ) (파이프와 리다이렉...

blog.naver.com

최종적으로 작성된 코드가 중복된 부분도 많고 급하게 만든 거라 고칠 부분이 상당히 많습니다.

참고용으로만 보시고 보다 더 깔끔한 코드를 작성하시는 것을 추천드립니다!!!

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

char *Trim(char *s);
char *right_Trim(char *s);
char *left_Trim(char *s);

int parsing_Ampersand(char *output_buffer[], char *input_line);
int parsing_blank(char *command, char **argument);
void excute_Command(char **argument, int index, char *re_Dir);
void parsing_Redirection(char ptr[], char *command, char *re_Dir);
int check_builtIn(char *command);
void ERROR();
void CD(char **argument, int index, char *re_Dir);
void path(char **argument, int index, char *re_Dir);
int count_Blank(char* re_Dir);
char PATH[1000][1000];
int index_path;


int main(int argc, char** argv){


    FILE *fp;          
    char *line = NULL;    // 입력한 문자열
    char *ptr[1000];      // 행 기준으로 명령줄 구분   
    char *command;        // 명령어 
    char *re_Dir;         // 리다이렉션 경로
    char *argument[100]; // 명령어와 옵션, 경로 
    char *redirection;

    int index;
    int count_reDir;
    int count;
    size_t len = 0;

    if(argc == 2){
        fp = fopen(argv[1], "r");

        if(fp == NULL){
            ERROR();
            return 1;
        }

        while(!feof(fp)){
            if(getline(&line, &len, fp) == 0 || !strcmp(line, ""))
                break;
            
            line[strlen(line)-1] = '\0';      
            index = parsing_Ampersand(ptr, line);
            
            for(int i=0; i<index; i++){
                re_Dir=NULL;
        
                if(strstr(ptr[i],">") != NULL){
                    command = strsep(&ptr[i], ">");
                    re_Dir  = strsep(&ptr[i], ">");

                    if( !strcmp(command, "") ){
                        ERROR();
                        continue;
                    }

                    if(strsep(&ptr[i], ">") != NULL){
                        ERROR();
                        continue;
                    }
                    
                    command = left_Trim(command);
                    command = right_Trim(command);
                    redirection = left_Trim(re_Dir);
                    redirection = right_Trim(redirection);
                    re_Dir = left_Trim(re_Dir);
                    re_Dir = right_Trim(re_Dir);

                    count_reDir = count_Blank(re_Dir);
                    if(!strcmp(redirection,"") || count_reDir > 1){
                        ERROR();
                        continue;
                    }
                    else{
                        count = parsing_blank(command, argument);
                        excute_Command(argument, count, re_Dir);
                        continue;
                    }
                    redirection = NULL;
                }
                count = parsing_blank(ptr[i], argument);
                for(int i=0; i<count; i++){
                    if(argument[i] != NULL){
                        excute_Command(argument, count, NULL);
                        break;
                    }
                }
            }
            line = NULL;
            while(wait(NULL) > 0);
        }
        free(line);
    }
        
    if(argc == 1){
        fp = stdin;
        while(1){
            printf("wish> ");
            getline(&line, &len, fp);
            
            line[strlen(line)-1] = '\0';      
            index = parsing_Ampersand(ptr, line);
        
            for(int i=0; i<index; i++){
                re_Dir=NULL;
                if(strstr(ptr[i],">") != NULL){
                    command = strsep(&ptr[i], ">");
                    re_Dir  = strsep(&ptr[i], ">");

                    if( !strcmp(command, "") ){
                        ERROR();
                        continue;
                    }

                    if(strsep(&ptr[i], ">") != NULL){
                        ERROR();
                        continue;
                    }
                    command = left_Trim(command);
                    command = right_Trim(command);
                    redirection = left_Trim(re_Dir);
                    redirection = right_Trim(redirection);
                    re_Dir = left_Trim(re_Dir);
                    re_Dir = right_Trim(re_Dir);
                    count_reDir = count_Blank(re_Dir);
                    if(!strcmp(redirection,"") || count_reDir > 1){
                        ERROR();
                        continue;
                    }
                    else{
                        count = parsing_blank(command, argument);
                        excute_Command(argument, count, re_Dir);
                        
                        continue;
                    }
                    redirection = NULL;
                }
                count = parsing_blank(ptr[i], argument);
                for(int i=0; i<count; i++){
                    if(argument[i] != NULL){
                        excute_Command(argument, count, NULL);
                        break;
 
 
                    }
                }
            }
            line = NULL;
            while(wait(NULL) > 0);
            
        }
        free(line);
        
    }

    if(argc > 2){
        ERROR();
        return 1;
    }
}

int parsing_Ampersand(char *output_buffer[], char *input_line){
    int index = 0;
    char token[2] = {'&', '\n'}; // 공백, 개행문자, &, 옵션

    while((output_buffer[index] = strsep(&input_line, token)) != NULL){
        index++;
    }
    return index;
}


int parsing_blank(char *command, char **argument){
    int index = 0;
    char token[1] = {' '}; // 공백, 개행문자, &, 옵션
    char *input;

    while((input = strsep(&command, token)) != NULL){
        if(argument == NULL)
            continue;

        if(strcmp(input, "")){
            argument[index] = input;
            index++;
        }
    }
    return index;
}


void excute_Command(char **argument, int index, char *re_Dir){
    int flag;
    int fd1;
    char base1[100] = "/bin/";
    char base2[100] = "/usr/bin/";
    char direction[100];
    //int status;
    char *argv[1000];
    int idx=0;
    for(int i=0; i<index; i++){
        if(!strcmp(argument[i], " "))
            continue;
        argv[idx++] = argument[i];
    }
    argv[idx]=NULL;

        
    if(!strcmp(argument[0], "cd")){
        CD(argument, index, re_Dir);
        return;
    }
        
    else if(!strcmp(argument[0], "path")){
        path(argument, index, re_Dir);
        return;
    }
        
    else if(!strcmp(argument[0], "exit")){
        if(index -1 >0)
            ERROR();
        exit(0);
    }
        

    flag = check_builtIn(argument[0]);

    if(flag != -1){
        if(flag == 1){
            strcpy(direction, base1);
            strcat(direction, argument[0]);
        }
            
        else if(flag == 2){
            strcpy(direction, base2);
            strcat(direction, argument[0]);
        }
            
        else{
            int i;
            for(i=0; i<index_path; i++){
                strcpy(direction,PATH[i]);
                strcat(direction, argument[0]);
                if(!access(direction, X_OK))
                    break;
            }
            if(i==index_path && access(direction, X_OK))
                ERROR();
            
        }
            


        if(fork() == 0){
            if(re_Dir != NULL){
                fd1 = open(re_Dir, O_RDWR | O_CREAT);

                if (fd1 < 0)
                    ERROR();

                dup2(fd1, STDOUT_FILENO);
                close(fd1);
            }
            //자식코드
            execv(direction, argv);
            exit(0);
        }
    }

    else if(flag == -1)
        ERROR();
}




int check_builtIn(char *command){
    // 1: /bin/ in found
    // 2: /usr/bin/ in found
    // 3: path
    // -1 : not found!!!
    
    char base1[100] = "/bin/";
    char base2[100] = "/usr/bin/";

    
    if(strstr(PATH[0], "/"))
        return 3;
    
    strcat(base1, command);
    if(!access(base1, X_OK))
        return 1;

    strcat(base2, command);
    if(!access(base2, X_OK))
        return 2;
    
    
    return -1;
}

int count_Blank(char* re_Dir){
    int index = 0;
    char token[1] = {' '}; // 공백, 개행문자, &, 옵션
    while(strsep(&re_Dir, token) != NULL){
        index++;
    }
    return index;
}

void ERROR(){
    char error_message[30] = "An error has occurred\n";
    write(STDERR_FILENO, error_message, strlen(error_message));
}

void CD(char **argument, int index, char *re_Dir){
    if(index > 2 || index == 1){
        ERROR();
        exit(0);
    }
        
    chdir(argument[1]);
}

void path(char **argument, int index, char *re_Dir){
    if(index == 1){
            strcpy(PATH[0], "/");
            index_path = 1;
            return;
    }
    
    for(int i=1 ; i< index; i++){
        if(!strcmp(argument[i], ""))
            continue;
        strcpy(PATH[i-1], argument[i]);
        strcat(PATH[i-1], "/");
        index_path++;
    }
}

char *left_Trim(char *s){
    while(isspace(*s)) 
        s++;

    return s;
}

char *right_Trim(char *s){
    char* back_st;
    back_st = s + strlen(s);

    while(isspace(*--back_st));
    *(back_st+1) = '\0';

    return s;
}

char *Trim(char *s){
    return right_Trim(left_Trim(s)); 
}

/*
test 1: passed
test 2: passed
test 3: passed
test 4: passed
test 5: passed
test 6: passed
test 7: passed
test 8: passed
test 9: passed
test 10: passed
test 11: passed
test 12: passed
test 13: passed
test 14: passed
test 15: passed
test 16: passed
test 17: passed
test 18: passed
test 19: passed
test 20: passed
test 21: passed
test 22: passed
*/

 

이 외에도 제가 생략한 부분이 많으나 Ostep project 부분을 꼭 참고하여 직접 과제를 푸신다면

분명 운영체제의 동작뿐만 아니라 여러분들의 코딩 실력도 늘일 수 있을 겁니다.

 

이상! ICMP였습니다.

감사합니다!

 

반응형