Skip to content

Latest commit

 

History

History
322 lines (228 loc) · 27.4 KB

system programming 이란.md

File metadata and controls

322 lines (228 loc) · 27.4 KB

System Programming 이란

(먼저 여기서 다루는 내용들은 최신 리눅스 시스템에 탑재된 시스템 프로그래밍 API 환경에 대한 내용이다.)

시스템 프로그래밍과 어플리케이션 프로그래밍을 비교해보자.

  • 시스템 프로그래밍은 커널 및 시스템 라이브러리를 써서 작업하는 반면에 어플리케이션 프로그래밍은 추상화 된 고급 라이브러리를 통해서 개발한다.
    • 시스템 라이브러리는 커널에서 제공해주는 인터페이스와 C, C++ 라이브러리 들을 말한다.
    • 이런 고급 라이브러리들은 추상화를 한 것을 말한다.
    • 추상화는 더 이식성을 제공해주거나, 더 편의성을 제공해주거나 더 사용하기 쉽게 만드는 것들을 말한다.
  • 여기서 시스템 프로그래밍을 배워야 하는 이유로는 여기서의 레벨을 알고 있으면 어플리케이션 프로그래밍을 하는데 도움을 줄 것 이라고 생각해서 그렇다.
    • 점점 더 요즘은 어플리케이션 프로그래밍을 많이 하는 추세지만 자바스크립트의 인터프리터나 자바의 JVM 은 시스템 프로그래밍 지식이 없으면 만들기 어렵다.
    • 어플리케이션 프로그래밍에서는 트렌드가 변화하고 있다고 하더라도 밑바닥의 시스템 프로그래밍은 변화하는 부분이 거의 없다.

시스템 프로그래밍의 주춧돌

시스템 콜

System Programming 은 시스템 콜로 시작해서 시스템 콜로 끝난다.

시스템 콜이란 운영체제에 리소스나 서비스를 요청하려고 사용자 영역에서 시작해서 커널 내부로 들어가는 함수 호출이다.

  • 시스템 콜은 read(), write() 와 같은 익숙한 함수부터 익숙하지 않은 함수까지 여러개가 있다.

사용자 영역의 어플리케이션을 커널 영역과 직접적인 연결이 불가능하다. 보안과 안정성의 이유로. 이 이유로 어플리케이션은 커널 코드를 직접 실행하거나 커널 내부 데이터를 조작할 수 없다.

사용자 영역 어플리케이션이 시스템 콜을 호출하려면 인터럽트 번호와 시스템 콜 번호, 시스템 콜 호출에 필요한 매개변수를 레지스터에 담고나서 소프트웨어 인터럽트 명령을 날려야한다. 소프트웨어 인터럽트를 날리면 커널에 제어를 전달하고 커널 내부로 진입해서 커널이 허용된 코드를 실행한다.

  • 인터럽트 번호는 인터럽트 식별자이다. 인터럽트는 하드웨어와 소프트웨어에서 발생할 수 있다. 인터럽트 번호는 CPU 에서 지정하고 각 인터럽트 번호마다 실행되야하는 인터럽트 처리를 위한 핸들러가 있다. 이런 핸들러는 커널 내부에 정의되어있다.
    • 시스템 콜을 호출하려면 이와 관련된 인터럽트 번호를 설정해야하며 이는 시스템 콜 인터럽트 벡터 번호를 설정해야한다.
  • 소프트웨어 인터럽트는 프로그램에서 실행할 수 있는 특별한 명령이다. 이를 통해서 CPU 에게 특정한 인터럽트를 실행하 수 있도록 한다. CPU 는 그러면 현재 실행중인 프로그램을 잠시 일시중지하고 인터럽트를 먼저 처리한다.
    • 소프트웨어 인터럽트를 호출은 아키텍처마다 다르다. x86 아키텍처 기준으로는 int 0x80 또는 syscall 명령어를 사용할 수 있다.
      • int 0x80 에서 int 는 인터럽트를 말한다.0x80 은 시스템 콜 인터럽트 벡터 번호이다. 사용자 모드에서 커널 모드로 전환할 때 쓰인다.

