fork, exec에 대한 상세 내용과 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

system함수

system함수는 유닉스 운영체제에는 모두 지원합니다. system함수는 입력받은 command의 문자열을 실제로 실행시켜주는 함수입니다.

system함수를 사용하기 위해서는 stdlib.h 헤더파일을 include 해야합니다.

#include <stdlib.h>

system함수의 원형은 아래와 같습니다.

int system(const char *command);

사용하는 방법은 매우 간단합니다. command에 실행할 명령어를 전달해주기만 하면 됩니다. 아래의 사용 예를 보시면 금방 사용하실수 있을겁니다.

사용예)

//system_test.c
#include <stdlib.h>
#include <stdio.h>

int main(){
        char *command="ls -al";
        int ret;
        ret = system(command);
        printf("system함수 종료 :%d\n",WEXITSTATUS(ret));
}
# gcc system_test.c
# ./a.out
합계 208
drwxr-xr-x 19 ubuntu ubuntu  4096  4월 11 17:22 .
drwxr-xr-x  6 root   root    4096  4월  1 15:38 ..
-rw-------  1 ubuntu ubuntu   378  4월 11 17:17 .Xauthority
-rw-------  1 ubuntu ubuntu  5496  4월 11 12:42 .bash_history
-rw-r--r--  1 ubuntu ubuntu   220  2월 22  2021 .bash_logout
...
system 함수 호출 완료 ret:0

 

system함수의 내부

system함수에 NULL을 전달하게 되면 적절한 명령처리기가 존재한다면 0을 돌려줍니다. 그 외에는 상황에 따라 다릅니다.  system함수를 내부적으로 들여다보면 fork, exec, waitpid로 이루어진 함수입니다. 이 세개의 함수에 대해서 모르신다면 아래의 포스팅을 참고하시기 바랍니다.

- fork()

https://reakwon.tistory.com/45

 

[리눅스] 프로세스 생성과 특징, 종료 (fork, wait), 예제 코드

프로세스(process) 프로세스는 간단히 말하자면 실행 중인 프로그램을 의미합니다. 아마 여러분들은 컴퓨터를 하면서 아주 빈번하게 듣는 용어이기도 합니다. 실행 중인 프로그램이라?? 컴퓨터에

reakwon.tistory.com

- exec()

https://reakwon.tistory.com/207?category=300674 

 

[리눅스] exec류의 함수 사용방법,간단한 쉘구현 -execl,execv,execve...

exec famliy exec~로 되는 함수들이 리눅스에 몇가지 존재하고 있습니다. 리눅스에서는 exec family라고 부릅니다. 이 함수들은 모두 공통적으로 프로그램을 실행한다는 특징을 갖고 있습니다. 그 함수

reakwon.tistory.com

- waitpid

https://reakwon.tistory.com/99

 

[리눅스] 조건변수를 통한 스레드 동기화 설명, 예제(pthread_cond_wait, pthread_cond_signal)와 생산자-소

조건 변수 조건 변수를 설명하기 전에 다음과 같은 상황이 발생했다고 칩시다. 먼저 스레드 2개가 존재합니다. 저는 짧은 코드를 좋아하므로 아주 간단한 역할을 하는 2개의 쓰레드를 생성했습

reakwon.tistory.com

 

 

[리눅스] 조건변수를 통한 스레드 동기화 설명, 예제(pthread_cond_wait, pthread_cond_signal)와 생산자-소

조건 변수 조건 변수를 설명하기 전에 다음과 같은 상황이 발생했다고 칩시다. 먼저 스레드 2개가 존재합니다. 저는 짧은 코드를 좋아하므로 아주 간단한 역할을 하는 2개의 쓰레드를 생성했습

reakwon.tistory.com

 

system함수의 반환 값

1. fork 호출이 실패했거나 waitpid가 EINTR외의 오류를 돌려주면 system함수는 errno를 EINTR오류로 설정하고 -1를 반환합니다.

2. exec함수가 실패했다면 이런 경우에는 shell을 실행할수 없다는 뜻이며, exit(127)과 동일합니다. 

3. 그 외의 경우에는 waitpid에 지정된 셸의 종지 상태가 return됩니다.

 

아래의 코드는 system함수를 흉내낸 코드입니다. 

#include <stdio.h>
#include <errno.h>
#include <unistd.h>

