본문 바로가기

개발

그림으로 배우는 리눅스 구조 2주차

목차

-  프로세스 확인하기

-  프로세스 생성

-  ELF

-  랜덤 스택(ASLR)

-  첫 프로세스

-  프로세스의 상태

-  좀비 프로세스, 고아 프로세스

-  시그널

-  세션

-  프로세스 그룹

-  데몬

  -  service vs systemctl

-  정리

-  Rerference

 

프로세스 확인하기

 

 

 

 

VSZ: 가상 메모리

RSS: 실제 메모리

VSZ와 RSS의 관계를 어떻게 봐야 할지 모르겠지만, 어떤 뜻인지는 알아두면 좋을 것 같다.

 

 

내 기준 STAT에서 자주 참고할만한 요소이다.

D: 디스크 I/O 대기중

L: 메모리에서 페이지가 lock된 상태

R: 실행중인 상태

S: 인터럽트에 의한 대기

 Z: 좀비 프로세스

 <: 높은 우선순위

 +: 포어 그라운드

 

 

 

프로세스 생성

 

 

fork

'fork

 

 

 

현재 프로세스의 메모리를 복사해 새로운 공간에 자식 프로세스를 만든다.
fork 호출 전에 지역 변수를 초기화했다면, 같이 복사된다.

 

 

다음 항목들은 복사되지 않는다.

 메모리 락 정보(스왑 공간으로 페이징 하지 못하도록 막음)

 부모 프로세스와 관련된 레코드 락

 프로세스 ID

 리소스 사용률 및 CPU 카운터

 부모 프로세스에서 보류 중인 syscall

 세마포어 조정 값

 타이머

 미해결 비동기 I/O 작업 및 컨텍스트

 

 

 

execve

프로세스의 메모리르 새로운 프로세스의 데이터로 덮어쓴다.

다르게 말하면 새로운 컨텍스트로 바꾼다.

 

 

 

ELF

 

 

리눅스 실행 파일 포맷. TMI로 윈도우의 파일 포맷은 PE다.

다음 요소들을 가지고 있다.

-  ELF 헤더

 프로그램 헤더 테이블

        런타임 실행에 필요한 정보를 가지고 있다.

 섹션 헤더 테이블

        섹션의 링킹과 재배치에 필요한 정보를 가지고 있다.

.text

        코드 영역

 .rodata

        읽기 가능한 데이터 부분

 .data

        데이터 영역

        초기화 되지 않은 데이터는 bss 영역으로 관리된다.

 

 

 

랜덤 스택(ASLR)

 

 

한 짤 요약

 

 

OS의 보안 기능 중 하나. 실행할 때마다 각 섹션을 다른 주소에 매핑한다.

열심히 찾은 중요한 함수나 상수들의 메모리 오프셋을 바꿔서
메모리 주소가 고정된 정직한 환경에서 문제들을 풀어온 초보 시스템 해커들의 뒷목을 잡게 한다.

 

물리 주소는 그대로고, 가상 주소만 다르기 때문에 pwntools 쓰면 무시할 수 있다.

 

 

 

첫 프로세스

 

 

과거엔 init이 PID 1번이었지만, 현재는 개량된 버전의 systemd를 사용한다.
이 둘의 차이를 알아보려 했지만 생각보다 논란이 많은 주제여서 더 파고들진 않기로 했다.

reddit의 설명을 인용하면 다음과 같다. (고마워요 구글 번역기!)

 

 

일부 사람들은 init 시스템에 대해 매우 강한 감정을 가지고 있습니다. 하지만 대부분의 사람들에게는 시스템이 잘 돌아가는 한(그리고 종종 그렇지 않더라도) 학문적 관심사에 불과할 것입니다. Systemd는 상당히 새롭고 다르기 때문에 어떤 사람들은 그것을 좋아하고 다른 사람들은 그것을 싫어합니다. 때로는 그들의 이유가 타당하고 때로는 극도로 어리석습니다. (양쪽 모두에서.) 논란의 여지가 있기 때문에 많은 잘못된 정보가 있으므로 사람들이 하는 말을 소금 한 알 정도로 받아들이십시오.



프로세스의 상태

 

 

exit() 함수를 호출하면 내부에서 exit_group() 시스템 콜을 통해 이루어진다.

내가 호출하지 않아도, libc 등에서 내부적으로 호출한다.

 

부모 프로세스는 wait() 또는 waitpid() 같은 시스템 콜을 호출해 다음 정보를 얻는다.

-  프로세스 반환값

 시그널에 따라 종료했는지

 CPU를 얼마나 사용했는지

 

 

 

좀비 프로세스, 고아 프로세스

 

 

 

 

 

자식 프로세스가 종료되면 SIGNAL을 보낸다.
여기서 부모 프로세스가 wait 처리를 잘못하면 다음 상태로 나뉜다.

-  고아 프로세스: wait 호출 전 부모 프로세스 종료
-  좀비 프로세스: wait 호출 안 함

 

좀비 프로세스의 경우, 부모 프로세스가 wait 시스템 콜을 하면 종료된다.
하지만, 자식 프로세스들을 모두 정리하지 않고, 부모 프로세스가 종료되면
init(systemd)가 대신 부모가 돼서 wait 시스템 콜로 정리한다.

 

 

 

시그널

 

 

다른 프로세스에 신호를 보내 외부에서 실행 순서를 강제로 바꾸는 방법

대표적으로 다음 시그널들이 있다.

 