어플리케이션은 실행할 시스템 콜과 매개 변수를 레지스터를 통해서 전달한다.

  • 시스템 콜은 0부터 시작하는 숫자로 구별된다.
  • 예를 들어서 i386 아키텍처에서는 시스템 콜 5번 (= open()) 을 호추하려면 응용 프로그램은 eax 레지스터에 5를 저장해야한다. 매개변수 전달은 i386 아키텍처 기준으로 ebx, ecs, edx, esi, edi 레지스터에 순서대로 전달해야한다. 레지스터가 5개 이상이 필요하다면 레지스터 하나에 나머지 모든 매개변수를 담은 버퍼를 가리키도록 한다. (메모리 주소를 던져야한다.)

C 라이브러리

C 라이브러리는 시스템 프로그래밍의 핵심 중 하나이다. 다른 언어로 프로그래밍해도 상위 레벨의 라이브러리에 이것들이 포함되는 경우가 많다.

최신 리눅스 시스템에서는 GNU C 라이브러리인 glibc 가 제공된다. 여기에서는 시스템 콜에 대한 래퍼 뿐 아니라 스레드 지원 그리고 기본적인 어플리케이션 기능 지원에 대한 것들이 포함되어있다.

이 외의 것들

C 컴파일러, API, ABI 에 대한 개념이 있음.

표준

리눅스 시스템에서의 표준은 POSIX 와 SUS 이다.

리눅스 프로그래밍 개념

파일에 대한 설명

우리가 부르는 파일은 일반 파일을 의미하며 바이트 스트림 형식으로 기록되어 있다. 바이트 배열이라고 생각하면 된다.

  • 리눅스에서는 파일에 대한 특별한 자료구조가 없다.

파일은 바이트를 읽고 쓰는 것이 가능한데 이를 위해서는 바이트의 위치를 알아야 하며 이것과 관련된 게 파일 오프셋 (file offset or file position) 이라고 한다.

파일 오프셋은 열린 파일에서 관리하는 주요한 메타 데이터 중 하나이다.

  • 파일이 처음 열리면 파일 오프셋은 0 이다.
  • 보통 파일은 바이트 단위로 읽고 쓰기 때문에 바이트 단위의 숫자로 증가하거나 감소한다.
  • 파일 오프셋은 0부터 시작하며 음수가 될 수 없다.
  • 파일 오프셋은 직접 지정할 수 있으며 파일의 끝을 지정할 수도 있다. 파일의 끝을 넘어서는 지정도 가능하며 이 경우에는 파일의 끝에서부터 해당 위치까지는 바이트가 0 으로 기록된다.

파일 중간에 데이터를 기록하면 이미 존재하는 데이터는 덮어쓰여진다. 따라서 파일의 중간에 데이터를 쓰는 것으로 파일을 확장하는 것보다 파일의 끝에서 주로 데이터를 쓴다.

파일 오프셋의 최대 크기는 C언어의 off_t 타입의 크기로 결정되며 최신 리눅스 시스템에서는 64bit 값이다.

파일의 길이는 바이트배열의 길이이다.

하나의 파일은 다른 프로세스에서도 열 수 있고, 동일 프로세스에서도 한 번 이상 열 수 있다. 그러나 파일은 열릴 때마다 고유한 파일 디스크립터를 반환한다.

그렇지만 프로세스는 파일 디스크립터를 공유할 수 있다. 하나의 파일 디스크립터는 하나 이상의 프로세스에서 사용될 수 있다. 커널은 파일에 대한 동시 접근을 막지 않는다.

여러개의 프로세스에서 동시적으로 파일을 읽거나 쓰는 것도 가능하다.

  • 그렇지만 동시 접근은 연산 순서에 대해서 다른 결과를 낼 수 있기 때문에 사용자 영역의 프로그램은 이러한 것들을 회피하도록 신경 써야한다.

일반적으로 파일 이름을 통해서 파일을 접근하지만 파일에 대한 접근은 파일 이름과 직접적인 연관은 없다.

  • 파일은 inode (information node) 라고 하는 파일 시스템 내에서 고유한 정수 값으로 참조된다.
  • 이 값은 inode 번호 라고 하며 ino 라고 줄여 쓰기도 한다.
  • inode 는 변경된 날짜, 소유자, 타입, 길이, 데이터 저장 위치 등과 같이 파일에 대한 메타 정보를 관리한다. 하지만 여기에는 파일 이름은 포함되어 있지 않다.
  • inode 는 디스크에 저장되는 물리적인 객체임과 동시에 리눅스 커널에서 자료구조로 표현되는 논리적인 개념이기도 하다.
  • inode 에는 파일의 실행중인 정보인 파일 오프셋과 같은 정보는 저장되어 있지 않다.
  • 파일 테이블에서 inode 에 대한 참조를 포함하고 있는 것.

