Auxiliary Memory
  • Auxiliary Memory
  • Recent Changes
  • Disclaimer
  • general
    • Homelab
      • Planning
      • Configuring RPi
      • Dockerize Unifi Controller
      • Moving Unifi Controller to Bare Metal RPi
    • Lifehack
      • Coding on iPad
      • Faster internet with Cloudflare WARP
  • lifelog
    • Links
    • Movies
    • Books
      • Reading Queue
    • Public Memos
      • 2020 Memo
    • Yearly Records
      • Records of 2020
      • Records of 2019
  • books
    • The Rust Programming Language
    • Lambda Calculus
    • SICP
    • Introduction To Algorithms
      • 1.1. 알고리즘의 역할
      • 1.2. 시작하기
    • Linux System Programming 2/E
      • 1. 핵심 개념 소개
  • Programming
    • Git
    • When to refactoring?
    • Microservices
    • Functional Programming
      • ADT
      • Functor and Monads
    • OS
      • CPU Modes
    • Debugging
      • objdump
    • DevOps
      • How our infrastructure organized
      • Optimize Dockerfile
    • Spring Framework
    • Web
      • OAuth
        • Sign in with Apple
    • SQL
      • Prepared Statement
    • Programming Languages
      • TypeScript
      • Python
        • GIL
      • Rust
      • F#
        • Dos & Don'ts
      • Go
      • JVM
        • JVM memory structure
        • JVM GC
        • Kotilin
        • Java
          • Why main method should be static
  • My Environment
    • My Macbook
    • My Keyboards
    • My PyCharm
    • My CLI
      • iTerm2
      • Dotfiles
        • Refactoring .zshrc
      • Useful Commands
Powered by GitBook
On this page
  • 이 책에서 다루는 내용
  • 시스템 프로그래밍의 구성 요소
  • API & ABI
  • 표준
  • 리눅스 프로그래밍의 개념
  • 파일과 파일시스템
  • 프로세스
  • 사용자와 그룹
  • 권한
  • 시그널
  • 프로세스간 통신
  • 헤더 파일
  • 에러 처리

Was this helpful?

  1. books
  2. Linux System Programming 2/E

1. 핵심 개념 소개

PreviousLinux System Programming 2/ENextGit

Last updated 4 years ago

Was this helpful?

시스템 프로그래밍이란 커널 및 핵심 시스템 라이브러리를 직접 사용하면서 하위 레에서 동작하는 시스템 소프트웨어를 작성하는 기술

시스템 프로그래밍은 모든 소프트웨어의 핵심이다. 상위 레벨에서 동작 하더라도 시스템 프로그래밍을 알고 개발한다면 더 나은 애플리케이션을 만들 수 있다. 시스템 내부에 대한 이해는 모든 형태의 프로그래밍에 있어 큰 도움이 된다.

이 책에서 다루는 내용

  • 시스템 레벨 인터페이스란?

  • 어떻게 시스템 레벨 애플리케이션을 작성할 수 있을까?

  • 커널과 libc는 정확히 무엇을 제공하나?

  • 최적의 코드 작성법과 리눅스에서의 그 방법은?

  • 유닉스 vs 리눅스?

  • 모든 것의 동작 방식?

시스템 프로그래밍의 구성 요소

  • 시스템 콜(syscall)

    • OS에 리소스/서비스를 요청하기 위해 사용자 영역에서 커널 내부로 들어가는함수 호출

    • 리눅스는 다른 운영체제보다 시스템 콜이 적다

    • 표준 시스템 콜을 개별 아키텍처별로 조금씩 확장

    • 사용자 영역에서 직접 커널을 건드리는것은 불가능하기 때문에 시그널을 커널로보낸다.

      • i386 기준: 애플리케이션에서 INT 명령에 0x80이라는 값을 넘기면커널 영역에서 소프트웨어 인터럽트 핸들러를 (시스템 콜 핸들러) 실행한다.

    • 애플리케이션을 시스템 콜과 매개 변수를 레지스터를 통해 전달한다.

      • 시스템 콜을 실행하려면 레지스터에 해당 시스템 콜을 먼저 저장해야 한다.

      • i386 기준: eax 레지스터에 명령을 저장

        • ebx, ecx, edx, esi, edi 이 다섯 레지스터에 순서대로매개변수 저장.

        • 모자랄 경우 매개변수를 담은 사용자 영역의 버퍼를 가리키도록한다.

  • libc

    • 유닉스 애플리케이션의 핵심. 다른 언어로 해도 어딘가에선 쓰임

    • 보통은 gnu libc = glibc 를 씀

  • C 컴파일러

    • 리눅스는 GCC가 표준

    • 컴파일러는 개 중요함 -> C 표준과 ABI 구현에 관여함

