2022. 4. 30. 15:21ㆍ컴퓨터 공학/운영체제
본 티스토리 블로그는 PC 환경에 최적화되어 있습니다.
모바일 유저분들은 아래 네이버 블로그를 이용해 주세요.
안녕하세요, ICMP입니다.
이번에 제가 작성한 프로그램은 Unix의 가장 핵심 기능인 명령어 해석기 쉘입니다.
코딩을 잘 못해서 상당히 어려웠으나 Ostep 과제 제작자가 요구 사항과 기술 명세서를 상세히
잘 작성해 놓아서 해당 내용을 잘 읽고 따라간다면 충분히 만들 수 있을 것이라고 생각합니다.
일단 구현해야 할 부분을 생각해 보도록 하겠습니다.
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);
}
여기서 주의해야 할 점은 표준 입출력을 닫았다가 다시 복구하는 루틴을 자식 프로세스가 진행하도록 하여 추가적인 연산 코드를 제거하는 것입니다.
아래는 다른 프로그래머 분이 리눅스 입출력 리다이렉션을 구현하는 과정을 담은 블로그 포스팅 링크입니다.
최종적으로 작성된 코드가 중복된 부분도 많고 급하게 만든 거라 고칠 부분이 상당히 많습니다.
참고용으로만 보시고 보다 더 깔끔한 코드를 작성하시는 것을 추천드립니다!!!
#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였습니다.
감사합니다!
'컴퓨터 공학 > 운영체제' 카테고리의 다른 글
ostep initial-xv6 - Syscall 추가하기 (2) | 2022.05.13 |
---|---|
ostep-reverse : reverse 명령어 구현 (0) | 2022.04.28 |
ostep-utility : wcat 명령어 구현 (0) | 2022.03.11 |
1. Operation System OT (0) | 2020.02.12 |