디렉터리와 링크

inode 번호로 파일에 대한 접근을 하면 귀찮고 보안 문제가 있기 때문에 일반적으로 파일 이름을 통해서 접근한다.

디렉터리는 파일에 대한 접근을 위해서 이름을 제공한다. 이렇게 파일 이름과 inode 의 쌍을 링크라고 한다.

파일과 디렉터리는 유사하지만 디렉터리는 파일 이름과 inode 매핑을 저장한다는 점에서 차이가 있다.

사용자 영역 어플리케이션에서 파일을 열겠다고 한다면 커널은 파일 이름을 통해서 디렉터리를 열고 파일을 찾는다.

  • 여기서 파일 이릉므로 inode 번호를 찾고 이렇게 얻은 inode 번호로 inode 를 찾는다.
  • Inode 에서는 디스크에 저장된 파일 위치와 같은 정보가 들어 있어서 찾을 수 있다.

리눅스에서 /home/blackbeard/concorde.png 파일을 찾는 과정을 보면 다음과 같다.

  • 루트에서 시작해서 home 의 inode 를 찾고, home 으로 가서 blackbeard 의 inode 를 찾고 blackbeard 로 가서 concorde.png inode 를 찾는다.
  • 커널에서 디렉터리 같은 경우는 dentry 라고 부르며 이것도 캐시가 되기 때문에 탐색 속도가 더 빠르다.

하드 링크

지금까지는 다른 이름으로 동일한 inode 를 가리키는 방법은 없었다.

그런데 이런 방법도 가능하다고 한다. 동일한 inode 에 대한 여러가지 파일 이름을 지원하는 다중 링크.

하드 링크는 복잡한 파일 시스템 구조에서 동일한 데이터를 여러 경로 이름이 가리킬 수 있도록 하는 것이다.

  • 특정 inode 를 가리키는 경로 이름은 /home/bluebeard/treasure.txt 와 /home/blackbeard/to_streal.txt 로 가리킬 수 있다.

하드 링크는 동일한 디렉토리에 있어도 되고 그러지 않아도 된다.

파일 삭제는 링크를 삭제하는데 관여한다.

  • 단순히 디렉터리에서 파일 이름과 inode 쌍을 삭제하면 끝난다. inode 자체를 삭제하기는 어렵다. 하드 링크가 있기 때문에 다른 곳에서 파일을 또 참조할 수 있으니까.
  • 그래서 리눅스에서는 내부에 링크 카운터를 둬서 자신을 가리키는 링크 개수를 추적한다. 파일 링크가 해제될 때마다 링크 카운터도 하나씩 줄어서 링크 카운트가 0 이되면 inode 가 삭제된다.

심볼릭 링크

심벌릭 링크는 파일 이름으로 참조되지 inode 번호로 참조되지 않는다. 하드 링크보다 더 단순하다. 하드 링크는 inode 번호로 파일을 식별하기 때문에 inode 가 속한 파일 시스템 외부에서는 무의미하다고 한다.

하나의 리눅스에서도 여러가지 파일 시스템이 있을 수 있다.

  • ext4, XFS, Btrfs 등.
  • inode 를 사용하는 파일 시스템은 ext2, ext3, ext4, XFS, JFS, ReiserFS, Btrfs 이 있다.
  • 파일 시스템은 데이터를 저장하고 관리하는 방법을 정의한다.

컴퓨터안에 운영체제가 여러개가 있다면 (ex 윈도우) 이 또한 파일 시스템이 여러개가 있을 수 있다.

  • NTFS, FAT32 등.

심볼릭 링크는 다양한 파일 시스템에서 사용할 수 있다. ext4 파일 시스템과 XFS 파일 시스템 사이에 심볼릭 링크를 걸 수도 있다.

하드 링크보다 심볼릭 링크를 다룰 때의 오버헤드가 더 크다. 심볼릭 링크는 심볼릭 링크 파일과 그 링크로 연결된 파일 둘 다 다뤄야하기 떄문에.

특수 파일

리눅스에서 특수 파일은 커널 객체이다. 리눅스는 4종류의 특수 파일을 지원한다.

  • 블록 디바이스 파일
  • 캐릭터 디바이스 파일
  • 네임드 파이프
  • 유닉스 도메인 소켓
  • 리눅스에서는 모든게 파일이므로 이런 것도 파일이다.