API & ABI

  • API: Application Programming Interface

    • 소스코드 레벨

  • ABI: Application Binary Interface

    • 특정 아키텍처 간에서 동작하는 소프트웨어 간의 바이너리 인터페이스를정의한다. -> 바이너리의 호환성 보장

      • 같은 ABI를 지원한다면 재컴파일 ㄴㄴ

    • 애플리케이션 내에서의 상호 동작, 커널-애플리케이션 &애플리케이션-라이브러리 간의 상호 동작에 대하여 정의

      • 콜링 컨벤션, 바이트 순서, 레지스터 활용, 시스템 콜 실행, 라이브러리링크, 라이브러리 동작 방식, 바이너리 오브젝트 형식 같은 내용과 관련있음

      • 콜링 컨벤션만 하더라도 실행 방식, 인자 전달 방식, 레지스터에서 값을저장하는 방식, 함수를 호출한 측에서 반환값을 가져가는 형식 등을 정의한다.

      • 표준화는 망했음

      • 사실 툴체인이 해주는 일이라 다 알아야 할 필요는 ㄴ

표준

POSIX, SUS 가 있음 -> 유닉스 호환 운영체제를 위한 C API 명세

리눅스는 딱히 인정받은 표준은 아니다.

리눅스 프로그래밍의 개념

파일/프로세스, 파이프와 소켓의 인터페이스 등등

파일과 파일시스템

리눅스에선 모든게 파일이다. 모든 인터렉션은 파일을 읽고/쓰기 하는것으로 이루어진다.

파일은 R, W, X, and RW 모드로 읽을 수 있다. 열린 파일은 fd(file descriptor)로 참조할수 있다. fd는 사용자 영역에 공유되며 직접 접근 가능하다. 리눅스 시스템 프로그래밍의 대부분은 fd를 다루는 일

일반 파일

  • 흔히 생각하는 파일

  • 정확히는 바이트 스트림이라 부르는 연속적으로 나열된 바이트 배열에 저장된 데이터를의미한다. 리눅스는 딱히 구조까지 정의하진 않는다.

  • 바이트 읽고 쓰기가 가능. 이 작업은 파일 내부의 위치(파일 오프셋)를 지정해서수행 가능함.

    • 파일 오프셋은 커널이 열린 파일마다 유지하는 메타데이터의 핵심

    • 바이트단위로 증감

    • 파일 끝 넘어가서 읽으면 거기까지는 0으로 채워짐. 파일 중간에 데이터를 쓰면덮어씀 -> 중간부분 확장은 불가

    • 잘라내기(truncation)

  • 한 파일은 다른 프로세스나 같은 프로세스에서 한번 이상 열기 가능

    • 열릴때마다 파일 디스크립터는 고유

    • 프로세스는 fd를 공유가능 -> 동시접근 가능

      • 이거 컨트롤은 애플리케이션 몫

  • 사실 파일 이름와 파일은 직접 연결이 아니다.

    • 파일은 inode란 파일시스템 내에서만 고유한 값으로 참조됨.

    • inode엔 길이, 타입, 저장 위치 같은 정보가 있음

디렉터리와 링크

  • inode로 파일 접근은 귀찮고-보안 이슈가 있어서 보통은 파일 이름을 사용한다.

  • 디렉터리는 inode대신 사람이 읽을수 있는 이름으로 나타냄. 이 이름-inode쌍을링크라고 부름

    • 개념적으론 파일과 같으나 이름-inode 매핑만 저장

  • /home/pictures/profile.png 를 열 경우

    • dentry를 탐색해서 다음 항목을 찾아감

    • 루트에서 home을 가리키는 inode를 얻음 -> pictures를 가리키는inode를 얻음 -> 최종적으로 profile.png의 inode를 얻어냄.

    • 리눅스 커널은 이때 캐시의 지역성을 활용해 디렉터리 찾기 결과를 저장하고탐색 속도를 높임

  • 디렉터리는 일반 파일처럼 취급되지만 시스템 콜로만 다룰수 있음 -> 잘못 건들면파일시스템 파킨!

