프로세스 관리(1)

2023. 11. 5. 21:45그림 리눅스

728x90
반응형

시스템에는 다양한 프로세스가 존재, 시스템에 존재하는 모든 프로세스를 확인하려면 "ps aux"명령어를 실행

ps 명령어는 --no-header 옵션으로 헤더 출력을 제거 가능

ps aux --no-header | wc -l  프로세스 개수 조사

프로세스 생성

새로운 프로세스를 생성하는 목적은 다음 두 종류

  • 동일한 프로그램 처리를 여러 프로세스에 나눠서 처리하기 (예 : 웹 서버에서 다수의 요청 받기)
  • 다른 프로그램을 생성하기 ( 예: bash에서 각종 프로그램을 새로 생성)

프로세스 생성을 실행하는 방법으로 리눅스는 fork(), execve() 함수를 사용 

내부적으로 각각 clone(), execve() 시스템 콜을 호출합니다. 

같은 프로세스를 두 개로 분열시키는 fork()함수

원본 프로세스를 부모 프로세스, 생성된 프로세스를 자식 프로세스라 부릅니다.

순서는 다음과 같습니다.

  • 부모 프로세스가 fork() 함수 호출
  • 자식 프로세스용 메모리 영역을 확보한 후 그곳에 부모 프로세스의 메모리를 복사
  • 부모 프로세스와 자식 프로세스는 둘 다 fork() 함수에서 복귀, 부모 프로세스와 자식 프로세스는 나중에 설명하듯 fork() 함수 반환값이 서로 달라서 처리 분기가 가능합니다.

#!/usr/bin/python3

import os, sys

ret = os.fork()

if ret == 0:
    print("자식 프로세스: pid={}, 부모 프로세스 : pid={}".format(os.getpid(),os.getppid()))
    exit()
elif ret > 0 :
    print("부모 프로세스: pid={}, 자식 프로세스: pid={}".format(os.getpid(),ret))
    exit()

sys.exit(1)

fork() 함수에서 복귀할 때, 부모 프로세스라면 자식 프로세스의 프로세스 ID를 반환하고, 자식 프로세스라면 0을 반환합니다. 프로세스 ID는 반드시 1 이상이므로 이 점을 이용하면 부모 프로세스와 자식 프로세스를 구분해서 fork()함수를 호출한 이후의 처리를 나눌 수 있습니다. 

 

위 프로그램에서 pid = fork()가 실행되는 순간, 위 프로세스와 똑같은 프로세스 하나가 별도의 메모리 공간에 생성된다.

이때 두 프로세스의 변수 값, PC(Program Count)값은 정확히 똑같다. 단, pid값만 유일하게 달라진다.

따라서 pid값을 이용해 Child Process가 할 일을 하게 하고, Parent Process가 할 일을 하게 하면 된다.


간단히 정리하면, fork()의 결과는 프로세스가 하나 더 생기는 것이다.( = 프로세스 id- PID 가 완전히 다른 또 하나의 프로세스가 생기는 것). 반면 exec()실행의 결과로 생성되는 새로운 프로세스는 없고, exec()를 호출한 프로세스의 PID가 그대로 새로운 프로세스에 적용이 되며, exec()를 호출한 프로세스는 새로운 프로세스에 의해 덮어 쓰여지게 된다.


다른 프로그램을 기동하는 execve() 함수

fork() 함수의 프로세스 복사본을 만들었면 자식 프로세스에서 execve() 함수를 호출합니다. 그러면 자식 프로세스는 새로운 프로그램으로 바뀝니다.

  1. execve() 함수를 호출합니다.
  2. execve() 함수 인수로 지정한 실행 파일에서 프로그램을 읽어서, 메모리에 배치(메모리 맵이라고 부름)하는데 필요한 정보를 가져옵니다.
  3. 현재 프로세스의 메모리를 새로운 프로세스 데이터로 덮어씁니다.
  4. 프로세스를 새로운 프로세스의 최초에 실행할 명령(엔트리 포인트)부터 실행하기 시작합니다.

fork()함수는 프로세스 개수가 늘어나는 것이지만 전혀 다른 프로그램을 생성하는 경우라면 어떤 프로세스를 새롭게 치환하는 형태가 됩니다.

#!/usr/bin/python3

import os, sys

ret = os.fork()

if ret == 0:
    print("자식 프로세스: pid={}, 부모 프로세스 : pid={}".format(os.getpid(),os.getppid()))
    os.execve("/bin/echo",["echo","pid={}에서 안녕".format(os.getpid())],{})
    exit()
elif ret > 0 :
    print("부모 프로세스: pid={}, 자식 프로세스: pid={}".format(os.getpid(),ret))
    exit()

sys.exit(1)

 

 

execve() 함수가 동작하려면 실행 파일은 프로그램 코드와 데이터 이외에도 다음과 같은 데이터가 필요합니다.

  • 코드 영역의 파일 오프셋, 크기 및 메모리 맵 시작 주소
  • 데이터 영역의 파일 오프셋, 크기 및 메모리 맵 시작 주소
  • 최초로 실행할 명령의 메모리 주소(엔트리포인트)

정보 확인 방법

Executable and Linking Format(ELF) 포맷 사용

우선 -no-pie을 사용해서 object 파일을 다시 만들어준다(주소가 변경되는걸 맞기위해서..)

엔트리포인트 확인 명령어 : readlf -h pause

파일 오프셋, 크기, 시작주소 확인 명령어 : readlf -S

  • 실행 파일은 여러 영역으로 나눠져 있고 각각을 "섹션"이라고 부릅니다.
  • 섹션 정보는 들이 한 묶음으로 표시됩니다.
  • 숫자는 모두 16진수
  • 섹션의 주요정보는 다음과 같습니다.
    • 색셩명 : 첫 줄의 두번째 필드 (Name)
    • 메모리 맵 시작 주소 : 첫 줄의 네번째 필드 Address
    • 파일 오프셋: 첫 줄의 다섯 번째 필드(Offset)
    • 크기 : 두 번째 줄 첫 번째 필드(Size)
    • 섹션명이 .text라면 코드 섹션 .data라면 데이터섹션

프로그램에서 작성한 프로세스의 메모리 맵은 /proc/<pid>/maps 파일에서 확인합니다.

 

pstree -p 옵션과 같이 사용하면 pid도 표시되면서 프로세스의 부모 자식 관계를 트리 구조로 볼 수 있습니다.

 

참조사이트:

https://woochan-autobiography.tistory.com/207

 

fork() 와 exec()

목차 fork() & exec() fork(), exec()의 차이점 exec() 관련 함수 System Call 에서의 fork, exec fork() 예시 exec() 예시 fork() & exec() fork()와 exec()는 모두 한 프로세스가 다른 프로세스를 실행시키기 위해 사용하게

woochan-autobiography.tistory.com

https://incarose86.hatenadiary.org/entry/20110715/1310745881

 

fork, exec, pipe - とあるSIerの憂鬱

UNIX(Linux)のプロセス起動は createProcess というような1つのシステムコールではなく、fork と exec の組み合わせ(Fork-Exec)で実現される。 fork は現在のプロセスをまるまるコピーし、別々の実行コン

incarose86.hatenadiary.org

 

반응형

'그림 리눅스' 카테고리의 다른 글

리눅스 개요  (1) 2023.11.04