반응형

Linux의 프로그램, 프로세스, 쓰레드

 

1. 실행환경 

  1.1) Foreground Process : 실행 후 종료시까지 사용자가 다른 입력을 하지 못하는 프로세스

      * Foreground Process는 프로세스 실행 도중에 사용자의 입력을 받지 않기 때문에 입력을 넣기 위해서는 기존 실행중인 프로세스를 중지시켜야 하고, 이 명령은 [Ctrl + Z]로 가능하다. 이렇게 중지시킨 프로세스는 jobs 명령어를 통해 확인이 가능하고, bg 명령어를 통하여 재시작할 수 있다.

 

  1.2) Background Process : 사용자 입력과 상관없이 실행되는 프로세스

      * ex) find / -name '*.py' > list.txt & 

             와 같이 맨 뒤에 &를 붙이면 Background Process로 실행된다.

 

2. 프로세스

 : 메모리에 적재되어 실행중인 프로그램

1) 프로세스의 생성

* 프로세스 ID : 파일의 inode처럼 각 프로세스는 해당 시점에 unique한 값을 가진다. 최초의 프로세스는 init으로 프로세스 id가 1이며 운영체제에 의해 생성된다. 프로세스 id의 최대값은 32768(2의 15승, 16비트)이다.

1) 기존 프로세스에서 fork() 시스템 콜을 호출하면, 기존 프로세스를 복사하여 새로운 프로세스 공간을 만든다. 

(부모 프로세스 : 기존 프로세스, 자식 프로세스 : 기존 프로세스를 복사하여 생성된 프로세스)
2) fork() 시스템 콜 이후 자식 프로세스 → 부모 프로세스 순으로 코드를 실행한다.

( 자식 프로세스의 pid = 0)
3) 이 때 부모 프로세스가 먼저 종료되는 일이 없도록 wait()함수를 사용한다. 
4) 프로세스가 종료되면 exit()을 통하여 종료 상태가 기록되고, 부모 프로세스에 SIGCHLD 시그널이 보내지며, wait()가 풀리면서 부모 프로세스의 코드가 실행된다. 

 

* 부모 프로세스 : 기존에 존재하는 프로세스

* 자식 프로세스 : fork()를 통해 기존에 존재하는 프로세스를 복제하여 생성되는 프로세스

 

* 프로세스 관련 시스템콜

* getpid() : 프로세스 id를 리턴한다.
* getppid() : 부모 프로세스의 프로세스 id를 리턴한다.
* fork() : 새로운 프로세스 공간을 별도로 만들고, fork()시스템콜을 호출한 프로세스(부모 프로세스)공간을 모두 복사한다.
자식 프로세스는 pid가 0으로 리턴, 부모 프로세스는 실제 pid리턴, 두 프로세스의 변수 및 PC 값은 동일하다. fork 이후의 코드만 두번 실행된다. * exec() 시스템콜 : 시스템콜을 호출한 현재 프로세스 공간의 text, data, bss 영역을 새로운 프로세스의 이미지로 덮어씌움, 시스템 콜 여러가지가 있다.

 

* exec(실행파일이름/인자) : 해당 실행 파일의 데이터를 현재 프로세스에 덮어씌운다. 일반적으로 fork 후에 자식 프로세스에서 exec를 실행하여 부모 프로세스와 다른 프로세스를 만든다. exec는 다양한 종류의 함수가 존재한다.

1) execl("파일이름", "argv[0]", "argv[1]", ...., NULL(맨 마지막은 무조건 null))
2) execlp : 파일 이름이 path에 있으면 path 안써줘도 된다.
3) execle 는 직접 환경변수 지정
4) execvp()
5) execv()
6) execve()

* wait(int *status) : fork()함수 호출 시, 자식 프로세스가 종료될 때까지 부모 프로세스가 기다리게 된다. 이 때, 부모 프로세스가 자식 프로세스보다 먼저 죽는 경우를 막기 위해 사용한다. 자식 프로세스가 종료될 때 exit(int *status) 시스템 콜을 통하여 status 정보가 전달되고, wait()에서 해당 값을 읽고 진행한다. 
(부모 프로세스는 status & 0377 계산값으로 자식 프로세스 종료 상태 확인 가능하다.)

 

* exit() : 시스템 콜, 프로세스를 강제로 즉시 종료시킨다. 인자로 프로세스 종료 상태 번호를 전달한다.  
프로세스 종료 상태 번호 :  
1) 0 : 정상 종료 
2) 1 : 비정상 종료 

주요 동작 과정 :  
1) atexit()에 등록된 함수 실행(등록한 함수를 역순으로 실행한다.) 
2) 열려있는 모든 입출력 스트림 버퍼 삭제 
3) 프로세스가 오픈한 파일을 모두 닫음 
4) tmpfile() : 함수를 통해 생성한 임시 파일 삭제 

 

