프로세스를 실행하는 방법

신준우·2023년 5월 31일
0

시스템 프로그래밍

목록 보기
4/12

자, 이전의 학습내용을 생각해보자.

  • 쉘이란?
  • 쉘에서 프로그램을 실행하는 방식
  • execvp 함수를 사용해서 프로그램을 프로세스화하는법

그렇다면 이번 단원에서의 학습 목표는 무엇이었을까?

  • 리눅스 쉘은 무엇을 하는가?
  • 프로세스의 리눅스 모델
  • 프로그램을 실행하는 방법 << 대충 이쯤까지 한거 아닐까?
  • 프로세스를 생성하는 방법
  • parent와 child 프로세스간의 소통방법
  • for, exec, wait, exit 라는 System call과 Function에 대해 이해하기
  • 명령어 sh, ps에 대해 알아보기

새로운 예시 코드가 주어졌다. 살펴보자

after.c

// after.c: execvp의 작업을 보여준다

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

int main(int argc, char* argv[]){
	pid_t pid = getpid();
    printf("After execvp(): %d\n", pid);
    
    return 0;
}

이 코드를 살펴보자

  1. getpid()함수를 사용해서 pid라는 정수형 변수에 pid값을 저장한다.
  2. 현재 프로세스의 pid를 출력한다

이 두가지 기능을 갖고있다

이렇게 실행된다. execvp가 실행된 후의 프로세스 ID값을 출력해준다


before.c

//before.c: shows the same pid
// no matter how many times get executed.

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

//int main(int argc, char* argv[]){
void main(){
	char* arglist[2];
    pid_t pid = getpid();
    
    arglist[0] = "./after";
    arglist[1] = 0;
    
    printf("Before execvp(): %d\n", pid);
    
    execvp(arglist[0], arglist);
    return;
}

자, 이번에는 before.c를 살펴보자

  1. 현재 프로세스의 pid를 가져온다
  2. execvp 함수를 호출해서 arglist[0]에 지정된 실행 파일을 실행하게된다
    execvp함수가 호출되면 현재 프로세스는 지정된 실행 파일로 대체된다
  3. 그렇게 얻어온 현재 프로세스의 PID를 출력


위 두 코드, 서로 연계되어있다.
before.c는 arglist 배열에 "./after"이 할당되어 있다.
즉, after.c를 실행파일로 지정하고, 이 실행파일을 실행시키는,
프로세스에 할당하는 그런 방식의 코드이다

결론적으로,

  1. pid를 따올수 있게됨
  2. 이전에 실행되던 프로세스와 동일한 pid를 가짐으로서 execvp 함수의 작동방식을 이해

이 두가지가 목적인 예시 코드들이다

직접 쉘을 만들어보자!

psh1.c

//psh1.c

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

#define MAXARGS 20
#define ARGLEN 100

int execute (char *arglist[]){
	execvp(arglist[0]. arglist);
    perror("execvp failed");
    if(arglist != NULL){
    	free(arglist);
    }
    exit(1);
}

char* makestring(char *buf){
	char	*cp;
    buf[strlen(buf)-1]='\0';
    cp = malloc(strlen(buf)+1);
    if( cp == NULL){
    	fprintf(stderr, "no memory\n");
        exit(1);
    }
    strycpy(cp, buf);
    return cp;
}

int main(int argc, char*argv[]){
	char *arglist[MAXARGS+1];
    int numargs;
    char argbuf[ARGLEN];
    char *makestring();
    numargs = 0;
    
    while(numargs < MAXARGS){
    	printf("Arg[%d]?", numargs);
        if(fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n'){
        	arglist[numargs++] = makestring(argbuf);
        }
        
        else{
        	if(numargs > 0){
            	arglist[numargs] = NULL;
                execute (arglist);
                numargs = 0;
            }
        }
    }
    return 0;
}

Idea

자, 쉘이란 무엇인가?

  • 기능에 대해 생각해보자,
    • 프로그램 실행
    • 입/출력 관리
    • 사용자가 직접적으로 프로그래밍 가능한 ui

그러려면 어떻게 해야할지 생각해 보자

메인함수

자, 일단 메인함수부터 살펴볼까?

