20. The Linux System

Linux 운영 체제에 대해 알아보자.

20.1. Linux History

리눅스는 UNIX 시스템과 비슷하나 훨씬 더 역사가 짧다. 이는 1991년에 시작되었다. 초기에는 소스 코드가 많은 개발자들의 자발적 무급 참여로 이루어졌다. 리눅스 커널은 리눅스 커뮤니티에서 시작된 소프트웨어이고, 리눅스 시스템은 커널 위에 올려진 소프트웨어이며 리눅스 커뮤니티에서 모두가 시작된 것은 아니다. 리눅스 배포는 이 모두와 이를 위한 관리/설치/삭제 도구를 포함한다.

20.1.1. The Linux Kernel

리눅스 커널의 최초 버전은 1991년, 그 다음 버전인 1.0은 1994년에 이루어졌다. 1.0은 파일 시스템을 대폭 발달시켰으며, 프로세스간 통신(IPC)도 구현되었다. 마이너 버전이 홀수인 커널은 개발 커널, 짝수인 커널은 생산 커널이다. 1995년에는 1.2 버전이 출시되었으며 마지막 PC 한정 리눅스 커널이다. 이는 하드웨어 지원을 확대하고 기능을 강화시켰다. 1996년에는 2.0, 1999년에는 2.2가 출시되었다. 2.4/2.6 버전은 SMP 시스템, 메모리 관리, 블로킹 입출력, 더 효과적인 파일 시스템, 스케쥴링이 추가되었다. 2011년에는 3.0이 추가되어 가상화, 페이징 등이 개선되었다. 2015년에는 4.0이 출시되었다.

20.1.2. The Linux System

리눅스 운영 체제는 리눅스 커널 외에 리눅스 시스템 라이브러리 등이 있다. 예를 들면 GNU C 컴파일러(gcc) 등. 또한 파일 시스템 계층 표준은 여러 시스템 컴포넌트간 호환을 위한 리눅스 내 파일 시스템 표준을 정립시켰다.

20.1.3. Linux Distributions

리눅스가 발달하면서 설치를 쉽게 하기 위한 여러 배포판들이 나왔다. 이들은 리눅스 기본 시스템 외에 여러 편리한 도구들을 포함한다. 슬랙웨어 배포판은 리눅스 배포판의 질을 크게 향상시켰다. 이외엔 레드햇, 데비안, 캐노니컬, SuSE 등이 있다.

20.1.4. Linux Licensing

리눅스 커널은 GPL 라이센스이고, 저작권이 면제되는 공적 영역 소프트웨어가 결코 아니다. 즉, 리눅스나 그 변형을 배포하려면 그 소스 코드를 공개해야 한다.

20.2. Design Principles

전체적인 설계로는 리눅스는 다른 유닉스 구현과 비슷하다. 리눅스는 여러 플랫폼에서 작동될 수 있지만 초기에는 PC 아키텍쳐를 위해 개발되었다. PC가 더 강력해짐에 따라 리눅스 커널의 최소한도의 기능도 추가되었다. 유닉스 사용자의 혼란을 막기 위해 유닉스와 많은 부분이 비슷하게 고안되었다. 이는 개발 속도를 늦추기도 했다.

20.2.1. Components of a Linux System

리눅스 시스템은 다음 등으로 이루어진다: 커널, 시스템 라이브러리 (C 라이브러리 등), 시스템 유틸리티 (데몬 등). 모든 커널 코드는 커널 모드로만 사용할 수 있다. 커널 모드로 사용할 필요가 없는 코드는 사용자 모드로 사용된다. 리눅스는 유닉스와 비슷하게 단일 거대 바이너리로 커널이 만들어진다. 그러나 그 안에서도 모듈화된 구조는 존재한다. 리눅스 커널은 리눅스 운영 체제의 핵심이다. 시스템 라이브러리는 여러 기능을 수행하고, 기본 시스템 호출의 더 복잡한 버전을 제공하기도 한다. 시스템 유틸리티 등에는 데몬, 등이 있다. 쉘에는 본-어게인 쉘 (bash) 등이 있다.

20.3. Kernel Modules

리눅스 커널은 커널 코드의 어떤 부분이든 필요에 따라 로드/언로드할 수 있다. 커널 모듈은 여러 이유에서 편리한데, 컴파일/링크/로딩을 모듈화할 수 있다는 점과 드라이버를 모듈로 배포할 수 있다는 점에서 중요하다. 커널 모듈은 리눅스 시스템이 다른 추가적인 드라이버 없이 표준 최소 커널만으로 배포될 수 있도록 한다. 이에는 4개의 컴포넌트가 있다: 모듈 관리 시스템, 모듈 로더/언로더, 드라이버 등록 시스템, 충돌 해결 메커니즘.