리눅스는 특수 파일을 생성하는 시스템 콜을 제공한다.

유닉스에서는 하드웨어 장치에 대한 접근도 파일로 관리되고 실행된다. 이런게 디바이스 파일인데 디바이스 파일을 열고 읽고 쓰는 방식으로 하드웨어를 조작한다.

  • 하드웨어 장치는 캐릭터 디바이스와 블록 디바이스로 나뉜다.

캐릭터 디바이스는 바이트로 구성된 선형 큐처럼 접근한다. 디바이스 드라이버는 큐에 바이트를 하나씩 집어넣고 (write()) 사용자 영역에서는 큐에 쌓인 순서대로 바이트를 읽는다.

  • 키보드는 캐릭터 디바이스의 대표적인 예로 키보드에 pig 를 입력하면 디바이스 드라이버는 p,i,g 를 큐에 바이트를 집어넣고 어플리케이션은 읽는다.
  • 다 읽으면 EOF (End Of File) 을 반환한다.

블록 디바이스는 저장장치와 관련있다. 데이터를 고정 크기의 블록 단위로 읽고 쓴다. 대표적인 블록 디바이스는 하드 디스크, SSD, CD-ROM 이 있다.

  • 블록 디바이스는 바이트 배열로 접근이 가능하다. 이 말은 데이터가 연속된 바이트 시퀀스로 구성되어있어서 이러한 바이트 배열을 블록 단위로 읽어올 수 있다는 뜻이다.
  • 디바이스 드라이버가 블록 디바이스에서 읽어온 데이터를 메모리에 매핑해준다. 이렇게 메모리에 들어온 데이터를 사용자 영역의 어플리케이션에서 읽고 쓰는게 가능하다.

네임드 파이프 (FIFO, First In, First Out) 이라는 것도 특수 파일로 관리된다.

  • IPC 통신을 할 때 쓰인다. 특수 파일을 파일 디스크립터 형태로 통신할 수 있다.
    • 파일 디스크립터를 통해서 데이터를 주고 받을 수 있다는 뜻이다.
    • 파일 디스크립터는 일반 파일 뿐 아니라, 네임드 파이프, 소켓 등에서도 다양하게 쓰인다. (블록 디바이스를 다룰 떄도 파일 디스크립터를 통해서 데이터를 가져오는건가? ㅇㅇ) 파일 디스크립터라는 일관된 인터페이스를 통해서 데이터를 읽어오는 것.
      • 블록 디바이스의 경우 디바이스 드라이버가 실제로 운영체제로 데이터를 가져오는 역할을 하지만 추상화된 파일 디스크립터를 통해서 데이터를 읽어오고 쓰게된다. 파일 디스크립터로 전달된 요청들이 디바이스 드라이버에게 전달되는 것.
  • 일반 파이프는 특정 프로그램의 출력을 다른 프로그램의 입력으로 연결하는게 가능하다.
  • 네임드 파이프는 일반 파이프와 동일하게 동작하지만 FIFO 라는 특수한 파일을 거쳐서 접근한다. 이 방법으로 프로세스의 통신이 가능해진다.
    • 네임드 파이프를 통해서 데이터를 보낼 수 있는데 이게 FIFO 방식으로 처리된다는 걸 말한다. 다른 프로세스는 데이터를 받아서 보낼 수 있다.
    • 일반 파이프는 자식-부모 프로세스에서만 사용할 수 있다. 네임드 파이프는 다른 프로세스에서도 사용할 수 있다.

마지막 유형으로는 소켓이 있다.

  • 소켓은 서로 다른 프로세스끼리 통신할 수 있는 고급 IPC 의 일종이다. 같은 머신 뿐 아니라 다른 머신과도 통신할 수 있다.
    • 다른 머신과도의 통신은 네트워크 통신을 말한다.
  • 소켓은 많은 변종이 있다. 인터넷 통신을 위한 소켓은 통신 목적지를 위해서 호스트 이름과 포트를 사용하지만 유닉스 도메인 소켓은 파일 시스템에서 만들어진 특수 파일 (소켓 파일) 을 사용한다.
    • 인터넷 통신을 위한 소켓과 유닉스 도메인 소켓이 어떻게 다른지 설명하고 있는 것.
    • 유닉스 도메인 소켓은 같은 시스템 내에서 IPC 를 위해서 사용된다. 시스템 내에서 빠른 통신을 위한 목적으로 사용되며 목적지를 알기 위해서 소켓 파일을 쓴다. 인터넷 용 통신과 목적지를 인식하는 방식이 다름.

