리눅스 스터디 - 7
1. 프로세스 생성
- 사용자(user process가) 프로세스 생성을 위해 syscall을 사용해 (쉘을 거쳐)os에 요청(fork, exec등)
- 프로세스 생성시, os에서는 프로세스 테이블의 엔트리(file descriptor) 생성
- fork와 exec
fork
- 리눅스의 fork()는 시스템콜을 이용해 구현되며, 공유할 자원을 지정할 수 있는 플래그를 사용한다
- 프로세스 생성은 대부분 do_fork()에서 일어난다(fork()를 사용해 syscall, clone()[vfork()등 여러가지로 표현된다]을 통해 do_fork()호출, do_fork()는 copy_process()[여기서 자식 프로세스 생성]호출)
- 자식프로세스를 생성하는 함수(유닉스는 모든섹션을 복사하고, 리눅스는 text는 공유하고 나머지를 복사한다)
- (<=> 스레드는 stack만 분리, 나머지는 공유)
- 부모와 자식 프로세스가 동시에 수행된다. 만일 부모가 다 수행한 이후에도 자식이 아직 동작중이면, 부모는 자식이 완료될 때까지 대기한다(자식이 리턴될 때까지 대기)
※ copy_process()에서 일어나는 과정(순서대로 일어남)
- 프로세스 개수 제한 체크
- task_struct 구조체 생성
- TASK_UNINTERRUPTIBLE 설정
- Flag 맴버 갱신
- pid부여
- 자원복제 및 공유
- (스케줄링이 가능하게)타임 슬라스 분배
- 새자식 프로세스 포인터 반환
exec
- 새 프로세스를 현재 프로세스 위에 덮어 쓰는 방식, 이러한 방식으로 원래코드로 돌아오지 않고 바로 빠져나온다.
- 보통 홀로 사용하지않는다(fork를 사용한후 사용)
- 원 프로세스의 PID, PPID 번호와 동일하다
- exec계열의 함수들
execl: argv인자를 char *로 하나씩 넘겨줄 때 사용
execv: argv인자를 char *[]배열로 한번에 넘겨줄 때 사용
그밖에 execlp,execle,execvp, execve가 있는데, 끝에 붙는 e는 '환경변수를 넘겨줄때'를, p는 '절대경로를 입력하지 않는(환경변수 PATH를 참조) 경우'를 나타낸다
- exec계열 함수 원형에 대한 정보
http://forum.falinux.com/zbxe/?mid=C_LIB&page=3&document_srl=408563
- exec계열 함수 사용법
http://bbolmin.tistory.com/35
https://blog.naver.com/youtoo2/20011337681
- 참조
fork와 exec의 차이: http://channelofchaos.tistory.com/55
2. 프로세스 종료
- 프로세스 생성과 마찬가지로 syscall을 통해 os에 요청한다(exit, abort)
- 부모는 자식이 종료될 때까지 기다린다(wait)
- 자식프로세스의 종료이유
자식이 할당된 자원을 초과 사용할때
자식의 task가 더이상 필요 없을때
부모가 종료될 때(부모가 종료될 때 자식을 먼저 종료시키고 부모를 종료시켜야 한다. 아니면 좀비, 고아가 발생할 수 있다)
- exit()함수
정상적인 종료
- abort()함수
강제적인 종료, 즉시 종료된다
SIGABRT 시그널에 의해서 종료된다
- wait()함수
부모 프로세스가 자식프로세스가 종료될때까지 기다리는 과정에서 사용하는 함수이다.(즉, 일반적으로 fork와 같이 사용한다)
- 참조
wait함수 응용: http://forum.falinux.com/zbxe/index.php?document_srl=408545&mid=C_LIB
abort
http://forum.falinux.com/zbxe/index.php?document_srl=408379&mid=C_LIB
https://msdn.microsoft.com/ko-kr/library/tcwt7yw6.aspx
3. fork 동작방식
- 전통적인 fork
공유자원을 포함한 모든 자원을 복제하는 방식
비효율적임
최악의 경우 새로 생성된 프로세스가 바로 새이미지를 실행하는 것으로 복제 자체가 무의미해짐
- copy-on-write
리눅스에서 fork()를 구한하는데 사용
데이터의 복제를 지연 또는 방지하는 기법
프로세스 공간을 복제하기보다는 공유(데이터를 쓸(w)경우에만 공간을 복제한다)
복제를 방지(복제를 안하니)로 성능개선효과가 있다
4. 프로세스 생성과 종료 함수들의 상세 설명
- fork
필요한 헤더파일
- <unistd.h>
- <sys/types.h> //pid_t라는 자료형때문에 필요(#define pid_t int이기때문에 그냥 int를 써도 무리는 없다)
원형
- pid_t fork();
들어가는 인자 의미
- 보통 안넣는듯(없는것 같기도하고)
반환되는 값 의미
- fork생성을 실패하면 -1을. 부모에게는 새로 생성된 자식 프로세스 PID, 자식 프로세스에는 0이 반환된다.
특징
- 부모, 자식의 PC(program count)값은 같지만 pid의 값은다르다
- 리눅스에서는 text영역은 공유, 나머지는 복사하여 분리동작
- 부모와 자식은 개별로 동작하고 각 프로세스의 동작은 서로에게 영향을 주지 않는다
- 즉 자식 생성이후, 부모와 자식은 os의 스케줄러에 의해 개별적으로 동작한다
참조
- vfork란
http://forum.falinux.com/zbxe/index.php?mid=C_LIB&page=5&document_srl=408536
- fork예
http://forum.falinux.com/zbxe/index.php?document_srl=412814&mid=C_LIB
- fork와 exec의 차이
http://channelofchaos.tistory.com/55
- execl
필요한 헤더파일
- <unistd.h>
원형
- int execl( const char *path, const char *arg, ...);
들어가는 인자 의미
- char *path 실행 파일의 디레토리 포함 전체 파일 명
- const char *arg 넣을 인수들
반환되는 값 의미
- int형을 반환(실패일 때만 -1)
특징
- 새로운 프로그램을 기존 프로그램에 덮어쓴다(=다른 프로그램을 실행하고 자신은 종료한다)
- execv와는 달리 인자를 하나하나 넣는다
- 인자를 모두 넣었으면 맨마지막 인자로 널포인터(NULL, (char *)0)를 넣어 인자의 나열이 끝났음을 알린다
기타설명
- ① execl( "/bin/ls", "/bin/ls", "-al", "/tmp", NULL);
이런식으로 사용하는데, 두번째 인자는 그냥 실행파일(혹은 명령)만 넣어도 잘된다
execl( "/bin/ls", "ls", "-al", "/tmp", NULL); //이렇게
- ② path경로 입력후 그다음은 인자를 넣어야하는데 "/bin/ls"를 한번더 넣는다(혹은 "ls")
개인적으로 생각한 이유는 다음과 같다
- c언어에서 공부 했듯이(16장의 6.) 명령행 인수를 받을때 실행파일명을 포함하여 명령행 인수를 센다
- 즉 리눅스의 명령행에서 "ls -al"이라고 입력했으면 ls와 al 두개의 인수를 받는것이다.
- (그렇기때문에 /bin/ls를 하던 ls를 하던 문제가없다. 이미 경로를 지정하였기 때문에..)
- 널값(널포인터)을 넣는것도 같은이유이다
참조
- http://forum.falinux.com/zbxe/index.php?document_srl=408554&mid=C_LIB
- execv
필요한 헤더파일
- <unistd.h>
원형
- int execv( const char *path, char *const argv[]);
들어가는 인자 의미
- char *path 실행 파일의 디레토리 포함 전체 파일 명
- char *argv[] 인수 목록
반환되는 값 의미
- int형을 반환(실패일 때만 -1)
특징
- 새로운 프로그램을 기존 프로그램에 덮어쓴다(=다른 프로그램을 실행하고 자신은 종료한다)
- execl와는 달리 포인터배열을 이용해 한번에 넣는다
- 인자를 모두 넣었으면 맨마지막 인자로 널포인터(NULL, (char *)0)를 넣어 인자의 나열이 끝났음을 알린다
사용예
char *argv[] ={ "/bin/ls", "-al", "/tmp", NULL};
execv( "/bin/ls", argv);
참조
- http://forum.falinux.com/zbxe/index.php?mid=C_LIB&page=4&document_srl=408563
- exit
필요한 헤더파일
- <stdlib.h>
원형
- void exit(int status);
들어가는 인자 의미
- 해당 프로세스에게 알려 줄 종료 값
- 일반적으로 0은 정상종료, 1은 에러를 의미
반환되는 값 의미
- 없음
특징
- 주로 에러가 났을 때 강제 종료시키기 위해 if문 속에서 사용한다
- atexit(함수명);을 통해 종료될때 실행될 함수를 지정할 수 있다
참조
- http://ehpub.co.kr/exit-%ED%95%A8%EC%88%98/
※ exit와 return의 차이
C 표준에 따르면,
①재귀적으로 불리지 않은) 처음 시작한 main()이 "return X"를 실행하는 것은, exit(X)를 부르는 것과 완전히 같다.
②(재귀적으로 불리지 않은) 처음 시작한 main()이 블럭의 끝을 알리는 "}"에 다다르면 "return 0" 즉 exit(0)을 부른 것과 완전히 같다.
예외는 main()에서 만든 local variable이 파괴되는 시점이다. exit()를 부르면, exit()가 atexit()로 등록한 함수들을 실행할 때까지
local variable은 남아 있지만, return으로 끝냈을 경우, local variable이 다 파괴된 다음에 exit()가 호출된다. 따라서 atexit()로
등록한 함수가 없다면, return이나 exit()를 부르는 것이나 같다고 볼 수 있다
=> 즉 return은 변수등의 메모리 삭제후 exit호출, exit로 끝낼경우는 exit의 과정이 다끝나고 변수등의 메모리삭제
=> 자세한 내용을 알고자 할때는 컴파일쪽을 알아야한다고 한다
- 추가적으로 exit는 블록 중간에 삽입이 가능하지만, return은 블록 끝에만 위치해야하는 것 같다 참조: https://kldp.org/node/47649
- abort
필요한 헤더파일
- <stdlib.h>
원형
- void abort(void);
들어가는 인자 의미
- 없음
반환되는 값 의미
- 없음
특징
- abort함수를 호출하면 SIGABRT 시그널이 발생하여 강제종료된다(즉 비정상 종료를 의미한다)
- exit() 함수와 마찬가지로 abort() 함수는 프로그램을 종료하기 전에 버퍼를 삭제하고 열린 파일을 닫는다.
- 프로그램이 종료되고 오류메세지가 뜬다(리눅스에서는 'Aborted (core dumped)'이런식으로 끈다)
참조
- https://www.ibm.com/support/knowledgecenter/ko/ssw_ibm_i_73/rtref/abort.htm
- wait
필요한 헤더파일
- <wait.h>
- <sys/types.h> //pid_t라는 자료형때문에 필요(#define pid_t int이기때문에 그냥 int를 써도 무리는 없다)
원형
- pid_t wait(int *status)
들어가는 인자 의미
- int status 자식 프로세스 종료 상태
- (포인터로 넘겼으니 해당변수는 부모프로세스에 있고, 부모프로세스는 자식프로세스가 종료되면 그 상태값을 받아올 수 있다)
반환되는 값 의미
- pid_t 종료된 자식 프로세스의 PID
특징
- 자식 프로세스 작업이 끝날 때 까지 대기하며, 자식 프로세스가 종료한 상태를 구한다.
- 자식프로세스의 종료시 그 상태값은 status에 저장되고, 종료된 자식프로세스의 pid값이 반환된다
기타설명
- ① status는
정상종료의 경우 상위8비트에 자식이 반환한(return, exit)값이 있고, 하위 8비트는 0값
비정상종료의 경우 상위8비트는 0, 하위 8비트에 종료시킨 시그널의 번호가 저장된다
- ② status를 통해 정상종료인지, 비정상인지 구분하는 법은 다음과 같다
status &= 0xFF; //상위8비트는 무조건 0이되고 하위8비트는 그 값이 그대로 남으니, 정상종료의 경우 0, 비정상종료의 경우 시그널 번호가 남는다
- ③ status를 통해 정상종료인것을 확인하면( if(status &= 0xFF==0) ) 상위비트의 값을 구한다
status>>8; //8번 비트시프트하여 값을 구한다
사용예
pid_t pid;
pid_t child_pid;
int status;
pid = fork();
switch( pid)
{
case -1 :
{
printf( "자식 프로세스 생성 실패\n");
return -1;
}
case 0 :
{
printf( "이프로세스는 자식이다\n");
return 99;
}
default :
{
printf( "이 프로세스는 부모이다\n");
child_pid = wait(&status);
printf( "자식의 PID: %d\n,", child_pid);
if ( 0 == ( status & 0xff))
{
printf( "정상적으로 종료되었고 반환값은 %d입니다\n", status >> 8);
}
else
{
printf( "비 정상으로 종료되었고 종료 시그널 번호는 %d입니다\n", status);
}
}
참조
- http://forum.falinux.com/zbxe/index.php?document_srl=408545&mid=C_LIB
- waitpid같이 자식 프로세스 종료를 확인하는 함수도 있다
http://forum.falinux.com/zbxe/index.php?document_srl=408548&mid=C_LIB
'Study > Linux' 카테고리의 다른 글
프로세스의 잘못된 종료상태 실습(zombie, orphan) (0) | 2018.02.05 |
---|---|
프로세스 생성 및 삭제 실습(fork, exec[execl, execv], fork+exec, wait) (0) | 2018.02.05 |
리눅스 스터디 - 6 (0) | 2018.02.04 |
정적, 동적 라이브러리 실습 (0) | 2018.01.22 |
리눅스 스터디 - 5 (0) | 2018.01.21 |