시스템 프로그래밍과 운영체제 인터페이스
프로그램에서 파일을 읽는 함수 하나를 호출했다고 하자. 소스코드는 먼저 기계가 실행할 형태로 번역·결합되어야 한다. 실행 중에는 프로그램이 임의로 디스크 명령을 내리지 않고 운영체제에 보호된 요청을 전달한다. 운영체제는 권한과 인수를 검사하고 파일체계와 장치 경로를 거쳐 작업을 시작하며, 장치는 완료를 알린다. 결과…
이어 읽을 선수 설명: 프로그램·프로세스·스레드를 구분한다에서 실행 주체와 공유 자원을, 동기화·경쟁·교착상태에서 기록 작업자의 순서·소유권을 확인한다. 이 절의 12바이트 기록 사례에서는 수신 완료, 파일 쓰기 완료, 요구한 영속 완료를 각각 판단한다.
함수 한 줄도 번역·운영체제·장치를 거쳐 결과가 된다
프로그램에서 파일을 읽는 함수 하나를 호출했다고 하자. 소스코드는 먼저 기계가 실행할 형태로 번역·결합되어야 한다. 실행 중에는 프로그램이 임의로 디스크 명령을 내리지 않고 운영체제에 보호된 요청을 전달한다. 운영체제는 권한과 인수를 검사하고 파일체계와 장치 경로를 거쳐 작업을 시작하며, 장치는 완료를 알린다. 결과가 준비될 때까지 호출자를 기다리게 할지 다른 일을 하게 할지도 정해야 한다.
이 경로의 한 층이라도 모르면 함수를 호출하면 운영체제가 알아서 처리한다는 검은 상자만 남는다. 그러면 이식성 문제, 실행파일 결합 실패, 요청량보다 적게 처리된 입출력, 장치 사건 폭주, 파일 손상과 프로그램 사이 데이터 교환 오류를 원인별로 나누기 어렵다. 시스템 프로그래밍은 프로그램과 실행환경의 경계를 명시하고, 번역·적재·보호전환·입출력·자원수명을 끝까지 통제하는 일이다.
한 문장으로 말하면
시스템 프로그래밍(system programming)은 운영체제·실행환경·하드웨어 경계에서 프로그램의 번역과 적재, 보호된 자원, 입출력과 프로세스 간 협력을 다루는 프로그래밍 영역이다.
커널(kernel)은 장치·메모리·프로세스 같은 보호 자원을 높은 권한으로 관리하는 운영체제의 핵심 부분이고, 시스템콜(system call)은 사용자 프로그램이 그 커널 서비스를 요청하는 규정된 진입방법이다. 바이너리(binary)는 소스가 번역된 기계명령·데이터와 실행에 필요한 부가정보를 뜻한다.
일반 응용 개발과 별개의 언어를 뜻하지 않는다. 같은 언어를 써도 운영체제가 돌려준 입출력 자원 식별자의 수명, 메모리 매핑, 시스템콜의 실패와 재시작, 프로세스 사이 데이터 교환과 바이너리 호환성을 직접 책임지면 시스템 프로그래밍의 문제가 된다. 핵심 질문은 어느 경계의 계약인가, 누가 자원을 소유하는가, 언제 완료된 것인가, 중간 실패 뒤 상태가 무엇인가다.
정확히 말하면: 경계별 계약과 자원수명을 보존한다
시스템 프로그램의 한 작업을 요청 입력 → 실행형태 변환 → 보호경계 호출 → 자원 조작 → 완료 통지 → 정리로 나타낼 수 있다. 올바른 프로그램은 다음 계약을 함께 만족해야 한다.
- 표현 계약: 소스의 이름·자료배치·호출이 대상 실행환경의 기계코드와 바이너리 규칙으로 일관되게 변환된다.
- 보호 계약: 사용자 프로그램은 허용된 인터페이스로만 커널과 장치 자원을 요청하고, 커널은 주소·길이·권한을 검증한다.
- 완료 계약: 요청을 접수한 시점, 장치가 끝낸 시점, 영속 저장이 보장된 시점을 구분한다.
- 수명 계약: 파일·메모리·프로세스 간 통신 자원을 만든 주체와 닫는 시점, 상속·공유·오류 시 정리를 정한다.
- 실패 계약: 부분 처리, 중단, 재시도, 프로세스 종료와 시스템 재시작 뒤 관찰 가능한 상태를 정의한다.
따라서 호출이 한 번 성공했다는 사실만으로 데이터가 영구 저장되었다거나 상대 프로세스가 처리했다거나 모든 자원이 정리되었다고 추론하지 않는다. 각 경계의 완료 의미를 따로 확인한다.
전체에서 어디인가: 소스에서 장치 완료까지
이 그림의 입출력(Input/Output, I/O)은 프로그램이 파일·장치 등과 데이터를 읽고 쓰는 동작이다. 호출 인터페이스인 API의 정의는 API와 ABI: 소스 계약과 바이너리 계약에서 확인한다.
상자 안의 대상과 화살표의 조건을 함께 읽으세요. 좁은 화면에서는 같은 관계를 세로로 재배치한 도식을 보여 줍니다.
컴파일·어셈블, 목적파일 결합, 실행 준비의 세 관계다. 번역·결합 경계를 지난 실행파일을 로더가 사용자 프로세스의 주소공간으로 준비한다. 아래 그림은 이 프로세스에서 이어지는 실행 중 경로다.
상자 안의 대상과 화살표의 조건을 함께 읽으세요. 좁은 화면에서는 같은 관계를 세로로 재배치한 도식을 보여 줍니다.
번역, 사용자·커널 보호, 장치 완료를 구분한 개념 경로다. API가 커널 서비스를 요구하는 조건을 보존하며, 프로세스 간 통신 분기를 모두 장치 작업으로 연결하지 않는다. 통지는 요청별 완료 의미로 해석한다.
첫 그림은 실행하기 전의 번역 계약이다. 둘째 그림의 사용자 프로세스→시스템콜은 보호된 서비스 요청이며, 파일·IPC·장치 요청은 검증 뒤 서비스 분기다. IPC 분기를 장치로 억지로 연결하지 않는다. 장치 작업의 완료→상태 갱신→통지는 요청별 완료 계약으로 읽고, 수용과 영속 보장을 섞지 않는다.
먼저 알아둘 것
앞에서 프로그램은 명령의 정적 표현, 프로세스는 독립 주소공간과 자원을 가진 실행 중 프로그램이며, 컴파일러와 런타임은 소스 의미를 실행으로 연결한다고 배웠다. API는 기능 호출의 입력·출력·상태·오류 계약이고, 파일은 영속 데이터를 이름 있는 논리 단위로 조직한다. 여기서는 이 개념들이 시스템 프로그래밍의 실행 형태와 커널·장치 경계에서 만나는 방식을 다룬다.
API와 ABI: 소스 계약과 바이너리 계약을 나눈다
응용 프로그램 인터페이스(Application Programming Interface, API)는 소스 수준에서 호출할 함수·자료형·상수와 입력·출력·오류 의미를 정한 계약이다. 같은 API를 여러 운영체제나 라이브러리가 각자 구현할 수 있다.
응용 바이너리 인터페이스(Application Binary Interface, ABI)는 이미 번역된 코드가 서로 호출되고 실행환경과 결합되는 바이너리 수준의 규칙이다. 함수 인수를 어느 레지스터나 스택 위치에 둘지 정하는 호출규약, 자료형 크기·정렬과 구조체 배치, 심볼 이름, 목적파일·실행파일 형식, 시스템콜 인수 전달규칙 등이 포함될 수 있다.
| 축 | API | ABI |
|---|---|---|
| 적용 수준 | 소스코드와 문서화된 기능 의미 | 기계코드·목적파일·실행 중 결합 |
| 주요 요소 | 함수명, 자료형, 입력·출력, 오류·상태 | 호출규약, 자료배치, 심볼, 바이너리 형식 |
| 호환 실패 | 소스 수정 또는 재컴파일 필요 | 링크 실패·실행 오류·잘못된 값 전달 가능 |
| 이식 방식 | 같은 API를 대상 환경용으로 다시 구현·컴파일 | 같은 ABI를 지키는 바이너리는 재컴파일 없이 결합 가능할 수 있음 |
API가 같아도 ABI가 다르면 기존 실행파일을 그대로 섞지 못할 수 있고, ABI가 맞아 링크되어도 API의 의미나 버전 계약이 달라지면 결과가 틀릴 수 있다. 빌드 성공과 의미 호환을 분리한다.
컴파일·어셈블·링크·로드: 실행 가능한 전체를 만든다
컴파일(compile)은 고수준 소스의 구문과 의미를 검사하고 더 낮은 수준의 명령표현으로 번역하는 과정이다. 어셈블(assemble)은 사람이 읽을 수 있는 기계명령 표기인 어셈블리 코드를 기계명령과 부가정보가 든 목적파일(object file)로 바꾼다. 도구에 따라 컴파일러가 목적파일까지 한 번에 만들 수 있으므로 단계는 개념적으로 구분하되 명령 개수와 동일시하지 않는다.
목적파일은 아직 다른 파일에 있는 함수나 데이터의 실제 주소를 모를 수 있다. 심볼(symbol)은 함수·전역 데이터처럼 결합할 대상을 이름으로 표시한 것이고, 재배치(relocation)는 최종 배치가 정해진 뒤 명령이나 데이터 안의 주소 참조를 고치는 작업이다. 링커(linker)는 여러 목적파일과 라이브러리를 결합하고 심볼 참조를 해소하며 필요한 재배치를 수행해 실행파일이나 공유 라이브러리를 만든다.
로더(loader)는 실행파일과 필요한 라이브러리를 프로세스 주소공간에 매핑하고, 남은 결합·재배치와 초기화를 수행하여 시작점으로 제어를 넘기는 운영체제·런타임 구성요소다. 전체 불변조건은 모든 필요한 심볼이 하나의 의도한 정의에 연결되고, 코드와 데이터가 ABI에 맞는 주소·권한으로 배치되며, 초기화 전에는 사용자 코드가 그 상태를 사용하지 않는다이다.
작은 두 파일로 결합을 추적해 보자. 자체 교육 예제로 main 파일에는 addTwo(2,3) 호출이 있고 helper 파일에는 두 입력을 더해 돌려주는 addTwo의 정의가 있다. 괄호의 2와 3은 전달할 두 인수이며, 호출의 결과는 5다. 각 파일을 따로 번역하면 main 목적파일에는 addTwo를 호출해야 한다는 이름 참조가 있고 helper 목적파일에는 addTwo의 코드는 여기 있다는 정의가 있다. 링커는 두 파일의 이름을 맞춰 호출할 코드를 결정하고 배치에 맞게 주소 참조를 고친다. 그다음 로더가 실행에 필요한 영역을 마련하여 main을 시작하면 호출이 helper의 계산으로 이어진다.
helper 목적파일도 해당 정의가 있는 라이브러리도 링크 입력에 없다면 언제 실패하는가? 이 예의 정적 결합에서는 addTwo의 정의를 찾지 못해 링크 단계에서 실패한다. main의 문법이 맞아 컴파일됐다는 사실만으로 전체 실행파일이 완성되지는 않는다. 반대로 정의 이름을 찾았어도 호출자와 함수가 인수 위치·자료형 크기에 서로 다른 ABI 규칙을 쓰면 2와 3을 의도대로 전달하지 못할 수 있다. 이름 해소, 바이너리 전달규칙, 함수의 계산 의미를 각각 확인해야 한다.
정적 링크와 동적 링크: 결합 시점과 의존성을 바꾼다
정적 링크(static linking)는 필요한 라이브러리 코드를 링크 시점에 실행파일 안으로 선택·복사하여 결합하는 방식이다. 동적 링크(dynamic linking)는 공유 라이브러리의 코드가 별도 파일로 존재하고 적재 또는 실행 중 필요한 심볼을 결합하는 방식이다.
| 축 | 정적 링크 | 동적 링크 |
|---|---|---|
| 결합 시점 | 빌드 시 대부분 완료 | 적재 시 또는 첫 사용 시 일부 완료 가능 |
| 배포 의존성 | 실행파일에 포함된 부분은 외부 버전 의존이 작음 | 대상 환경의 공유 라이브러리와 탐색규칙 필요 |
| 크기·공유 | 실행파일이 커지고 프로세스별 중복 가능 | 여러 프로세스가 읽기 전용 코드를 공유 가능 |
| 갱신 | 실행파일을 다시 링크·배포해야 반영 | 호환 ABI를 지킨 라이브러리 교체로 반영 가능 |
| 대표 실패 | 큰 파일, 보안수정 반영 누락 | 라이브러리 부재·잘못된 버전·탐색경로 오염·ABI 불일치 |
동적 링크가 자동으로 작고 안전한 것은 아니다. 사용하지 않는 코드 제거, 라이브러리 수, 실제 공유 여부에 따라 크기가 달라지고, 호환되지 않는 라이브러리 교체는 실행을 깨뜨린다. 반대로 정적 링크도 운영체제 인터페이스와 설정·인증서 같은 런타임 의존성까지 없애지는 않는다. 빌드 산출물 목록, 라이브러리 버전·해시와 탐색경로를 재현 가능하게 기록한다.
사용자 모드와 커널 모드: 위험한 자원을 보호한다
사용자 모드(user mode)는 응용 프로세스가 제한된 권한으로 실행되는 CPU 실행상태이고, 커널 모드(kernel mode)는 운영체제 핵심이 장치·주소공간·스케줄링 같은 보호 자원을 관리할 수 있는 높은 권한의 실행상태다. 응용 프로그램이 커널 메모리나 장치 제어를 직접 바꾸지 못하게 하여 한 프로그램의 오류가 전체 시스템으로 번지는 범위를 줄인다.
모드가 바뀌는 것과 프로세스가 바뀌는 것은 다르다. 시스템콜 처리 동안 같은 프로세스의 요청을 커널 모드에서 수행한 뒤 돌아올 수 있다. 반대로 요청이 입출력을 기다려야 하면 스케줄러가 다른 프로세스를 선택하여 실행 문맥까지 바뀔 수 있다. 답안에서 시스템콜마다 반드시 다른 프로세스로 전환한다고 쓰지 않는다.
커널은 높은 권한을 대신 사용하므로 사용자 포인터의 주소·길이·정렬, 파일 디스크립터(file descriptor, 이하 descriptor)의 소유와 권한, 요청 크기와 현재 상태를 신뢰하지 않고 검사한다. descriptor는 열린 커널 입출력 자원을 가리키는 작은 정수 손잡이이며, 정식 구조와 수명은 파일 descriptor: 커널 자원을 가리키는 프로세스의 손잡이에서 설명한다. 사용자 메모리는 검사 뒤에도 다른 실행흐름이 바꿀 수 있으므로 필요한 데이터는 안전한 경계로 복사하거나 고정된 규칙으로 접근한다.
시스템콜: 보호된 커널 서비스에 요청을 전달한다
시스템콜(system call)은 사용자 프로그램이 파일·프로세스·메모리·통신 같은 보호된 운영체제 서비스를 요청하는 규정된 진입방법이다. 라이브러리의 API 함수가 항상 시스템콜 하나와 일대일인 것은 아니다. 함수가 사용자 공간에서 끝날 수도 있고, 여러 시스템콜을 조합하거나 한 시스템콜을 감쌀 수도 있다.
전형적 경로는 다음과 같다.
- 호출자는 ABI에 맞춰 요청번호와 인수 또는 인수 위치를 준비한다.
- 전용 명령으로 동기적인 보호전환을 일으켜 커널 진입점으로 이동한다.
- 커널은 요청번호, 주소·길이, descriptor, 권한과 현재 상태를 검증한다.
- 파일·메모리·스케줄러·통신 또는 장치 계층의 서비스를 수행하거나 대기열에 넣는다.
- 즉시 끝낼 수 없고 호출이 blocking이면 프로세스를 대기시키고 다른 실행가능 작업을 고를 수 있다.
- 결괏값과 오류정보를 ABI 규칙에 맞춰 돌려주고 사용자 모드의 다음 명령으로 복귀한다.
시스템콜의 반환이 뜻하는 완료 조건은 요청 종류마다 다르다. 메모리 버퍼로 복사가 끝난 것, 장치가 전송을 끝낸 것, 비휘발 저장장치가 정전 뒤에도 보존할 상태가 된 것, 상대 프로세스가 처리한 것은 각각 별도 시점일 수 있다.
시스템콜·예외·인터럽트: 발생 원인과 시간관계가 다르다
예외(exception)는 현재 실행 중인 명령과 동기적으로 연결되어 CPU가 정상 순서를 벗어나 처리기로 이동하는 사건이다. 잘못된 명령·0으로 나누기·페이지 폴트처럼 오류 또는 복구 가능한 조건이 원인이 될 수 있다. 시스템콜의 보호전환도 프로그램이 의도적으로 일으키는 동기적 사건으로 구현될 수 있다.
인터럽트(interrupt)는 장치 완료나 타이머처럼 현재 명령 흐름 바깥의 사건이 CPU에 처리를 요청하는 비동기적 신호다. CPU는 허용된 경계에서 현재 상태를 보존하고 등록된 처리기로 이동한 뒤 원래 실행 또는 스케줄링된 다른 실행으로 돌아간다.
| 축 | 시스템콜 | 예외 | 외부 인터럽트 |
|---|---|---|---|
| 발생 주체 | 실행 중 프로그램의 명시적 요청 | 현재 명령 실행 조건 | 장치·타이머 등 외부 사건 |
| 시간관계 | 의도된 동기 사건 | 명령과 동기적 | 현재 명령과 비동기적 |
| 대표 목적 | 보호된 서비스 사용 | 오류 통지 또는 복구 | 완료·시간경과·장치 사건 통지 |
| 재개 | 서비스 결과 뒤 다음 명령 | 원인에 따라 명령 재시작·다음 명령·종료 | 처리 뒤 중단 위치 또는 새 스케줄 |
| 공통점 | 상태 보존, 권한 있는 처리기로 제어 이동, 복귀규칙 필요 | 상태 보존, 권한 있는 처리기로 제어 이동, 복귀규칙 필요 | 상태 보존, 권한 있는 처리기로 제어 이동, 복귀규칙 필요 |
페이지 폴트처럼 이름에 fault가 있어도 정상적인 요구 적재 경로일 수 있다. 반대로 복구할 수 없는 주소·권한 위반은 프로세스를 종료시킬 수 있으므로 사건 이름만으로 결과를 단정하지 않는다.
인터럽트 우선순위는 고정 목록이 아니라 중재 규칙이다
인터럽트 우선순위(interrupt priority)는 여러 인터럽트 요청 중 어떤 것을 먼저 처리하고 어떤 요청이 다른 처리를 중단할 수 있는지 정하는 순서규칙이다. 마스킹(masking)은 일정 종류의 인터럽트 수신을 잠시 막는 것이고, 인터럽트 벡터(interrupt vector)는 사건 종류를 대응 처리기와 연결하는 식별정보다. 높은 우선순위의 요청이 낮은 우선순위의 처리 중에 끼어드는 중첩(nesting)을 허용할 수도 있다.
결정에는 장치 또는 인터럽트 제어기의 하드웨어 우선순위, 운영체제가 정한 마스크와 처리기 규칙, CPU별 라우팅, 현재 실행수준이 함께 작용한다. 다중 CPU에서는 한 요청을 특정 CPU에 보낼지 여러 CPU에 분산할지도 정한다. 따라서 전원 오류 > 기계 오류 > 타이머 > 입출력 같은 하나의 순서를 모든 시스템의 보편 규칙으로 외우지 않는다. 안전 중요도, 처리 지연상한, 사건 손실 가능성과 폭주 비용을 근거로 구성한다.
처리기는 지연에 민감한 최소 작업만 즉시 수행하고 시간이 긴 후속 처리를 나중에 실행할 수 있는 작업으로 넘길 수 있다. 즉시 부분에서는 장치 상태 확인, 사건 기록과 재진입 통제만 하고, 데이터 변환·프로토콜 처리·사용자 통지는 지연 부분으로 넘기는 식이다. 즉시 처리에서 잠들거나 오래 잠금을 잡으면 다른 인터럽트와 시스템 지연이 커질 수 있다.
버퍼와 DMA: 속도가 다른 CPU·메모리·장치를 이어 준다
버퍼(buffer)는 생산자와 소비자의 처리속도나 전송단위 차이를 흡수하기 위해 데이터를 잠시 두는 메모리 영역이다. 작은 쓰기를 모아 큰 장치 전송으로 만들거나, 장치가 읽어 온 데이터를 응용이 가져갈 때까지 보관할 수 있다. 버퍼링은 처리량을 높일 수 있지만 복사와 메모리, 지연과 어느 버퍼까지 도착하면 완료인가라는 일관성 문제가 생긴다.
직접 메모리 접근(Direct Memory Access, DMA)은 장치 제어기가 CPU의 바이트별 복사 명령 없이 장치와 주기억장치 사이의 데이터 블록을 전송하는 방식이다. CPU는 전송 위치·길이·방향을 준비하고, 장치는 DMA를 수행한 뒤 완료 또는 오류를 인터럽트로 알릴 수 있다. CPU가 완전히 아무 일도 하지 않는다는 뜻은 아니다. 요청 설정, 메모리 고정·매핑, 완료처리와 오류복구는 여전히 필요하다.
장치 드라이버(device driver)는 장치 사용 요청을 특정 장치의 명령·상태와 연결하는 소프트웨어다. 이 절의 Linux 저장장치 경로에서는 커널 드라이버를 다루지만 모든 드라이버가 전부 커널 안에 있어야 하는 것은 아니다. 예를 들어 Linux UIO는 작은 커널 모듈을 통해 장치 메모리·인터럽트 접근을 제공하고 드라이버의 주요 로직을 사용자 공간에 둘 수 있다. UIO는 특정 장치에 맞는 별도 모델이며 모든 저장장치·네트워크 장치에 적용하는 보편 인터페이스는 아니다.
메모리 고정(memory pinning)은 DMA가 끝나기 전에 전송 대상 물리 페이지가 교체되거나 다른 용도로 재사용되지 않도록 수명을 붙드는 조치다. 장치가 접근할 주소로 매핑하고 완료 뒤에만 안전하게 해제한다.
CPU 캐시에 오래된 데이터가 남거나 장치가 CPU의 최신 쓰기를 보지 못하면 DMA 결과가 틀릴 수 있다. 하드웨어가 일관성을 자동 보장하지 않는 환경에서는 운영체제와 드라이버가 전송 전후 캐시·메모리 가시성 규칙을 지킨다. 버퍼 수명보다 DMA가 오래 지속되어 메모리가 먼저 재사용되는 문제도 막아야 한다.
Blocking·nonblocking·asynchronous I/O는 기다림의 위치가 다르다
blocking I/O는 요청을 낸 실행흐름이 요청한 작업을 진행시킬 수 있는 조건이 될 때까지 호출 안에서 기다리는 방식이다. nonblocking I/O는 지금 즉시 처리할 수 있는 범위만 수행하고, 준비되지 않았으면 지금 처리할 수 없다는 결과를 돌려주는 방식이다. asynchronous I/O는 작업 시작 요청과 완료 통지를 분리하여, 호출자가 다른 일을 하는 동안 작업이 진행되고 나중에 완료결과를 받는 방식이다.
| 축 | Blocking | Nonblocking | Asynchronous |
|---|---|---|---|
| 호출 반환 | 결과·오류가 진행될 때까지 대기 가능 | 즉시 가능한 결과 또는 아직 준비 안 됨 | 요청 접수 뒤 반환, 완료는 별도 통지 |
| 대기 주체 | 호출 실행흐름 | 응용이 재시도 시점을 관리 | 운영체제·런타임이 작업 진행·완료 통지 |
| 장점 | 직선적인 제어흐름, 이해·오류처리 용이 | 한 실행흐름이 여러 자원의 준비상태 관리 가능 | 계산과 I/O 중첩, 많은 동시 요청 가능 |
| 대표 실패 | 느린 요청 하나가 실행흐름을 장시간 점유 | 반복 조회로 CPU 낭비, 부분 처리 누락 | 버퍼 수명·완료 순서·취소·중복 통지 오류 |
준비상태 다중대기(readiness multiplexing)는 여러 descriptor 중 지금 읽거나 쓸 준비가 된 대상을 한 번에 통지받고, 실제 nonblocking I/O는 응용이 수행하는 방식이다. 이는 작업 자체의 완료 결과를 나중에 받는 asynchronous I/O와 구분한다. 어느 방식이든 한 번의 읽기·쓰기가 요청한 전체 바이트를 처리한다고 가정하지 말고 반환된 실제 길이와 오류·중단을 반복 처리해야 한다.
이 구분을 실제 descriptor 종류와 연결해야 한다. Linux에서 socket에 설정한 O_NONBLOCK과 readiness 통지는 수신·송신 루프의 기다림을 제어하지만, 일반 파일에 같은 플래그를 붙여도 저장장치 접근이나 fsync 대기가 비차단으로 바뀌지는 않는다. 따라서 같은 이벤트 루프에서 일반 파일 write·fsync까지 직접 수행하면 느린 저장장치가 모든 연결의 수신을 지연시킬 수 있다. 기록 작업을 별도 실행흐름으로 넘기거나 실제 비동기 파일 I/O 인터페이스를 선택하고 완료 통지를 받아야 두 경계가 분리된다.
수신 루프는 반환 길이와 오류를 함께 읽는다
POSIX 바이트 흐름 socket에서 0보다 큰 길이를 요청한 read를 생각하자. 한 번의 호출 결과는 다음처럼 해석한다. 데이터그램의 0바이트 메시지나 요청 길이가 0인 읽기에 이 표를 그대로 적용하지 않는다.
| 관찰한 결과 | 뜻 | 현재 메시지와 다음 동작 |
|---|---|---|
n > 0 | 실제로 n바이트를 받음 | 그 바이트만 누적하고, 완성된 메시지를 분리한 뒤 남은 바이트를 보존 |
n == 0 | 더 받을 데이터 없이 상대가 정상적으로 송신을 종료한 스트림의 끝 | 미완성 메시지가 있으면 잘린 메시지로 처리; 이미 완성된 기록의 저장 여부와는 별개 |
n == -1, EAGAIN 또는 EWOULDBLOCK | nonblocking socket에 지금 받을 데이터가 없음 | 누적 상태를 유지하고 다음 준비 통지를 기다림; 연결 종료로 판단하거나 즉시 무한 재시도하지 않음 |
n == -1, EINTR | 데이터가 전달되기 전에 signal로 호출이 중단됨 | 누적 길이는 그대로 두고 종료 요청·기한을 확인한 뒤 필요하면 재시도 |
| 그 밖의 오류 | 연결 재설정 등 별도 실패 | 오류 계약에 따라 연결과 미완성 상태를 정리; 성공 ACK를 추정하지 않음 |
signal이 일부 바이트 전달 후 끼어들어 양의 길이가 반환되면 그 실제 길이를 반영한다. “중단됐을 것”이라고 추측하여 이미 받은 범위를 다시 시작하지 않는다. 준비 통지는 시도할 시점을 알려 주는 것이므로, 읽기 결과에서 다시 EAGAIN을 만나는 경로도 상태기계에 남겨 둔다.
회전식 디스크 스케줄링: 이동시간·공정성·현행 경계를 함께 본다
하드 디스크 드라이브(Hard Disk Drive, HDD)는 회전하는 원판의 트랙 위에서 헤드를 움직여 자기적으로 데이터를 읽고 쓰는 저장장치다. 운영체제나 장치 제어기는 기다리는 블록 입출력 요청의 순서를 바꾸어 헤드 이동과 회전 대기를 줄일 수 있다. 이 순서결정이 디스크 스케줄링(disk scheduling)이다. 요청은 보통 파일명이 아니라 장치가 이해하는 논리 블록 주소(Logical Block Address, LBA) 범위로 내려오며, LBA와 실제 원판 위치의 정확한 대응은 장치 내부에서 바뀌거나 가려질 수 있다.
HDD 요청 하나의 완료시간을 다음처럼 나누면 알고리즘의 목적이 보인다.
T_total = T_queue + T_seek + T_rotation + T_transfer
T_queue : 앞선 요청 때문에 장치 큐에서 기다린 시간
T_seek : 헤드를 목표 트랙 부근으로 옮기는 탐색시간
T_rotation : 원하는 섹터가 헤드 아래로 올 때까지의 회전지연
T_transfer : 실제 데이터가 매체와 제어기 사이를 지나가는 전송시간엄밀히는 장치가 요청을 처리하기 시작한 뒤의 장치 서비스시간을 T_seek + T_rotation + T_transfer로, 큐 대기를 포함한 값을 응답시간으로 부르기도 한다. 반면 일부 문제는 서비스시간을 넓은 뜻으로 사용한다. 따라서 답안 첫머리에 어떤 항을 포함하는지 식으로 선언한다. 제어기 처리, 명령 전달과 오류복구 시간도 실제로는 더해질 수 있지만, 고전 비교에서는 위 네 항을 기본축으로 둔다.
| 방식 | 다음 요청을 고르는 규칙 | 얻는 것과 선택 조건 | 실패·공정성 통제 |
|---|---|---|---|
| 선입선처리(First-Come, First-Served, FCFS) | 도착한 순서 그대로 처리 | 단순하고 도착순 공정성이 분명하다. 부하가 낮거나 예측 가능성이 더 중요할 때 기준선이 된다. | 먼 위치를 왕복하면 평균 탐색시간과 처리량이 나빠질 수 있다. |
| 최단탐색시간우선(Shortest Seek Time First, SSTF) | 현재 헤드에서 탐색거리가 가장 짧다고 예상되는 요청을 선택 | 위치정보가 의미 있는 HDD에서 헤드 이동을 줄여 평균 서비스시간과 처리량을 개선할 수 있다. | 가까운 요청이 계속 들어오면 먼 요청이 기아에 빠진다. 최대대기시간, 에이징 또는 주기적 방향 서비스가 필요하다. |
| 스캔(SCAN) | 한 방향으로 이동하며 만나는 요청을 처리하고 끝 경계에서 반대 방향으로 바꾼다. 엘리베이터 방식이라고도 한다. | SSTF보다 진행방향이 예측 가능하고 넓은 범위를 반복 서비스한다. | 끝과 중앙의 대기분포가 다르고, 요청이 없어도 물리 끝까지 간다고 정의하면 불필요한 이동이 생긴다. 방향·끝 경계·동률 규칙을 명시한다. |
| 순환 스캔(Circular SCAN, C-SCAN) | 한 방향에서만 서비스하고 끝에 닿으면 반대편 시작으로 되돌아간 뒤 같은 방향을 반복한다. 되돌아가는 동안에는 보통 서비스하지 않는다. | 위치에 따른 대기 편차를 줄여 더 균일한 순환 서비스를 노린다. | 되돌림 이동비용이 있고 여전히 한 순환만큼 기다릴 수 있다. 최대 순환시간과 대기 상한을 측정한다. |
| 룩(LOOK) | SCAN처럼 왕복하되 현재 방향의 마지막 대기 요청에서 방향을 바꾼다. | 요청 없는 물리 끝까지 가지 않아 불필요한 탐색을 줄인다. | 새 요청 도착에 따라 방향전환점과 대기가 달라진다. 지속 유입 시 최대대기를 별도로 통제한다. |
| 순환 룩(Circular LOOK, C-LOOK) | C-SCAN처럼 한 방향으로 서비스하되 마지막 대기 요청에서 반대편의 첫 대기 요청으로 되돌아간다. | 빈 끝 구간 이동을 줄이면서 순환 서비스의 비교적 균일한 대기를 노린다. | 되돌림 거리는 남고, 도착시점에 따라 특정 요청이 다음 순환까지 밀릴 수 있다. |
| 최단지연시간우선(Shortest Latency Time First, SLTF) | 트랙마다 고정된 헤드가 있어 헤드 탐색이 필요 없는 회전 저장장치에서, 원하는 섹터가 가장 먼저 헤드 아래에 오는 요청을 고른다. | T_rotation을 줄이는 정책이다. 고정 헤드 장치라는 전제를 답안에 써야 한다. | 회전위치 정보가 필요하며, 회전상 가까운 요청이 계속 들어오면 다른 요청이 밀릴 수 있다. |
| 최단위치결정시간우선(Shortest Positioning Time First, SPTF) | 헤드가 움직이는 HDD에서 예상 T_seek + T_rotation이 가장 작은 요청을 고른다. | 탐색거리만 보는 SSTF보다 실제 위치결정시간에 가까운 값을 줄이려 한다. | 탐색·회전 정보와 예측이 필요하고 가까운 완료만 반복 선택하면 먼 요청이 기아에 빠질 수 있다. |
최단접근시간우선(Shortest Access Time First, SATF)은 이 교재에서는 OSTEP 근거 범위에 맞춰 SPTF의 다른 명칭으로만 다루고, 문제에 별도 비용식이 주어지면 그 식을 따른다. 이 절에서는 고정 헤드의 회전지연만 보는 SLTF와 이동 헤드의 탐색·회전을 보는 SPTF를 구분한다.
예를 들어 이동 헤드가 트랙 50 부근에 있고 10, 55, 58, 90의 요청이 기다린다면 SSTF는 우선 탐색거리가 가장 짧은 55를 고른다. 그러나 55의 원하는 섹터가 방금 헤드를 지나갔고 58의 섹터가 곧 도착한다면 탐색과 회전을 함께 추정하는 SPTF는 58을 먼저 고를 수 있다. 이 예시는 가까운 트랙 = 가장 빨리 위치를 잡는 요청이 항상 성립하지 않음을 보여 준다. 고정 헤드 회전 장치라면 트랙 간 헤드 이동이 없으므로 SLTF가 회전상 가장 먼저 도착할 섹터를 고른다. 어느 경우든 현재 위치에서 빨리 끝나는 요청이 계속 들어오면 다른 요청이 오래 밀릴 수 있으므로 평균값과 함께 최장대기·상위지연·기아 건수를 측정한다.
선택 절차는 ① 장치가 실제 회전 HDD인지 → ② 물리 위치와 회전정보를 어느 계층이 신뢰성 있게 아는지 → ③ 순차·무작위, 읽기·쓰기, 요청크기와 큐 깊이가 어떤지 → ④ 평균 처리량과 꼬리지연·공정성 중 무엇이 우선인지 → ⑤ 최대대기 규칙과 장애 시 재시도·완료순서를 어떻게 보장할지의 순서로 잡는다. 가상 디스크, 저장장치 배열이나 내부 캐시 뒤에서는 운영체제가 보는 LBA 순서가 실제 헤드 이동과 다를 수 있으므로 고전 알고리즘의 효과를 계측 없이 단정하지 않는다.
솔리드 스테이트 드라이브(Solid-State Drive, SSD)는 움직이는 헤드와 회전 원판 없이 반도체 메모리에 데이터를 저장한다. 따라서 HDD의 탐색거리와 회전지연을 줄이려는 SSTF·SCAN·SLTF의 물리적 선택근거를 그대로 적용할 수 없다. 비휘발성 메모리 익스프레스(Non-Volatile Memory Express, NVMe)는 호스트 소프트웨어와 비휘발성 저장장치 사이의 인터페이스 규격 계열이다. SSD에 널리 쓰이지만 매체 그 자체의 이름은 아니며, NVMe 2.4는 회전 매체 기능도 별도로 언급한다. 따라서 NVMe라는 이름만으로 기계적 탐색·회전이 없다고 단정하지 않고 실제 매체를 먼저 확인한다. 비회전 SSD와 현대 다중 큐 장치에서는 요청 병합, 읽기·쓰기 우선순위, 큐 깊이, CPU별 큐 배치, 처리량·꼬리지연·공정성과 장치 내부 병렬성이 주요 축이 된다.
Linux의 다중 큐 블록 계층(multi-queue block layer, blk-mq)은 CPU별 소프트웨어 큐와 장치 측 하드웨어 큐를 연결해 병렬 블록 입출력을 전달하는 현행 구현 사례다. 이는 현대 장치의 큐 구조와 스케줄링 경계를 보여 주지만, 모든 운영체제·장치의 구조를 뜻하지도 않고 FCFS·SSTF·SCAN·SLTF·SPTF 같은 고전 알고리즘의 역사·정당성을 입증하는 근거도 아니다. 시험 답안은 회전 HDD의 물리비용과 고전 원리 → 공정성 실패와 통제 → 비회전 SSD에서 사라진 전제 → NVMe 등 인터페이스의 큐 구조의 순서로 경계를 분명히 한다.
IM-0137-P03-Q01 전용 답안 전환: 25점
현재 확보된 원문 후보의 요구를 학습용으로 풀어 쓰면 다음과 같다.
CPU 스케줄링의 최단작업우선(SJF)과 최단잔여시간우선(SRT), 디스크 스케줄링의 최단탐색시간우선(SSTF)과 최단지연시간우선(SLTF)의 작동원리와 차이를 설명하고, 성능·기아·공정성과 현대 저장장치 관점의 선택기준을 제시하시오.
이 문항의 최단잔여시간우선(Shortest Remaining Time, SRT; Shortest Remaining Time First, SRTF)은 같은 선점형 계열을 가리키는 통용 표기다. 원문 후보의 SRT를 이 교재 본문의 SRTF와 연결해 읽는다. SJF와 SRTF는 모두 짧은 CPU 작업을 먼저 한다, SSTF와 SLTF는 모두 가까운 디스크 요청을 먼저 한다라고 한 줄로 끝내면 25점 답안이 되지 않는다. CPU에서는 예측한 버스트·남은 시간과 선점 여부를 구분한다. 하드 디스크 드라이브(Hard Disk Drive, HDD)는 회전 원판과 헤드를 사용하는 저장장치이며, 디스크 부분에서는 큐 대기·헤드 탐색·회전지연·전송시간과 물리정보의 가시성을 구분해야 한다.
이 문항의 SLTF는 고정 헤드 회전 저장장치에서 회전지연이 가장 짧은 요청을 고르는 방식으로 읽는다. 이동 헤드 HDD에서 탐색과 회전을 합친 위치결정시간을 줄이는 방식은 최단위치결정시간우선(Shortest Positioning Time First, SPTF)으로 구분한다. 최단접근시간우선(Shortest Access Time First, SATF)은 이 교재에서는 OSTEP 근거 범위에 맞춰 SPTF의 다른 명칭으로만 다루고, 문제에 별도 비용식이 주어지면 그 식을 따른다. 솔리드 스테이트 드라이브(Solid-State Drive, SSD) 같은 비회전 매체에는 기계적 탐색·회전을 일반화하지 않는다. 비휘발성 메모리 익스프레스(Non-Volatile Memory Express, NVMe)는 매체 종류가 아니라 호스트 소프트웨어와 비휘발성 저장장치 사이의 인터페이스 규격 계열이므로, NVMe라는 이름만으로 헤드·회전의 유무를 단정하지 않는다. 자세한 정의·비교는 회전식 디스크 스케줄링: 이동시간·공정성·현행 경계에서 확인한다.
이 문항과 학습용 답안은 미검증 기출 후보다. 원문 최종 대조·대표성·현행성·학습등급과 사람의 기술 검수는 완료되지 않았다.
답안을 보기 전 되찾기: SJF와 SRTF의 선택 값·선점 여부, SSTF와 SLTF의 장치 전제를 각각 적어 본다. 헷갈리면 회전식 디스크 스케줄링: 이동시간·공정성·현행 경계로 돌아가 비용 항과 실제 매체를 먼저 확인한다.
풀이 방향과 완성 답안 보기
25점 답안 목차
- 스케줄링 목적과 공통 평가축
- 제한 자원의 대기 요청 순서결정, 평균·상위·최장 지연, 처리량, 공정성
- CPU의 SJF·SRTF
- 다음 예측 CPU 버스트와 남은 예측시간
- 비선점·선점, 평균 대기 개선 전제, 예측오차·기아·전환비용
- HDD 요청시간과 SSTF·SLTF
큐 + 탐색 + 회전 + 전송, 논리 블록과 실제 물리위치의 경계- 이동 헤드 SSTF의 탐색거리, 고정 헤드 SLTF의 회전지연
- 이동 헤드 SPTF의 탐색+회전, SATF는 SPTF의 다른 명칭으로 연결
- 네 알고리즘의 비교와 실패통제
- 짧은 요청 우대의 이익, 먼·긴 요청 기아
- 에이징·최대대기·방향순환, 예측·계측과 동률규칙
- 현행 적용 경계와 선택
- 회전 HDD, 비회전 SSD와 NVMe 인터페이스의 분류축 구분
- 현대 다중 큐의 병렬성·큐 깊이·꼬리지연·공정성 검증
25점 전용 답안 도식
| 대상·선택 기준 | 선택 방식 | 얻는 것과 위험 |
|---|---|---|
| CPU: 다음 실행흐름의 예측 버스트 | SJF는 비선점, SRTF는 남은 예측시간 기준 선점 | 평균 대기 감소를 기대하지만 예측오차·긴 작업 기아·전환비용을 확인 |
| 이동 헤드 HDD: 헤드 탐색시간 | SSTF | 짧은 탐색을 택하지만 기아·물리정보 오차를 통제 |
| 고정 헤드 HDD: 회전지연 | SLTF | 원하는 섹터의 회전 대기를 줄이는 역사적 기준 |
| 이동 헤드 HDD: 탐색 + 회전 위치결정 | SPTF | 평균 장치시간과 기아·물리정보 오차를 함께 확인 |
HDD의 합계식은 기계 시간의 분해에 제시한 T_total = T_queue + T_seek + T_rotation + T_transfer를 사용한다. 에이징·최대대기·방향순환과 평균·상위·최장 지연·기아 건수를 함께 검토한다. 비회전 SSD에는 기계 탐색·회전이 없고 NVMe는 매체가 아니라 인터페이스이므로 실제 매체, 병렬 큐·우선순위·꼬리지연·공정성으로 판단한다.
25점 모범답안
스케줄링은 제한된 자원을 기다리는 여러 요청 중 다음 대상을 선택하는 정책이다. CPU 스케줄링은 실행가능 프로세스·스레드에 CPU 시간을 배분하고, 디스크 스케줄링은 저장장치 큐의 블록 입출력 순서를 정한다. 둘 다 평균 대기와 처리량을 개선하려 하지만 일부 요청의 최장대기와 기아를 숨기지 않도록 평균·상위분포·최악값과 공정성을 함께 평가해야 한다.
최단작업우선(Shortest Job First, SJF)은 준비된 작업 중 다음 CPU 버스트가 가장 짧다고 예측된 작업을 골라 그 버스트가 끝나거나 스스로 대기할 때까지 실행하는 비선점 방식이다. 모든 작업이 동시에 도착하고 실제 버스트를 안다는 이상조건에서는 평균 대기시간을 최소화할 수 있지만, 실제 길이는 과거 실행으로 예측해야 하므로 오차가 있고 짧은 작업이 계속 오면 긴 작업이 기아에 빠진다.
최단잔여시간우선(Shortest Remaining Time First, SRTF)은 SJF의 선점형으로, 현재 작업의 남은 예측시간보다 짧은 작업이 도착하면 현재 실행을 중단하고 새 작업에 CPU를 준다. 짧은 작업의 응답·반환시간을 더 줄일 수 있으나 예측오차와 선점·문맥교환 비용이 커지고 긴 작업의 기아 가능성도 높다. 즉 둘의 핵심 차이는 전체 다음 버스트와 남은 시간, 그리고 새 도착이 현재 실행을 선점하는지다.
반면 이 답안의 고전 디스크 정책은 진행 중인 레코드나 블록 전송을 선점하지 않고, 그 전송이 완료된 뒤 대기 큐에서 다음 요청을 고르는 것으로 가정한다. 따라서 CPU SRTF의 선점 여부와 디스크 큐의 다음 요청 선택을 같은 의미로 쓰지 않는다.
하드 디스크 드라이브(Hard Disk Drive, HDD)는 회전 원판 위에서 헤드를 움직여 데이터를 읽고 쓴다. 요청 완료시간은 답안에서 T_total = T_queue + T_seek + T_rotation + T_transfer로 선언한다. 이는 각각 장치 큐 대기, 목표 트랙까지의 헤드 탐색, 원하는 섹터가 올 때까지의 회전지연, 실제 데이터 전송시간이다. 장치 서비스시간을 큐 대기 제외로 쓰는 정의도 있으므로 포함 범위를 먼저 밝힌다.
최단탐색시간우선(Shortest Seek Time First, SSTF)은 이동 헤드 HDD에서 현재 헤드 위치로부터 탐색시간이 가장 짧다고 예상되는 요청을 선택한다. 헤드 이동을 줄여 평균 장치시간과 처리량을 개선할 수 있지만 현재 위치 근처의 요청이 계속 들어오면 먼 트랙 요청이 무기한 밀릴 수 있다. 최단지연시간우선(Shortest Latency Time First, SLTF)은 트랙마다 고정된 헤드가 있어 탐색 이동이 필요 없는 회전 저장장치에서 원하는 섹터가 가장 먼저 헤드 아래에 오는 요청, 즉 T_rotation이 가장 짧은 요청을 고른다. 두 방식은 같은 정책의 다른 이름이 아니라 장치 전제가 다르다.
이동 헤드 HDD에서 탐색과 회전을 함께 줄이는 최단위치결정시간우선(Shortest Positioning Time First, SPTF)은 예상 T_seek + T_rotation이 가장 짧은 요청을 선택한다. 최단접근시간우선(Shortest Access Time First, SATF)은 이 교재에서는 OSTEP 근거 범위에 맞춰 SPTF의 다른 명칭으로만 다루고, 문제에 별도 비용식이 주어지면 그 식을 따른다. SLTF·SPTF 모두 회전위치 정보가 필요하고, 빨리 끝나는 요청만 반복 선택하면 기아와 긴 꼬리지연이 생길 수 있다.
따라서 CPU에서는 버스트 예측의 정확도, 선점비용과 대화형 응답 필요성을 먼저 보고, 디스크에서는 고정 헤드인지 이동 헤드인지, 실제 회전 HDD인지, 물리위치 정보의 신뢰도, 순차·무작위 요청과 큐 깊이를 본다. 기아에는 대기시간에 따라 우선도를 높이는 에이징, 최대대기시간, 스캔(SCAN), 순환 스캔(Circular SCAN, C-SCAN), 룩(LOOK), 순환 룩(Circular LOOK, C-LOOK)처럼 방향을 정해 범위를 순환 서비스하는 방식을 대안으로 둔다. 동률과 새 요청 도착 시점, 선점·방향전환 규칙도 명시하고 실제 부하에서 평균뿐 아니라 상위·최장 지연과 기아 건수를 측정한다.
솔리드 스테이트 드라이브(Solid-State Drive, SSD)는 기계식 헤드와 회전지연이 없으므로 SSTF·SCAN·SLTF의 물리적 전제를 그대로 적용하지 않는다. 비휘발성 메모리 익스프레스(Non-Volatile Memory Express, NVMe)는 호스트 소프트웨어와 비휘발성 저장장치 사이의 인터페이스 규격 계열이다. 흔히 SSD와 결합해 높은 병렬성과 다중 큐를 활용하지만 NVMe 자체가 비회전 매체의 동의어는 아니다. 따라서 실제 매체의 물리비용을 먼저 확인하고, 현대 다중 큐 장치에서는 요청 병합, 큐 깊이, 읽기·쓰기 우선순위, CPU별 큐와 장치 내부 병렬성, 처리량·꼬리지연·공정성을 함께 본다. Linux의 다중 큐 블록 계층(multi-queue block layer, blk-mq)은 이런 현대 다중 큐의 현행 구현 사례일 뿐 고전 알고리즘의 근거나 모든 운영체제의 보편 구조는 아니다.
결론적으로 SJF·SRTF와 SSTF·SLTF는 모두 짧게 끝날 것으로 보이는 대상을 우대하지만, CPU는 예측 실행시간과 선점 여부, 이동 헤드 SSTF는 탐색, 고정 헤드 SLTF는 회전지연이 결정축이다. SATF는 이 교재에서 SPTF의 다른 명칭으로 연결하고, 평균 개선뿐 아니라 예측·위치정보의 한계, 전환비용과 기아 통제를 함께 써야 한다. 현행성은 비회전 SSD에서 사라진 기계적 전제와 NVMe라는 인터페이스의 큐 구조를 분리하여 판단한다. 이 답안은 원문과 기술 내용의 검토가 남아 있는 기출 후보의 학습용 답안이다.
파일 descriptor: 커널 자원을 가리키는 프로세스의 손잡이
파일 descriptor는 한 프로세스가 열린 파일·파이프·소켓 같은 커널 입출력 자원을 가리킬 때 사용하는 작은 정수 식별자다. 정수 자체가 파일 내용이나 디스크 주소는 아니다. 프로세스의 descriptor 표를 거쳐 커널의 열린 자원 상태로 연결된다.
열린 자원 상태에는 현재 읽기·쓰기 위치인 파일 오프셋, 접근 방식과 상태 플래그, 실제 파일 또는 통신객체에 대한 참조가 포함될 수 있다. descriptor를 복제하거나 프로세스 생성 뒤 상속하면 여러 descriptor가 같은 열린 상태를 공유하여 오프셋이 함께 움직일 수 있다. 반대로 같은 경로를 각각 열면 별도 열린 상태를 가질 수 있다. 정확한 공유범위는 인터페이스 계약을 확인한다.
수명은 획득 → 사용 → 오류·부분처리 → 닫기로 관리한다. 닫지 않으면 descriptor 표와 커널 객체가 고갈되고, 너무 일찍 닫으면 비동기 작업이 재사용된 번호나 해제된 버퍼를 잘못 참조할 수 있다. 번호는 닫힌 뒤 재사용될 수 있으므로 오래 저장한 정수만 보고 같은 자원이라고 믿지 않는다.
닫기 오류의 재시도는 부분 읽기·쓰기의 재시도와 같은 규칙으로 처리하지 않는다. POSIX Issue 8(2024)의 close() 계약은 EINTR을 반환할 때 descriptor가 열린 상태로 남는다고 규정한다. 반면 Linux man-pages 6.19의 close(2)는 Linux에서 EINTR일 때도 descriptor가 이미 닫힌다고 설명한다. Linux에서는 실패한 close()를 무조건 다시 호출하면 그 사이 재사용된 번호의 다른 자원을 닫을 수 있다. 대상 운영체제의 계약, 자원 소유권과 오류 기록을 확인하여 정리 경로를 설계한다.
파일시스템과 VFS: 이름을 저장 블록의 연산으로 바꾼다
파일시스템(file system)은 저장장치의 데이터를 파일·디렉터리라는 이름 있는 논리 객체, 내용 블록과 속성정보로 조직하고 할당·조회·복구하는 규칙과 구현이다. 메타데이터(metadata)는 파일 내용 자체가 아니라 크기·소유자·시간·권한처럼 파일을 설명하고 관리하는 정보다. 경로명(pathname)은 디렉터리 계층을 따라 파일을 찾기 위한 이름 표현이다. 경로 해석은 단순 문자열 연결이 아니라 시작 디렉터리, 접근권한, 연결과 각 구성요소의 현재 상태에 의존하는 연산이다.
가상 파일시스템(Virtual File System, VFS)은 서로 다른 파일시스템 구현을 공통 파일·디렉터리·열기·읽기·쓰기 연산 뒤에 연결하는 커널 추상화 계층이다. Linux의 경우 VFS 객체와 연산표가 구체 파일시스템의 구현을 호출하고, 파일 내용은 페이지 캐시(page cache)라는 주기억장치 사본을 거쳐 장치 쓰기로 이어질 수 있다. 이는 Linux 구현 사례이며 모든 운영체제 내부가 같은 객체명과 계층을 쓴다는 뜻은 아니다.
전형적 쓰기 경로는 descriptor 검증 → VFS 공통 연산 → 구체 파일시스템의 블록·메타데이터 갱신 → 페이지 캐시·블록 I/O → 장치다. 시스템콜이 사용자 버퍼의 바이트를 커널에 받아들였다는 성공과 장치의 비휘발 매체에 반영된 성공을 구분한다.
저널링: 중간에 멈춘 파일시스템 변경을 복구 가능하게 기록한다
저널링(journaling)은 파일시스템의 관련 변경을 본 위치에 완전히 반영하기 전이나 함께, 복구에 사용할 기록 영역인 저널(journal)에 일정한 순서로 기록하는 기법이다. 저널의 한 변경묶음이 완료표시를 얻은 뒤 장애가 나면 재시작 때 완료된 묶음을 다시 적용하고, 미완료 묶음은 버리는 방식으로 메타데이터 구조의 일관성을 회복할 수 있다.
저널이 곧 응용 트랜잭션이나 백업은 아니다. 파일시스템 메타데이터가 연결 가능한 상태로 복구되어도 사용자가 기대한 여러 파일의 논리적 변경이 모두 함께 반영되었다는 보장은 별도다. 모드에 따라 메타데이터만 저널에 넣고 파일 데이터는 다른 순서로 쓸 수 있다. 저장장치나 제어기가 완료를 잘못 보고하거나 쓰기 순서를 보존하지 않아도 약속이 깨질 수 있다.
영속 완료가 필요한 프로그램은 운영체제가 제공하는 동기화 인터페이스로 필요한 파일 데이터와 메타데이터의 반영을 요청하고, 파일 교체·이름 변경의 순서까지 실패 시나리오로 검증한다. 쓰기 증폭(write amplification)은 사용자가 바꾼 데이터 양보다 저널·메타데이터 등을 포함한 실제 장치 쓰기 양이 더 커지는 현상이다. 저널의 크기·쓰기 증폭과 복구시간도 비용이다. 저널링을 사용한다는 이유만으로 최신 사용자 데이터가 장애 뒤 반드시 남는다고 쓰지 않는다.
IPC: 서로 격리된 프로세스가 사건과 데이터를 교환한다
프로세스 간 통신(Inter-Process Communication, IPC)은 서로 다른 주소공간의 프로세스가 사건·바이트·메시지 또는 공유 데이터를 교환하고 순서를 맞추는 운영체제 메커니즘의 총칭이다.
- signal은 프로세스에 종료·타이머·사용자 정의 사건 같은 제한된 비동기 사건을 알리는 통지다. 일반적으로 큰 데이터 전달통로로 쓰지 않고, 처리 도중 다른 signal이 다시 들어올 수 있는 조건과 처리기 안에서 안전하게 호출할 수 있는 연산을 제한한다.
- pipe는 한쪽이 쓴 바이트 흐름을 다른 쪽이 읽는 커널 버퍼 통로다. 읽기와 쓰기의 경계가 응용 메시지 경계를 자동 보존한다고 가정하지 않고 길이나 구분규칙을 둔다.
- socket은 같은 기계 또는 네트워크의 통신 끝점을 나타내며 주소·연결 또는 메시지 전달 규칙을 제공한다. 바이트 흐름 방식에서는 한 번의 송신과 수신 크기가 일치한다고 가정하지 않는다.
- shared memory는 여러 프로세스의 주소공간에 같은 물리 메모리 객체를 매핑하는 방식이다. 큰 데이터를 반복 복사하지 않을 수 있지만 누가 언제 읽고 쓰는지 정하는 동기화와 소유권·수명·장애복구를 응용이 책임진다.
IPC 선택은 속도만의 문제가 아니다. 메시지 경계, 복사량, 신뢰경계, 참여 프로세스의 생명주기, 한쪽 종료 시 의미와 역압을 함께 정한다. 역압(backpressure)은 소비자가 느릴 때 생산자가 무한히 데이터를 쌓지 않도록 속도를 늦추거나 거절하는 통제다.
IPC 방식을 같은 축으로 비교한다
| 축 | Signal | Pipe | Socket | Shared memory |
|---|---|---|---|---|
| 전달 중심 | 작은 사건 통지 | 바이트 흐름 | 로컬·네트워크 바이트/메시지 | 공동 데이터 구조 |
| 데이터 복사 | 데이터 본문에 부적합 | 커널 버퍼 경유 | 프로토콜·커널 경로 경유 | 매핑 뒤 직접 접근 가능 |
| 경계·순서 | 사건 종류, 합쳐짐·순서 제약 가능 | 메시지 경계를 별도 정의 | 방식별 흐름/메시지 계약 | 응용의 동기화 구조가 정의 |
| 범위 | 주로 같은 호스트의 프로세스 | 관련 프로세스 또는 이름 있는 통로 | 같은 호스트·네트워크 | 같은 호스트의 공유 가능 객체 |
| 대표 실패 | 처리기에서 위험한 작업, 사건 유실 가정 | 부분 읽기·쓰기, 양 끝의 닫기 누락 | 상대 종료, 부분전송, 혼잡·역압 | 경쟁조건, 오래된 데이터, 소유자 종료 |
| 적합한 경우 | 중단·종료·재읽기 같은 통지 | 단순 생산자–소비자 흐름 | 독립 서비스·네트워크 통신 | 대용량 저복사와 엄격한 동기화 가능 시 |
shared memory를 쓴다고 전체 시스템이 항상 가장 빠른 것은 아니다. 동기화 대기, 캐시 일관성, 복구·검증 비용까지 포함해야 한다. 단순 명령 파이프라인은 pipe가, 장애가 독립적인 서비스는 socket이, 사건 하나는 signal이 더 설명 가능할 수 있다.
하나의 파일 쓰기로 전체 경로를 연결한다
로그 수집 프로세스가 받은 메시지를 파일에 기록한다고 하자.
write 수용·장치 완료·fsync 성공은 다른 확인이다 · 상태 따라가기
write는 자료 수용을 확인하며 영속 완료와 다르다. 모든 단계에서 앱·ACK·OS·장치의 위치를 유지하며 값과 허용 조건을 함께 확인합니다.
넓은 화면과 모바일에서 같은 관계를 각각의 배치로 읽을 수 있습니다.
이 절의 고정된 파일·메타데이터와 단일 기록자 조건에서 성공한 대표 추적이다. write가 반환한 길이는 커널이 수용한 양이며 장치 완료나 영속성 성공이 아니다. 실제로는 장치 작업이 더 일찍 시작되거나 단계 시점이 겹칠 수 있다. 호출자가 영속 동기화를 요청하고 요구한 장치·저장 경로의 성공을 확인한 뒤에만 ACK를 보낸다. 장치 완료는 비동기 열린 화살촉, 호출 반환은 점선의 채운 화살촉으로 구분한다.
1은 C의 write 요청, 2는 K가 실제 수용한 길이를 C로 반환하는 메시지다. 2가 곧 장치 완료나 영속 성공이라는 뜻은 아니다. 3은 C가 요구한 영속 동기화 요청, 4는 K→D의 블록 I/O·DMA, 5는 D→K의 비동기 장치 완료 통지다. 5의 점선 열린 화살촉은 비동기 통지이며 2와 6의 점선 채운 화살촉은 호출 반환이다. 6에서 요구한 파일·장치 경로의 fsync 성공을 확인한 뒤에만 응용 ACK를 보낸다. 고정된 파일·메타데이터와 단일 기록자 조건의 대표 성공 추적이며 실제 장치 작업은 먼저 시작되거나 겹칠 수 있다. 원문의 12B 표에서는 write가 8과 4를 수용해도 9ms의 fsync 성공 전에는 ACK를 보내지 않는다. 직접 풀기의 5·3·4 수용 뒤 fsync 오류에도 ACK를 보내지 않는 답안은 그대로 유지한다.
이 예의 조건
- 이 절의 고정된 파일·메타데이터와 단일 기록자 조건에서 성공한 대표 추적이다. write가 반환한 길이는 커널이 수용한 양이며 장치 완료나 영속성 성공이 아니다. 실제로는 장치 작업이 더 일찍 시작되거나 단계 시점이 겹칠 수 있다. 호출자가 영속 동기화를 요청하고 요구한 장치·저장 경로의 성공을 확인한 뒤에만 ACK를 보낸다. 장치 완료는 비동기 열린 화살촉, 호출 반환은 점선의 채운 화살촉으로 구분한다.
- 호출자 겸 기록자 C, 커널 K, 장치 D 세 생명선에서 write 요청, C로 실제 수용 길이 반환, 동기화 요청, D로 블록 I/O와 DMA, D에서 비동기 완료 통지, C로 fsync 성공 반환 순서의 대표 성공 추적을 표시한다. 장치 완료는 점선 열린 화살촉, 호출 반환은 점선 채운 화살촉이다. 실제 장치 작업은 앞서 시작하거나 겹칠 수 있으며 성공한 영속 동기화를 확인한 뒤에만 응용 ACK를 보낸다.
- 이 원문의 교육용 사례·조건 안에서 상태를 대조합니다. 실제 구축·실험 결과나 원문에 없는 성능 보장을 추가하지 않습니다.
관계도의 보충 설명
| 원 항목 | 설명·조건 |
|---|---|
| 17 | 1write |
| 19 | 3sync |
| 20 | 4DMA |
| 29 | 수용 ≠ 장치 완료 ≠ 영속성 |
| 31 | 대표 순서 · 장치 작업은 겹칠 수 있음 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| write 수용 write는 자료 수용을 확인하며 영속 완료와 다르다. |
|
|
| fsync 요청 fsync 경로에서 장치로 DMA·기록을 진행한다. |
|
|
| 장치·영속 확인 장치 완료와 정한 영속화 조건을 확인한다. |
|
|
| fsync 뒤 ACK fsync 성공을 확인한 뒤 앱이 ACK한다. |
|
|
write 수용·장치 완료·fsync 성공은 다른 확인이다 · 상태 따라가기
write는 자료 수용을 확인하며 영속 완료와 다르다. 모든 단계에서 앱·ACK·OS·장치의 위치를 유지하며 값과 허용 조건을 함께 확인합니다.
넓은 화면과 모바일에서 같은 관계를 각각의 배치로 읽을 수 있습니다.
이 절의 고정된 파일·메타데이터와 단일 기록자 조건에서 성공한 대표 추적이다. write가 반환한 길이는 커널이 수용한 양이며 장치 완료나 영속성 성공이 아니다. 실제로는 장치 작업이 더 일찍 시작되거나 단계 시점이 겹칠 수 있다. 호출자가 영속 동기화를 요청하고 요구한 장치·저장 경로의 성공을 확인한 뒤에만 ACK를 보낸다. 장치 완료는 비동기 열린 화살촉, 호출 반환은 점선의 채운 화살촉으로 구분한다.
1은 C의 write 요청, 2는 K가 실제 수용한 길이를 C로 반환하는 메시지다. 2가 곧 장치 완료나 영속 성공이라는 뜻은 아니다. 3은 C가 요구한 영속 동기화 요청, 4는 K→D의 블록 I/O·DMA, 5는 D→K의 비동기 장치 완료 통지다. 5의 점선 열린 화살촉은 비동기 통지이며 2와 6의 점선 채운 화살촉은 호출 반환이다. 6에서 요구한 파일·장치 경로의 fsync 성공을 확인한 뒤에만 응용 ACK를 보낸다. 고정된 파일·메타데이터와 단일 기록자 조건의 대표 성공 추적이며 실제 장치 작업은 먼저 시작되거나 겹칠 수 있다. 원문의 12B 표에서는 write가 8과 4를 수용해도 9ms의 fsync 성공 전에는 ACK를 보내지 않는다. 직접 풀기의 5·3·4 수용 뒤 fsync 오류에도 ACK를 보내지 않는 답안은 그대로 유지한다.
이 예의 조건
- 이 절의 고정된 파일·메타데이터와 단일 기록자 조건에서 성공한 대표 추적이다. write가 반환한 길이는 커널이 수용한 양이며 장치 완료나 영속성 성공이 아니다. 실제로는 장치 작업이 더 일찍 시작되거나 단계 시점이 겹칠 수 있다. 호출자가 영속 동기화를 요청하고 요구한 장치·저장 경로의 성공을 확인한 뒤에만 ACK를 보낸다. 장치 완료는 비동기 열린 화살촉, 호출 반환은 점선의 채운 화살촉으로 구분한다.
- 호출자 겸 기록자 C, 커널 K, 장치 D 세 생명선에서 write 요청, C로 실제 수용 길이 반환, 동기화 요청, D로 블록 I/O와 DMA, D에서 비동기 완료 통지, C로 fsync 성공 반환 순서의 대표 성공 추적을 표시한다. 장치 완료는 점선 열린 화살촉, 호출 반환은 점선 채운 화살촉이다. 실제 장치 작업은 앞서 시작하거나 겹칠 수 있으며 성공한 영속 동기화를 확인한 뒤에만 응용 ACK를 보낸다.
- 이 원문의 교육용 사례·조건 안에서 상태를 대조합니다. 실제 구축·실험 결과나 원문에 없는 성능 보장을 추가하지 않습니다.
관계도의 보충 설명
| 원 항목 | 설명·조건 |
|---|---|
| 17 | 1write |
| 19 | 3sync |
| 20 | 4DMA |
| 29 | 수용 ≠ 장치 완료 ≠ 영속성 |
| 31 | 대표 순서 · 장치 작업은 겹칠 수 있음 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| write 수용 write는 자료 수용을 확인하며 영속 완료와 다르다. |
|
|
| fsync 요청 fsync 경로에서 장치로 DMA·기록을 진행한다. |
|
|
| 장치·영속 확인 장치 완료와 정한 영속화 조건을 확인한다. |
|
|
| fsync 뒤 ACK fsync 성공을 확인한 뒤 앱이 ACK한다. |
|
|
첫 성공 반환은 커널이 요청한 바이트 일부 또는 전부를 받아들였다는 뜻일 수 있다. 부분 쓰기라면 남은 범위를 다시 처리해야 한다. 영속 보장이 필요하면 별도 동기화 완료와 오류를 확인해야 한다. 장치는 DMA로 메모리의 블록을 전송하고 완료 인터럽트를 보낼 수 있으며, 커널은 대기 중인 요청을 깨우거나 비동기 완료를 통지한다.
동시에 여러 프로세스가 같은 로그를 쓰면 각 호출의 바이트가 원하는 메시지 경계로 보존되는지 인터페이스 계약을 확인해야 한다. 보장되지 않으면 한 기록 프로세스로 pipe/socket을 통해 직렬화하거나 공유 큐와 동기화를 둔다. 수집 속도가 저장 속도를 넘을 때는 무한 버퍼 대신 최대 대기량, 생산자 감속·거절, 유실 정책을 정한다.
장애시험은 시스템콜 직전·부분 쓰기 뒤·저널 완료 전후·장치 완료 전후에 프로세스나 시스템을 중단하고 재시작 상태를 확인한다. 이 사례는 Linux의 VFS·저널·블록 I/O를 한 구현 사례로 사용한 것이며 모든 운영체제의 내부 명칭을 보편 규범으로 주장하지 않는다.
12바이트 메시지를 받고 영속 확인을 보내는 예
확인 응답(Acknowledgement, ACK)은 수신자에게 정한 처리 경계를 충족했다고 알리는 응답이다. 아래 예의 ACK 경계는 단순 수신이나 일반 쓰기 반환이 아니라, 이 레코드에 요구한 영속 동기화 성공이다.
교육 예로 메시지는 길이 헤더 4바이트와 본문 8바이트이며, ACK는 이 12바이트 레코드의 영속 동기화 성공 뒤에만 보낸다. 기존에 이름·메타데이터가 안정적으로 생성된 로그 파일에 단일 기록 작업자가 순서대로 추가한다고 가정한다. 시간은 이 동작을 설명하기 위한 가정값이며 제품 성능 측정치가 아니다.
| 시각 | 반환된 실제 결과 | 남은 작업·상태 | 응용 ACK |
|---|---|---|---|
| 0ms | socket read가 5바이트 반환 | 헤더 4+본문 1, 본문 7바이트 부족 | 보내지 않음 |
| 2ms | read가 7바이트 반환 | 총 12바이트 완성, 기록 큐에 넣음 | 보내지 않음 |
| 3ms | 파일 write(12)가 8 반환 | 기록 위치를 8만큼 전진, 남은 4 재시도 | 보내지 않음 |
| 4ms | 남은 write(4)가 4 반환 | 총 12바이트 수용, 동기화 요청 | 보내지 않음 |
| 9ms | fsync 성공 반환 | 정한 파일·장치 계약의 영속 경계 충족 | 레코드 ID로 성공 통지 |
수신 이벤트 루프와 파일 기록 작업자는 분리한다. 예를 들어 연결 최대 64개·메시지 최대 4KiB·연결당 미완성 수신버퍼 4KiB, 기록 대기와 진행 중의 레코드 바이트 합계 최대 64KiB를 정한다. 12바이트씩만 들어오는 이 예에서는 5461개가 65532바이트를 차지하고 다음 레코드는 한도를 넘으므로 더 받지 않거나 명시적으로 거절한다. 이 계산의 64KiB는 레코드 바이트의 예산이다. 큐 객체·연결별 수신버퍼·복구용 메타데이터·할당 오버헤드까지 합친 프로세스 전체 메모리 한도가 아니므로, 실제 구현에서는 그 비용도 별도로 계측하고 제한한다. 큐가 찼는데 사용자 공간의 다른 목록에 계속 쌓아서는 안 되며, 연결별 한도와 총량을 함께 적용해야 한다.
부분쓰기 뒤 오류 또는 동기화 실패가 나면 성공 ACK를 보내지 않고 해당 레코드를 미확정으로 둔다. 재시작 시 길이·체크섬·레코드 ID로 끝부분의 불완전 기록과 재전송 중복을 판별하는 복구 형식을 별도로 설계한다. 앞의 4바이트 길이와 8바이트 본문만으로는 체크섬·ID가 자동으로 생기지 않으며, 추가 필드나 별도 기록의 바이트 예산과 영속 경계도 정해야 한다. 동기화는 성공했지만 ACK가 유실되는 경우도 있으므로 송신자의 재전송을 무조건 새 레코드로 더하지 않는다. 새 파일 생성·이름변경이 있는 설계라면 디렉터리 메타데이터의 영속 경계도 추가로 검토한다.
단일 기록 작업자의 부분 쓰기를 다음 교육용 의사흐름으로 읽을 수 있다. 이 작업자는 blocking 일반 파일을 쓰며, written은 파일의 절대 오프셋이 아니라 이번 레코드에서 이미 수용된 바이트 수다.
written = 0
while written < record.length:
종료 요청 또는 처리 기한 초과이면 미확정으로 종료
n = write(file, record[written:], record.length - written)
if n > 0:
written = written + n
else if n == -1 and error == EINTR:
길이는 바꾸지 않고 다음 반복에서 종료·기한 확인
else:
0 또는 다른 오류를 진전 실패로 기록하고 미확정으로 종료
전체 길이를 수용했을 때만 요구한 fsync 수행
fsync 성공일 때만 해당 레코드의 성공 ACK 허용양의 반환 길이만큼 포인터와 남은 요청 길이를 함께 바꾼다. 요청한 길이만큼 전진하면 수용하지 않은 남은 바이트를 건너뛰고, 처음부터 다시 쓰면 이미 수용된 접두부가 중복된다. 남은 길이가 양수인데 0이 반환되면 이 설계는 진전 실패로 끝내어 무한 반복을 막는다. 이는 읽기의 스트림 끝 판정과 다른 처리다. nonblocking socket 송신에 이 흐름을 옮길 때는 EAGAIN·EWOULDBLOCK에 현재 위치를 유지하고 다음 쓰기 준비 통지를 기다리는 분기를 별도로 둔다.
메시지 경계 연습: 한 번의 read에 두 레코드가 섞였다면
같은 바이트 흐름 연결에 12바이트 레코드 A와 B가 차례로 도착한다. 이 연습에서만 4바이트 헤더는 본문 길이를 나타내는 부호 없는 big-endian 정수이며 두 레코드의 본문 길이는 각각 8이다. big-endian은 상위 바이트를 먼저 보내는 순서다. 각 전체 레코드는 헤더를 포함해 최대 4KiB(4096바이트)이며, 앞의 연결별·전체 버퍼 한도도 지킨다. 수신 호출은 차례로 17, 7바이트를 반환했다. 각 호출 뒤 완성한 레코드와 남은 길이는 얼마인가? 첫 17바이트 대신 5바이트만 받고 다음 양의 길이 요청이 0을 반환하거나, 새 헤더의 본문 길이가 4093이라면 어떻게 처리하는가?
프레임 분리·잘린 수신·길이 상한의 풀이 보기
첫 17바이트는 A의 헤더4+본문8=12와 B의 헤더4+본문1=5다. A를 완성된 레코드로 넘기고 B의 5바이트를 보존한다. 두 번째 7바이트는 B의 남은 본문 7을 채워 B도 완성한다. read 두 번의 경계가 레코드 두 개의 경계와 일치하는 것은 아니다. A와 B를 수신했다고 바로 ACK하지 않고 각 기록의 요구한 영속 성공을 따로 확인한다.
5바이트만 받은 뒤 스트림 끝을 만나면 헤더 4·본문 1만 있고 본문 7이 빠졌으므로 완성된 레코드로 넘기지 않는다. 새 본문 길이 4093은 4+4093=4097 > 4096이므로 헤더를 해석하는 시점에 프로토콜 오류로 거절한다. 그 길이를 믿고 먼저 큰 버퍼를 할당하거나 본문을 더 받지 않는다. 길이 덧셈의 정수 범위도 확인해야 하며, 이 4096바이트는 레코드 길이 상한이지 기존 64KiB 기록 예산이나 프로세스 전체 메모리 한도가 아니다.
직접 풀기: 같은 기록의 길이와 성공 응답을 판단한다
앞의 파일·완료 조건을 그대로 사용하고 반환된 길이만 바꾼 자체 연습이다. 길이 헤더 4바이트와 본문 8바이트인 레코드 하나를 받는다. 수신 read는 차례로 4, 8바이트를 반환하고, 기록 작업자의 write는 남은 범위를 요청할 때 차례로 5, 3, 4바이트를 반환한다. 마지막 fsync는 오류를 반환한다. 기존에 이름·메타데이터가 안정적으로 생성된 파일, 단일 기록 작업자, 요구한 영속 동기화 성공 뒤에만 응용 ACK를 보낸다는 조건은 바꾸지 않는다.
- 각 수신 뒤 완성된 헤더·본문 길이와 아직 필요한 길이를 적는다.
- 각 쓰기 뒤 누적 기록 길이, 다음 시작 위치와 요청 길이를 적는다.
- 마지막 쓰기의 성공과
fsync오류를 함께 보았을 때 ACK를 보낼지 판단하고 근거를 쓴다. - 연결이 끊긴 뒤 송신자가 같은 레코드를 다시 보내면 무엇으로 불완전 기록·중복을 구별할지 설명한다.
표를 스스로 만든 뒤 답안을 연다. 12바이트 기록 경로와 보호·모드 구분에서 필요한 계약만 되찾고, 확인하지 못한 영속성을 추측하지 않는다.
길이 계산·응답 판단과 오답 교정 보기
| 사건 | 누적 결과 | 다음 작업 | 성공 ACK |
|---|---|---|---|
| read가 4 반환 | 헤더 4, 본문 0 | 본문 8바이트 수신 | 보내지 않음 |
| read가 8 반환 | 전체 12 완성 | 기록 작업자에 넘김 | 보내지 않음 |
| write가 5 반환 | 파일이 5바이트 수용 | 시작 위치를 5만큼 옮겨 7 요청 | 보내지 않음 |
| 남은 write가 3 반환 | 누적 8바이트 수용 | 시작 위치를 다시 3만큼 옮겨 4 요청 | 보내지 않음 |
| 남은 write가 4 반환 | 누적 12바이트 수용 | 요구한 영속 동기화 결과 확인 | 보내지 않음 |
| fsync 오류 | 영속 경계를 확인하지 못함 | 미확정으로 기록하고 오류·복구 경로 수행 | 보내지 않음 |
잘못된 추론은 ‘마지막 write가 성공했으니 앞부분도 영구 보존됐다’이다. write의 반환 길이는 그 호출이 수용한 범위를 말하며, 이 사례의 성공 조건은 별도의 영속 동기화 성공이다. 전체 12바이트 수용을 확인해도 마지막 조건에 실패했으므로 성공 ACK를 보내지 않는다. 오류 뒤 실제 저장 상태를 이번 관찰만으로 전부 유실 또는 전부 보존이라고 단정하지 않는다.
재시작에서는 원래 사례의 길이·체크섬·레코드 ID로 불완전 끝부분과 재전송 중복을 판별한다. ID 비교는 중복 여부의 재료이며 복구 정책·기록의 실제 상태 확인을 대신하지 않는다. 같은 12바이트 사례에서 ‘받았다’, ‘썼다’, ‘영속 조건을 확인했다’의 세 문장을 구별하는 것이 교정 목표다.
어디서 실패하는가
| 실패 | 원인·징후 | 결과 | 예방·검증 |
|---|---|---|---|
| API와 ABI 혼동 | 함수명이 같으니 기존 바이너리가 호환된다고 판단 | 링크·실행·자료배치 오류 | 소스 의미, 호출규약·자료배치를 별도로 확인 |
| 심볼·재배치 누락 | 목적파일 단계와 실행파일을 동일시 | 미해결 심볼·잘못된 주소 | 링크 맵과 의존 산출물·대상 ABI 검사 |
| 동적 라이브러리 경로 오염 | 신뢰하지 않은 위치가 먼저 탐색됨 | 잘못된 코드 적재 | 고정된 탐색정책, 버전·해시·배포목록 통제 |
| 사용자 인수 신뢰 | 주소·길이·권한 검증 누락 | 커널 메모리 손상·정보노출 | 경계에서 복사·범위·권한·상태 검사 |
| 모드전환과 프로세스전환 혼동 | 모든 시스템콜이 다른 프로세스로 바뀐다고 설명 | 처리·비용모형 오류 | 권한상태와 실행문맥을 별도로 도식화 |
| 부분 I/O 누락 | 요청 길이와 반환 길이를 같다고 가정 | 데이터 잘림·중복 | 실제 길이·중단·재시도 상태기계 구현 |
| 비동기 버퍼 조기 해제 | 완료 전 메모리·descriptor 재사용 | 다른 데이터 손상 | 완료소유권, 취소·종료 후 회수 규칙 |
| 회전식 장치 평균만 최적화 | SSTF·SLTF·SPTF의 가까운 요청 우대만 적용 | 먼·늦은 요청 기아, 긴 꼬리지연 | 최대대기·에이징·방향순환과 최장대기 계측 |
| 긴 인터럽트 처리 | 즉시 처리기에서 대기·복잡한 연산 | 전체 지연·사건 손실 | 최소 확인·기록 후 나머지 지연 처리 |
| 저널을 백업으로 오해 | 메타데이터 복구와 사용자 데이터 보존 혼동 | 장애 뒤 최신 데이터 유실 | 영속 동기화·복구시험·별도 백업 |
| IPC 수명·역압 누락 | 상대 종료·느린 소비자·공유상태 소유자 부재 | 무한 대기·메모리 폭증·경쟁 | 종료 신호, 제한 큐, 동기화·복구계약 |
설명을 답안으로 바꾸기
용어를 나열한 뒤에는 경계의 의미, 실패할 수 있는 조건, 확인할 방법을 한 문장씩 연결한다. 다음 두 답을 비교하고 개선 이유를 스스로 설명해 본다.
부족한 답: “시스템콜을 호출하면 다른 프로세스로 전환한다.” 왜 부족한가: 권한 상태인 모드와 실행 주체인 프로세스를 섞었다. 개선한 답: “시스템콜은 보호된 커널 서비스로 진입하는 경로다. 같은 프로세스의 요청을 커널 모드에서 처리한 뒤 돌아올 수 있고, I/O 대기가 필요하면 다른 실행가능 프로세스를 고를 수 있다.” 사용자 모드와 커널 모드의 구분을 근거로 쓴다.
부족한 답: “write가 성공하면 저장이 끝났으므로 ACK를 보낸다.” 왜 부족한가: 부분 수용, 장치 완료와 요구한 영속 완료를 하나로 보았다. 개선한 답: “이 예에서는 반환된 길이로 12바이트 전체의 쓰기를 마친 뒤 정한 파일·장치 계약의 fsync 성공을 확인해야 ACK를 보낸다. 실패하거나 ACK가 유실되면 레코드 ID로 미확정 상태와 재전송 중복을 판별한다.” 12바이트 trace와 영속 완료 경계를 근거로 쓰며, 이것을 모든 파일·응용 트랜잭션의 전원장애 보장으로 확대하지 않는다.
이 절에서 다루는 시스템 프로그래밍 범위
시스템 프로그래밍은 시스템콜 용어 하나로 축소할 수 없다. 이 절에서 연결해 설명하는 논점은 다음과 같다.
- API와 ABI를 구분하고 소스의 컴파일·어셈블·링크·로딩 경로를 설명한다.
- 정적·동적 링크의 결합시점, 크기·공유·배포·호환 실패를 비교한다.
- 사용자/커널 보호경계와 시스템콜·예외·인터럽트의 발생원인·처리흐름을 구분한다.
- blocking·nonblocking·asynchronous I/O, 버퍼·DMA와 완료 의미를 연결하고, 회전 HDD의 탐색·회전, 비회전 SSD의 매체 특성, NVMe 인터페이스의 큐 구조를 구분한다.
- descriptor·파일시스템·VFS·저널링의 자원수명과 장애복구를 설명한다.
- signal·pipe·socket·shared memory IPC를 메시지 경계·복사·동기화·역압·실패로 비교한다.
위 여섯 논점은 이 절에서 다루는 학습 범위다. 실제 기출과의 연결은 아래에 별도로 표시하며, 원문·기술·출제 적합성의 검토가 남아 있다.
기출은 어떻게 이어지는가
IM-0089-P03-Q04는 Unix 운영체제의 시스템콜 동작구조를 요구한다. 이 절의 API와 시스템콜 구분, ABI 인수준비, 보호전환, 커널 검증·서비스·복귀가 직접 대응한다.IM-0113-P02-Q04는 멀티프로세스 환경에서 내·외부 인터럽트의 처리 우선순위를 요구한다. 이 절은 시스템콜·동기 예외·외부 인터럽트를 구분하고 하드웨어 중재, 마스킹·벡터·중첩·CPU 라우팅과 즉시/지연 처리를 답안축으로 제공한다.IM-0137-P03-Q01의 디스크 스케줄링 요구는 회전식 디스크 스케줄링의 설명과 연결된다. 큐·탐색·회전·전송, 이동 헤드의 SSTF와 고정 헤드의 SLTF, 관련 SPTF 용어 경계, 기아 통제와회전 HDD–비회전 SSD–NVMe 인터페이스의 분류축을 전용 25점 답안으로 옮긴다.
세 문항과 본문의 연결은 미검증 기출 후보의 요구를 학습 내용에 대응시킨 것이다. 특히 인터럽트의 보편 고정 우선순위가 존재한다고 가정하지 않는다. 관련 고전 디스크 자료는 연결했지만 원문 최종 대조·대표성·현행성·학습등급의 검토는 완료되지 않았다.
시험에서는 어떻게 묻는가: 10점
다음은 번역과 호환성 논점을 압축하는 자체 제작 문제다.
시스템 프로그래밍 관점에서 API와 ABI의 차이를 설명하고, 소스코드가 컴파일·어셈블·링크·로딩을 거쳐 실행되는 과정을 정적 링크와 동적 링크의 비교와 함께 설명하시오.
도구 이름만 순서대로 나열하면 부족하다. 목적파일의 미해결 심볼, 링커의 심볼해결·재배치, 로더의 주소공간 구성과 API/ABI 호환 실패를 연결해야 한다.
자체 10점 학습 연습: 공식 기출이나 공식 배점표가 아니다. 먼저 API·ABI, 목적파일·링커·로더, 정적·동적 링크의 차이를 자신의 문장으로 연결한다.
답안을 보기 전 되찾기: 컴파일이 성공했는데 링크가 실패하는 예 하나와, 링크가 성공했는데 의미가 틀리는 예 하나를 써 본다. 컴파일·어셈블·링크·로드, 정적 링크와 동적 링크에서 각 실패의 경계를 확인한다.
풀이 방향과 완성 답안 보기
10점 답안 목차
- 정의와 계약층
- 시스템 프로그래밍, API의 소스 의미, ABI의 바이너리 규칙
- 번역·결합·적재
- compile→assemble→object→link(symbol·relocation)→load
- 정적·동적 링크 비교
- 결합시점, 크기·공유, 배포·갱신, 의존성
- 실패와 통제
- 미해결 심볼, ABI 불일치, 잘못된 라이브러리·재현성
답안 도식
상자 안의 대상과 화살표의 조건을 함께 읽으세요. 좁은 화면에서는 같은 관계를 세로로 재배치한 도식을 보여 줍니다.
자체 10점 연습의 답안 구조다. 목적파일의 기계명령·심볼·재배치정보, 두 라이브러리 결합 경로, 실행파일의 ABI와 로더 책임을 구분한다. 목적파일이 라이브러리로 변환된다는 뜻이나 두 결합 방식을 동시에 채택하라는 뜻은 아니다.
10점 모범답안
시스템 프로그래밍은 운영체제·실행환경·하드웨어 경계에서 프로그램의 번역·적재와 보호 자원·입출력의 계약을 다루는 영역이다. API는 함수명·자료형·입출력·오류 같은 소스 수준 의미이고, ABI는 호출규약·자료배치·심볼·바이너리 형식 같은 번역된 코드의 결합 규칙이다. API가 같아도 ABI가 다르면 재컴파일이나 중간 계층이 필요하고, ABI가 맞아도 API 의미가 바뀌면 실행결과가 틀릴 수 있다.
컴파일은 고수준 소스를 낮은 명령표현으로 번역하고, 어셈블은 어셈블리 표기를 기계명령·심볼·재배치정보가 든 목적파일로 바꾼다. 링커는 여러 목적파일과 라이브러리의 심볼 참조를 해소하고 최종 배치에 맞게 주소 참조를 재배치하여 실행파일 또는 공유 라이브러리를 만든다. 로더는 실행파일과 의존 라이브러리를 프로세스 주소공간에 매핑하고 남은 결합과 초기화를 수행한 뒤 시작점으로 제어를 넘긴다.
정적 링크는 필요한 라이브러리 코드를 빌드 때 실행파일에 결합해 배포 의존성을 줄일 수 있으나 파일·중복이 커지고 수정 반영에 재링크가 필요하다. 동적 링크는 별도 공유 라이브러리를 적재·실행 때 결합하여 코드 공유와 호환 갱신이 가능하지만 라이브러리 부재, 버전·ABI 불일치와 탐색경로 오염 위험이 있다. 통제는 대상 ABI, 심볼·재배치 결과, 라이브러리 버전·해시·탐색경로, 재현 가능한 빌드 산출물을 기록하고 깨끗한 환경에서 링크·기동·기능시험을 수행하는 것이다.
시험에서는 어떻게 묻는가: 25점
다음은 보호경계·입출력·파일·IPC를 통합하는 자체 제작 문제다.
다수의 수집 프로세스로부터 로그를 받아 파일에 영속 저장하는 시스템 프로그램을 설계하시오. 시스템콜·인터럽트의 처리경로, blocking/nonblocking/asynchronous I/O, 버퍼·DMA, descriptor·VFS·저널링과 signal·pipe·socket·shared memory IPC의 선택기준을 설명하고 실패통제와 검증방안을 제시하시오.
모든 IPC와 I/O 방식을 실제 설계에 동시에 넣으라는 뜻이 아니다. 요구조건을 고정하고 하나를 선택한 근거를 비교표로 보이며, 접수·커널 수용·장치 완료·영속 완료를 분리해야 한다.
자체 25점 학습 연습: 공식 기출이나 공식 배점표가 아니다. 문제에 제시한 요구를 만족할 방식 하나를 고르고 다른 후보를 채택하지 않은 이유까지 설명한다.
답안을 보기 전 되찾기: 확인 응답을 보낼 완료 경계, 부분 I/O의 남은 길이, 큐 상한과 상대 종료 시 처리부터 적는다. Blocking·nonblocking·asynchronous I/O, IPC 방식을 같은 축으로 비교한다, 하나의 파일 쓰기로 전체 경로를 연결한다에서 선택 근거를 확인한다.
풀이 방향과 완성 답안 보기
25점 답안 목차
- 요구와 완료계약
- 수집량·지연·유실·순서·영속성, 접수/커널/장치/영속 완료
- 보호경계와 I/O 경로
- API→system call→검증→VFS→파일시스템→buffer/DMA→interrupt
- 동시처리 방식
- blocking/nonblocking/readiness/asynchronous 비교와 선택
- 자원·저장 관리
- descriptor 수명, 부분 I/O, VFS·page cache·journal·동기화
- IPC 선택과 역압
- signal/pipe/socket/shared memory 비교, 제한 큐·소유권·종료
- 실패통제·검증
- 부분쓰기, 버퍼 조기해제, 인터럽트 폭주, 프로세스·시스템 중단
- 처리량·끝단지연·유실/중복·복구상태·자원누수 측정
25점 전용 답안 도식
여러 연결의 입력은 한 기록자와 64KiB 한도를 함께 쓴다 · 상태 따라가기
여러 연결이 한 기록자와 65536B 예산을 공유한다. 모든 단계에서 공유 예산·대기·처리의 위치를 유지하며 값과 허용 조건을 함께 확인합니다.
넓은 화면과 모바일에서 같은 관계를 각각의 배치로 읽을 수 있습니다.
이 절의 12B 레코드 바이트와 최대 64개 연결 조건을 사용한 자체 계산 예다. 객체·복구 메타데이터·수신버퍼는 이 바이트 예산과 별도다. 대기 5460건과 처리 중 1건, 합계 5461건이 65,532B다. 그다음 12B까지 수용하면 65,544B가 되어 64KiB(65,536B)를 넘으므로 수용 전에 읽기를 일시 정지하거나 명시적으로 거부한다. 큐와 처리 중 버퍼를 따로 상한까지 채우지 않는다. 번호는 대표 대기열을 읽는 순서이며 제품의 고정 스케줄링 정책이나 처리 성능을 보장하지 않는다.
대기 5460건은 대표 슬롯 1·2·3과 나머지 5457건을 합한 것이다. 번호는 대기 순서를 보이며 제품의 고정 스케줄링 정책을 뜻하지 않는다. 대기 5460×12=65520B와 처리 중 1×12=12B는 같은 64KiB(65536B) 경계를 사용한다. 합계 65532B에서 다음 12B를 받으면 65544B이므로 수용 전에 읽기를 일시 정지하거나 명시적으로 거부한다. 오른쪽 경로는 이미 대기한 레코드를 폐기한다는 뜻이 아니라 다음 레코드의 수용을 막는 역압이다. 왼쪽의 첫 슬롯은 기록자 1개로 나가며 요구한 영속 동기화 성공 뒤에만 ACK 경로를 따른다. 연결별 미완성 버퍼 한도와 전체 대기·진행 총량을 함께 적용하고, 큐가 찼는데 다른 목록에 계속 쌓지 않는 원문 계약을 유지한다.
이 예의 조건
- 이 절의 12B 레코드 바이트와 최대 64개 연결 조건을 사용한 자체 계산 예다. 객체·복구 메타데이터·수신버퍼는 이 바이트 예산과 별도다. 대기 5460건과 처리 중 1건, 합계 5461건이 65,532B다. 그다음 12B까지 수용하면 65,544B가 되어 64KiB(65,536B)를 넘으므로 수용 전에 읽기를 일시 정지하거나 명시적으로 거부한다. 큐와 처리 중 버퍼를 따로 상한까지 채우지 않는다. 번호는 대표 대기열을 읽는 순서이며 제품의 고정 스케줄링 정책이나 처리 성능을 보장하지 않는다.
- 수집 연결 A와 최대 63개의 다른 연결이 실제 서로 다른 경로로 대기열에 들어온다. 대표 슬롯 1, 2, 3과 나머지 5457건은 합계 5460건의 대기 레코드다. 첫 슬롯에서 기록자 1개로 다음 1건을 꺼내는 경로와 fsync 성공 뒤 ACK 경로, 다음 12B 수용을 일시 정지·거부하는 경로를 따로 추적한다. 대기 65520B와 처리 중 12B가 같은 총량 경계 65536B를 사용한다. 다음 12B를 더한 65544B는 한도를 넘으므로 수용 전에 제어한다. 번호는 대표 큐 순서이며 고정 제품 정책이 아니다.
- 이 원문의 교육용 사례·조건 안에서 상태를 대조합니다. 실제 구축·실험 결과나 원문에 없는 성능 보장을 추가하지 않습니다.
관계도의 보충 설명
| 원 항목 | 설명·조건 |
|---|---|
| 27 | 수집 연결 A와 최대 63개의 다른 연결이 실제 서로 다른 경로로 대기열에 들어온다. 대표 슬롯 1, 2, 3과 나머지 5457건은 합계 5460건의 대기 레코드다. 첫 슬롯에서 기록자 1개로 다음 1건을 꺼내는 경로와 fsync 성공 뒤 ACK 경로, 다음 12B 수용을 일시 정지·거부하는 경로를 따로 추적한다. 대기 65520B와 처리 중 12B가 같은 총량 경계 65536B를 사용한다. 다음 12B를 더한 65544B는 한도를 넘으므로 수용 전에 제어한다. 번호는 대표 큐 순서이며 고정 제품 정책이 아니다. |
| 29 | 기록 12B · 연결 최대 64개 |
| 36 | 연결 A |
| 47 | 처리 1건 12B |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 64KiB 한도 여러 연결이 한 기록자와 65536B 예산을 공유한다. |
|
|
| 현재 사용량 대기 5460건×12=65520B에 처리 중 12B를 더해 65532B다. |
|
|
| 다음 12B 거절 새 12B를 받으면 65544B여서 한도를 넘는다. |
|
|
| 완료 후 회수 처리와 필요한 확인 뒤 실제 자원 회수를 반영한다. |
|
|
여러 연결의 입력은 한 기록자와 64KiB 한도를 함께 쓴다 · 상태 따라가기
여러 연결이 한 기록자와 65536B 예산을 공유한다. 모든 단계에서 공유 예산·대기·처리의 위치를 유지하며 값과 허용 조건을 함께 확인합니다.
넓은 화면과 모바일에서 같은 관계를 각각의 배치로 읽을 수 있습니다.
이 절의 12B 레코드 바이트와 최대 64개 연결 조건을 사용한 자체 계산 예다. 객체·복구 메타데이터·수신버퍼는 이 바이트 예산과 별도다. 대기 5460건과 처리 중 1건, 합계 5461건이 65,532B다. 그다음 12B까지 수용하면 65,544B가 되어 64KiB(65,536B)를 넘으므로 수용 전에 읽기를 일시 정지하거나 명시적으로 거부한다. 큐와 처리 중 버퍼를 따로 상한까지 채우지 않는다. 번호는 대표 대기열을 읽는 순서이며 제품의 고정 스케줄링 정책이나 처리 성능을 보장하지 않는다.
대기 5460건은 대표 슬롯 1·2·3과 나머지 5457건을 합한 것이다. 번호는 대기 순서를 보이며 제품의 고정 스케줄링 정책을 뜻하지 않는다. 대기 5460×12=65520B와 처리 중 1×12=12B는 같은 64KiB(65536B) 경계를 사용한다. 합계 65532B에서 다음 12B를 받으면 65544B이므로 수용 전에 읽기를 일시 정지하거나 명시적으로 거부한다. 오른쪽 경로는 이미 대기한 레코드를 폐기한다는 뜻이 아니라 다음 레코드의 수용을 막는 역압이다. 왼쪽의 첫 슬롯은 기록자 1개로 나가며 요구한 영속 동기화 성공 뒤에만 ACK 경로를 따른다. 연결별 미완성 버퍼 한도와 전체 대기·진행 총량을 함께 적용하고, 큐가 찼는데 다른 목록에 계속 쌓지 않는 원문 계약을 유지한다.
이 예의 조건
- 이 절의 12B 레코드 바이트와 최대 64개 연결 조건을 사용한 자체 계산 예다. 객체·복구 메타데이터·수신버퍼는 이 바이트 예산과 별도다. 대기 5460건과 처리 중 1건, 합계 5461건이 65,532B다. 그다음 12B까지 수용하면 65,544B가 되어 64KiB(65,536B)를 넘으므로 수용 전에 읽기를 일시 정지하거나 명시적으로 거부한다. 큐와 처리 중 버퍼를 따로 상한까지 채우지 않는다. 번호는 대표 대기열을 읽는 순서이며 제품의 고정 스케줄링 정책이나 처리 성능을 보장하지 않는다.
- 수집 연결 A와 최대 63개의 다른 연결이 실제 서로 다른 경로로 대기열에 들어온다. 대표 슬롯 1, 2, 3과 나머지 5457건은 합계 5460건의 대기 레코드다. 첫 슬롯에서 기록자 1개로 다음 1건을 꺼내는 경로와 fsync 성공 뒤 ACK 경로, 다음 12B 수용을 일시 정지·거부하는 경로를 따로 추적한다. 대기 65520B와 처리 중 12B가 같은 총량 경계 65536B를 사용한다. 다음 12B를 더한 65544B는 한도를 넘으므로 수용 전에 제어한다. 번호는 대표 큐 순서이며 고정 제품 정책이 아니다.
- 이 원문의 교육용 사례·조건 안에서 상태를 대조합니다. 실제 구축·실험 결과나 원문에 없는 성능 보장을 추가하지 않습니다.
관계도의 보충 설명
| 원 항목 | 설명·조건 |
|---|---|
| 27 | 수집 연결 A와 최대 63개의 다른 연결이 실제 서로 다른 경로로 대기열에 들어온다. 대표 슬롯 1, 2, 3과 나머지 5457건은 합계 5460건의 대기 레코드다. 첫 슬롯에서 기록자 1개로 다음 1건을 꺼내는 경로와 fsync 성공 뒤 ACK 경로, 다음 12B 수용을 일시 정지·거부하는 경로를 따로 추적한다. 대기 65520B와 처리 중 12B가 같은 총량 경계 65536B를 사용한다. 다음 12B를 더한 65544B는 한도를 넘으므로 수용 전에 제어한다. 번호는 대표 큐 순서이며 고정 제품 정책이 아니다. |
| 29 | 기록 12B · 연결 최대 64개 |
| 36 | 연결 A |
| 47 | 처리 1건 12B |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 64KiB 한도 여러 연결이 한 기록자와 65536B 예산을 공유한다. |
|
|
| 현재 사용량 대기 5460건×12=65520B에 처리 중 12B를 더해 65532B다. |
|
|
| 다음 12B 거절 새 12B를 받으면 65544B여서 한도를 넘는다. |
|
|
| 완료 후 회수 처리와 필요한 확인 뒤 실제 자원 회수를 반영한다. |
|
|
| 전체 경로의 역할 | 재사용할 근거와 완료 계약 |
|---|---|
| IPC·수집 | signal은 제어 사건, pipe·socket은 바이트·메시지, shared memory는 공동 큐다. 목적에 맞게 선택한다. |
| 호출 경계 | blocking / nonblocking / asynchronous는 선택 가능한 방식이다. 사용자 API 뒤 시스템콜에서 인수·descriptor·권한을 검증한다. |
| 저장 경로 | VFS → 구체 파일시스템 → 페이지 캐시·저널 → 블록 큐·DMA·장치와 완료 인터럽트는 앞의 기존 실행 중 경로를 사용한다. |
| 완료 단계 | 요청접수, 커널수용, 장치완료, 요구한 영속완료를 구별한다. 인접 단계가 같은 시점일 수도 있다. 새 순서 도식과 12B 추적 표에서 ACK 조건을 대조한다. |
| 오류·복구 | 부분 I/O·취소·수명·우선순위·재시작·유실/중복·복구 시험을 생략하지 않는다. 아래 완성 답안의 계약을 유지한다. |
25점 모범답안
요구는 다수 수집 프로세스의 로그를 메시지 단위로 받아 제한된 지연 안에 파일로 영속화하는 것이다. 최대 유입률·평균/최대 메시지 크기, 허용 유실·중복, 순서범위, 저장장치 장애와 재시작 뒤 보존조건을 먼저 고정한다. 요청 접수, 커널 버퍼 수용, 장치 전송 완료, 정전 뒤 보존 가능한 영속 완료를 구분하고 송신자에게 어느 시점을 확인할지 정한다.
같은 호스트의 단순 바이트 흐름이면 pipe, 독립 서비스와 확장 가능한 경계면 socket, 큰 데이터의 복사를 줄이고 엄격한 동기화를 감당할 수 있으면 shared memory를 후보로 둔다. signal은 종료·설정 재읽기 같은 작은 제어 사건에만 사용한다. 이 설계에서는 메시지 길이와 버전을 붙인 로컬 socket을 기본으로 선택하고, 기록 프로세스 앞에 최대 크기가 정해진 큐를 둔다. 소비가 느리면 생산자를 대기·거절하거나 정한 유실정책을 적용해 무한 메모리 증가를 막는다.
기록 프로세스의 수신 이벤트 루프는 socket 연결을 readiness 통지와 nonblocking I/O로 관리하고, 준비된 descriptor에서 반환된 실제 바이트만 누적해 완전한 메시지를 만든다. 단일 요청이 적고 단순성이 중요하면 실행흐름별 blocking도 대안이며, 장치 작업과 계산을 깊게 겹쳐야 하고 버퍼 수명·취소를 관리할 수 있으면 asynchronous I/O를 검토한다. 요청 길이와 반환 길이가 다를 수 있으므로 부분 읽기·쓰기를 명시적인 상태 전이로 처리한다.
이 설계에서 선택한 저수준 write 계열 시스템 인터페이스의 라이브러리 함수는 ABI 계약에 맞춰 필요한 시스템콜 진입을 수행하고, 커널은 주소·길이, descriptor·권한을 검사한다. 모든 라이브러리 API가 시스템콜과 1:1 대응하거나 반드시 커널로 들어가는 것은 아니다. 이 경로에서는 VFS 공통 연산이 구체 파일시스템을 호출하고 페이지 캐시·저널 상태와 블록 I/O 요청을 만든다. 장치 제어기는 준비된 메모리와 장치 사이를 DMA로 전송하고 완료·오류를 인터럽트로 알릴 수 있다. 인터럽트 즉시 부분은 상태확인·사건기록만 짧게 수행하고 긴 후속 처리는 지연한다. 우선순위는 고정 암기표가 아니라 장치 중요도·지연상한·마스킹·중첩·CPU 라우팅으로 정한다.
시스템콜의 쓰기 성공이 곧 영속 완료는 아니다. 저널링은 완료된 변경묶음을 재실행하고 미완료 묶음을 버려 파일시스템 구조 복구를 돕지만 응용의 여러 로그가 모두 보존된다는 보장은 아니다. 배치별 영속 동기화 정책을 정하고 데이터·파일 메타데이터와 이름변경 순서를 장애시험으로 확인한다. descriptor는 획득·상속·복제·비동기 작업 완료·닫기 수명을 기록하고, 번호 재사용과 누수를 막는다.
주요 실패는 잘못된 라이브러리·ABI, 사용자 인수 검증 누락, 부분 I/O, 비동기 버퍼 조기해제, 느린 소비자, 긴 인터럽트 처리, 장치 완료 전 성공확인과 저널의 데이터 보존 과신이다. 시험은 최대 동시 연결, 부분 바이트 경계, 상대 프로세스 종료, 큐 포화, descriptor 고갈, 쓰기·저널·장치 완료 각 지점의 프로세스 강제종료와 전원중단을 포함한다. 처리량, 평균·상위분포·최악 끝단지연, 유실·중복·순서 위반, 재시작 후 파일상태, CPU·메모리·I/O와 descriptor 누수를 측정하여 요구계약과 대조한다.
이 설계는 socket 수신 루프와 일반 파일 기록 작업자를 분리한다. Linux 일반 파일의 O_NONBLOCK이 저장 대기를 제거한다고 가정하지 않으며, 기록 작업자가 부분쓰기를 끝내고 배치 fsync 성공을 알린 뒤에만 해당 레코드의 응용 ACK를 보낸다. 본문의 12바이트 trace와 같이 수신 완료·write 완료·영속 완료를 분리하고, 연결 수·미완성 메시지·기록 대기 총량에 한도를 둔다. 큐 포화는 읽기 중지 또는 명시적 거절로 생산자에게 전달하며, 저장 오류·ACK 유실에는 레코드 ID 기반 재시도·복구 규칙을 적용한다.
근거와 현행성
응용과 운영체제의 이식 가능한 인터페이스 범위, 프로세스·스레드 계약은 IEEE/The Open Group의 The Open Group Base Specifications Issue 8, IEEE Std 1003.1-2024를 대조했다. 이 규격 계열을 이 절에서는 이식형 운영체제 인터페이스(Portable Operating System Interface, POSIX)라고 부른다. 일반 쓰기와 동기화·저장장치 완료계약은 POSIX Issue 8의 write() 계약과 fsync() 계약을 대조했다. 앞 자료는 일반 write 완료와 O_DSYNC·O_SYNC 조건의 동기화 완료를 구분하고, 뒤 자료는 열린 파일의 데이터를 연관 저장장치로 전송하도록 요청한 뒤 완료·오류까지 반환하지 않는 fsync 계약을 다룬다. 다만 fsync의 저장장치 전송 성격에도 구현정의 부분이 있으며, 응용 트랜잭션과 전원장애 뒤 보존 전체를 자동 보장한다고 일반화하지 않는다.
링커의 목적파일 결합·심볼해결·재배치 경계는 GNU Binutils 2.47의 LD: The GNU Linker §1을 확인했다. 인터럽트 요청(Interrupt Request, IRQ)은 Linux Kernel Documentation의 Generic IRQ를 대조했다. VFS, ext4 Journal, Block Multi-Queue는 각각 Linux의 VFS 객체·연산, ext4 저널, 다중 큐 블록 I/O 구현 경로를 보여 주는 현행 사례이며, POSIX의 일반 인터페이스 계약이나 모든 운영체제의 공통 내부구조를 뜻하지 않는다. 집필자가 기록한 기준 확인일은 2026-09-01이며, 그 날짜의 열람 이력은 이번 공개 출처 대조로 확인하지 않았다.
이동 헤드 HDD의 탐색·회전·전송시간, SSTF·SCAN·C-SCAN·SPTF는 저자 공식본인 OSTEP 1.10의 Hard Disk Drives §§37.3·37.5를 대조했다. Operating System Concepts 10판 공식 Chapter 11 slide deck은 실제 PPTX의 physical slide 20(FCFS), 21(SCAN), 23(C-SCAN), 25(선택 논의의 SSTF 언급)만 근거로 사용한다. LOOK·C-LOOK은 MIT Martin Rinard의 Operating Systems Lecture Notes, Lecture 16: Disk Scheduling의 해당 두 알고리즘 설명을 별도 근거로 대조했다. 고정 헤드 회전 저장장치의 SLTF는 Fuller·Baskett의 1972년 Stanford 보고서 An Analysis of Drum Storage Units, STAN-CS-72-317의 archive copy report pp. 3–4(physical PDF pp. 13–14)를 역사적 1차 자료 후보로 대조했다. archive가 Stanford 공식 호스트가 아니므로 provenance와 권리는 사람 검토 전 승인하지 않는다. SATF는 이 교재에서는 OSTEP 근거 범위에 맞춰 SPTF의 다른 명칭으로만 다루고, 문제에 별도 비용식이 주어지면 그 식을 따른다.
현행 구현과 매체 경계는 Linux blk-mq 문서와 NVM Express Base Specification 2.4 공식 landing을 사용했다. blk-mq는 기계식 HDD 헤드 위치 정렬과 비기계식 솔리드 스테이트 드라이브(SSD)·비휘발성 메모리(non-volatile memory, NVM)의 병렬 접근, software·hardware queue의 Linux 구현을 보여 주지만 고전 알고리즘의 근거가 아니다. NVMe 2.4 landing은 SSD를 위해 시작한 인터페이스 규격 계열이라는 설명과 함께 Rotational Media 기능도 명시하므로, NVMe를 곧 비회전 매체의 동의어로 쓰지 않는다. 출처를 연결했다는 사실만으로 기술·출제 적합성이나 이용 권리를 확인했다고 판단하지 않는다.
| 설명할 내용 | 근거와 적용 한계 |
|---|---|
| 응용에서 보이는 프로세스·파일·메모리·I/O 시스템 인터페이스 계약 | POSIX Issue 8은 이식 가능한 응용 인터페이스 경계만 직접 지지한다. 사용자/커널 보호전환, 소프트웨어가 일으키는 동기적 예외인 트랩(trap), ABI, 인수검증·복귀 절차에는 별도 구현 1차 출처가 필요하다. Generic IRQ 문서는 인터럽트 구현 사례이며 시스템콜 동작 전체의 근거로 삼지 않는다. |
| 링크 단계의 목적파일 결합·심볼해결·재배치 | GNU LD 2.47의 구체 도구 설명을 링커 일반 이론의 유일한 구현으로 일반화하지 않는다. |
일반 write, 동기화·저장장치 완료계약과 VFS·저널링·블록 I/O 경로 | POSIX의 write()와 fsync()는 일반 쓰기와 동기화·저장장치 완료계약을 분리한다. fsync가 응용 트랜잭션·전원장애 보존 전체를 자동 보장한다고 일반화하지 않는다. VFS·ext4 Journal·blk-mq 문서는 Linux 구현 경로이며 모든 운영체제의 내부구조로 일반화하지 않는다. |
| 이동 헤드 HDD 비용모형과 FCFS·SSTF·SCAN·C-SCAN·LOOK·C-LOOK·SPTF | OSTEP, Operating System Concepts 슬라이드, MIT 강의노트가 직접 지지하는 범위를 분리하며 모든 부하의 평균개선·기아방지를 보장하지 않는다. |
| 고정 헤드 회전 저장장치의 역사적 SLTF | Fuller·Baskett의 1972년 보고서 아카이브 사본은 출처 이력·이용 권리 검토가 남아 있는 역사적 1차 자료 후보이며, 이동 헤드 SPTF와 동일시하지 않는다. |
| 비회전 SSD 매체, NVMe 인터페이스와 Linux 다중 큐 경계 | Linux blk-mq 문서와 NVMe Base 2.4 공식 소개 페이지가 직접 설명하는 범위만 사용하며 NVMe를 비회전 매체의 동의어로 쓰지 않는다. |
추가한 수신·부분 쓰기의 길이와 signal 중단 분기는 POSIX Issue 8의 read(), recv(), write() 계약을, 드라이버의 커널·사용자 공간 경계는 Linux의 Userspace I/O HOWTO를 2026-10-09에 대조했다. 17·7바이트 프레임과 길이 상한, 부분 쓰기 의사흐름은 조건을 명시한 자체 교육 예다. 의사흐름은 실제 비동기 라이브러리나 파일시스템의 코드가 아니며, 오류 복구와 기한 정책까지 한 운영체제에 보편적으로 정해졌다고 주장하지 않는다.
POSIX는 구현 내부를 강제하는 교과서가 아니고, Linux의 rolling 문서는 특정 안정 릴리스의 영구 보증이 아니다. 본문의 도식·표·로그 사례와 답안은 원문 코드·표·도식을 복제하지 않고 독자 구성했다. 시스템별 ABI·인터럽트 우선순위·파일 영속 의미는 대상 아키텍처와 현행 구현 문서로 다시 확인해야 한다. 기술·현행성·이용 권리와 기출 원문 연결은 추가 검토가 남아 있다.
판본과 적용 범위 읽기: 2026-10-04의 공개 출처 대조에서는 POSIX Issue 8 / IEEE Std 1003.1-2024의 외부 응용 인터페이스 계약, GNU LD 2.47의 결합·재배치·심볼 설명, OSTEP의 표시 판본 1.10을 확인했다. Linux kernel 문서는 rolling 공개 문서이므로 정확한 안정 커널 릴리스와 동일시하지 않는다. descriptor 닫기·일반 파일의 비차단 경계는 Linux man-pages 6.19의 close(2)와 open(2)를 대조했다. 이 문서 판본은 커널 버전이 아니며, 페이지의 기준 날짜 2026-02-08과 생성 기록 2026-09-09도 구분한다.
NVMe 공식 landing은 Base 2.4를 표시하며 규격 공개일을 2026-08-04로, 연결된 문서의 제목에는 ratified 날짜를 2026-07-31로 표시한다. 두 날짜를 같은 의미로 쓰거나 PDF 세부 계약을 읽은 것으로 간주하지 않는다. MIT 강의의 LOOK·C-LOOK은 역사적 정의의 근거이며 현재 모든 시스템의 사용 실태를 입증하지 않는다. OS Concepts 슬라이드의 실제 좌표와 Fuller·Baskett 아카이브 사본의 출처 이력·이용 권리는 이번 대조에서도 미검증 범위로 남는다. 기출 후보의 최종 원문·기술 검토도 남아 있다.
30초 정리
- API는 소스의 기능 의미, ABI는 호출규약·자료배치·심볼 같은 바이너리 계약이며 compile→assemble→link→load로 실행을 만든다.
- 시스템콜은 사용자/커널 보호경계를 넘는 의도된 요청이고, 예외는 현재 명령과 동기적, 외부 인터럽트는 비동기적 사건이다.
- blocking·nonblocking·asynchronous I/O는 대기와 완료통지 위치가 다르며, 버퍼·DMA·부분 I/O와 자원수명을 함께 설계한다.
- HDD는 큐·탐색·회전·전송을 나누고 이동 헤드의 SSTF·SPTF와 고정 헤드의 SLTF를 구분한다. 비회전 SSD에서는 기계적 전제를 제거하고, NVMe는 매체가 아닌 인터페이스로 분리해 병렬 큐·꼬리지연·공정성을 본다.
- descriptor→VFS→파일시스템→저널→장치 완료와 영속 완료를 구분하고, signal·pipe·socket·shared memory는 경계·복사·동기화·역압으로 고른다.
이 장에서 운영체제의 CPU 배분, 동기화, 가상메모리와 시스템 프로그래밍을 연결했다. 이후 장에서는 같은 자원·격리·완료 계약을 수치처리, 가상화, 네트워크와 분산 시스템으로 확장한다.