int main(int argc, char*argv[]){		// 기본적인 메인함수선언, 명령행 인자와 데이터 전달
	char *arglist[MAXARGS+1];	// arglist문자열 포인터를 선언한다, 길이는 MAXARGS+1
    							// 실행할 명령행 인수 저장
    int numargs;	// int형 변수 numargs선언, 현재까지 입력된 명령행 인수의 개수
    char argbuf[ARGLEN];		// 사용자로부터 입력받은 명령행 인수를 임시로 저장
    char *makestring();		// makestring() 함수의 프로토타입을 선언
    numargs = 0;		// numargs는 0으로 초기화됨
    
    while(numargs < MAXARGS){		//MAXARGS값이 numargs값보다 클 경우 반복문이 실행된다
    	printf("Arg[%d]?", numargs);		//numargs, 즉 입력된 명령행 인수의 인덱스를 출력
        if(fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n'){	// fgets함수, 파일에서 문자열 한개씩
        														// 입력받는 함수, argbuf에 저장한다.
                                                                // 입력된 문자열이 '\n', 즉 엔터키가
                                                                // 눌리지 않으면 실행됨
        	arglist[numargs++] = makestring(argbuf);	// makestring 함수를 사용, 
            											// argbuf안에 저장된 문자열을 복사한다
                                                        // numargs 값 증가되면서 arglist 배열 하나씩 채움
        }
        
        else{											// 나머지 경우에는 어떨까?
        	if(numargs > 0){							// numargs값이 0보다 큰 경우?
            	arglist[numargs] = NULL;				// arglist의 마지막 배열에 NULL포인터를 할당
                										// 인수 목록의 끝을 표시
                execute (arglist);						// execute 함수를 호출, 명령행 인수를 실행
                numargs = 0;							// numargs를 0으로 초기화, 
                										//새로운 명령어를 받을 준비
            }
        }
    }
    return 0;
}

음... 전반적 기능을 살펴보니, 명령어를 문자열 방식으로 입력받아 실행시키는 기능을 하고 있다
쉘의 기능중 "프로그램 실행" 부분에 집중한 쉘이라고 볼 수 있을것같다

여기서 쓰인, "makestring()" 함수가 무엇인지 봐야한다

※ fgets함수

  • 함수 원형:
char* fgets(char* str, int num, FILE* pFile);
  • 첫번째 인자: 파일에서 가져온 문자열을 넣는 변수, 문자열을 가리키는 char타입의 포인터
  • 두번째 인자: 한번에 가지고 올 문자열의 길이를 넣는 변수
  • 세번째 인자: 파일의 파일포인터를 집어넣음
  • 가지고온 문자열 반환, or 파일의 끝일경우 NULL포인터 반환

자, 이제 makestring 함수를 찬찬히 살펴보자

makestring()

char* makestring(char *buf){	// buf라는 이름의 문자열 버퍼를 입력받음,
								// 동적으로 할당된 새로운 문자열 포인터 반환
	char	*cp;				// 문자열 포인터 cp를 선언, 새로운 문자열 저장을 위한 포인터
    buf[strlen(buf)-1]='\0';	//문자열의 마지막 문자를 NULL문자로 바꿈, '\n'을 제거하기 위함
    cp = malloc(strlen(buf)+1);	// 'strlen(buf)+1'만큼의 메모리를 동적으로 할당,
    							// strlen(buf)는 문자열의 길이, strlen(buf)+1은 널 문자까지 저장하기 위함
    if( cp == NULL){		//cp값이 NULL이라면, 즉 동적 메모리 할당이 실패한경우
    	fprintf(stderr, "no memory\n");		//stderr스트림에 'no memmory' 를 출력함
        exit(1);		//프로그램 종료, 반환값으로 1 전달
    }
    strycpy(cp, buf);	// 입력된 buf의 내용을 cp에 복사함
    return cp;		// cp값을 반환, 즉 새로 입력된 내용을 반환해줌
}

내용을 살펴봤다시피, 입력받은 문자열을 새로운 문자열인 cp에 저장하여 반환해주는 함수이다!
어떻게 보면, 기존 프로세스를 삭제하고 새 프로세스를 출력할때의 과정과 같다!

그럼 나머지 함수인 'execute()' 함수는 뭘까?

execute()

int execute (char *arglist[]){		// execute 함수의 선언, 문자열 포인터 배열 arglist를 입력받음
									// int형 반환값을 가짐
	execvp(arglist[0]. arglist);	// excvp 함수를 호출, 명령행 인수 실행
    perror("execvp failed");		// perror함수는 stderr스트림에 시스템 오류 메세지 출력
    if(arglist != NULL){			// arglist 배열이 NULL이 아닌 경우
    	free(arglist);				// arglist 배열을 해제
    }
    exit(1);						// execute 함수의 실행오류로 종료
}

핵심은 몇가지 되지 않는다

  • execvp 호출을 통해 프로그램 실행
  • 에러 메세지 출력

실행화면

  • 첫번째 인자, 명령어 입력
  • 두번째 인자, 옵션 입력
  • 세번째 인자, 경로 입력
  • 네번째 인자에 엔터를 입력함으로서 실행

간단하게 ls 명령어를 실행한거다

profile
보안

0개의 댓글