20.3.1. Module Management

모듈을 로딩하는 것은 단지 바이너리 내용을 커널 메모리로 불러오는 것만이 아니다. 리눅스는 커널 내 내부 심볼 테이블을 유지해 이를 이용한다. 커널 함수로부터 심볼을 내보내는 것은 프로그래머의 명시적 요구를 필요로 하지만, 이 심볼을 모듈로 불러오는 데는 추가적인 노력이 필요치 않다. 모듈의 로딩은 두 단계로 이루어진다. 첫째로, 모듈 로더 유틸리티가 커널에 모듈을 위한 가상 커널 메모리의 연속된 영역을 예약 요청한다. 두 번째 시스템 호출은 모듈과 그것이 내보낼 심볼 테이블을 커널에 전달한다. 마지막 모듈 관리 컴포넌트는 모듈 요청자로, 커널은 이것이 연결할 수 있는 통신 인터페이스를 정의한다.

20.3.2. Driver Registration

모듈이 로딩된 이후에는 그것이 커널에 새로운 기능을 위해 요구하기 전까지는 필요한 메모리만으로 충분하다. 모듈은 여러 기능을 등록할 수 있다. 디바이스 드라이버, 파일 시스템, 네트워크 프로토콜, 바이너리 포맷 등. sysctl과 /proc 테이블에 새 엔트리를 등록해 동적으로 설정될 수도 있다.

20.3.3. Conflict Resolution

상업적 UNIX 구현체는 벤더의 하드웨어에서 구동되도록 판매된다. 이에 비해 리눅스는 하드웨어 자원에 대한 요구 충돌을 중재하는 메커니즘을 구현한다. 이는 하드웨어 자원 접근으로 인해 모듈이 충돌해 크래시되는 것을 막고, 디바이스 드라이버가 존재하는 디바이스 드라이버와 충돌하는 자동 탐지를 막고, 여러 드라이버가 같은 하드웨어에 접근하려는 시도를 막아 충돌을 막는다. 이를 위해, 커널은 할당된 하드웨어 자원의 목록을 유지한다. 모듈은 이 메커니즘을 통해 사용하려는 하드웨어 자원을 예약해야 한다.

20.4. Process Management

리눅스의 프로세스 관리 방법을 알아보자.

20.4.1. The fork() and exec() Process Model

UNIX 프로세스 관리의 기본 원리는 새 프로세스의 생성과 새 프로그램의 가동을 fork()와 exec()으로 분리하는 것이다. 이 모델은 매우 간단하다는 이점이 있다. 유닉스에서 운영 체제는 단일 프로그램의 실행 중에서의 컨텍스트를 추적하고 있어야 한다.

20.4.1.1. Process Identity

프로세스 식별은 다음 등으로 이루어진다: 프로세스 ID(PID), 자격, 개성, 이름공간.

20.4.1.2. Process Environment

프로세스의 환경은 부모로부터 상속되고 두 널로 끝나는 벡터로 구성된다. 커맨드 라인 명령어를 목록으로 하는 명령어 벡터와 환경 변수를 목록으로 하는 환경 벡터. 새 프로세스가 생성되더라도 이는 바뀌지 않으나, 새 프로그램이 실행되면 새로운 환경이 셋업된다. 한 프로세스에서 다음 프로세스로 환경 변수를 전달하고 상속받는 것은 사용자 모드 시스템 소프트웨어에 대해 정보 전달에 대해 유연성을 제공해 준다.

20.4.1.3. Process Context

프로세스가 실행 중일 때의 컨텍스트는 다음 등이 있다: 스케쥴링 컨텍스트, 자원 계산, 파일 테이블(파일 디스크립터(fd)를 포함), 파일 시스템 컨텍스트, 시그널 핸들러 테이블, 가상 메모리 컨텍스트.

20.4.2. Processes and Threads

리눅스는 fork() 시스템 호출을 지원하며 이는 새 실행파일 이미지를 로드하지 않고 프로세스를 복제한다. 또한 리눅스는 clone() 시스템 호출로 스레드를 생성할 수도 있다. 리눅스는 스레드와 프로세스를 구분하지 않고 태스크로 일괄적으로 다룬다. clone()은 어떤 자원을 공유할지를 명시하는 플래그를 설정할 수 있다. 프로세스와 스레드를 리눅스에서 구분하지 않는 것은 리눅스가 메인 프로세스 자료 구조에 프로세스의 전체 컨텍스트를 두지 않기 때문이다. 대신, 독립적인 부분 컨텍스트에 이를 보관한다. clone() 시스템 호출의 인자는 어떤 부분컨텍스트를 복제하고 공유할지를 특정한다.

20.5. Scheduling