파일 시스템과 네임 스페이스

리눅스는 파일과 디렉토리를 나타내기 위한 통합된 전역 네임 스페이스를 제공한다.

  • 다른 운영체제는 독립적인 네임 스페이스를 할당한다.
    • 플로피 디스크는 A:\plank.jpg 로 분리해서 접근되지만 하드 드라이브는 C:\ 로 접근한다.
  • 유닉스에서는 전부다 루트 디렉토리로부터 시작하므로 네임 스페이스가 통합되어있다.
    • 플로피 디스크 같은 경우는 /media/floopy/plank.jpg 라는 경로로 되어있다.

파일 시스템을 추가하거나 제거하는 과정을 mount (마운트: 추가) unmount (언마운트: 제거) 라고 한다.

  • 파일 시스템을 추가한다는 건 파일과 디렉토리 계층 구조를 전역 네임스페이스 안에 추가한다는 뜻이다.
  • 파일과 디렉토리 계층 구조를 모아놓는 걸 파일 시스템이라고 한다.
  • 네임 스페이스에 정해진 장소를 마운트 포인트라고한다. 마운트 포인트에 파일 시스템이 마운트된다.
    • 예를 들어서 CD 를 /media/cdrom 에 마운트한다면 마운트 포인트는 /media/cdrom 이다.
  • 리눅스는 가장 먼저 마운트 되어있는 파일 시스템이 루트 이다.

리눅스는 메모리에만 존재하는 가상 파일 시스템이 있고 네트워크를 통해서 가져오는 네트워크 파일 시스템을 지원한다.

  • 다만 일반적으로는 물리적인 디스크에 파일 시스템이 존재한다.
  • 물리적인 파일 시스템은 CD, 플로피 디스크, SD 같은 블록 디바이스를 말한다.
  • 어떤 디바이스는 파티션을 지원한다. 이는 물리적인 파일 시스템을 여러개의 파일 시스템으로 나눌 수 있다는 뜻이다.

리눅스는 다양한 파일 시스템을 지원한다.

  • 네트워크 파일 시스템 (NFS)
  • 네이티브 파일 시스템 (ext4)
  • 다른 유닉스에서 지원하는 파일 시스템 (XFS)
  • 유닉스 계열이 아닌 파일 시스템 (FAT)

블록 디바이스의 최소 접근 단위는 섹터이며 물리적인 속성을 말한다. 섹터는 2의 승수이며 512 가 가장 일반적이다.

  • 한번에 512 바이트를 읽어올 수 있다는 뜻이 아닐까? ㅇㅇ 맞다.
    • 최근에 고용량의 디스크에서는 4096 바이트를 섹터로 쓰는 경우도 있다.
  • 섹터보다 작은 단위의 데이터를 가져올 수 없다.
  • 섹터가 블록이라고 생각하면 될까? 아니다.
    • 블록 디바이스는 데이터를 블록 단위로 데이터를 가지고오는데 이 블록에서 구성하는 최소 단위가 섹터라고 하는 것. 디스크나 SSD 의 물리적인 속성이다.
    • 불록은 파일 시스템이 데이터를 관리하는 논리적인 단위이다. 일반적으로 블록이 섹터를 포함한다.
    • 블록은 섹터보다 크지만 페이지 크기 (메모리 관리 유닛에서 최소 단위) 보다는 작다. 보통 블록 크기는 512, 1024, 4096 바이트이다.

리눅스는 프로세스별 네임 스페이스를 지원한다.

  • 이 공간은 전역 네임스페이스와는 분리된 공간이다. 메모리 상에서 만들어내는 공간이다. 이 공간을 통해서 독자적인 파일 시스템을 구조를 제공한다.
    • 그래서 각 프로세스가 서로 다른 환경에서 독자적인 파일 시스템을 가지고 실행되는 것처럼 보이게 만든다.
  • 프로세스는 독자적인 마운트 포인트와 단일 루트 디렉토리로 독자적인 네임 스페이스를 생설할 수 있다.

프로세스

유닉스의 두 번째 중요 개념은 프로세스이다.

프로세스는 커널이 이해하는 실행 포맷으로 만들어져 있다. ELF (Executable and Linkable Format) 이다.

