독서

모던 리눅스 교과서 정리 (1장 ~ 4장)

Debin 2024. 4. 22.
반응형

모던 리눅스 교과서를 드디어 완독했다.

오로지 본인이 기억하고 싶고, 중요하다고 생각했던 부분을 정리할 예정이다.

리눅스가 운영체제이므로 운영체제에 대한 기초적인 내용(ex 시스템 콜 기본, 운영체제 기본)이 나오는데 이와 같은 분을 생략할 예정이다.

 

1장. 리눅스 소개

리눅스는 유닉스의 전통을 따라 기본적으로 리소스의 전역 보기(글로벌 뷰)를 지원한다.

 

리소스란 소프퉤어 실행을 지원하는데 사용할 수 있는 모든 것을 말한다.

예를 들어 하드웨어와 그 추상화(CPU, RAM, 파일), 파일 시스템, HDD, SSD, 프로세스, 디바이스나 라웉이 테이블 같은 네트워킹 관련 항목, 사용자를 나타내는 자격증명이 포함된다.

 

리눅스는 프로세스 ID를 사용해 프로세스를 식별한다.

동일한 PID를 가진 프로세스가 리눅스에 여러 개 있을 수 있는데, 이것이 바로 컨테이너의 기초다.

 

네임 스페이스라고 하는 서로 다른 컨텍스트에는 동일한 PID를 가진 여러 프로세스가 있을 수 있다.

이런 상황은 도커나 쿠버네티스에서 앱을 실행할 때와 같이 컨테이너화된 설정에서 발생한다.

 

모든 프로세스는 PID 1이 특별하다고 여긴다. 전통적인 설정에서 PID 1은 사용자 공간 프로세스 트리의 루트 용으로 예약 되어 있다.

 

리소스는 전역 보기와 로컬 또는 가상화된 보기가 존재한다.

 

그럼 리눅스의 모든 것은 기본적으로 전역일까? 정답은 아니다. 

 

리눅스에서 리소스에 대한 로컬 보기를 제공하는 방법은 네임 스페이스를 사용하는 것이다.

 

한 프로세스가 다른 프로세스를 고갈시키지 않도록 메모리 소비를 제한하는 것도 가능하다.

리눅스에서는 cgroup이라는 커널 기능을 사용해 이러한 종류의 격리를 제공한다.

 

2장. 리눅스 커널

리눅스에서 가장 큰 단위부터 가장 작은 단위는 아래와 같다.

세션

하나 이상의 프로세스 그룹을 포함하고 선택적으로 tty가 연결된 상위 수준의 사용자 대면 유닛을 나타낸다.

커널은 세션 ID(SID)라는 번호를 통해 세션을 식별한다.

 

프로세스 그룹

프로세스 그룹: 하나 이상의 프로세스가 포함돼 있으며, 한 세션에는 포그라운드 프로세스 그룹이 둘 이상일 수 없다.

커널은 PGID라는 숫자를 통해 프로세스 그룹을 식별한다.

 

 

프로세스

여러 리소스를 그룹으로 추상화한 것이며, 커널은 /proc/self를 통해 현재 프로세스를 사용자에게 노출한다.

커널은 프로세스를 PID로 식별한다.

 

스레드

커널에 의해 프로세스로 구현된 유닛을 말한다. 즉 스레드를 나타내는 전용 데이터 구조는 없다.

오히려 스레드는 특정 리소스를 다른 프로세스오 ㅏ공유하는 프로세스다.

