From 7f6de0567a23e113ef40a3434d4a24f9b5584f0c Mon Sep 17 00:00:00 2001 From: Minseong Gwak Date: Wed, 7 Jan 2026 20:02:39 +0900 Subject: [PATCH] progress: make gmsh great again --- userprog/gmsh/Makefile | 2 +- userprog/gmsh/builtin.c | 57 +--------- userprog/gmsh/exec.c | 127 ++++++++++++++++++++++ userprog/gmsh/gmsh.c | 231 ++++++++++------------------------------ userprog/gmsh/job.c | 115 ++++++++++++++++++++ userprog/gmsh/job.h | 60 +++++++++++ userprog/gmsh/parser.c | 81 ++++++++++++++ 7 files changed, 442 insertions(+), 231 deletions(-) create mode 100644 userprog/gmsh/exec.c create mode 100644 userprog/gmsh/job.c create mode 100644 userprog/gmsh/job.h create mode 100644 userprog/gmsh/parser.c diff --git a/userprog/gmsh/Makefile b/userprog/gmsh/Makefile index 99b28df..8bb7e72 100644 --- a/userprog/gmsh/Makefile +++ b/userprog/gmsh/Makefile @@ -1,7 +1,7 @@ # userprog/gmsh/Makefile TARGET = gmsh -SRCS = gmsh.c builtin.c +SRCS = gmsh.c job.c parser.c exec.c builtin.c CC = gcc CFLAGS = -static -Wall diff --git a/userprog/gmsh/builtin.c b/userprog/gmsh/builtin.c index 1b9780c..d398d3d 100644 --- a/userprog/gmsh/builtin.c +++ b/userprog/gmsh/builtin.c @@ -114,59 +114,4 @@ void cmd_resize(char* row, char* col) { } } -void cmd_help() { printf("Built-ins: ls, cd, pwd, cat, sz, poweroff, reboot\n"); } - -void handle_redirection(const char *argv[]) { - int i = 0; - while (argv[i] != NULL) { - int fd = -1; - int is_redirect = 0; - - // 1. Output Redirection (Overwrite): > - if (strcmp(argv[i], ">") == 0) { - if (argv[i+1] == NULL) { - fprintf(stderr, "gmsh: syntax error near unexpected token 'newline'\n"); - exit(1); - } - fd = open(argv[i+1], O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (fd < 0) { perror("open"); exit(1); } - - dup2(fd, STDOUT_FILENO); // stdout(1) -> fd - close(fd); - is_redirect = 1; - } - // 2. Output Redirection (Append): >> - else if (strcmp(argv[i], ">>") == 0) { - if (argv[i+1] == NULL) { - fprintf(stderr, "gmsh: syntax error\n"); - exit(1); - } - // O_APPEND: 뒤에 추가 - fd = open(argv[i+1], O_WRONLY | O_CREAT | O_APPEND, 0644); - if (fd < 0) { perror("open"); exit(1); } - - dup2(fd, STDOUT_FILENO); - close(fd); - is_redirect = 1; - } - // 3. Input Redirection: < - else if (strcmp(argv[i], "<") == 0) { - if (argv[i+1] == NULL) { - fprintf(stderr, "gmsh: syntax error\n"); - exit(1); - } - fd = open(argv[i+1], O_RDONLY); - if (fd < 0) { perror("open"); exit(1); } - - dup2(fd, STDIN_FILENO); // stdin(0) -> fd - close(fd); - is_redirect = 1; - } - - if (is_redirect) { - argv[i] = NULL; - break; - } - i++; - } -} +void cmd_help() { printf("Built-ins: ls, cd, pwd, cat, sz, poweroff, reboot\n"); } \ No newline at end of file diff --git a/userprog/gmsh/exec.c b/userprog/gmsh/exec.c new file mode 100644 index 0000000..b85665a --- /dev/null +++ b/userprog/gmsh/exec.c @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include +#include +#include "job.h" + +// 리다이렉션 처리 헬퍼 +static void setup_redirection(command_t *cmd) { + if (cmd->input_path) { + int fd = open(cmd->input_path, O_RDONLY); + if (fd < 0) { + perror("gmsh: input error"); + exit(1); + } + dup2(fd, STDIN_FILENO); + close(fd); + } + + if (cmd->output_path) { + int flags = O_WRONLY | O_CREAT; + if (cmd->append_mode) flags |= O_APPEND; + else flags |= O_TRUNC; + + int fd = open(cmd->output_path, flags, 0644); + if (fd < 0) { + perror("gmsh: output error"); + exit(1); + } + dup2(fd, STDOUT_FILENO); + close(fd); + } +} + +// Job 실행 (핵심 함수) +void launch_job(job_t *job) { + command_t *cmd; + int next_in_fd = STDIN_FILENO; // 파이프라인의 이전 단계 출력 (현재 단계의 입력) + int pipe_fds[2]; + pid_t pid; + + // 1. 명령어 리스트 순회 + for (cmd = job->head; cmd != NULL; cmd = cmd->next) { + // 다음 명령어가 있으면 파이프 생성 + if (cmd->next) { + if (pipe(pipe_fds) < 0) { + perror("pipe"); + exit(1); + } + } + + // 2. 프로세스 생성 + pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } + else if (pid == 0) { + // [자식 프로세스] + + // A. 프로세스 그룹 설정 (Job Control) + // 첫 번째 프로세스의 PID를 그룹 ID로 삼음 + if (job->pgid == 0) job->pgid = getpid(); + setpgid(0, job->pgid); + + // B. 시그널 핸들러 복구 (쉘의 보호막 해제) + signal(SIGINT, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + signal(SIGTTOU, SIG_DFL); + + // C. 입력 연결 (이전 파이프 -> 내 입력) + if (next_in_fd != STDIN_FILENO) { + dup2(next_in_fd, STDIN_FILENO); + close(next_in_fd); + } + + // D. 출력 연결 (내 출력 -> 다음 파이프) + if (cmd->next) { + dup2(pipe_fds[1], STDOUT_FILENO); + close(pipe_fds[1]); + close(pipe_fds[0]); // 읽기 쪽은 안 씀 + } + + // E. 리다이렉션 (파이프보다 우선순위 높음) + setup_redirection(cmd); + + // F. 실행 + execvp(cmd->argv[0], cmd->argv); + fprintf(stderr, "gmsh: command not found: %s\n", cmd->argv[0]); + exit(127); + } + else { + // [부모 프로세스] + + // A. Job PGID 설정 (첫 번째 자식의 PID로) + if (job->pgid == 0) job->pgid = pid; + setpgid(pid, job->pgid); // 자식과 부모 양쪽에서 해줘야 경쟁상태 방지 + + // B. 파이프 정리 + // 이전에 썼던 입력 파이프 닫기 + if (next_in_fd != STDIN_FILENO) close(next_in_fd); + + // 현재 만든 파이프의 읽기 쪽을 다음 루프로 넘김 + if (cmd->next) { + next_in_fd = pipe_fds[0]; + close(pipe_fds[1]); // 쓰기 쪽은 자식이 가져갔으니 닫음 + } + } + } + + // 3. 대기 (Foreground인 경우) + if (!job->is_bg) { + // 이 Job에 속한 모든 프로세스가 끝날 때까지 대기 + int status; + while (waitpid(-job->pgid, &status, WNOHANG) == 0) { + // 아직 안 끝난 자식이 있으면 잠시 대기 (CPU 절약) + // 실제로는 sigsuspend 등을 쓰는 게 정석이지만 간단히 sleep/pause + // 여기선 blocking wait로 단순화 + waitpid(-job->pgid, &status, 0); + } + } else { + // Background + printf("[%d] %d\n", job->id, job->pgid); + add_job(job); // Job 리스트에 등록 (나중에 확인용) + } +} \ No newline at end of file diff --git a/userprog/gmsh/gmsh.c b/userprog/gmsh/gmsh.c index 74c428e..9e14883 100644 --- a/userprog/gmsh/gmsh.c +++ b/userprog/gmsh/gmsh.c @@ -9,122 +9,62 @@ #include #include -#include "para.h" #include "builtin.h" +#include "para.h" +#include "job.h" +job_t *parse_line(char *line); +void launch_job(job_t *job); static bool verbose = false; +volatile pid_t fg_pgid = 0; -int parse_cmd(char *cmd, char *argv[]) { - int argc = 0; - char *ptr = cmd; - char *token_start; - char quote_char = 0; - - while (*ptr) { - while (*ptr == ' ' && quote_char == 0) ptr++; - if (*ptr == '\0') break; - - token_start = ptr; - char *write_ptr = ptr; - - while (*ptr) { - if (quote_char == 0 && *ptr == ' ') { - ptr++; - break; - } - - if (*ptr == '"' || *ptr == '\'') { - if (quote_char == *ptr) quote_char = 0; - else if (quote_char == 0) quote_char = *ptr; - else *write_ptr++ = *ptr; - } - else { - *write_ptr++ = *ptr; - } - - ptr++; - } - - *write_ptr = '\0'; - argv[argc++] = token_start; - - if (argc >= MAX_ARGS - 1) break; - } - - argv[argc] = NULL; - return argc; +void handle_sigint(int sig) { + printf("\n"); } -// 반환값: 1(처리함), 0(파이프 없음) -int handle_pipe(char *argv[]) { - int pipe_idx = -1; - - for (int i = 0; argv[i] != NULL; i++) { - if (strcmp(argv[i], "|") == 0) { - pipe_idx = i; - break; - } - } - - if (pipe_idx == -1) return 0; - - argv[pipe_idx] = NULL; - char **left_cmd = &argv[0]; - char **right_cmd = &argv[pipe_idx + 1]; - - if (right_cmd[0] == NULL) { - printf("gmsh: syntax error near unexpected token `|'\n"); - return 1; - } - - int fds[2]; - if (pipe(fds) == -1) { - perror("pipe"); - return 1; - } - - pid_t pid1 = fork(); - if (pid1 == 0) { - close(fds[0]); - dup2(fds[1], STDOUT_FILENO); - close(fds[1]); - - handle_redirection((const char**) left_cmd); - execvp(left_cmd[0], left_cmd); - perror("execvp left"); - exit(1); - } - - pid_t pid2 = fork(); - if (pid2 == 0) { - close(fds[1]); - dup2(fds[0], STDIN_FILENO); - close(fds[0]); - - if (handle_pipe(right_cmd) == 1) { - exit(0); - } - else { - handle_redirection((const char**) right_cmd); - execvp(right_cmd[0], right_cmd); - perror("execvp right"); - exit(1); - } - } - - close(fds[0]); - close(fds[1]); - - waitpid(pid1, NULL, 0); - waitpid(pid2, NULL, 0); - - return 1; // 처리 완료 +void handle_sigchld(int sig) { + while (waitpid(-1, NULL, WNOHANG) > 0); } +int builtin_cmd(job_t *job) { + command_t *cmd = job->head; + if (!cmd || !cmd->argv[0]) return 0; + + // if (strcmp(cmd->argv[0], "exit") == 0) exit(0); + // if (strcmp(cmd->argv[0], "cd") == 0) { + // if (cmd->argc < 2) fprintf(stderr, "cd: missing argument\n"); + // else if (chdir(cmd->argv[1]) != 0) perror("cd"); + // return 1; + // } + bool ret = 0; + if (strcmp(cmd->argv[0], "exit") == 0) exit(0), ret = 1; + else if (strcmp(cmd->argv[0], "ls") == 0) cmd_ls(cmd->argv[1]), ret = 1; + else if (strcmp(cmd->argv[0], "cd") == 0) cmd_cd(cmd->argv[1]), ret = 1; + else if (strcmp(cmd->argv[0], "pwd") == 0) cmd_pwd(), ret = 1; + // else if (strcmp(cmd->argv[0], "cat") == 0) cmd_cat(argc, (const char**) argv); + // else if (strcmp(cmd->argv[0], "proc") == 0) cmd_proc(); + // else if (strcmp(cmd->argv[0], "echo") == 0) cmd_echo(argc, (const char**) argv); + + else if (strcmp(cmd->argv[0], "size") == 0) cmd_size(), ret = 1; + else if (strcmp(cmd->argv[0], "resize") == 0){ + if(cmd->argc != 3){ + printf("Usage: resize \n"); + } + else cmd_resize(cmd->argv[1], cmd->argv[2]); + ret = 1; + } + + else if (strcmp(cmd->argv[0], "poweroff") == 0) cmd_poweroff(), ret = 1; + else if (strcmp(cmd->argv[0], "reboot") == 0) cmd_reboot(), ret = 1; + else if (strcmp(cmd->argv[0], "help") == 0) cmd_help(), ret = 1; + + return ret; +} + + void run_shell() { char cmd_buf[MAX_CMD_LEN]; - char *args[MAX_ARGS]; char cwd[1024]; while (1) { @@ -136,82 +76,23 @@ void run_shell() { cmd_buf[strcspn(cmd_buf, "\n")] = 0; if (strlen(cmd_buf) == 0) continue; - // int argc = 0; - // args[argc] = strtok(cmd_buf, " "); - // while (args[argc] != NULL && argc < MAX_ARGS - 1) args[++argc] = strtok(NULL, " "); + job_t *job = parse_line(cmd_buf); + if (job == NULL) continue; - int argc = parse_cmd(cmd_buf, args); - - if (argc == 0) continue; - - if (handle_pipe(args)) { + if (builtin_cmd(job)) { + free_job(job); continue; } - if (verbose) { - printf("[debug] argc: %d\n", argc); - for(int i=0; i \n"); - } - else cmd_resize(args[1], args[2]); - } - - else if (strcmp(args[0], "poweroff") == 0) cmd_poweroff(); - else if (strcmp(args[0], "reboot") == 0) cmd_reboot(); - else if (strcmp(args[0], "help") == 0) cmd_help(); - else { - pid_t pid = fork(); - - // if (pid == 0) { - // execvp(args[0], args); - // printf("%s: command not found\n", args[0]); - // exit(1); - // } else wait(NULL); - - if (pid == 0) { - handle_redirection((const char**) args); - execvp(args[0], args); - - fprintf(stderr, "%s: command not found\n", args[0]); - exit(127); - } else if (pid > 0) { - int status; - - if (verbose) { - printf("[debug] Spawned child PID: %d\n", pid); - } - - waitpid(pid, &status, 0); - - if (verbose) { - if (WIFEXITED(status)) { - printf("[debug] Process %d exited with code %d\n", pid, WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - printf("[debug] Process %d killed by signal %d\n", pid, WTERMSIG(status)); - } - } - } else { - perror("fork"); - } - } + launch_job(job); + if (!job->is_bg) free_job(job); } } int main(int argc, char** argv) { - printf("GMSH (GMS Minimal SHell) v1.0 initialized.\n"); + signal(SIGINT, handle_sigint); + signal(SIGCHLD, handle_sigchld); + signal(SIGTTOU, SIG_IGN); int opt; while ((opt = getopt(argc, argv, "v")) != -1) { @@ -225,7 +106,9 @@ int main(int argc, char** argv) { exit(1); } } - + + if(verbose) printf("GMSH (GMS Minimal SHell) v1.0 initialized.\n"); + run_shell(); return 0; } diff --git a/userprog/gmsh/job.c b/userprog/gmsh/job.c new file mode 100644 index 0000000..1f66b95 --- /dev/null +++ b/userprog/gmsh/job.c @@ -0,0 +1,115 @@ +#include +#include +#include +#include +#include "job.h" + +job_t *job_list_head = NULL; +static int next_job_id = 1; + +// 초기화 +void init_job_list() { + job_list_head = NULL; + next_job_id = 1; +} + +// 새로운 Job 생성 (껍데기) +job_t *create_job(char *line) { + job_t *job = (job_t *)malloc(sizeof(job_t)); + memset(job, 0, sizeof(job_t)); + + job->id = next_job_id++; // ID 자동 할당 + job->command_line = strdup(line); // 원본 보존 + job->state = JOB_RUNNING; + job->next = NULL; + job->head = NULL; + + return job; +} + +// 명령어 추가 (파이프라인 연결) +void append_command(job_t *job, command_t *cmd) { + if (job->head == NULL) { + job->head = cmd; + } else { + // 리스트 끝을 찾아서 연결 + command_t *curr = job->head; + while (curr->next != NULL) { + curr = curr->next; + } + curr->next = cmd; + } +} + +// Job 리스트에 등록 +void add_job(job_t *job) { + if (job_list_head == NULL) { + job_list_head = job; + } else { + job_t *curr = job_list_head; + while (curr->next != NULL) { + curr = curr->next; + } + curr->next = job; + } +} + +// Job 삭제 (PGID로 찾아서) +int delete_job(pid_t pgid) { + job_t *curr = job_list_head; + job_t *prev = NULL; + + while (curr != NULL) { + if (curr->pgid == pgid) { + // 연결 끊기 + if (prev == NULL) job_list_head = curr->next; + else prev->next = curr->next; + + free_job(curr); + return 1; // 성공 + } + prev = curr; + curr = curr->next; + } + return 0; // 못 찾음 +} + +// PGID로 Job 찾기 +job_t *find_job(pid_t pgid) { + job_t *curr = job_list_head; + while (curr != NULL) { + if (curr->pgid == pgid) return curr; + curr = curr->next; + } + return NULL; +} + +// ID로 Job 찾기 (fg %1, bg %2 처리용) +job_t *find_job_by_id(int id) { + job_t *curr = job_list_head; + while (curr != NULL) { + if (curr->id == id) return curr; + curr = curr->next; + } + return NULL; +} + +// 메모리 해제 (재귀적으로 Command도 해제) +void free_command(command_t *cmd) { + if (cmd == NULL) return; + free_command(cmd->next); // 재귀 호출로 다음꺼 먼저 삭제 + + // 인자 해제 + // (주의: argv 문자열들이 한 덩어리 buffer를 가리키는지, 각각 malloc인지에 따라 다름) + // 여기서는 파서 구현에 따라 달라지므로 일단 구조체만 해제 + if (cmd->input_path) free(cmd->input_path); + if (cmd->output_path) free(cmd->output_path); + free(cmd); +} + +void free_job(job_t *job) { + if (job == NULL) return; + free_command(job->head); // 연결된 명령어들 해제 + if (job->command_line) free(job->command_line); + free(job); +} \ No newline at end of file diff --git a/userprog/gmsh/job.h b/userprog/gmsh/job.h new file mode 100644 index 0000000..56a28f8 --- /dev/null +++ b/userprog/gmsh/job.h @@ -0,0 +1,60 @@ +#ifndef _JOB_H_ +#define _JOB_H_ + +#include + +#define MAX_ARGS 64 + +// [1단계] 단일 명령어 (Simple Command) +// 예: "grep error > log.txt" +typedef struct command_t { + char *argv[MAX_ARGS]; // 인자 리스트 (예: {"grep", "error", NULL}) + int argc; // 인자 개수 + + char *input_path; // 리다이렉션 < (없으면 NULL) + char *output_path; // 리다이렉션 > (없으면 NULL) + int append_mode; // >> 모드 여부 (0 or 1) + + struct command_t *next; // 파이프라인의 다음 명령어 (Linked List) +} command_t; + +// [2단계] 작업 상태 (Job State) +typedef enum { + JOB_RUNNING, // 실행 중 + JOB_STOPPED, // 정지됨 (Ctrl+Z) + JOB_DONE // 완료됨 +} job_state_t; + +// [3단계] 작업 (Job) - 파이프라인 전체를 하나의 작업으로 봄 +// 예: "ls | grep c &" +typedef struct job_t { + int id; // Job ID (쉘이 관리하는 번호: 1, 2, 3...) + pid_t pgid; // Process Group ID (이 작업의 대표 PID) + job_state_t state; // 현재 상태 + int is_bg; // 백그라운드 실행 여부 (1=BG, 0=FG) + char *command_line; // 원본 명령어 문자열 (jobs 출력용) + + command_t *head; // 명령어 리스트의 첫 번째 (ls) + + struct job_t *next; // 다음 작업 (Job List용) +} job_t; + +// --- 함수 원형 --- + +// Job 관리 +void init_job_list(); +job_t *create_job(char *line); +void add_job(job_t *job); +int delete_job(pid_t pgid); +job_t *find_job(pid_t pgid); +job_t *find_job_by_id(int id); +void free_job(job_t *job); + +// Command 관리 +void append_command(job_t *job, command_t *cmd); +void free_command(command_t *cmd); + +// 전역 변수 (외부에서 접근 가능) +extern job_t *job_list_head; + +#endif \ No newline at end of file diff --git a/userprog/gmsh/parser.c b/userprog/gmsh/parser.c new file mode 100644 index 0000000..19c2b08 --- /dev/null +++ b/userprog/gmsh/parser.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include "job.h" + +// 문자열을 공백 기준으로 쪼개서 argv에 넣기 +static void parse_args(char *cmd_str, command_t *cmd) { + char *token = strtok(cmd_str, " \t\n"); + while (token != NULL && cmd->argc < MAX_ARGS - 1) { + + // 리다이렉션 처리 (간이 버전) + if (strcmp(token, "<") == 0) { + token = strtok(NULL, " \t\n"); + if (token) cmd->input_path = strdup(token); + } + else if (strcmp(token, ">") == 0) { + token = strtok(NULL, " \t\n"); + if (token) cmd->output_path = strdup(token); + cmd->append_mode = 0; + } + else if (strcmp(token, ">>") == 0) { + token = strtok(NULL, " \t\n"); + if (token) cmd->output_path = strdup(token); + cmd->append_mode = 1; + } + else { + // 일반 인자 + cmd->argv[cmd->argc++] = strdup(token); + } + + token = strtok(NULL, " \t\n"); + } + cmd->argv[cmd->argc] = NULL; +} + +// 메인 파서 함수 +job_t *parse_line(char *line) { + if (line == NULL || strlen(line) == 0) return NULL; + + // 1. Job 껍데기 생성 + job_t *job = create_job(line); + + // 2. 백그라운드(&) 확인 + // 문자열 끝에서부터 역탐색하거나, 토큰화 과정에서 체크해야 함. + // 편의상 strrchr 사용 (단, 따옴표 안에 있는 &는 무시 못함. 일단 간단히 구현) + char *bg_ptr = strrchr(line, '&'); + if (bg_ptr != NULL) { + job->is_bg = 1; + *bg_ptr = ' '; // &를 공백으로 치환해서 명령어 파싱 방해 안 되게 함 + } + + // 3. 파이프(|) 단위로 자르기 + // strtok는 원본을 훼손하므로 복사본 사용 + char *line_copy = strdup(line); + char *cmd_str = strtok(line_copy, "|"); + + while (cmd_str != NULL) { + command_t *cmd = (command_t *)malloc(sizeof(command_t)); + memset(cmd, 0, sizeof(command_t)); + + // 내부 인자 및 리다이렉션 파싱 + parse_args(cmd_str, cmd); + + if (cmd->argc > 0) { + append_command(job, cmd); + } else { + free(cmd); // 빈 명령어는 버림 + } + + cmd_str = strtok(NULL, "|"); + } + + free(line_copy); + + if (job->head == NULL) { // 명령어가 하나도 없으면 + free_job(job); + return NULL; + } + + return job; +} \ No newline at end of file