C++

11. [C++] 파티션 메모리 복사 (dd 명령어, 파일 입출력)

만수르코딩방 2025. 6. 3. 17:27

개요

eMMC와 같은 저장 장치는 하나의 물리적 장치입니다.

이 장치를 논리적으로 나누는 것을 파티션이라고 하는데, 저장장치 안에서 영역을 구분해주는 역할을 합니다.

리눅스와 같은 운영체제에서는 커널 내부에 VFS(Virtual File System)이라는 추상화 계층이 있어 여러 파일 시스템과 장치 파일을 통합 관리합니다. eMMC와 같은 특정 하드웨어에 유저가 접근하려면, VFS가 유저의 요청을 디바이스 드라이버로 전달하고, 드라이버가 하드웨어와 직접 통신하여 데이터를 읽거나 씁니다. 

따라서 유저는 직접 물리 메모리에 접근하지 않고, 커널을 통해 메모리와 하드웨어 자원에 접근할 수 있습니다. 

리눅스 커널 내부의 VFS(Virtural File System)는 /proc, /sys, /dev 와 같은 가상 파일 시스템뿐만 아니라 ext4, xfs 같은 실제 파일 시스템까지 다양한 파일 시스템을 통합관리하며 사용자 공간에서 발생하는 open(), read(), write() 등의 호출을 적절한 파일 시스템이나 디바이스 드라이버에 전달합니다. 

eMMC 저장장치의 첫번째 파티션을 /dev/mmblk0p1, 두 번째 파티션을 /dev/mmbl0p2라고 할 때 첫번째 파티션의 메모리를 두 번째 파티션으로 복사하는 방법에 대해 알아보겠습니다. 

메모리 복사의 방법은 크게 파일 입출력 기반 복사, 메모리 직접 복사, 커널 레벨 복사 등이 있습니다. 

 

1. 파일 입출력 기반 복사 (read, write)

일반적인 open(), read(), write(), close() 와 같은 파일 시스템 API를 사용합니다. 

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

#define BUFFER_SIZE 4096

int main() {
    int fd_src = open("/dev/mmcblk0p1", O_RDONLY);
    int fd_dst = open("/dev/mmcblk0p2", O_WRONLY);
    if (fd_src < 0 || fd_dst < 0) {
        perror("open");
        return 1;
    }

    char buffer[BUFFER_SIZE];
    ssize_t bytes_read;

    while ((bytes_read = read(fd_src, buffer, BUFFER_SIZE)) > 0) {
        ssize_t bytes_written = 0;
        while (bytes_written < bytes_read) {
            ssize_t ret = write(fd_dst, buffer + bytes_written, bytes_read - bytes_written);
            //read 데이터 중 아직 쓰지 않은 남은 바이트를 확인하며 write
            if (ret < 0) {
                perror("write");
                close(fd_src);
                close(fd_dst);
                return 1;
            }
            bytes_written += ret;
        }
    }

    if (bytes_read < 0) perror("read");

    close(fd_src);
    close(fd_dst);

    return 0;
}

 

2. 메모리 매핑 (memcpy)

mmap() 같은 함수로 파일이나 장치를 메모리 공간에 매핑하고, memcpy()로 메모리 영역끼리 복사합니다.

#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <string.h>

int main() {
    int fd_src = open("/dev/mmcblk0p1", O_RDONLY);
    int fd_dst = open("/dev/mmcblk0p2", O_RDWR);
    if (fd_src < 0 || fd_dst < 0) {
        perror("open");
        return 1;
    }

    // 파티션 크기 얻기 (예: fstat)
    struct stat st;
    if (fstat(fd_src, &st) < 0) {
        perror("fstat");
        return 1;
    }
    size_t size = st.st_size;

    void* src_map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd_src, 0);
    void* dst_map = mmap(NULL, size, PROT_WRITE, MAP_SHARED, fd_dst, 0);
    if (src_map == MAP_FAILED || dst_map == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    memcpy(dst_map, src_map, size);

    msync(dst_map, size, MS_SYNC);

    munmap(src_map, size);
    munmap(dst_map, size);

    close(fd_src);
    close(fd_dst);

    return 0;
}

 

3. 커널 레벨 복사(dd)

dd 명령어는 사용자 공간에서 실행되는 프로그램이며, 파일 또는 디바이스를 열고 read() / write() 시스템 콜을 통해 커널에 데이터를 복사하도록 요청합니다.

#include <stdlib.h>  // system() 사용을 위한 헤더
#include <stdio.h>

int main() {
    int ret = system("dd if=/dev/mmcblk0p1 of=/dev/mmcblk0p2 bs=1M status=progress");

    if (ret == -1) {
        perror("system");
        return 1;
    }

    printf("eMMC 파티션 복사 완료\n");
    return 0;
}

 

4. dd 명령어 사용 시 유저 공간 -> 커널 -> 드라이버 전달과정 예시

유저가 dd if=/dev/mmcblk0p1 of= /dev/mmcblk0p2 bs=1M  실행할 때의 전달 과정을 살펴보겠습니다. 

단계 설명

1. 사용자 공간에서 dd 실행 사용자가 dd if=/dev/mmcblk0p1 of= /dev/mmcblk0p2 bs=1M count=10 명령어를 입력하면, dd라는 프로그램이 실행됩니다. dd는 먼저 입력 장치(/dev/mmcblk0p1)를 open() 시스템 콜로 엽니다.
2. read() 시스템 콜 호출 dd는 내부에서 read(fd, buffer, 1MB)를 반복 호출하여 eMMC 장치에서 데이터를 읽으려고 합니다. 여기서 커널로 제어가 넘어갑니다.
3. VFS가 장치 파일 처리 커널에 들어온 read() 요청은 먼저 VFS(Virtual File System)로 전달됩니다. VFS는 /dev/mmcblk0p1가 일반 파일이 아닌 블록 디바이스 파일임을 알고, 알맞은 디바이스 드라이버로 연결해 줍니다.
4. 디바이스 드라이버가 실제 eMMC와 통신 연결된 eMMC 디바이스 드라이버는 MMC 컨트롤러와 통신하여 eMMC에서 데이터를 읽습니다. 이 때 하드웨어는 DMA를 통해 메모리에 데이터를 채울 수 있습니다.
5. 커널이 데이터를 유저 공간으로 복사 읽은 데이터는 커널의 커널 공간 버퍼에 잠시 저장된 뒤, read() 호출이 반환되며 사용자 공간의 dd 버퍼로 복사됩니다. 이 단계에서 데이터가 사용자 프로그램으로 넘어옵니다.
6. dd가 output.img에 write() 요청 dd는 이제 유저 공간의 버퍼에 있는 데이터를 /dev/mmcblk0p2 라는 파일에 쓰기 위해 write() 시스템 콜을 호출합니다.
7. VFS가 일반 파일 처리 write() 요청도 VFS를 거쳐 /dev/mmcblk0p2 라는 일반 파일에 연결된 실제 파일 시스템(예: ext4, xfs 등)으로 전달됩니다.
8. 파일 시스템 → 디스크 드라이버 → 저장 파일 시스템은 데이터를 페이지 캐시나 버퍼 캐시에 저장하고, 이후 실제 디스크로 플러시합니다. 이때는 디스크 드라이버가 관여합니다.