하드 링크

  • 동일한 inode에 여러 이름을 매핑하게 허용

  • 하드링크의 존재로 이름-inode쌍만 삭제한다고 진짜 삭제를 할수는 없음

    • GC처럼 링크 카운터를 두고 이게 0이 되면 실제로 삭제

심벌릭 링크

  • 하드링크는 다른 파일시스템으로 확장 불가능함 <- inode는 다른 파일 시스템간에는유효하지 않기 때문

  • 여러 파일시스템에 걸쳐 사용할 수 있도록 심벌릭 링크 제공

    • 연결한 파일의 완전한 경로 이름을 포함하는 독자적인 inode와 데이터를 포함

  • 하드링크보단 오버헤드 있음

  • 일종의 바로가기로 동작

특수 파일

  • 파일로 표현되는 커널 객체

    • 리눅스 기준으론 4종류 지원

  • 블록 디바이스

    • 바이트 배열로 임의접근

  • 캐릭터 디바이스

    • 바이트로 구성된 선형 큐 느낌

    • 더 없으면 EOF

  • 네임드 파이프

    • IPC

  • 유닉스 도메인 소켓

    • 고급 IPC & 네트워크 통신

  • 모든것이 파일 철학에 맞추어 파일시스템 위에 구현된 추상화 개념

    • 관련 시스템 콜 제공

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

윈도우와 다르게 리눅스에선 루트 디렉토리 안에 디바이스가 다 들어있음 -> 네임스페이스가 통합되어 있음 네임스페이스에 마운트 포인트가 정해져있고 마운트된 파일시스템의 루트는 마운트 포인트에서 접근 가능.

프로세스

실행중인 오브젝트 코드를 말하는데 언제나 활성화 상태로 실행 중인 프로그램이다. 정확히는 오브젝트 코드를 넘어 데이터, 리소스, 상태, 가상화된 컴퓨터를 포함

프로세스는 실행 가능한 포맷의 오브젝트 코드로부터 시작된다. 리눅스에서 가장 일반적인 파일 포맷은 ELF(Executable and Linkable Format) 이다. 실행 파일은 메타데이터, 코드, 데이터 등 여러 섹션(선형 메모리 공간에 적재된 바이트 배열)으로 구성된다.

가장 중요한 공통 섹션(ELF)

  • 텍스트 섹션

    • 코드, 상수, 변수같은 읽기 전용 데이터

    • 읽기 전용 또는 실행 가능

  • 데이터 섹션

    • C 변수처럼 초기화된 자료

    • 일반적으로 읽고 쓰기 가능으로 표시

  • bss(block started by symbol) 섹션

    • 값이 0인 모든 페이지가 매핑됨

  • 기타 등등 절대 섹션과 미정의 섹션

프로세스는 시스템 콜을 이용해서 리소스(타이머, 시그널, 열린 파일, 네트워크 연결 등등)를 요청하고 조작한다. 프로세스 리소스는 자신과 관련된 데이터의 통계 정보를 포함하고 있음 -> 프로세스 디스크립터 형태로 커널 내부에 저장된다.

프로세스는 가상화를 위한 추상 개념이다 -> 선점형 멀티태스킹, 가상 메모리 지원하는 리눅스 커널이 가상화 프로세서/메모리를 프로세스에 제공 -> 스케줄러는 지가 다인줄 암

  • 스케줄러를 통해 프로세스 각각이 독점하는듯이 동작

    • 커널이 프로세서를 공유하도록 선점하고 스케줄링함

  • 단일 선형 주소 공간 제공 -> 메모리를 독점하는것처럼 동작

    • 가상 메모리와 페이징 기법 사용

최신 프로세서의 도움을 받아 이런 가상화를 관리하기도 함

스레드

각 프로세스는 실행 스레드(걍 스레드)를 한개 이상 포함 스레드는 프로세스 내부에서 실행하는 활동 단위 -> 코드 실행하고 프로세스 동작 상태를 유지하는 추상 개념

프로세스는 대부분 싱글스레드 <- 간결함 우선 철학과 적은 오버헤드, 견고한 IPC 메커니즘 때문에 멀티스레드 요구가 적었음