int system(const char *cmd){
        pid_t pid;
        int status;

        if(cmd == NULL) return 1;       //UNIX에는 명령 처리기가 존재
        if((pid = fork()) < 0){
                status = -1;    //프로세스 생성 에러
        }else if(pid == 0){     //자식 프로세스
                execl("/bin/sh","sh","-c",cmd,(char*)0);
                _exit(127);		//위 2번읜 case
        }else{                  //부모 프로세스 : 자식이 끝날때까지 기다림
                while(waitpid(pid, &status, 0) < 0){
                        if(errno != EINTR){	//위 1번의 case
                                status = -1;
                                break;
                        }
                }
        }
        return status;
}
int main(){
        int ret;
        ret = system("ls -al");
        printf("system함수 종료 :%d\n",WEXITSTATUS(ret));
}

 

이러한 구현사항때문에 내부적으로 fork()로 자식 프로세스를 수행하고 자식 프로세스는 exec함수를 호출하는데요. 부모 프로세스는 waitpid로 자식 프로세스를 기다리기 때문에 system다음 줄의 printf가 실행될수 있는 것이죠.

 

종지 상태 확인

WEXITSTATUS로 실제 exit()이나 return값을 확인할수 있습니다. 아래는 main에서 바로 return 18로 빠져 나오는 한 프로그램입니다. 혹은 exit(18)을 해도 똑같습니다.

//program.c
int main(){
        //exit(18);
        return 18;
}
# gcc program.c -o program
# ls program
program

program이라는 실행파일이 생겨납니다. 이제 이 실행파일을 실행시키기 위해 system함수를 사용해보겠습니다.

//system_test.c
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(){
        int ret;
        ret = system("./program");
        printf("system함수 종료 :%d\n",WEXITSTATUS(ret));
}
# gcc system_test.c
# ./a.out
system함수 종료 :18

 

우리가 return했던 값을 확인할 수 있죠? 단순 ret값을 출력하는게 아닌 매크로를 통해서 종지상태를 확인해야한다는 점을 기억하세요. 

 

이상으로 system에 관한 포스팅을 마치도록 하겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

exec와 관련한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

exec famliy 

exec~로 되는 함수들이 리눅스에 몇가지 존재하고 있습니다. 리눅스에서는 exec family라고 부릅니다. 이 함수들은 모두 공통적으로 프로그램을 실행한다는 특징을 갖고 있습니다. 그 함수들이 어떤 것들이 있는지는 다음과 같습니다.

int execl(const char *pathname, const char *arg, .../* (char  *) NULL */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */);
int execve(const har* pathname, char *const argv[], char *const ecnp[]);
int execlp(const char *file, const char *arg, .../* (char  *) NULL */);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

 

pathname은 경로이름을 인수로 받습니다. file은 파일이름을 받는데, 만약 /가 포함되어 있다면 경로명으로 간주하게 됩니다. /없이 파일이름을 쓰게 되면 PATH 환경 변수에 지정된 경로들에서 실행 파일을 찾아서 실행하게 됩니다. 

환경 변수를 출력해보시면 아래와 같이 echo를 통해서 확인할 수 있습니다.

# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:

또한 환경 변수에 경로를 추가할 경우에는 아래와 같이 export를 통해서 할 수 있습니다. 아래와 같이 /home을 환경변수에 추가해보았습니다.

# export PATH=$PATH:/home
# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home

 

위의 모든 함수들을 외울수는 없지요. 그런데 규칙이 존재합니다. exec은 공통 접두사이고 그 다음 알파벳따라 정리한 표가 아래에 있습니다.

알파벳 설명
l list형식으로 arg0, arg1, ... , NULL로 인자를 전달하는 방식을 의미합니다.
v vector 형식으로 *argv[] 형태로 전달합니다. 끝에 NULL을 넣어줄 필요가 없습니다.
p 기본 환경 변수(PATH)의 경로를 삼습니다.
e environment를 입력받습니다. 

 

기본 사용법

아래의 코드는 모든 exec함수를 사용하는 간단한 예입니다. 따로 오류처리는 하지 않았습니다. 

#include <unistd.h>
#include <stdio.h>

int main(){
        char *arg1="-al";
        char *arg2="/etc";
        char *file = "ls";
        char *argv[]={file,arg1,arg2,NULL};
        char *path = "/bin/ls";

        printf("execl호출\n");
        execl(path, file, arg1, arg2, NULL);

        //printf("execv호출\n");
        //execv(path, argv);

        //printf("execle호출\n");
        //execle(path, file, arg1, arg2, NULL, NULL);

        //printf("execve호출\n");
        //execve(path, argv, NULL);

        //printf("execlp호출\n");
        //execlp(file, file, arg1, arg2, NULL);

        //printf("execvp호출\n");
        //execvp(file,argv);

        //printf("execvpe호출\n");
        //execvpe(file,argv,NULL);

        return 0;
}

 