스케쥴링은 CPU 시간을 운영 체제 내의 다른 작업에 할당하는 작업이다. 리눅스는 다른 유닉스 시스템과 마찬가지로 선점적 멀티태스킹을 지원한다. 일반적으로 스케쥴링은 스레드의 구동과 다른 스레드에 대한 간섭으로 생각하지만 리눅스에서는 여러 커널 태스크의 수행에 대한 스케쥴링도 중요하다.

20.5.1. Thread Scheduling

리눅스는 두 개의 독립적인 프로세스 스케쥴링 알고리즘을 쓴다. 하나는 복수 스레드간 선점적 스케쥴링을 공정하게 지원하기 위한 시간 공유 알고리즘이고 다른 하나는 절대적인 우선도가 중요한 실시간 작업에 대한 알고리즘이다. 전자는 2.6 버전에 도입된 완전 공정 스케쥴러(CFS)이다. 후자의 경우 리눅스는 리얼 타임나이스 값이라는 두 가지 척도로 우선도를 둔다. CFS는 전통적 유닉스 스케쥴러와 크게 다른데, 타임 슬라이스의 개념을 없애 분배하는 공정 스케쥴링이라는 것이 다르다. 스레드가 동작하는 실제 시간을 계산하기 위해, CFS는 대상 지연 시간이라는 설정 가능한 변수에 의존한다. 이는 모든 작업이 한 번 이상 가동되어야 하는 시간을 말한다. 또한 CFS는 프로세스에 의해 스레드에 부여되는 최소 시간인 최소 단위도 사용한다. 공정 스케쥴링으로의 전환과 함께, CFS는 전통적인 유닉스 프로세스 스케쥴러와는 여러 면에서 다르다. 가장 눈여겨볼만한 것은 정적 시간 슬라이스의 개념을 없앤 것이다.

20.5.2. Real-Time Scheduling

리눅스의 실시간 스케쥴링 알고리즘은 일반 시간 공유 스레드에 적용되는 공정 스케쥴링보다 훨씬 간단하다. 선입 선수행과 순차 순환의 두 알고리즘을 가진다. 리눅스의 실시간 스케쥴링 알고리즘은 강실시간이라기보단 약실시간이다.

20.5.3. Kernel Synchronization

커널이 자신의 동작을 스케쥴링하는 방식은 스레드를 스케쥴링하는 것과 근본적으로 다르다. 커널 모드 동작에 대한 요청은 수행 중인 프로그램이 운영 체제 서비스를 시스템 호출 등 명시적으로 요청하거나 페이지 폴트 등으로 인해 묵시적으로 요청함으로써 일어난다. 하드웨어 인터럽트 등으로 인해 생길 수도 있다. 커널의 문제는 이 모든 작업들이 같은 내부 자료 구조에 접근하려 시도할 수 있다는 것이다. 2.6 버전 이전에는 리눅스는 비선점적 커널이었으나, 2.6부터는 선점적 커널로 변하였다. 리눅스 커널은 커널을 잠그는 데 스핀락과 세마포어를 제공한다. 리눅스는 커널 선점을 비활성화하고 활성화하는 데 있어 커널 인터페이스를 제공한다. 스핀락은 락이 짧은 시간 동안 유지될 때만 커널에서 쓰인다. 이외에는 세마포어가 쓰인다. 리눅스에서 임계 영역에 쓰이는 두 번째 보호 기법은 인터럽트 서비스 루틴이다. 하지만 인터럽트를 비활성화는 데에는 성능 패널티가 있다. 이를 위해 리눅스 커널은 긴 임계 영역이 전체 기간 동안 인터럽트 비활성화 없이 동작할 수 있는 동기화 아키텍쳐를 제공한다. 리눅스는 이 아키텍쳐를 구현하기 위해 인터럽트 서비스 루틴을 위쪽 절반과 아래쪽 절반으로 분리시킨다. 위쪽 절반에서는 재귀적 인터럽트가 비활성화된다. 아래쪽 절반에서는 모든 인터럽트가 활성화되지만 그 자신을 인터럽트하지는 않는다. 이러한 분리는 커널이 인터럽트에 반응해야 하는 모든 처리를 할 때 그 자신에 의해 인터럽트되는 일 없이 처리하는 것을 가능케 한다. 이 위쪽/아래쪽 절반 구조는 일반 전경 커널 코드를 실행하는 동안 선택된 아래쪽 절반을 비활성화하는 메커니즘으로 완성된다. 각 층은 상위 층에서 동작하는 코드에 의해서는 인터럽트될 수 있지만 같은 층이나 그 아래 층의 코드에 의해서는 인터럽트되지 않는다.

20.5.4. Symmetric Multiprocessing