ELF 는 여러가지 섹션으로 이뤄져있다.

  • Stack Section
    • 함수 호출과 변수 값이 담기는 곳으로 실행될 때 메모리에 할당되며 메모리에 올라가는 데이터는 동적으로 변경된다.
  • Code Section
    • 프로세스를 실행하는 명령어를 포함하며 디스크에 저장된다. 실행될 때 메모리로 올라온다.
  • Data Section
    • 전역 변수와 같은 초기화된 데이터를 위한 공간으로 실행될 때 메모리로 올라온다.
  • Heap Section
    • 프로세스의 메모리 공간에서 동적으로 메모리를 할당하는 영역. 필요에 따라 메모리를 할당하고 해제할 수 있는 공간을 말한다.
      • 동적 메모리 할당 (malloc, calloc, realloc) 을 통해서 메모리 할당할 수 있다.
      • 여기 영역은 프로그래머가 관리해줘야한다. free() 를 통해서 명시적으로 메모리를 반환해줘야한다.
      • 이는 프로세스 내의 스레드 끼리는 공유되지만 프로세스 끼리는 공유되지 않는 영역이다.

프로세스는 실행 가능한 오브젝트 코드로 이뤄져있다. 활성화된 프로그램을 말하며 오브젝트 코드는 데이터, 리소스, 상태, 가상화된 컴퓨터를 포함한다.

  • 데이터: 프로세스는 프로그램에 필요한 데이터와 변수를 저장하는 메모리 공간을 가진다.
  • 리소스: 프로세스는 실행에 필요한 다양한 리소스를 필요로 한다. 파일, 네트워크 연결, 그래픽 출력 등과 같은 하드웨어, 소프트웨어 리소스가 필요하다.
    • 프로세스는 필요한 리소스를 시스템 콜을 통해서 요청한다.
    • 리소스 종류를 더 말하자면 열린 파일, 타이머, 대기중인 시그널, IPC 매커니즘, 메모리, CPU 등이 있다.
    • 프로세스가 필요한 리소스들은 추적이 가능하다. 리소스 사용량, 실행 시간등을 추적할 수 있다.
    • 프로세스 리소스는 커널 내의 프로세스 디스크립터 형태로 저장되는데 이를 통해서 운영체제가 쉽게 리소스 사용량 같은 것들을 볼 수 있다. 운영체제가 프로세스를 관리하는데 필요한 리소스들을 모니터링하고 추적하기 쉽도록.
  • 상태: 프로세스는 실행 중인 상태를 포함한다. 실행 중, 대기 중, 종료된 상태를 가지고 있다.
  • 가상화된 컴퓨팅: 프로세스는 가상화된 컴퓨팅 환경을 가진다. 이 말은 독립적인 실행 환경을 가지고 다른 프로세스와 충돌하지 않는다. 라는 뜻이다.
    • 커널은 프로세스에게 단일 선형 주소 공간 (single linear address space) 를 제공하지만 실제로는 가상 메모리와 페이징 기법을 통해서 제공한다.
    • 이를 통해서 커널은 여러 프로세스를 실행할 수 있고, 프로세스는 혼자서 시스템을 독차지 하고 있는 듯하게 실행되는 것처럼 느낀다.

스레드

각 프로세스는 실행 스레드를 하나 이상 포함한다. 스레드는 코드를 실행하고 동작을 유지한다.

스레드는 프로세스 스택과 같은 스택을 가지고 있고, 프로세스의 상태, 현재 실행하고 있는 프로그램 명령어 (= 오브젝트 코드의 현재 위치) 등을 포함한다.

  • 싱글 스레드 프로세스라면 스레드 스택과 프로세스의 스택은 동일하나, 멀티 스레드 프로세스라면 다르다. 스택이 구별되니.

이외의 기타 리소스는 스레드끼리 공유한다.