* nice(int num) : 현재 실행중인 프로세스의 우선순위 변경, 변경할 숫자를 인자로 전달한다. 
* getpriority(int which, id_t who) : 현재 프로세스의 우선순위 조회 
* setpriority(which, id_t who, int num) : 특정 프로세스의 우선순위 변경 

3. 프로세스간 커뮤니케이션 : IPC

1) 파이프 : 프로세스에서 파이프를 만들고 fork()를 하면, 부모가 자식에게 단방향 통신 가능하다.

* pipe(int arr) : 파이프 생성
-> pipe를 생성한 뒤 인자값으로 int형 배열 int arr[2]를 전달한다. 이 때 부모는 arr[1]에다 write하고, 자식은 arr[0]을 읽는다.

2) 메시지 큐

* msgget(key, msgflg) : 메세지 큐 생성
* msgsnd(msqid, &sbuf, buf_length, IPC_NOWAIT) : 메시지 전송
* msgrcv() : 메시지 읽기, 읽는 메시지 타입 지정할 수 있음

* ftok() : 키 생성을 위한 함수

3) 공유메모리 : kernel space에 메모리 공간을 만들고, 해당 공간을 변수처럼 쓰는 방식

* shmget(key_t key, size_t size, int shmflg) : 공유 메모리 생성
* shmat(int shmid, const void *shmaddr, int shmflg) : 공유 메모리 연결
* shmdt(int shmid) : 공유메모리 해제

*ipcs : 리눅스 명렁어, 현재 message queue, shared memory, semaphore 등 생성된 것들을 확인할 수 있다.

4) 시그널 : 커널 또는 프로세스에서 다른 프로세스에 어떤 이벤트가 발생되었는지를 알려주는 전통적인 기법
프로세스는 PCB에 해당 프로세스가 블록 또는 처리해야하는 시그널 정보를 관리한다. 


시그널의 종류 : 
1) SIGKILL : 프로세스를 죽여라
2) SIGALARM : 알람을 발생한다.
3) SIGSTP : 프로세스를 멈춰라(Ctrl + z)
4) SIGCONT : 멈춰진 프로세스를 실행하라
5) SIGINT : 프로세스에 인터럽트를 보내서 프로세스를 죽여라(Ctrl + c)
6) SIGSEGV : 프로세스가 다른 메모리 영역을 침범했다.
(이것 외에도 많다)


* signal(SIGINT, SIG_IGN) : 받은 시그널에 따른 동작 정의
SIG IGN - TLRMSJF ANTL, SIG_DFL - 디폴트 동작


3) 기타

* copy-on-write : fork()는 새로운 프로세스 공간을 생성하고 기존 프로세스를 복사한다. 복사할 때, 4GB나 복사하는 것은 많은 자원의 소모를 야기한다. 따라서 자식 프로세스에서 데이터 사용 시, 부모 프로세스의 페이지를 우선 사용한다.(내용은 동일하기 때문) 읽기에 대해서는 부모 페이지를 계속 참조하고, 쓰기를 할 때 부모 페이지를 복사하고 분리한다. 
(필요한 페이지만 새로 생성하여 사용)

 

ex) 리눅스의 프로세스에 할당된 4GB의 가상 메모리 중 1GB는 커널 메모리인데, 해당 메모리는 각 프로세스마다 모두 공유한다.(물리적으로 동일) 

 

* Pthread : 유닉스 시스템 핵심 스레딩 라이브러리 

pthread.h에 정의되어 있으며, 모든 함수는 pthread_로 시작한다.

1) 스레드 관리 

1. pthread_create() 
2. pthread_exit() 
3. pthread_join() : 특정 쓰레드가 끝나는 시점을 결정, 해당 함수 아래 코드는 해당 함수에 인자로 전달된 쓰레드가 종료될때까지 실행되지 않는다.(이 때 쓰레드 종료 시 상태값을 전달하며, 리소스 해제) 
4. pthread_detach() : 특정 쓰레드가 종료되면 즉시 관련 리소스를 해제한다. 종료 후 추가 처리 없음 

2) 동기화 : 임계영역 관리를 위해서 mutex와 같은 방법을 사용해야 한다. 

1. pthread_mutex_lock() 
2. pthread_mutex_unlock() 

* 파일시스템 

1) 동적 메모리 생성 : 
  1.1) malloc : heap 영역에 동적 메모리 할당 
  1.2) free : 메모리 해제 
  1.3) mmap : 파일의 특정 공간을 메모리의 특정 공간에 mapping해둔다. 따라서 시간,자원이 많이 걸리는 프로세스와 파일간의 interaction을 최소화한다.(메모리의 주소값을 리턴) 
  1.4) munmap : mapping된 물리 메모리 주소를 해제한다. 
  1.5) msync : 메모리와 파일의 데이터를 동기화 

2) inode 
  2.1) stat() : inode 상태값들을 가져온다. 

반응형

+ Recent posts