커널은 스레드 ID(TID)와 스레드 그룹(TGID를 통해 스렏르르 식별하며, 공유된 TGID 값은 멀티스레드 프로세스를 의미한다.

 

테스크

태스크: 커널에는 sched.h에 정의된 task_struct라는 데이터 구조가 있으며, 이는 프로세스와 스레드 구현의 기반을 형성한다.

이 데이터 구조는 스케줄링 관련 정보, 식별자, 시그널 처리기 ,성능이나 보안과 관련된 기타 정보를 수집한다.

위 모든 유닛은 태스크에서 파생되며 고정된다. 하지만 테스크는 커널 외부에 그대로 노출되는 일이 없다.

 

eBPF

커널 기능을 확장하는 방법으로 인기를 끌고 있는 기술은 eBPF이다.

원래 BPF(Berkeley Packet Filter)로 불렸으며, 최근의 커널 프로젝트의 기술은 일반적으로 eBPF로 알려져있다.

 

최초에는 패킷 필터링 용도로 개발되었지만,

현재는 그 기능이 훨씬 확장되어 시스템의 다양한 부분에서 커널의 동작을 확장하고 커스터마이즈할 수 있게 되었다.

 

eBPF는 안전하게 커널 내에서 실행할 수 있는 작은 프로그램을 작성할 수 있게 해주며, 이 프로그램들은 커널이나 하드웨어에서 발생하는 이벤트에 반응하여 실행된다.

이를 통해 개발자들은 시스템의 성능을 모니터링하거나, 네트워크 트래픽을 분석하거나, 보안 정책을 적용하는 등 다양한 작업을 할 수 있다.

 

eBPF는 bpf 시스템 콜을 사용해 리눅스 커널 기능을 안전하고 효율적으로 확장한다.

eBPF는 맞춤형 64비트 RISC 명령어 세트를 사용한 커널 내 가상 머신으로 구현됐다.

 

eBPF는 이미 여러 곳에서 다음과 같은 사례에 사용되고 있다.

  • 쿠버네티스에서 포드 네트워킹을 활성하기 위한 CNI 플러그인: ex 실리움, 프로젝트 칼리코
  • 옵저빌리티: iovisor 같은 리눅스 커널 추적과 허블을 사용한 클러스터 설정의 경우를 위해 사용
  • 보안 제어 역할: CNCF 팔코 와 같은 프로젝트에서 활용하듯이 컨테이너 런타임 스캔을 수행
  • 네트워크 로드밸런싱용: 페이스북의 L4 카트란 라이브러리에서 활용

 

3장. 셸과 스크립팅

CLI 관점에서 리눅스 상호 작용하는 방법은 두 가지가 있다.

 

첫 번째 방법은 수동이다.

즉 사용자가 터미널 앞에 앉아 대화식으로 명령을 입력하고 출력값을 받는 것이다.

 

두 번째는 셸스크립팅 또는 스크립팅이라고 한다.

특수한 종류의 파일에 있는 일련의 명령을 자동으로 처리하는 것이다.

 

터미널

터미널은 텍스트로 된 사용자 인터페이스를 제공하는 프로그램이다. 터미널은 키보드에서 문자를 읽어 화면에 표시하는 기능을 지원한다.

기본적인 문자 중심의 입력과 출력외에도 터미널은 커서, 화면 처리, 그리고 잠재적으로 색상 지원이 가능하도록 이스케이프 시퀀스 또는 이스케이프 코드를 지원한다.

 

터미널 내부에서 실행되며 명령 인터프리터 역할을 하는 프로그램이 셸이다.

셸은 스트림을 통해 입력, 출력을 처리하고 변수를 지원하며, 사용 가능한 내장 명령이 몇 가지 있으며, 명령 실행 및 상태를 처리하고, 일반적으로 대화식 사용과 스크립트 사용을 모두 지원한다.

공식적으로 셸은 sh로 정의하며, 종종 POSIX 셸이라는 용어를 접하게 되는데, 이는 스크립트와 이식성 맥락에서 더욱 중요하다.

원래 sh가 많이 쓰였지만, 최근에는 대부분 배시 셸이 sh의 자리를 대체해 기본으로 널리 쓰이고 있다.

 

스트림

셸은 입력과 출력을 위한 세 가지 파일 디스크립터(FD)를 프로세스에 제공한다.

 

stdin(FD 0)

  • Standard Input (표준 입력)이란 사용자로부터 데이터를 입력 받기 위한 기본 수단
  • 예를 들어, 키보드를 통한 입력이 이에 해당한다.
  • 프로그램이 실행될 때 이 스트림은 자동으로 열리며, 데이터를 프로그램에 전달한다.

stdout (FD 1)

  • Standard Output (표준 출력)은 프로그램이 처리한 결과를 출력하기 위한 기본 경.
  • 일반적으로 이 스트림은 콘솔이나 터미널 화면으로 연결되어 있다.
  • 예를 들어, 프로그램에서 print() 함수나 기타 출력 함수를 사용하면 이 표준 출력을 통해 사용자에게 정보를 제공한다.

stderr (FD 2)

  • Standard Error (표준 오류): 오류 메시지와 경고를 출력하기 위한 스트림
  • 이 스트림 또한 기본적으로 콘솔이나 터미널에 연결되어 있으나, stdout과는 별도로 관리된다.
  • 이를 통해 프로그램의 일반적인 출력과 오류 메시지를 구분할 수 있으며, 오류 로깅을 더 효율적으로 관리할 수 있다.

 

이 FD들은 기본적으로 화면과 키보드에 각각 연결되어 있다.

셸에 입력하는 명령은 키보드에서 입력(stdin)을 가져오고 출력(stdout)을 화면에 전송한다.

 

$FD>와 <$FD를 사용해 프로세스의 출력 스트림을 재지정할 수 있따. 여기서 $FD는 파일 디스크립터다.

예를 들어 2>는 stderr 스트림을 재지정한다는 의미다. 1>과 >는 stdout이 기본 값이므로 동일한 뜻이다.

stdout과 stderr을 모두 재지정하려면 &>를 사용하고 스트림을 제거하려면 /dev/null 을 사용하면 된다.

 

구체적인 예시는 아래에서 살펴보자.

curl https://example.com &> /dev/null #1
curl https://example.com > /tmp/content.txt 2> /tmp/curl-status #2
head -3 /tmp/content.txt
tr < /tmp/curl-status [A-Z] [a-z]
  1. stdout과 stderr을 모두 /dev/null로 지정해 출력값을 버린다.
  2. 출력 값과 상태 값을 다른 파일로 재지정
  3. 대화식으로 값을 입력하고 파일을 저장.
  4. stdin에서 값을 읽는 tr 명령을 사용해 모든 단어를 소문자로 만든다.

 

셸은 일반적으로 다음과 같은 여러 특수 문자를 이해한다.

  • &: 명령 마지막에 배치되며 백그라운드에서 명령을 실행한다.
  • \: 긴 명령의 가독성을 높이기 위해 다음 행에서 명령을 계속할 때 사용한다.
  • |: 한 프로세스의 stdout 값을 다음 프로세스의 stdin 과 연결해 데이터를 파일에 임시로 저장하지 않고 바로 전달할 수 있다.

참고로 셸을 닫은 후에도 백그라운드 프로세스를 계속 실행하려면 nohup 명령을 앞에 추가하면 된다.

 

다양한  환경에서 셸 스크립트를 테스트할 수 있는지가 중요하다.

스크립트는 어떻게 실행될까? 스크립트는 실제로는 단순한 텍스트 파일이라는 점을 기억하자.

즉 확장자는 중요하지 않지만 .sh가 관례로 사용되는 경우가 많다.

이런 텍스트 파일을 실행 가능한 스크립트로 바꾸며 셸에서 실행할 수 있는 것은 다음 두가지 사항 덕분이다.

 

  • 텍스트 파일은 첫번째 행에서 #!로 작성하는 셔뱅을 사용해 인터프리터를 선언해야 한다.
  • 그런 다음 chmod + x 등을 사용해 스크립트를 실행 가능하게 만들어야 한다. chmod 750을 추천.
    • 소유자는 읽기, 쓰기 및 실행 권한을 가진다. (4+2+1 = 7).
    • 그룹은 읽기 및 실행 권한을 가진다 (4+1 = 5).
    • 다른 사용자에게는 어떠한 권한도 부여되지 않는다 (0).

 

다양하게 사용할 수 있는 셸 스크립트의 템플릿은 아래와 같다.

#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail

firstargument="${1:somedefaultvalue}"

echo "$firstargument"

 

위 코드는 아래와 같다.

  1. 해시뱅은 프로그램 로더에게 배시를 사용해 이 스크립트를 해석하도록 지시한다.
  2. 오류가 발생하면 스크립트 실행을 중지하고 싶다고 정의
  3. 설정되지 않은 변수를 오류로 처리하도록 정의
  4. 파이프의 한 부분이 고장나면 전체 파이프가 고장난 것을 간주하도록 정의. 이는 아무 에러 없이 실패하는 것을 방지하는데 도움이 된다.
  5. 기본 값이 있는 명령행 매개변수의 예제
 
스크립트의 우수 사례는 다음과 같다.
  1. 빠르고 요란하게 실패
  2. 민감한 정보는 하드코딩 X
  3. 입력 값 정리
  4. 의존성 확인
  5. 에러처리
  6. 문서화
  7. 버전 관리
  8. 테스트

 

4장. 접근제어

리눅스는 다중 사용자 운영체제로 유닉스로부터 사용자 개념을 물려받았다.

각 사용자 계정은 실행 파일, 파일, 장치, 기타 리눅스 자산에 접근할 수 있는 사용자 ID와 연결된다.

 

인간 사용자는 사용자 계정으로 로그인할 수 있으며 프로세스도 사용자 계정으로 실행할 수 있다.

그리고 사용자가 이용할 수 있는 하드웨어 또는 소프트웨어 구성요소인 리소스가 있다.

 

일반적으로 시스템 콜과 같이 다른 종류의 리소스에 대한 접근을 명시적으로 언급하지 않는 한 리소스는 파일로 지칭한다.

 

  • 사용자: 프로세스를 실행하고 파일을 소유한다. 프로세스는 커널이 메인 메모리에 로드해 실행하는 일종의 프로그램이다.
  • 파일: 소유자가 존재한다. 기본적으로는 파일을 만든 사용자가 파일을 소유한다.
  • 프로세스: 의사소통과 지속성을 위해 파일을 사용한다. 물론 사용자도 파일을 간접적으로 사용하기도 하지만 그러려면 프로세스를 통해야 한다.
 

리눅스 관점에서 임의 접근 제어  강제 접근 제어가 있다.

  • 임의 접근 제어
    • 임의 접근제어를 사용하면 사용자의 신분을 기반으로 리소스에 대한 접근을 제한할 수 있다.
    • 여기서 임의라 함은 특정 권한을 가진 사용자가 이를 다른 사용자에게 전달할 수 있음을 뜻한다.
  • 강제 접근 제어
    • 강제 접근 제어는 보안 수준을 나타내는 계층 모델을 기반으로 한다.
    • 사용자에게는 허용 등급이 할당되고 리소스에는 보안 레이블이 할당된다.
    • 사용자는 자신의 허용 등급 이하로 설정된 리소스에만 접근할 수 있다.
    • 강제 접근 제어 모델에서는 관리자가 모든 권한을 설정하여 엄격하고 배타적으로 접근을 제어한다.
    • 즉 사용자는 리소스를 소유하고 있어도 권한을 스스로 설정할 수 없다.

 

리눅스에서는 목적 또는 의도적인 사용 관점에서 사용자 계정을 두 가지 유형으로 구별한다.

  • 시스템 사용자 또는 시스템 계정
    • 일반적으로 프로그램은 이런 유형의 계정을 사용하여 백그라운드 프로세스를 실행한다.
    • 이들 프로그램이 제공하는 서비슨느 네트워킹과 같은 운영체제의 일부이거나 애플리케이션 계층에 있을 수 있다.
  • 일반 사용자
    • 예를 들어 셸을 통해 리눅스를 대화식으로 사용하는 인간 사용자 유형

파일 권한

파일 권한은 '리소스에 접근'한다는 리눅스 개념의 핵심이다. 리눅스에서는 어쨌든 모든 것이 파일이기 때문이다.

다음과 같이 좁은 범위부터 넒은 범위까지 세 가지 유형/범위의 권한이 있다.

  • 사용자: 파일의 소유자.
  • 그룹: 그룹은 하나 이상의 구성원이 있다.
  • 나머지: 그 밖의 모두가 이 카테고리에 들어간다.

파일에는 읽기, 쓰기, 실행의 권한이 있다.

패턴 적용되는 권한 십진법 표기
--- 없음 0
--x 실행 1
-w- 쓰기 2
-wx 쓰기와 실행 3
r-- 읽기 4
r-x 읽기와 실행 5
rw- 읽기와 쓰기 6
rwx 읽기, 쓰기, 실행 7

 

  • 755: 사용자는 모든 권한을 가지며, 그 외 모든 사람은 읽기와 쓰기 권한을 가진다.
  • 700: 소유자는 모든 권한을 가지며, 그 외 모든 사람은 아무 권한이 없음
  • 664: 소유자와 그룹은 읽기/쓰기 권한이 있으며, 나머지 사용자는 읽기만 가능.
  • 644: 소유자는 읽고 쓰기가 가능하며, 그 외 모든 사람은 읽기만 가능
  • 400: 소유자는 읽고 쓰기가 가능하며, 그 외 모든 사람은 읽기만 가능함

664는 특별한 의미가 있는데 이 권한은 내가 파일을 만들 때 할당하는 기본 권한이다.

chmod로 파일의 권한을 변경할 수 있다. 

예를 들어 모든 사람에게 파일 실행 권한을 부여하고 싶지 않으면 chmod 744를 선택하면 된다.

 

참고자료

https://product.kyobobook.co.kr/detail/S000210138053

 

반응형

댓글