-  SIGINT: 프로세스 종료. 우리가 흔히 쓰는 ctrl + c이다.

 SIGSTOP: 프로세스 실행 중지. ctrl + z

 SIGHOLD: 자식 프로세스가 종료될 때 부모 프로세스한테 보내는 신호

 SIGCONT: 정지한 프로세스 재실행

 SIGKILL: 프로세스 강제 종료. 긴 I/O 요청 등으로 오랫동안 대기하는 경우, 이를 무시하기도 한다.

 

SIGTERM: 프로그램에 종료 요청을 보냄. SIGKILL과는 다르게 바로 죽이지 않는다.
해당 시그널을 받으면, 종료 작업 절차를 걸쳐 안전하게 프로세스를 종료할 수 있다.
spring의 graceful shutdown이 이 원리이다.

 

 

 

세션

 

 

ssh로 시스템에 로그인할 때 그 세션이다.

각 세션마다 단말이 배정되며 pty/{단말 번호} 형식으로 이름이 지어진다.

 

SID라는 세션 ID 값이 있는데 bash 같은 셸의 PID다.

이런 셸들은 세션 리더라고도 한다.

즉, SID = 셸 PID이다.

 

여기서 셸(Shell)은 운영체제의 서비스를 사용할 수 있는 인터페이스다.
bash, sh 등이 있으며, 입력 장치로부터 받은 명령어를 실행한다.

OS의 외부를 둘러싼다고 해서 Shell이라고 한다.

 

 

 

프로세스 그룹

 

 

세션마다 여러 프로세스를 사용할 건데 이 중엔 겹치는 것도 있다.
OS는 프로세스들을 그룹으로 관리해 일괄적으로 처리할 수 있다.

프로세스 그룹은 백그라운드, 포그라운드로 나뉜다.


이 부분은 잘 몰라서 세션들의 프로세스를 일괄적으로 처리할는 기술이 이거구나 하고 넘어간다.

 

 

 

데몬

 

 

OS에 상주하는 프로세스. 다음 특징이 있다.

-  단말이 할당되지 않는다. 즉, 단말의 입출력이 필요 없다.

 독자적인 세션을 가지고 있다.

 init가 부모가 된다.

 이름 뒤에 d가 붙는 경우가 있다. ex) httpd, sshd, ftpd

 

 

 

service vs systemctl

 

 

데비안 계열은(ubuntu) systemctl을,

레드햇 계열은(centos) service를 사용한다.

 

이 부분도 차이를 알아보려 했지만, 위의 init과 systemd처럼 찬반이 있는 주제인 것 같아서 더 파고들진 않았다.

 

tmi로 과거엔 /etc/rc.d/init.d 디렉토리에 데몬 프로세스들이 있었는데
systemd가 나오면서 /etc/rc.d로만 관리되는 것 같다.

 

스터디원분이 리눅스가 어떻게 하위 호환을 유지하는지 알려주신 내용을 기반으로 추가로 정리했다.
요약하면 service와 systemctl은 동일하다.

 

 

사실은 service가 아니라 systemctl 쓰고 있던 거임!

 

 

rocky9.4 기준 /usr/sbin/service의 스크립트의 일부를 보면
내부적으로 systemctl 을 호출하는 것을 볼 수 있다.

 

 

if [ "${ACTION}" = 'start' ] \
   && [ "$(systemctl show -p ActiveState "${SERVICE}".service --value)" = 'active' ] \
   && [ "$(systemctl show -p SubState "${SERVICE}".service --value)" = 'exited' ]; then
    /bin/systemctl stop "${SERVICE}".service
fi

 

 

우분투 20.04 기준 /usr/sbin/service도 systemctl을 호출한다.

 

 

if [ -n "$is_systemd" ]
then
   UNIT="${SERVICE%.sh}.service"

   case "${ACTION}" in
      restart|status|try-restart)
         exec systemctl $sctl_args ${ACTION} ${UNIT}

 

 

이것 외에도 redhat 기반에서 패키지 명령어인 yum과 dnf을 보면
yum과 dnf는 최신 패키지 명령어인 dnf-3에 링크된 것을 확인할 수 있다.

이렇게 최신 명령어를 스크립트에서 호출, 심볼릭 링크를 통해 하위 호환성을 유지한다.

 

 

$ ll | grep dnf
lrwxrwxrwx. 1 root root           5  4월 14  2024 dnf -> dnf-3
-rwxr-xr-x. 1 root root        2094  4월 14  2024 dnf-3
lrwxrwxrwx. 1 root root           5  4월 14  2024 yum -> dnf-3

 

 

 

정리

이번 장에서는 리눅스 프로세스에서 자주 거론되는 부분들에 대해 공부했다.

기초 편이기 때문에 자세한 부분들은 다루지 않았지만
랜덤 스택, init과 systemd의 차이 등 고민할 법한 주제들에 대해 키워드를 남겨
책에서만 끝나지 않게 했다.

 

다음 장인 프로세스 스케줄러 편에서는 OS가 프로세스를 처리하면서 어떤 상태들이 되는지를 알아본다.

 

 

Reference

https://man7.org/linux/man-pages/man1/ps.1.html

https://man7.org/linux/man-pages/man2/fork.2.html

https://www.reddit.com/r/linux/comments/c2721s/what_is_the_difference_between_systemd_and/

https://dongwooklee96.github.io/post/2021/04/03/좀비-프로세스와-고아-프로세스/

https://ko.wikipedia.org/wiki/ELF_파일_형식

https://blog.naver.com/tmk0429/222318530824