프로세스 생성
fork()
- 부모 프로세스를 똑같이 복제하여 새로운 자식 프로세스를 생성
- 새로운 프로세스를 생성하는 유일한 방법
- 코드, 데이터, 스택, 힙 등을 똑같이 복제
- fork()는 한 번 호출되면 두 번 리턴함 (호출 후에는 프로세서가 둘이기 때문에)
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
//새로운 자식 프로세스를 생성
//자식 프로세스에게는 0
//부모 프로세스에게는 자식 프로세스 ID를 리턴
자식은 부모의 fd 테이블을 복사한다
자식에게 상속되지 않는 성질
- fork()의 반환 값
- 프로세스 ID
- 파일 잠금 속성
- 설정된 알람과 시그널
fork1.c
자식 프로세스를 생성하는 코드를 짠 후 프로세스의 실행 결과를 확인해 보자
#include <stdio.h>
#include <unistd.h>
/* 자식 프로세스를 생성한다. */
int main(){
int pid;
printf("[%d] 프로세스 시작 \n", getpid());
pid = fork();
printf("[%d] 프로세스 : 리턴값 %d\n", getpid(), pid);
}
부모 프로세스는 자식 프로세스의 ID를, 자식 프로세스는 0을 return
fork2.c
fork() 호출 후에 리턴 값이 다름을 이용하여
부모 프로세스와 자식 프로세스를 구별해 서로 다른 일을 하도록 하는 프로그램을 만들어보자
#include <stdlib.h>
#include <stdio.h>
/* 부모 프로세스가 자식 프로세스를 생성하고 서로 다른 메시지를 프린트 */
int main() {
int pid;
pid = fork();
if (pid ==0) { // 자식 프로세스
printf("[Child] : Hello, world pid=%d\n“, getpid());
}
else { // 부모 프로세스
printf("[Parent] : Hello, world pid=%d\n", getpid());
}
}
if 문은 0을 반환받은 자식 프로세스가 실행하고, else 문은 부모 프로세스가 실행
fork3.c
두 개의 자식 프로세스를 생성하는 프로그램을 작성해보자
#include <stdlib.h>
#include <stdio.h>
/* 하나의 부모 프로세스가 두 개의 자식 프로세스를 생성한다. */
int main() {
int pid1, pid2;
pid1 = fork();
if (pid1 == 0) { //첫번째 자식만 실행, 부모는 실행부분 없음
printf("[Child 1] : Hello, world ! pid=%d\n", getpid());
exit(0); //첫번째 자식 종료
}
pid2 = fork(); //부모의 두번째 자식 생성
if (pid2 == 0) {
printf("[Child 2] : Hello, world ! pid=%d\n", getpid());
exit(0); //두번째 자식 종료
}
}
부모는 두 자식 프로세스를 생성하는 기능만 수행하고 종료
두 자식들은 각각 해당 문장을 출력 후 종료
wait()
자식 프로세스 중의 하나가 끝날 때까지 기다리는 함수
실행이 끝난 자식 프로세스의 종료 코드 (exit(0), exit(1)등)을 status에 저장하고, 해당 자식 프로세스의 PID를 리턴한다.
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
//특정 pid 프로세스의 종료 대기. 옵션은 주로 0
forkwait.c
wait() 함수를 사용하여 부모 프로세스가 자식 프로세스를 생성하고 끝나기를 기다리는 프로그램을 작성해보자
#include <stdio.h>
#include <unistd.h>
/* 부모 프로세스가 자식 프로세스를 생성하고 끝나기를 기다린다. */
int main() {
int pid, child, status; // 정수형 변수 status (4바이트값)
printf("[%d] 부모 프로세스 시작 \n", getpid( ));
pid = fork();
if (pid == 0) {
printf("[%d] 자식 프로세스 시작 \n", getpid( ));
exit(1); // 종료 코드 1이 wait 함수 인자(여기서는 status)에 저장
}
child = wait(&status); // 자식 프로세스가 끝나기를 기다리고 자식 PID 반환
printf("[%d] 자식 프로세스 %d 종료 \n", getpid(), child); //부모가 수행
printf("\t종료 코드 %d\n", status>>8);
}
종료 코드 값은 status 변수 값 4 바이트 가운데, 3번째 바이트에 저장. 따라서 우측 shift 8 bit 수행 후 출력
waitpid.c
wait() 함수 대신 waitpid() 함수를 사용해보자
waitpid()의 세 번째 옵션은 아무 실행 없이 자식 종료 시까지 대기하는 '0'을 사용
#include <sys/types.h>
/* 부모 프로세스가 특정 자식 프로세스를 생성하고 끝나기를 기다린다. */
int main() {
int pid1, pid2, child, status;
printf("[%d] 부모 프로세스 시작 \n", getpid( ));
pid1 = fork();
if (pid1 == 0) {
printf("[%d] 자식 프로세스[1] 시작 \n", getpid( ));
sleep(1);
printf("[%d] 자식 프로세스[1] 종료 \n", getpid( ));
exit(1);
}
pid2 = fork();
if (pid2 == 0) {
printf("[%d] 자식 프로세스 #2 시작 \n", getpid( ));
sleep(2);
printf("[%d] 자식 프로세스 #2 종료 \n", getpid( ));
exit(2);
}
// 자식 프로세스 #1의 종료를 기다린다.
child = waitpid(pid1, &status, 0);
printf("[%d] 자식 프로세스 #1 %d 종료 \n", getpid( ), child);
printf("\t종료 코드 %d\n", status>>8);
}
두 번째 프로세스는 종료하였으나 부모 프로세스가 대기하지 않아 좀비 프로세스가 된다.
자식 프로세스 1, 2가 생성되는 순서는 상관없음
프로그램 실행
fork() 후 자식 프로세스는 부모 프로세스와 똑같은 코드를 실행한다.
따라서 자식 프로세스에게 새 프로그램을 실행시켜야 할 때 exec() 시스템 호출 사용
- 프로세스 내의 프로그램을 새 프로그램으로 대치
- 프로세스 내에서 새로운 프로그램을 실행시키는 유일한 방법
exec()
프로세스가 exec() 호출을 하면, 그 프로세스 내의 프로그램은 완전히 새로운 프로그램으로 대치된다.
즉 새 프로그램의 main()부터 실행이 시작된다.
exec() 호출이 성공하면 리턴할 곳이 없어지므로 성공한 exec() 호출은 절대 리턴하지 않는다.
#include <stdio.h>
#include <unistd.h>
/* echo 명령어를 실행한다. */
int main( ) {
printf("시작\n");
execl("/bin/echo", "echo", "hello", NULL);
printf("exec 실패!\n");
}
fork() / exec()
- 보통 fork() 호출 후에 exec()를 호출
- 새로 실행할 프로그램에 대한 정보를 exec()의 arguments로 전달
- exec() 호출이 성공하면 자식 프로세스는 새로운 프로그램을 실행하게 되고, 부모는 계속해서 다음 코드를 실행하게 된다.
execute1.c
자식 프로세스를 생성하여 echo 명령어를 실행해보자
#include <stdio.h>
/* 자식 프로세스를 생성하여 echo 명령어를 실행한다. */
int main( ) {
printf("부모 프로세스 시작\n");
if (fork( ) == 0) {
execl("/bin/echo", "echo", "hello", NULL);
fprintf(stderr,"첫 번째 실패"); //exec이 성공하면 이 줄은 결코 실행되지 않음
exit(1);
}
printf("부모 프로세스 끝\n"); // 부모는 wait() 수행 없이 자식 생성 후, 바로 종료
}
실행결과
부모 프로세스 시작
부모 프로세스 끝
hello
execute2.c
세 개의 자식 프로세스를 생성하여 각각 다른 명령어를 실행해보자
#include <stdio.h> …
/* 세 개의 자식 프로세스를 생성하여 각각
다른 명령어를 실행한다. */
int main( ) {
printf("부모 프로세스 시작\n");
if (fork( ) == 0) {
execl("/bin/echo", "echo", "hello", NULL);
fprintf(stderr,"첫 번째 실패");
exit(1);
}
if (fork( ) == 0) {
execl("/bin/date", "date", NULL);
fprintf(stderr,"두 번째 실패");
exit(2);
}
if (fork( ) == 0) {
execl("/bin/ls","ls", "-l", NULL);
fprintf(stderr,"세 번째 실패");
exit(3);
}
printf("부모 프로세스 끝\n");
}
execute3.c
명령줄 인수로부터 받은 임의의 명령어가 실행 가능한 코드를 작성해보자
#include <stdio.h> …
/* 명령줄 인수로 받은 새로운 명령을 실행시킨다. */
int main(int argc, char *argv[]){
int child, pid, status;
pid = fork( );
if (pid == 0) { // 자식 프로세스
execvp(argv[1], &argv[1]);
fprintf(stderr, "%s:실행 불가\n",argv[1]); //성공하면 이 문장은 결코 실행되지 않음
}
else { // 부모 프로세스
child = wait(&status); //종료 코드를 status에 넣고, status와 자식 pid 반환
printf("[%d] 자식 프로세스 %d 종료 \n", getpid(), pid);
printf("\t종료 코드 %d \n", status>>8);
}
execute3 ls >>
execute4.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/* 자식 프로세스를 생성하여 echo 명령어를 실행한다. */
int main( ) {
int pid, child, status;
printf("부모 프로세스 시작\n");
pid = fork();
if (pid == 0) {
execl("/bin/echo", "echo", "hello", NULL);
fprintf(stderr,"첫 번째 실패");
exit(1);
}
else {
child = wait(&status);
printf("자식 프로세스 %d 끝\n", child);
printf("부모 프로세스 끝\n");
}
}
system()
fork() 시스템 호출을 수행하고, exec() 시스템 호출을 연속하여 명령어를 실행시키는 것은 상당히 번거로운 절차이다.
따라서 이러한 과정을 자동으로 수행하는 C 라이브러리 함수 system()이 있다.
자식 프로세스를 생성하고 /bin/sh로 하여금 지정된 명령어를 실행시킨다.
ex) system("ls - asl")
#include <sys/wait.h>
#include <stdio.h>
int main(){
int status;
if ((status = system("date")) < 0)
perror("system() 오류");
printf("종료코드 %d\n", WEXITSTATUS(status));
if ((status = system("hello")) < 0) //fork() is ok, but no exec(hello) 해당 명령어 없음
perror("system() 오류"); //i.e., exec 실패, 종료 코드 127
printf("종료코드 %d\n", WEXITSTATUS(status));
if ((status = system("who; exit 44")) < 0) // 명령 실행 후, 특정 종료 코드 지정 가능
perror("system() 오류");
printf("종료코드 %d\n", WEXITSTATUS(status));
}
WEXITSTATUS() : <sys/wait.h>에 정의된 매크로 함수. 내부적으로 표현된 종료 코드 값을 찾아 반환함
date 명령어 실행
존재하지 않는 명령어 실행 -> exec 실패, 종료 코드 127
who 명령어 실행 후 특정 종료 코드 44 반환
프로세스 그룹
프로세스 그룹은 여러 프로세스들의 집합이다.
보통 부모 프로세스가 생성하는 자손 프로세스들은 부모와 같은 프로세스 그룹에 속한다.
프로세스 그룹 리더는 Process GID와 PID가 동일하다.
프로세스 그룹은 그룹 내의 모든 프로세스에 시그널을 보낼 때 사용된다.
- kill -9 pid
- kill -9 0 (pid 대신 0을 입력하면 현재 속한 그룹내의 모든 프로세스 종료)
- kill -9 -pid (음수로 표시된 pid는 gid. 특정 그룹 모두에게 전달)
pgrp1.c
각 프로세스는 자신이 속한 프로세스 그룹 ID를 가지며 fork 시 같은 그룹을 물려받는다.
#include <sys/types.h>
#include <unistd.h>
int main(){
int pid, gid;
printf("PARENT: PID = %d GID = %d\n", getpid(), getpgrp());
pid = fork();
if (pid == 0) { // 자식 프로세스
printf("CHILD: PID = %d GID = %d\n", getpid(), getpgrp());
}
}
PID는 다르고, GID는 부모 자식 사이에 동일하게 생성된다.
'Linux' 카테고리의 다른 글
[리눅스] 시스템 프로그래밍 - 메모리 관리 (1) | 2022.12.09 |
---|---|
[리눅스] 시스템 프로그래밍 - 프로세스 (0) | 2022.12.02 |
[리눅스] 시스템 프로그래밍 - 파일 시스템 (0) | 2022.11.23 |
[리눅스] 시스템 프로그래밍 - 파일 입출력 (0) | 2022.11.23 |
[리눅스] 유틸리티 (0) | 2022.10.17 |