Linux 파일 시스템과 저수준 I/O 시스템 호출

파일 조작의 계층적 구조

운영체제에서 파일을 다루는 방식은 여러 계층으로 구성됩니다. 가장 하위에는 물리 저장 장치를 제어하는 하드웨어 수준이 있고, 그 위에 커널의 파일 시스템 지원 함수, 시스템 호출, I/O 라이브러리 함수, 사용자 명령어, 셸 스크립트가 차례로 위치합니다. 이 계층들은 사용자 공간의 파일 스트림 입출력부터 시작해 커널 공간의 시스템 호출, 최종적으로 장치 드라이버 수준의 입출력까지 긴하게 연결됩니다.

저수준 파일 조작에는 저장 장치 분할, 파티션 테이블 확인, 파일 시스템 포맷, 마운트 등이 포함됩니다. 리눅스의 EXT 계열 파일 시스템, 특히 EXT2에서는 슈퍼블록, 그룹 기술자, 블록 및 아이노드 비트맵, 디터리 엔트리 등의 자료구조가 핵심 역할을 합니다.

시스템 호출을 통한 파일 입출력

프로그램이 파일에 접근하는 방법은 크게 두 가지입니다. 하나는 리눅스 커널이 직접 제공하는 시스템 호출 인터페이스이고, 다른 하나는 GNU에서 이를 감싸 구현한 표준 입출력 라이브러리 <stdio.h>입니다. 직접 시스템 호출을 사용하면 사용자 모드와 커널 모드 간 전환 오버헤드가 발생하므로, 라이브러리 함수들은 버퍼링을 통해 호출 횟수를 줄이고 효율을 높입니다.

시스템 호출은 파일 기술자(file descriptor)를 중심으로 동작합니다. 프로세스가 시작되면 표준 입력(0), 표준 출력(1), 표준 오류(2) 세 개의 파일 기술자가 자동으로 열립니다.

핵심 시스템 호출 함수

열기와 닫기

#include <fcntl.h>

int 파일열기(const char *경로, int 플래그, ...);

주요 플래그: O_RDONLY(읽기 전용), O_WRONLY(쓰기 전용), O_RDWR(읽고 쓰기), O_CREAT(으면 생성), O_TRUNC(기존 내용 삭제)

#include <unistd.h>

int 파일닫기(int 기술자);

파일 위치 이동

#include <unistd.h>

off_t 위치이동(int 기술자, off_t 오프셋, int 기준);
기준값의미
SEEK_SET파일 시작점에서 오프셋 바이트만큼 이동
SEEK_CUR현재 위치에서 오프셋 바이트만큼 이동
SEEK_END파일 끝에서 오프셋 바이트만큼 이동

데어터 읽기와 쓰기

#include <unistd.h>

ssize_t 데이터읽기(int 기술자, void *버퍼, size_t 바이트수);
ssize_t 데이터쓰기(int 기술자, const void *버퍼, size_t 바이트수);

링크 파일의 종류와 특성

하드 링크

하드 링크는 동일한 아이노드 번호를 공유하는 여러 파일 이름입니다. 리눅스 파일 시스템에서 모든 파일은 고유한 아이노드를 갖고, 이 아이노드는 메타데이터(생성 시각, 수정 시각, 크기, 소유자, 권한, 데이터 블록 위치 등)를 저장합니다. 하드 링크를 통해 하나의 파일에 여러 경로명을 부여할 수 있으며, 이는 실수로 원본을 삭제하는 것을 방지하는 효과가 있습니다.

심볼릭 링크

심볼릭 링크(소프트 링크)는 대상 파일의 경로를 담고 있는 독립적인 파일입니다. 윈도우의 바로가기와 유사하며, 자신만의 아이노드를 가니다. 복잡한 경로를 단순화하거나, 다른 파일 시스템의 디렉터리를 접근하는 용도로 활용됩니다. 원본 파일이 삭제되면 심볼릭 링크는 무효(죽은 링크)가 됩니다.

파일 정보 조회와 stat 활용

stat 시스템 호출은 파일의 상세 메타데이터를 얻는 데 사용됩니다. 이 정보를 바탕으로 파일 유형, 권한, 크기, 접근/수정/상태 변경 시각, 하드 링크 수 등을 파악할 수 있으며, 이를 응용하면 ls 명령어와 유사한 디렉터리 목록 도구를 직접 구현할 수 있습니다.

실습: 파일 내용 출력 프로그램

다음은 cat 명령어와 동일한 기능을 수행하는 예제입니다. 명령줄 인자로 지정한 파일의 내용을 표준 출력으로 보냅니다.

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

