# [Linux SystemProgramming] Process - Minishell

## Simple Minishell을 만들어보자.

#### 먼저, Process가 무엇인지 간단하게 알아보자

## Process

* An entity that is registered to the kernel for execution
    
* control flow passes from one process to another via **Context Switching**
    

## &lt;PCB&gt;

모든 프로세스는 각각 PCB(Process Control Block)을 가지며, 이는 Process의 Metadata를 지니고 있는 자료구조이다. 각 PCB는 아래 그림의 내용을 지니고 있다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699016417263/0058aa69-f0b2-42cd-888d-119c3c99e560.png align="center")

프로세스에 대한 자세한 이야기는\[OS - 프로세스 관리(Scheduling)\] 편에서 자세히 알아보고, 여기서는 Process를 어떻게 만들고, Minishell을 만드는데 집중해보자.

### Process 생성하기

먼저 2개의 Header File이 필요하다.

```c
#include <stdio.h>
#incude <unistd.h>
```

<s>&lt;stdio.h&gt;를 모르면 다른 진로를 알아보자.</s>

`<unistd.h>`는 유닉스 계열에서 동작하는 C 컴파일러에 있는 헤더 파일이다. 윈도우 환경에는 존재하지 않으니, 리눅스 환경에서 돌려야 한다. 대부분의 시스템 함수들은 여기에 선언되어 있다.

추가로, `<unistd.h>`에 있는 기본적인 리눅스 파일 I/O 함수들이다. (File I/O 관련 자세한 내용은 추후 다른 게시글로 정리하겠다)

### size\_t <mark>read</mark>(int fd, void\* buf, size\_t nbytes);

> fd : 파일 식별자, buf : 데이터 저장 위치, nbytes : 읽어올 바이트 수
> 
> > return : 파일에서 읽은 바이트 수(성공) / -1(실패)

### ssize\_t <mark>write</mark>(int fd, void\* buf, size\_t nbytes);

> fd : 파일 식별자, buf : 데이터 읽을 위치, nbytes : 쓸 바이트 수
> 
> > > return : 파일에 쓴 바이트 수(성공) / -1(실패)

---

***이제 진짜 프로세스를 만들어보자.***

프로세스를 만들기 전에 주의해야 할 사항은, 우리가 실행시키는 int main() 도 결국 하나의 프로세스라는 점이다!

프로세스의 생성은 `fork()` 를 통해 생성된다. 함수의 원형은 다음과 같다.

### **pid\_t <mark>fork</mark>(void)**

> return: 0(for child process), pid(for **child's pid**)

`fork()`를 실행하면, 해당 지점부터 새로운 프로세스가 생성되며, 이를 자식 프로세스라고 통칭한다. 자식 프로세스는 생성된 시점부터 해야할 본인의 일을(?) 수행한다고 생각하면 된다. 또한, 생성된 자식 프로세스는 pid\_t로 0을 return하며, 부모 프로세스(fork를 호출한 프로세스)는 자식 프로세스의 pid(프로레스 id)를 할당받는다. 예시를 통해 쉽게 이해해보자.

```c
//헤더파일, main 생략
.
.
. 
// 부모 프로세스가 여기까지 실행하다가
pid_t pid = fork(); // 자식 프로세스 생성
// 부모는 계속 실행, 자식 프로세스는 여기서부터 실행
if(pid == 0) {
    printf("This is child Process! %d\n",pid);
}
else if(pid != 0) {
    printf("This is Parent Process! %d\n",pid);
}
```

&lt;실행 결과&gt;

```c
// pid 번호는 실행시점마다 다를 것이다 (kernel이 그때그때 다르게 할당해줌)
This is Parent Process! 6096
This is child Process! 0
```

결국, `fork()`를 통해 생성한 자식 프로세스는 결과창 기준 윗줄을 출력하고, 부모 프로세스는 아랫줄을 출력한 것이다. 이때, 출력 순서와 `getpid()`의 값은 실행마다 달라질 수 있다.

흥미로운 점은, `fork()`는 1번 불렸지만, ***return 값이 2개(자식, 부모 프로세스)처럼 보인다는 것이다.*** 하지만, **❌이는 틀렸다❌.** `fork()`를 실행하여 프로세스 생성에 실패했다면 -1을 즉시 리턴한다. 반면에, 프로세스 생성에 성공했다면, `fork()`함수 내부에서 자식 프로세스에게는 0을 부모 프로세스에게는 자식 프로세스의 pid를 리턴하는 것이다. `fork()` *도 결국에는 함수라는 점을 잊지말자.*

또한, 프로세스를 다룰때에는 pid(Process ID)를 사용하여 다루는 경우가 많다. `fork()`를 통해 생성한 프로세스는 부모 프로세스 + 1의 pid를 가지게 된다. 예시를 통해 알아보자.

```bash
printf("Process pid:%d\n",getpid()); // 부모 pid
pid_t pid = fork(); 
if(pid == 0) {
    printf("This is child Process pid! %d\n",getpid()); // 자식 pid
    printf("This is child Process return! %d\n",pid);        
}
else if(pid != 0) {
    printf("This is parent Process pid! %d\n",getpid()); // 부모 pid
    printf("This is parent Process return! %d\n",pid);        
}
```

&lt;실행 결과&gt;

```bash
Process pid:9276
This is parent Process pid! 9276
This is parent Process return! 9277
This is child Process pid! 9277
This is child Process return! 0
```

---

### void <mark>exit</mark>(int status)

> return: 0(normally exit), !0 (Error)

`exit()` 함수는 간단하게, 프로세스를 종료한다고 보면 된다. status에는 보통 프로그램이 어떤 이유로 종료되었는지를 나타는 정수 값을 사용한다. 0은 일반적으로 프로그램이 성공적으로 종료되었음을 뜻하고, 1은 오류로 종료됬음을 의미한다. <s>추후에 알게되면 수정하겠지만, 지금은 웬만하면 0을 쓰도록 하자.</s>

cf) atexit(call back function) 을 통해 exit()이 수행되었을때 실행할 함수를 설정할 수도 있다.