리눅스 2.0 커널은 대칭적 멀티프로세서(SMP) 하드웨어를 지원해 별개의 프로세서에서 별개의 스레드가 병렬로 실행될 수 있게 하는 최초의 안정적 리눅스 커널이다. 커널 버전 2.2에서는 단일 커널 스핀락(빅 커널 락, BKL)이 생성되어 커널 내에서 복수의 스레드가 동시에 작동할 수 있도록 하였다. 그러나 이는 초기에는 확장성이 부족했으며, 복수의 스핀락으로 분리되면서 확장성이 보강되었다. 3.0과 4.0 커널은 여러 추가적인 SMP 개선을 제공하였다.

20.6. Memory Management

리눅스의 메모리 관리는 두 부분이 있다. 첫 번째는 물리적 메모리를 할당하고 해제한다. 두 번째는 ㅏ상 메모리를 다루는데 이는 동작 중인 프로세스의 주소 공간에 메모리를 매핑한다.

20.6.1. Management of Physical Memory

리눅스는 물리적 메모리를 4개의 다른 으로 분리한다. DMA, DMA32, NORMAL, HIGHMEM. 이 존들의 구체적인 분리는 아키텍쳐에 따라 다르다. 리눅스 커널의 주 물리적 주소 관리자는 페이지 할당자이다. 각 존은 그 존에 대한 물리적 페이지를 할당하는 할당자를 갖는다. 이 할당자는 인접한 물리적 페이지를 버디로 묶는데, 두 인접한 영역이 해제되면 하나의 버디 힙으로 병합된다. 이 과정은 재귀적으로 반복된다. 궁극적으로, 리눅스 커널 내 모든 메모리 할당은 드라이버의 시스템 부트 시점에서의 메모리 예약에 의해 정적으로 수행되거나, 페이지 할당자에 의해 동적으로 수행된다. 그러나 커널 함수들은 기본 할당자가 아니라 고유의 할당자를 갖는다. 리눅스 운영 체제의 많은 부분은 요청받은 페이지 전체를 할당해야 하지만, 더 작은 크기의 메모리가 필요할 때도 있다. 이를 위해 커널은 임의 크기 요청에 대한 추가적인 할당자를 갖는다. 리눅스에서 커널 메모리를 할당하는 추가적인 전략은 슬랩 할당자이다. 캐시는 하나 이상의 슬랩으로 구성된다. 각 캐시는 캐시가 표현하는 커널 자료 구조가 인스턴스화된 오브젝트로 점유된다. 슬랩 할당 알고리즘은 캐시를 이용해 커널 오브젝트를 저장한다. 슬랩은 3개의 상태일 수 있다: 꽉 참, 비어 있음, 부분적. 리눅스에는 물리적 페이지를 자체적으로 관리하는 두 개의 다른 부분시스템이 있다: 페이지 캐시와 가상 메모리 시스템. 이는 서로 밀접한 관계가 있다.

20.6.2. Virtual Memory

리눅스 가상 메모리 시스템은 각 프로세스에 접근 가능한 주소 공간을 유지한다. 주소 공간의 첫 번째 관점은 논리적 관점으로, 가상 메모리 시스템이 주소 공간의 레이아웃을 고려할 때 받는 명령어를 묘사한다. 두 번째 관점은 각 주소 공간에 대한 물리적 관점으로, 프로세스의 하드웨어 페이지 테이블에 저장된다.

20.6.2.1. Virtual Memory Regions

리눅스는 여러 가상 메모리 영역을 가진다. 이는 메모리의 배후 영역이 있을 수도 없을 수도 있는데 없는 경우를 영요구 영역이라 하며, 이는 프로세스가 그 영역의 페이지를 읽으려고 할 때 0으로 찬 메모리의 페이지를 받게 된다. 파일을 배후로 둔 영역은 그 파일의 섹션에 대한 관점으로 볼 수 있다. 가상 메모리 영역은 쓰기에 대한 반응으로 정의되기도 한다.

20.6.2.2. Lifetime of a Virtual Address Space

커널은 두 상황에서 새 가상 주소 공간을 만든다: 프로세스가 exec()으로 새 프로그램을 실행할 때와 새 프로세스가 fork()으로 만들어졌을 때. 첫 번째 경우는 새로운 완전히 빈 가상 주소 공간을 받는다. 두 번째 경우는 존재하는 프로세스의 가상 주소 공간을 복제해 받는다. 이 때 전용으로 매핑된 가상 주소 영역에 복사 연산이 발생할 경우 부모 프로세스에서 그 영역에서 페이지에 쓴 것은 다른 프로세스의 주소 공간에 써지면 안 된다.

20.6.2.3. Swapping and Paging