명령어는 /bin/ls를 사용합니다. 그리고 첫번째 인자는 옵션 "-al"이며 두번째 인자는 내용을 출력할 디렉토리인 /etc입니다. argv를 잘 보시면 가장 첫번째 배열 원소는 실행할 파일 이름이 있다는 것 마지막은 NULL을 기억하세요. exec명령을 여러번 실행할 수는 없습니다. 그러니까 하나씩 실행하면서 결과를 확인해보세요. 

 

exec의 특징

exec를 사용하게 되면 기존의 exec를 실행시킨 프로세스는 exec가 실행한 프로그램으로 대체가 됩니다. 그렇기 때문에 exec를 실행시킨 프로세스 ID와 exec로 실행된 프로세스 ID와 같습니다. 실험해볼까요?

아래의 프로그램 코드는 exec로 실행시킬 프로그램입니다. 이 프로그램은 프로세스 ID, 부모 프로세스 ID, 세션 ID를 출력합니다.

//myprogram.c

#include <stdio.h>
#include <unistd.h>

int main(){
        printf("after exec\n");
        printf("pid : %d, ppid : %d\n",getpid(),getppid());
        printf("session id : %d\n", getsid(getpid()));
        return 0;
}

그리고 아래는 exec를 실행시킬 프로그램입니다. 하는 동작은 위의 내용과 같습니다.

//myexec.c
#include <unistd.h>
#include <stdio.h>

int main(){
        char *file="myprogram";
        char *argv[]={file, NULL};
        printf("before exec\n");
        printf("pid : %d, ppid : %d\n",getpid(),getppid());
        printf("session id : %d\n", getsid(getpid()));
        printf("\n");
        execvp(file,argv);
        printf("exec end\n");   //출력되지 않을 것

        return 0;
}

 

이제 이 둘을 컴파일을 하고 다음과 같이 실행시켜보세요. PATH에 현재 디렉토리를 추가하여 myprogram을 실행시켜주도록 합시다. execvp는 PATH에 있는 환경 변수의 경로를 찾게 되니까요. 이렇게 현재 디렉토리를 PATH에 추가하면 보안상 매우 좋지 않습니다. 하지만 이해를 돕기 위해서 진행합니다.

# export PATH=$PATH:.
# gcc myprogram.c -o myprogram
# gcc myexec.c
# a.out
before exec
pid : 500050, ppid : 500036
session id : 499935

after exec
pid : 500050, ppid : 500036
session id : 499935

실행결과는 모두 같습니다.

이 밖에도 exec수행 시에 실행시킨 프로세스의 여러 속성들을 물려받는데, 그중 몇가지를 아래에 기재하였습니다. 

프로세스 그룹 ID
제어 터미널
실제 사용자 ID, 실제 그룹 ID
현재 작업 디렉토리
프로세스 Signal mask
유보 중인 신호들
파일모드 생성 마스크

 

초간단 shell 만들기

프로세스를 생성하는 함수인 fork()와 exec()를 사용하여 간단한 쉘을 만들어볼 수도 있습니다. 아래는 간단한 쉘을 흉내내본 프로그램 코드입니다.

#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>

#define MAXLINE 256

int main(){
        char buf[MAXLINE];
        pid_t pid;
        int status;
        printf("@ ");
        while(fgets(buf,MAXLINE, stdin) != NULL){
                if(buf[strlen(buf) - 1] == '\n')
                        buf[strlen(buf) -1] ='\0';

                if((pid = fork()) < 0){
                        printf("fork error\n");
                        exit(1);
                }else if(pid == 0){
                        execlp(buf, buf, NULL);
                        exit(127);
                }

                if((pid = waitpid(pid, &status, 0)) < 0){
                        printf("waitpid error\n");
                }
                printf("@ ");
        }
        return 0;
}

# gcc myshell.c
# ./a.out
@ pwd
/home/ubuntu
@ w
 00:03:26 up 9 days, 21:01,  3 users,  load average: 0.00, 0.01, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
ubuntu   :0       :0               07 3월21 ?xdm?   1:16m  0.04s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --systemd --session=ubuntu
ubuntu   pts/1    192.168.35.223   23:36    0.00s  0.81s  0.27s sshd: ubuntu [priv]
ubuntu   pts/8    :pts/7:S.0       03 3월22  3days  1.02s  0.69s vi poll_test.c
@

 

위의 쉘은 아주 제약적입니다. 2개 이상의 인자를 넣을 수는 없고, cd로 디렉토리를 이동하려해도 이동해지지가 않습니다. 하지만 exec를 통해 shell을 만들어볼수 있다는 것을 보여준 간단한 예입니다.

이상으로 exec family에 대해서 알아보았고 몇가지 특징도 알아보았습니다. 

반응형
블로그 이미지

REAKWON

와나진짜

,