---

### pid\_t <mark>wait</mark>(int\* status)

> return: pid\_t(종료된 자식 프로세스의 pid)
> 
> * status는 exit과 같이 종료 상태 정보를 저장하기 위한 변수이다.
>     

### pid\_t <mark>waitpid</mark>(pid\_t pid, int\* status, int options)

> wait의 변형으로 특정 pid를 지정해서 해당 프로세스가 끝나기를 기다린다.

wait 함수는 다음과 같이 쓰인다.

1. 부모 프로세스는 `wait` 함수를 호출하여 자식 프로세스의 종료를 기다립니다.
    
2. 만약 자식 프로세스가 이미 종료되었다면, `wait` 함수는 즉시 반환하고 자식 프로세스의 PID를 반환합니다.
    
3. 자식 프로세스가 종료되지 않았을 경우, 부모 프로세스는 자식 프로세스가 종료될 때까지 대기 상태에 있습니다.
    
4. 자식 프로세스가 종료되면, `wait` 함수는 자식 프로세스의 PID를 반환하고, 자식 프로세스의 종료 상태 정보를 `status` 매개변수에 저장합니다. 이 종료 상태 정보는 자식 프로세스가 어떻게 종료되었는지를 나타내며, 부모 프로세스가 이 정보를 검사하여 적절한 조치를 취한다.
    

---

Minishell을 만들기 위해서는 다른 프로그램을 실행시켜주는 execv를 사용해야 한다.(execl, execlp... 등 많다)

### int <mark>execv</mark>(char\* path, char\* argv\[\]);

> path : 실행 파일의 Directory 포함 전체 파일 명
> 
> argv\[\] : 인수 (사용자 입력)
> 
> > return: -1(실패)

보통 /bin/ 디렉토리 안에 있는 리눅스 명령어들을 사용한다. execv를 예시를 통해 알아보자.

```c
char *argv[] ={ "/bin/ls", "-al", "/tmp", NULL};
execv( "/bin/ls", argv);
```

보통 shell command를 입력할때, `ls -al /tmp` 와 같이 입력을 준다. 이를 Token화 하여 `argv` 문자열 배열에 넣어두고, `execv`의 인자로 주는 것이다. `exec`에는 여러가지가 존재하는데, `argv[0]`에 절대경로를 주어야 하는 함수와, 상대경로를 주는 함수가 있으므로, 주의해야 한다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699073374940/661b98c1-4446-4574-a052-762775be6d68.png align="center")

---

Rediecting(&gt;, &lt;), pipelining(|)와 같은 것들을 제외한 simple minishell을 만들어보자.

```c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> // getline
#include <string.h> // strtok, strcmp
#include <sys/wait.h> // wait
#include <sys/types.h> //size_t, ssize_t

#define MAX_LINE 64

int main(int argc, char* argv[]) {            
    pid_t pid;
    size_t size;

    char* cmd = NULL; // NULL -> getline automatically allign malloc.    
    char* path[MAX_LINE]; // argv[0]에 들어갈 절대경로
    char* input[MAX_LINE]; // Tokenized argv
    
    while(1) {
        getline(&cmd, &size, stdin); // User input
        cmd[strlen(cmd) -1] = '\0';  // EOS      
        // Tokenize
        int i = 0;
        char* ptr = strtok(cmd, " ");
        while(ptr != NULL) {
            input[i++] = ptr;
            ptr = strtok(NULL, " ");
        }
        input[i] = NULL;
        // Path
        sprintf(path, "/bin/%s", input[0]);
        // Exit by user
        if(strcmp(input[0], "quit") == 0){
            break;
        }
        pid = fork();
        if(pid == 0) {
            // Wrong input
            if(execv(path, input) == -1) { 
                perror("Command Error\n");
                exit(0);
            }
        }        
    }    
    if(pid > 0) {
        wait(NULL); // wait until all child process ends
        exit(0);
    }
}
```

---

시스템프로그래밍실습 수업은 어렵지만 신기하다는 느낌이 든다. 내가 shell command를 실행시키는 프로그램을 만들 수 있다니!!~

edited by 김지호