가상 메모리 시스템의 중요한 작업은 메모리가 필요할 때 디스크로부터 물리적 메모리에 페이지를 재할당하는 것이다. 페이징 시스템은 어떤 페이지가 배후 저장소에 써질지와 언제 쓸지를 결정하는 정책 알고리즘과 페이지 데이터를 필요할 때 물리적 메모리로 전환하고 다시 쓰는 페이징 메커니즘으로 나뉜다. 리눅스의 페이지아웃 정책은 일반적인 시계 알고리즘의 변형된 버전을 사용한다. 페이징 메커니즘은 전용 스왑 디바이스/파티션이나 이랍ㄴ 파일로의 페이징 모두를 지원하지만, 파일에 대한 스왑은 파일 시스템으로부터 생기는 추가 오버헤드로 인해 훨씬 더 비싸다.

20.6.2.4. Kernel Virtual Memory

리눅스는 각 프로세스의 가상 주소 공간에 대해 고정된 아키텍쳐 의존적 영역을 자체적인 사용을 위해 예약한다. 이는 두 부분으로 나뉘는데 첫 번째는 시스템의 가용 가능한 물리적 메모리 페이지에 대한 페이지-테이블 참조를 포함하는 정적 영역이고 두 번째는 특정한 목적 없이 예약된다.

20.6.3. Execution and Loading of User Programs

리눅스 커널의 사용자 프로그램의 실행은 exec() 시스템 호출에 의해 수행된다. 새 프로그램을 로딩하는 데는 단일한 루틴이 없다. 대신에 리눅스는 가능한 로더 함수의 테이블을 유지해 exec() 시스템 호출이 이루어졌을 때 그 파일들이 주어진 파일을 로드할 수 있게 한다. 이에 새 리눅스 시스템은 ELF 포맷을 사용한다.

20.6.3.1. Mapping of Programs into Memory

리눅스에서 바이너리 로더는 바이너리 파일을 물리적 메모리로 로드하지 않는다. 대신, 바이너리 파일의 페이지는 가상 메모리 영역으로 매핑된다. 초기 메모리 매핑은 커널 바이너리 로더에 의해 셋업된다. 주소 공간의 예약된 영역 중 하나에는 커널이 배치되며 이는 사용자 모드 프로그램이 접근할 수 없는 가상 메모리 영역이다. 로더의 일은 초기 메모리 매핑을 셋업해 프로그램의 실행을 시작할 수 있게 하는 것이다. 스택은 사용자 모드 가상 메모리의 맨 위에 생성되고, 낮은 번호 주소로 자란다. 이 고정 크기 영역 직후에는 런타임에 할당되는 데이터를 가변 크기 영역이 존재한다. 이 매핑이 셋업되고 나면 로더는 프로세스의 프로그램 카운터 레지스터를 ELF 헤더에 기록된 시작점으로 초기화해 프로세스가 스케쥴될 수 있게 한다.

20.6.3.2. Static and Dynamic Linking

프로그램이 로드되고 가동되기 시작할 때, 바이너리 파일의 모든 필요한 내용들은 프로세스의 가상 주소 공간으로 로드된다. 하지만 프로그램은 시스템 라이브러리의 함수도 동작시켜야 하며 이들도 로드되어야 한다. 이는 정적으로 링크될 수도 있는데 이의 단점은 이를 사용한 모든 프로그램이 동일한 공용 시스템 라이브러리 함수의 복제를 갖고 있어야 한다는 점이다. 이에 비해서는 시스템 라이브러리를 메모리에 한 번만 로드한 뒤 동적 링크하는 것이 더 효과적이다. 리눅스는 동적 링킹을 특별한 링커 라이브러리를 통해 사용자 모드에서 지원한다. 이런 공유 라이브러리가 메모리 내 정확히 어디로 매핑되는지는 중요치 않다: 이는 위치 독립적 코드(PIC)로 컴파일되므로 메모리 내 어디에서도 동작할 수 있다.

20.7. File Systems

리눅스는 유닉스의 표준 파일 시스템 모델을 따른다. 리눅스 커널은 파일 타입의 소프트웨어 층과 가상 파일 시스템 너머의 세부 구현을 숨김으로써 모든 파일 유형을 다룰 수 있다.

20.7.1. The Virtual File System

리눅스 가상 파일 시스템은 4가지의 오브젝트 타입을 다룬다:

  • 각 파일을 나타내는 inode 오브젝트
  • 열린 파일을 나타내는 file 오브젝트
  • 전체 파일 시스템을 나타내는 superblock 오브젝트
  • 각 디렉토리 엔트리를 나타내는 dentry 오브젝트

