반응형
프로세스 관리
- 프로그램: 바이너리, 코드 이미지, 응용프로그램 또는 실행 파일
- 프로세스: 실행 중인 프로그램 (메모리 적재 + 프로세스 상태 정보 포함)
- 스레드
- 리눅스 프로세스는 기본 스레드 포함
- 싱글 스레드 프로세스: 기본 프로세스
- 멀티 스레드 프로세싀: 여러 스레드 존재
프로세스 ID
- pid, 각 프로세스는 해당 시점에 unique한 pid를 가짐
- pid 최대 값은 32768 = 2의 15승
- 부호형 16비트 정수값 사용
- 최근 할당된 pid가 200이라면, 그 이후는 201, 202 식으로 할당
$ sudo vi/proc/sys/kernel/pid_max
프로세스 계층
- 최초 프로세스: init 프로세스, pid 1
- init 프로세스는 운영체제가 생성 (단군 할아버지)
- 다른 프로세스는 또다른 프로세스로부터 생성 (부모 프로세스, 자식 프로세스)
- ppid값이 부모 프로세스의 pid를 뜻함
ps -ef
- -e: 시스템상의 모든 프로세스에 대한 정보 출력
- -f: 다음 목록 출력(UID, PID, PPID, CPU%, STIME, TTY, TIME, CMD)
프로세스와 소유자 관리
리눅스 내부에서는 프로세스의 소유자와 그룹을 UID/GID (정수)로 관리
사용자에 보여줄때에만 UID와 사용자이름 매핑 정보를 기반으로 사용자 이름으로 제공
$ ps -ef
$ sudo vi /etc/passwd
$ sudo vi /etc/shadow
프로세스 관리 관련 시스템콜
해당 프로세스의 pid와 부모 프로세스의 id(ppid)
- 함수 원형
#include <sys/types.h>
#include <unistd.h>
pid_t getpid (void);
pid_t getppid (void);
- 실습 코드
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
printf ("pid=%d\n", getpid());
printf ("ppid=%d\n", getppid());
return 0;
}
프로세스 생성
- 기본 프로세스 생성 과정
- TEXT, DATA, BSS, HEAP, STACK의 공간을 생성
- 프로세스 이미지를 해당 공간에 업로드하고, 실행 시작
- 프로세스 계층: 다른 프로세스는 또다른 프로세스로부터 생성
- 부모 프로세스, 자식 프로세스
fork()와 exec() 시스템콜
fork() 와 exec() 이 순차적으로 실행 되어 새로운 프로세스를 만든다.
- fork()
- 새로운 프로세스 공간을 별도로 만들고 fork() 시스템콜을 호출한 프로세스(부모 프로세스) 공간을 모두 복사
- 별도의 프로세스 공간을 만들고, 부모 프로세스 공간의 데이터를 그대로 복사
- exec()
- exec() 시스템콜을 호출한 현재 프로세스 공간의 TEXT, DATA, BSS 영역을 새로운 프로세스의 이미지로 덮어씌움
- 별도의 프로세스 공간을 만들지 않음
fork() 시스템콜
- pid = fork()가 실행되면 부모 프로세스와 동일한 자식 프로세스가 별도 메모리 공간에 생성
- 자식 프로세스는 pid가 0으로 리턴, 부모 프로세스는 실제 pid 반환
- 두 프로세스의 변수 및 PC 값은 동일
- 새로운 프로세스 공간을 별도로 만들고, fork() 시스템콜을 호출한 프로세스 공간을 모두 복사한 후 fork() 시스템콜 이후 코드부터 실행
# 헤더파일
<unistd.h>
# 함수 원형
pid_t fork(void);
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
printf("Before fork() call\n");
pid = fork();
if (pid == 0)
printf("This is Child process. PID is %d\n", pid);
else if (pid > 0)
printf("This is Parent process. PID is %d\n", pid);
else
printf("fork is failed\n");
return 0;
}
exec() 시스템콜
- execl(전체 디렉토리와 파일 이름을 합친 이름, argv[0], argv[1], NULL);
- 기존의 프로세스를 새로운 프로세스 argv( ls -l )로 덮어씌운다 .
- execl() 시스템콜이 성공하지 못한 경우 다음 코드인 perror() 코드가 실행되어 성공 여부를 알 수 있다.
-
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { printf("execute ls\n"); execl("/bin/ls", "ls", "-l", NULL); perror("execl is failed\n"); exit(1); }
-
- execlp(환경변수가 설정된 파일 이름, argv[0], argv[1], NULL);
-
execl("ls", "ls", "-l", NULL);
-
- execle()
-
// 환경 변수를 지정하고자 할 때 char *envp[] = {"USER=david", "PATH=/bin", (char *)0}; execle("ls", "ls", "-al", NULL, envp);
-
- execv()
-
// 인수 리스트를 내용으로 하는 문자열 배열 char *arg[] = {"ls", "-al", NULL}; execle("bin/ls", arg);
-
- execvp()
-
// 환경변수 설정된 파일이름 + 인수 리스트를 내용으로 하는 문자열 배열 char *arg[] = {"ls", "-al", NULL}; execle("ls", arg);
-
- execve()
-
// 환경변수 지정 + 인수 리스트를 내용으로 하는 문자열 배열 char *envp[] = {"USER=david", "PATH=/bin", (char *)0}; char *arg[] = {"ls", "-al", NULL}; execle("ls", arg, envp);
-
wait() 시스템콜
- wait() 시스템콜을 사용하면, fork() 함수 호출시 자식 프로세스가 종료할 떄까지 부모 프로세스가 기다림 (시그널 사용)
- 자식 프로세스와 부모 프로세스의 동기화, 부모 프로세스가 자식 프로세스보다 먼저 죽는 경우를 막기 위해 사용 (고아 프로세스)
- 정상적인 동작은 자식 프로세스가 끝나면 자식 프로세스 정보는 메모리에 남아있고 부모 프로세스가 이를 확인하면 자식 프로세스는 메모리에서 해제된다.
- 부모 프로세스가 먼저 죽는 경우 확인할 수 없어 자식 프로세스가 메모리에서 해제되지 않는다.
- 자식 프로세스는 종료되면 좀비 프로세스가 되어 해당 프로세스 조사를 위한 최소 정보만 가지고 있는 상태가 된다.
- 완전히 끝나면 해당 정보도 삭제되고 부모 프로세스에 SIGCHLD 시그널이 보내진다.
- wait() 리턴값
- 에러가 발생한 경우
-
#include <sys/wait.h> pid_t wait (int *status) # 리턴값은 종료된 자식 프로세스의 pid
- status 정보를 통해 기본적인 자식 프로세스 관련 정보를 확인할 수 있음
- int WIFEXITED(status); - 자식 프로세스가 정상 종료 시 리턴값은 0이 아닌 값이 됨
fork(), execl(), wait() 예시
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
int pid;
int child_pid;
int status;
pid = fork(); // 자식 프로세스 생성
switch (pid) {
case -1:
perror("fork failed\n");
break;
case 0:
execl("bin/ls", "ls", "-al", NULL); // 자식 프로세스를 새로 덮어씌움
perror("execl failed\n");
break;
default:
child_pid = wait(NULL); // 부모 프로세스는 자식 프로세스가 끝날때까지 기다림
printf("ls is complete\n"); // 자식 프로세스가 끝남
printf("Parent PID (%d), Child PID (%d)\n", getpid(), child_pid);
exit(0);
}
}
exit() 시스템콜
프로세스 종료
#include <stdlib.h>
void exit(int status); # status: 프로세스 종료상태 번호
main 함수의 return 0;과 exit(0); 의 차이
- exit() 함수: 즉시 프로세스를 종료함 (exit() 함수 다음에 있는 코드는 실행되지 않음)
- return 0: 단지 main() 이라는 함수를 종료함
- 단 main()에서 return 할때, C언어 실행파일에 기본으로 포함된 _start()함수를 호출하고, 해당 함수는 결국 exit()함수를 호출한다.
- 따라서 main() 함수에서 return 0;과 exit() 호출은 큰 차이가 없다.
부모 프로세스는 status & 0377 계산 값으로 자식 프로세스 종료 상태를 확인할 수 있다.
- exit(EXIT_SUCCESS): EXIT_SUCCESS = 1
- exit(EXIT_SUCCESS): EXIT_SUCCESS = 1
exit() 시스템콜 주요 동작
- atexit()에 등록된 함수 실행
- 열려 있는 모든 입출력 스트림 버퍼 삭제 (stdin, stdout, stderr)
- 프로세스가 오픈한 파일을 모두 닫음
- tmpfile() 함수를 통해 생성한 임시 파일 삭제
- tmpfile(): - 임시파일을 wb+ (쓸 수 있는 이진파일 형대) 모드로 오픈가능
atexit()
- 프로세스 종료시 실행될 함수를 등록하기 위해 사용
- 등록된 함수를 등록된 역순서대로 실행
copy-on-write (write시에 복사한다.)
- fork()는 새로운 프로세스 공간 생성 후, 기존 프로세스 공간 복사
- 4GB를 복사한다면, 프로세스 생성 시간이 오래 걸림
- 자식 프로세스 생성시, 부모 프로세스 페이지를 우선 사용
- 부모 또는 자식 프로세스가 해당 페이지를 읽기가 아닌 쓰기를 할 때 페이지를 복사하고 분리한다
- write 요청시
- 새로 페이지를 복사하고
- page pointer를 변경한다.
- 장점
- 프로세스 생성 시간을 줄일 수 있다
- 새로 생성된 프로세스에 새롭게 할당되어야 하는 페이지 수도 최소화
반응형
'Computer Science' 카테고리의 다른 글
Pthread (0) | 2022.01.19 |
---|---|
쉘 스크립트 (0) | 2022.01.14 |
시스템 프로그래밍 핵심 기술 (0) | 2022.01.09 |
시스템 프로그래밍 - 쉘 사용법 (0) | 2022.01.06 |
시스템 프로그래밍 - 리눅스 (0) | 2022.01.03 |
댓글