리눅스에서 스레드는 몇몇 리소스를 공유하는 프로세스로 인식한다.

  • 스레드와 프로세스가 공유하는 리소스

    • 메모리 공간 (코드, 데이터, 힙 섹션). 프로세스 내의 스레드는 모든 메모리 공간을 공유한다. 그래서 효율적으로 스레드 간의 데이터 교환이 쉽다.
    • 파일 디스크립터 (열린 파일, 소켓, 파이프) 같은 리소스는 스레드와 프로세스가 공유하는 리소스이다.
    • 프로세스 ID (PID). 프로세스 내의 모든 스레드는 프로세스 ID 를 공유한다.
  • 스레드와 프로세스가 공유하지 않는 리소스

    • 스택: 각 스레드는 독립적인 실행 흐름을 유지하기 위해서, 함수 호출, 지역 변수 저장 등은 프로세스와 스레드가 공유하지 않는다.
    • 레지스터: 각 스레드는 자신만의 레지스터 셋을 포함한다. 레지스터는 프로세스가 데이터를 실행하고 처리하는데 필요한 공간이다.
    • 스레드 ID: 각 스레드는 고유의 스레드 ID 를 가진다.
    • 스레드 로컬 저장소 (TLS): 각 스레드는 고유의 로컬 저장소가 있다. 이를 통해서 독자적인 실행이 가능하게 해준다.

프로세스의 계층 구조

프로세스 ID 는 프로세스를 식별하는 값이며 양수의 값이다. 첫 번째 프로세스는 1 이고 이후에 생성되는 프로세스는 고유의 값을 받는다.

리눅스에서 프로세스 구조는 트리 구조를 따른다. init 프로세스가 가장 먼저 실행되는 프로세스이고 이 프로세스 외의 프로세스는 다 부모 프로세스가 있다.

프로세스를 생성하려면 fork() 라는 시스템 콜을 호출해야하며 호출로 만든 프로세스는 자식 프로세스가 된다. (부모 자식 관계가 만들어짐.) 부모 프로세스가 먼저 자식보다 죽으면 고아가 된 자식 프로세스는 init 프로세스의 자식이 된다.

부모 프로세스는 자식 프로세스의 종료까지 대기한다. 이유 자체는 다음과 같다.

  • 리소스 회수
    • 자식 프로세스가 완전히 종료되는 시점을 알아야지 파일 디스크립터와 같은 리소스를 운영체제가 확보할 수 있는 시기이기 때문에.
  • 종료 상태에 따른 핸들링을 위해서
    • 자식 프로세스의 종료 상태를 보고 에러라면 이렇게, 성공적으로 끝났다면 이렇게 등의 여러가지 핸들링을 하려고.
  • 좀비 프로세스를 방지하기 위해서
    • 자식 프로세스의 프로세스 디스크립터가 남아있어서 자원을 계속해서 점유하고 있는 것을 막으려고
    • 좀비 프로세스가 점유하고 있는 자원
      • 프로세스 ID
      • 프로세스 디스크립터
        • 프로세스 ID, 부모 프로세스 ID, 종료 상태, 프로세스 사용중인 리소스 정보, 프로세스 상태, 스케쥴링 정보 등
      • 좀비 프로세스는 파일 디스크립터 같은 것들은 반환했다.

부모 프로세스는 wait(), waitpid() 등을 통해서 기다릴 수 있다.

사용자와 그룹

리눅스에서 권한은 사용자와 그룹 형태로 제공된다. uid 는 고유한 양수 값이며 사용자를 구별한다.

uid 는 리눅스 커널 내부에서 사용자를 나타내는 개념이다. /etc/passwd 파일에 사용자 이름에 매핑되는 uid 가 저장되어있다.

로그인을 하면 이름과 암호를 login 프로그램에 넘기게 되고 일치한다면 /etc/passwd 에 저장되어있는 사용자 로그인 셀을 실행하고 셀의 uid 를 사용자의 uid 로 변경한다.

uid 0 은 루트를 가리킨다.

프로세스가 자신을 실행할 사용자가 누구인지 파악할 수 있도록 uid 를 쓰고 이를 실제 uid 라고 한다.

  • 이것말고도 프로세스마다 유휴 uid, 저장된 uid, 파일 시스템 Uid 가 있다.

권한

시그널

시그널은 비동기 단방향 알림 매커니즘이다.

  • 커널에서 프로세스로, 프로세스에서 프로세스로, 프로세스 자신이 자신에게 알림을 줄 수 있다.

시그널은 프로세스를 즉시 종료하는 SIGKILL 과 프로세스를 멈추는 SIGSTOP 과 같은 시그널이 아니라면 시그널을 받았을 때 어떻게 행동할 지 제어할 수 있다.

  • 프로세스 종료하거나
  • 프로세스를 종료하고 메모리 덤프를 남기거나
  • 프로세스를 멈추거나
  • 아무 작업을 하지 않고 기본 동을 하거나
  • 무시하거나

프로세스간 통신

헤더 파일

에러 처리