이 4개의 오브젝트 타입 각각에 대해 가상 파일 시스템은 가능한 연산들의 집합을 정의한다. 가상 파일 시스템은 오브젝트의 함수 테이블에서 적절한 함수를 호출해 그 오브젝트의 정확한 유형을 미리 알 필요 없이 이를 다룬다. inode와 file 오브젝트는 파일 접근에 쓰이는 메커니즘이다. 파일 오브젝트는 대개 단일 프로세스에 속하지만, inode 오브젝트는 그렇지 않다. 디렉토리 파일은 다른 파일들과 약간 다르게 다뤄지는데, 사용자가 그 파일을 열었는지를 고려하지 않는다. superblock 오븢게트는 독립된 파일 시스템을 형성하는 연결된 파일의 집합을 표현한다. dentry 오브젝트는 디렉토리 엔트리를 표현하고, 이는 파일의 경로명의 디렉토리 명을 포함할 수도 있고 실제 파일명을 포함할 수도 있다. dentry 캐시도 있는데 이로부터 inode를 얻는 것은 디스크에서 파일을 직접 읽는 것보다 빠르다.

20.7.2. The Linux ext3 File System

리눅스에서 쓰이는 표준 파일 시스템은 ext3이다. 초기에는 확장 파일 시스템(extfs) 이었으며 이것이 개선되어 이차 확장 파일 시스템(ext2)가 되었고 이것이 개선되어 삼차 확장 파일 시스템(ext3)이 된 것이다. 리눅스 커널 개발자는 이를 확장한 사차 확장 파일 시스템(ext4)도 만들었다. 리눅스의 ext3은 BSD의 FFS와 비슷한 메커니즘을 쓰며, 파일 시스템 내 간접 블록 내 데이터 블록 포인터를 저장해서 간접 가리킴이 3단계까지 일어나게 된다. ext3과 FFS의 핵심 차이점은 디스크 할당 정책이 다르다. FFS에서는 디스크가 8KB 단위 블록으로 이루어진 파일에 할당되며 이는 1KB로 파편화된다. ext3은 파편을 쓰지 않으며 전체 할당을 더 작은 단위로 수행한다. 더 좋은 성능을 위해, 운영 체제는 물리적으로 인접한 입출력 요청을 클러스터링해서 입출력을 가능한 큰 단위로 수행해야 한다. ext3 할당 정책은 다음과 같다: FFS처럼 파일 시스템은 블록 그룹으로 불리는 복수의 영역으로 분할된다. FFS는 실린더 그룹이라는 비슷한 개념을 쓴다. 파일을 할당할 때, ext3은 그 파일에 대한 블록 그룹을 선택해야 한다. 블록 그룹 내에서 ext3은 파편화를 줄이기 위해 가능하다면 물리적으로 연속된 영역을 할당하려 한다. 자유 블록이 식별되면 이는 할당된 블록이 조우될 때까지 이 탐색이 확장된다. 이를 통해 연속된 자유 블록을 미리 할당해서 파편화를 줄인다.

20.7.3. Journaling

ext3 파일 시스템은 저널링이라는 유명한 특성을 가졌는데, 이는 파일 시스템에 대한 수정이 저널에 순차적으로 쓰여지는 것을 말한다. 특정한 작업을 수행하는 연산들은 트랜잭션이라 한다. 시스템이 크래시되면, 어떤 트랜잭션들은 저널에 남을 수도 있는데 시스템이 복구되면 이 트랜잭션들은 반드시 완료되어야 한다. 메모리 내의 저널에 업데이트를 수행하는 것이 디스크 위의 자료 구조에 수행하는 것보다 빠르므로 저널링 파일 시스템에서는 여러 동작들이 비저널링 파일 시스템보다 빨라진다.

20.7.4. The Linux Proc File System

리눅스 가상 파일 시스템의 유연성은 파일 시스템이 데이터를 영구적으로 저장하지 않고 다른 기능을 수행할 수 있는 인터페이스를 구현할 수 있게 한다. 이 예로 /proc이 있는데 이는 리눅스에 한정된 것은 아니다. 리눅스는 파일 시스템의 루트 디렉토리에 추가적인 디렉토리와 텍스트 파일을 추가함으로써 /proc 파일 시스템을 구현한다. /proc 파일 시스템은 2가지를 구현해야 한다: 디렉토리 구조와 그 내의 파일 내용들. inode 번호로부터 정보 유형으로의 매핑은 inode 넘버를 PID와 그 외의 2부분으로 분리한다. PID가 0이면 이 inode는 전역 정보를 나타낸다. inode 범위의 모든 번호가 쓰이는 것은 아니다. 애플리케이션 내 이 변수들에 대한 효과적인 접근을 위해, /proc/sys는 sysctl()이라는 시스템 호출을 통해 파일 시스템 내 오버헤드 없이 같은 변수를 텍스트가 아닌 바이너리로 읽고 쓸 수 있도록 한다.