#define 청크크기 4096

int 주요함수(int 인자수, char *인자값[]) {
    int  입력기술자;
    char 버퍼[청크크기];
    ssize_t 읽은바이트, 쓴바이트;
    
    if (인자수 != 2) {
        데이터쓰기(STDERR_FILENO, "사용법: 프로그램 <파일명>\n", 25);
        return 1;
    }
    
    입력기술자 = 파일열기(인자값[1], O_RDONLY);
    if (입력기술자 < 0) {
        perror("파일 열기 실패");
        return 1;
    }
    
    while ((읽은바이트 = 데이터읽기(입력기술자, 버퍼, 청크크기)) > 0) {
        쓴바이트 = 데이터쓰기(STDOUT_FILENO, 버퍼, 읽은바이트);
        if (쓴바이트 != 읽은바이트) {
            perror("출력 오류");
            파일닫기(입력기술자);
            return 1;
        }
    }
    
    if (읽은바이트 < 0) {
        perror("읽기 오류");
    }
    
    파일닫기(입력기술자);
    return 0;
}

선택적 파일 복사: dd 유틸리티 구현

특정 오프셋에서 특정 크기만큼만 복사하는 기능은 다음과 같이 구현할 수 있습니다. 입력 파일의 임의 위치에서 데이터를 읽어 출력 파일의 지정한 위치에 기록합니다.

#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define 블록단위 512

int 복사프로그램(const char *원본경로, const char *대상경로,
              off_t 입력시작, off_t 출력시작, size_t 복사크기) {
    int 원본기술자, 대상기술자;
    char *작업버퍼;
    ssize_t 실제읽음, 실제쓰임;
    size_t  처리된양 = 0;
    
    원본기술자 = 파일열기(원본경로, O_RDONLY);
    if (원본기술자 < 0) return -1;
    
    대상기술자 = 파일열기(대상경로, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (대상기술자 < 0) {
        파일닫기(원본기술자);
        return -1;
    }
    
    작업버퍼 = (char *)malloc(블록단위);
    if (!작업버퍼) {
        파일닫기(원본기술자);
        파일닫기(대상기술자);
        return -1;
    }
    
    위치이동(원본기술자, 입력시작, SEEK_SET);
    위치이동(대상기술자, 출력시작, SEEK_SET);
    
    while (처리된양 < 복사크기) {
        size_t 이번처리량 = 블록단위;
        if (복사크기 - 처리된양 < 블록단위)
            이번처리량 = 복사크기 - 처리된양;
        
        실제읽음 = 데이터읽기(원본기술자, 작업버퍼, 이번처리량);
        if (실제읽음 <= 0) break;
        
        실제쓰임 = 데이터쓰기(대상기술자, 작업버퍼, 실제읽음);
        if (실제쓰임 != 실제읽음) {
            처리된양 = -1;
            break;
        }
        
        처리된양 += 실제쓰임;
    }
    
    free(작업버퍼);
    파일닫기(원본기술자);
    파일닫기(대상기술자);
    
    return (처리된양 == 복사크기) ? 0 : -1;
}

경로 해석 메커니즘

절대 경로 /home/hello에 접근할 때 커널은 다음 단계를 수행합니다:

  1. 루트 디렉터리의 아이노드(고정적으로 2번)를 참조
  2. 루트의 데이터 블록에서 home 엔트리를 찾아 해당 아이노드 획득
  3. home 아이노드를 통해 그 데이터 블록 접근, hello 엔트리 검색
  4. hello의 아이노드에서 최종 메타데이터 및 데이터 블록 포인터 확인
  5. 실제 파일 내용이 저장된 데이터 블록에 접근

시스템 호출과 일반 함수 호출의 차이

구분시스템 호출일반 함수 호출
바인딩 시점동적 바인딩 (기능 번호 기반)정적 바인딩 (고정 주소)
실행 모드 전환사용자 모드 ↔ 커널 모드동일 모드 내에서 실행
진입 방식소프트웨어 인터럽트/특수 명령어일반 점프 명령어
프로세스 스케줄링호출 중 선점 가능일반적으로 비선점
코드 위치커널 공간사용자 공간 (동일 프로세스)

시스템 호출의 동적 특성은 사용자 프로그램의 크기를 줄이고, 운영체제 업그레이드 시 호출부 수정 없이 새 기능을 활용할 수 있게 합니다. 또한 커널이 자원 접근을 중재함으로써 시스템 안정성과 보안이 강화됩니다.

태그: linux system-call file-descriptor EXT2 inode

7월 24일 08:19에 게시됨