스레드는 다음을 포함함

  • 스택

  • 프로세서 상태

  • 프로그램 카운터(인스트럭션 포인터)

  • 기타 프로세스에 남아있는 리소스는 공유함

내부적으로 리눅스 커널은 독특한 관점으로 스레드를 구현함 -> 스레드는 단순히 (특히 주소 공간) 몇몇 리소스를 공유하는 일반적인 프로세스일 뿐이다. -> 찾아보니 경량 프로세스라고 함 -> NPTL이란것도 있음 

프로세스의 계층 구조

프로세스는 PID라고 하는 고유한 양수 값으로 구분된다.

프로세스는 엄격한 계층 구조를 형성한다 -> init 프로세스를 제외한 모든 프로세스는 부모가 있다. 커널은 고아 프로세스를 init에 입양시킨다.

사용자와 그룹

리눅스에서 권한은 사용자와 그룹 형태로 제공됨 프로세스마다 실행한 사용자가 누구인지 파악하는 uid 붙음 자식 프로세스는 부모 프로세스의 uid를 상속받음 uid0은 root를 가리킴. root만이 프로세스의 uid를 바꿀 수 있다. -> login이 root로 동작해서 shell uid를 변경함

권한

표준 파일 접근 권한과 보안 메커니즘은 유닉스와 동일

파일마다 소유자, 소유자 그룹과 세가지 접근 권한 비트(R,W,X)가 있음 이 비트들은 소유자, 소유자 그룹, 그 외 모든 사용자 별로 할당되어 총 9비트로 표현된다. Inode에 저장되어 있다.

일반 파일과 특수 파일 모드 일기와 쓰기 (의미가 다를수는 있음) 가 적용되지만 실행 권한은 특수 파일에서 무시된다.

리눅스는 전통적인 유닉스 접근 권한 이외에 ACL도 지원한다.

시그널

비동기식 단방향 알림 메커니즘 (세그폴트나 Sigint 등을 알려줌)

  • 커널 -> 프로세스

  • 프로세스 -> 프로세스

  • 자기 자신에게도 가능

SIGKILL과 SIGSTOP 을 제외하고는 프로세스가 핸들링 할 수 있다.

프로세스간 통신

프로세스 간 정보 교환과 알림은 운영체제의 가장 중요한 작업

리눅스 커널은 POSIX, System V에서 내려오는 표준화된 메커니즘과 한두가지 독자적 메커니즘등 해서 대부분의 유닉스 IPC 메커니즘을 대부분 구현

  • 파이프

  • 네임드 파이프

  • 세마포어

  • 메시지큐

  • 공유 메모리

  • 퓨텍스

헤더 파일

리눅스 시스템 프로그래밍은 여러 헤더 파일을 중심으로 돌아간다. 커널 자체와 glibc는 시스템 프로그래밍에 사용되는 헤더 파일을 제공한다.

에러 처리

시스템 프로그래밍에서 에러는 함수 리턴 값으로 확인이 가능하며 특수한 변수인 errno로 에러 이유를 알 수 있다.

errno

  • 반환값 대신 자세한 에러 원인을 찾아내기 위해 사용

  • 함수 호출 직후에만 유효 (한 스레드 내에서는 전역변수)

  • RW-able lvalue

  • 값은 설명과 선행처리기에서 지정되는 문자열에 매핑되어있음.

  • 관련있는 함수들

    • void perror (const char *str); -> str 뒤에 콜론+에러 문자열 을 붙여 stderr 로 내보낸다.

      • str에는 함수 이름을 붙이는게 좋음

    • char * strerror(int errnum) -> 설명이 담긴 문자열에 대한 포인터 반환

      • 스레드 세이프 하지 않음

    • int strerror_r (int errnum, char *buf, size_t len) -> 버퍼에 길이만큼 채운다.

      • 성공하면 0 실패하면 -1. 이놈도 실패하면 errno를 세트함. <- 아니 뭔 ㅋㅋ

어떤 함수는 에러나도 -1 안줌. 이런녀석은 항상 호출 직전에 errno를 0으로 세트한 후 검사하자. 또한 errno를 유지하면서 코드를 실행시키고 싶다면 다른곳에 따로 저장해야 한다.

대략 30개 정도가 있음 ->

Chromium OS Docs - Linux System Call Table
Chmod Calculator
유닉스 신호 - 위키백과, 우리 모두의 백과사전
Chromium OS Docs - Linux Error Number Table (errno)