20.8. Input and Output

사용자에게 리눅스의 입출력 시스템은 다른 유닉스 시스템과 비슷하다. 리눅스는 모든 디바이스를 3개의 클래스로 분류한다. 블록 디바이스는 무작위 접근을 완전히 독립적인 데이터의 고정 크기 블록으로 허용하며 하드 디스크, 플로피 디스크, CD-ROM, 블루레이 디스크, 플래시 메모리 등이 있다. 캐릭터 디바이스는 마우스나 키보드 등이 있다. 네트워크 디바이스는 사용자들이 데이터를 이로 직접적으로 전송할 수 없으며 커널의 네트워킹 부분시스템에 연결을 열어 간접적으로 통신한다.

20.8.1. Block Devices

블록 디바이스는 시스템의 모든 디스크 디바이스에 대한 주 인터페이스를 제공한다. 요청 관리자는 블록 디바이스 로부터/로의 버퍼 내용의 읽기/쓰기을 관리하는 소프트웨어 층이다. 요청 목록은 각 블록 디바이스 드라이버에 대해서 독립적으로 유지된다. 리눅스 커널 버전 2.6은 새로운 입출력 스케쥴링 알고리즘인 완전 공정 큐(CFQ) 스케쥴러를 도입하였다. CFQ는 각 프로세스의 목록을 순차 순환으로 수행한다.

20.8.2. Character Devices

캐릭터 디바이스 드라이버는 고정 크기 데이터 블록에 대한 무작위 접근을 허용하지 않는 거의 모든 디바이스 드라이버이다. 이에 대한 주된 예외는 터미널 디바이스를 구현하는 캐릭터 디바이스 드라이버이다. 회선 규범은 터미널로부터 정보에 대한 인터프리터이다. 가장 흔한 것으로는 tty가 있다. 사용자 프로세스에 대한 입출력과는 상관이 없는 다른 회선 규범인 PPP, SLIP 등도 존재한다.

20.9. Interprocess Communication

리눅스는 프로세스간 통신을 할 수 있는 풍부한 환경을 제공한다.

20.9.1. Synchronization and Signals

프로세스에 이벤트가 발생했음을 알리는 표준 메커니즘은 시그널이다. 내부적으로 리눅스 커널은 커널 모드로 동작하는 프로세스와 통신하기 위해서는 시그널을 쓰지 않는다. 대신에 대기 큐 구조와 상태 스케쥴링을 통해 이를 이룬다. 리눅스는 시그널 뿐만 아니라 세마포어를 통해서도 프로세스간 비동기 이벤트를 다룬다.

20.9.2. Passing of Data among Processes

유닉스의 표준 파이프 메커니즘은 자식 프로세스가 부모로부터의 통신 채널을 상속받을 수 있도록 한다. 파이프의 한 쪽 끝에 써진 데이터는 다른 쪽 끝에서 읽어질 수 있다. 다른 프로세스 통신 방법인 공유 메모리는 크거나 작은 데이터를 통신할 수 있는 극히 빠른 방식을 제공한다. 리눅스 내 공유 메모리 영역은 프로세스에 의해 생성되거나 삭제될 수 있는 영구 오브젝트이다.

20.10. Network Structure

네트워킹은 리눅스의 핵심 기능 중 하나이다. 리눅스 커널 내에서 이는 대개 3개의 소프트웨어 층으로 구현된다: 소켓 인터페이스, 프로토콜 드라이버, 네트워크 디바이스 드라이버. 사용자 애플리케이션은 소켓 인터페이스를 통해 네트워킹 요청을 수행한다. 다음 소프트웨어 층은 포로토콜 스택으로, BSD의 자체 프레임워크와 구조가 비슷하다. 이 프로토콜 레이어는 패킷을 다시 쓰거나, 새 패킷을 만들거나, 패킷을 재조립하거나 분해하거나, 오는 데이터를 버릴 수 있다. 네트워킹 스택의 층간 모든 통신은 단일 skbuff(소켓 버퍼) 구조를 전달함으로써 이루어진다. 리눅스 네트워킹 시스템 내 가장 중요한 프로토콜 집합은 TCP/IP 프로토콜이다. 각네트워킹 스택의 프로토콜 소프트웨어에 도착하는 각 skbuff 패킷은 패킷이 연관된 프로토콜을 식별할 수 있도록 태그되어 있어야 한다. 이후 들어오는 IP 패킷은 IP 드라이버로 전달된다. IP 소프트웨어는 패킷을 방화벽을 관리하는 코드로 전달한다. 방화벽 관리자는 여러 방화벽 사슬을 유지해 skbuff를 이 사슬들 중 하나로 전달해 관리시킨다. IP 드라이버에 의해 수행되는 두 개의 다른 기능은 큰 패킷의 파편으로의 분해나 파편으로부터의 재조립이다. IP에 의해 식별되는 패킷은 그 도착지가 식별하는 다른 프로토콜 드라이버로 전달된다.

20.11. Security

리눅스의 보안 모델은 2개로 분류할 수 있다: 인증과 접근 제어.

20.11.1. Authentication

유닉스의 인증은 공개적으로 읽을 수 있는 비밀 번호 파일을 사용함으로써 수행된다. 이는 비밀번호 길이 제한 등의 여러 단점이 있었다. 그래서 사용자 인증이 필요한 임의의 시스템 컴포넌트에 쓸 수 있는 공유 라이브러리인 장착형 인증 모듈(PAM)이 등장하여 쓰이고 있다.

20.11.2. Access Control

리눅스를 포함한 유닉스 시스템의 접근 제어는 고유 번호 식별자를 통해 수행된다. 이는 시스템의 여러 오브젝트에 적용된다. 프로세스의 UID가 오브젝트의 UID와 맞으면 그 오브젝트에 대한 사용자 권한소유자 권한을 갖는다. 그렇지 않으면 그룹 권한이 고려되고 그것도 안 되면 전역 권한을 갖는다. 리눅스는 오브젝트에 사용자/소유자/그룹/전역 권한에 대한 접근 모드를 특정하는 보호 마스크를 부여해 접근 제어를 수행한다. 예외는 특권이 부여된 루트 UID이다. 리눅스는 유닉스 표준의 setuid 메커니즘을 구현한다. 이는 두 방식으로 보강되는데 리눅스는 POSIX 표준의 saved user-id 메커니즘을 쓰고 두 번째로는 fsuidfsgid를 쓴다. 마지막으로 리눅스는 프로그램으로부터 다른 프로그램으로 권한을 전달할 수 있는 유연한 메커니즘을 제공한다.

20.12. Summary

  • 리눅스는 유닉스 표준에 기반한 현대적인 무료 운영 체제이다. 이는 범용 PC 하드웨어에서 효율적이고 신뢰성 있게 작동할 수 있도록 설계되었지만, 모바일 폰 등 여러 다른 플랫폼에서도 동작할 수 있다. 이는 표준 유닉스 시스템과 호환 가능한 프로그래밍 인터페이스와 사용자 인터페이스를 제공하고 상업적으로 지원되는 많은 애플리케이션을 포함하는 많은 유닉스 애플리케이션을 가동시킬 수 있다.
  • 리눅스는 허공으로부터 나오지 않았다. 완전한 리눅스 시스템은 리눅스와는 독립적으로 개발된 많은 컴포넌트를 포함한다. 핵심 리눅스 운영 체제 커널은 완전히 고유하지만, 이는 존재하는 무료 유닉스 소프트웨어의 많은 수를 가동할 수 있게 해서, 전매 코드로부터 자유롭게 유닉스와 호환되는 전체 운영 체제를 가능케 했다.
  • 리눅스 커널은 성능 이유로 인해 전통적인 단일 거대 커널로 구현되었으나, 이는 충분히 모듈화되어서 많은 드라이버가 런타임에 동적으로 로드/언로드될 수 있게 한다.
  • 리눅스는 다사용자 시스템으로, 프로세스간 보호와 시간 공유 스케쥴러에 따른 복수 프로세스 실행을 제공한다. 새로 만들어진 프로세스는 그 부모 프로세스와 실행 환경의 일부를 공유함으로써 멀티스레드 프로그래밍을 가능케 한다.
  • 프로세스간 통신은 System V 메커니즘 (메시지 큐, 세마포어, 공유 메모리)와 BSD 소켓 인터페이스를 통해 지원된다. 복수의 네트워킹 프로토콜은 소켓 인터페이스를 통해 동시에 접근될 수 있다.
  • 메모리 관리 시스템은 페이지 공유와 쓰기 시 복사를 사용해 다른 프로세스간 공유되는 데이터의 중복을 최소화한다. 페이지는 최초에 참조될 때에는 필요에 따라 로드되고 물리적 메모리를 되찾아야 할 필요가 생길 때 최소 사용 빈도 알고리즘에 따라 배후 저장소로 되돌려진다.
  • 사용자에게, 파일 시스템은 유닉스 시맨틱을 따르는 계층적 디렉토리 트리로 구성된다. 내부적으로, 리눅스는 복수의 파일 시스템을 관리하는 추상 층을 사용한다. 디바이스 지향적인, 네트워크가 적용된, 그리고 가상 파일 시스템이 지원된다. 디바이스 지향 파일 시스템은 가상 메모리 내에서 통합된 페이지 캐시를 통해 디스크 저장소에 접근한다.

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중