ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리눅스] boot process overview
    Linux 2023. 8. 3. 02:34

    글의 참고

    - https://www.linkedin.com/pulse/how-u-boot-loads-linux-kernel-praveen-singh

    - https://en.wikipedia.org/wiki/Booting_process_of_Linux

    - https://en.wikipedia.org/wiki/Initial_ramdisk

    - https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard

    - https://www.embeddedrelated.com/showarticle/118.php

    - https://embeddedbuddies555.wordpress.com/2020/03/29/linux-booting-process-on-arm-processor/


    글의 전제

    - 내가 글을 쓰다가 궁금한 점은 파란색 볼드체로 표현했다.

    - 밑줄로 작성된 글은 좀 더 긴 설명이 필요해서 친 것이다.


    글의 내용

    " 임베디드 장치에서 리눅스는 어떻게 부팅될까? 오케이, u-boot가 리눅스를 load 한다. 그럼 u-boot는 누가 load 해줌? u-boot는 DDR에서 동작중인데, DDR은 아직 초기화가 되지도 않았다? 시작하기 전 용어부터 정리하자.

     

    1) BROM

    " ARM CPU가 Power on되면, PC 레지스터 포인터가 IC에 내장된 ROM의 시작 부분을 가리킨다. 이 ROM은 BROM(boot rom)이라고 하며, 이 BROM을 통해 시스템이 부팅된다.

     

    2) SRAM 

    " 칩 내부에 존재하며, 간단히 말해서 SRAM 은 Cache 다.

     

    3) PL/pre-loader

    " 실제로 u-boot를 DDR에 로드하는 보조 프로그램 로더.

     

    출처 - https://www.linkedin.com/pulse/how-u-boot-loads-linux-kernel-praveen-singh

    https://www.linkedin.com/pulse/how-u-boot-loads-linux-kernel-praveen-singh

     

     

    " 위 그림은 시스템의 부팅 프로세스에 대한 블락 다이어그램이다. bootloader는 2 단계로 나눠서 실행된다.

    1" pre-loader를 실행한다. 그리고 `u-boot`를 메모리에 로드한다.
    2" u-boot는 kernel을 메모리에 로드한다.

     

     

    " 이제부터 위의 그림을 기준으로 loading process를 자세하게 설명할 것이다.

    1) 디바이스가 Power-on 이후, NAND나 EMMC가 아닌 Boot ROM 의 boot code로 점프한다. 왜냐면, 이 지점이 CPU reset vector branch address 이기 때문이다. boot ROM 은 DRAM 에 하드웨어적으로 memory map 되어있기 때문에, CPU 가 boot ROM 과 mapping 된 RAM 영역을 읽으면 boot ROM 을 읽는 것과 같은 효과를 한다. 여기서 RAM, NAND 등이 초기화되지 않았다는 것은 `장치` 보다는 `컨트롤러`가 초기화 되어있지 않다는 것으 의미한다. 예를 들어, RAM 이 초기화되어 있지 않다는 것은 RAM Controller 가 초기화 되어 있지 않다는 것과 같다.

    " 다시 본론으로 돌아와서 BROM의 `pre-loader`를 ISRAM(Internal SRAM)에 load한다. 왜 ISRAM`으로 로드할까? 현재 DRAM이 아직 준비가 되지 않았기 때문이다. 근데 `pre-loader`는 무슨 일을 할까? BIOS와 같은 일을 한다고 보면 된다. BIOS는 기본적으로 SoC의 모든 하드웨어를 초기화한다. 고장난 하드웨어가 있는지, 현재 시스템의 하드웨어들이 몇 개인지, 어떤 구조로 연결되어 있는지 등을 알고 있다. `pre-loader`도 마찬가지다. 그런데, 이중에서 가장 중요한 기능은 2가지가 있다.

    0" 메모리 컨트롤러의 초기화
    1" 디스크 컨트롤러의 초기화
     
    " 만약, `pre-loader`가 위의 작업들을 하지 않으면, 어떻게 될까? 혹은 둘 중 하나만 했다면 어떻게 될까? 메모리 컨트롤러만 초기화 됬다고 치자. `pre-loader` 2단계 스테이지 부트 로더인 `U-boot`를 메모리에 로드해야 한다. 그런데 어떻게 로드하지? `U-boot`는 ROM에 있다. 그런데, 디스크 컨트롤러가 초기화가 되지 않았기 때문에 ROM 컨트롤러와 통신이 안된다. 망했다. 만약, 메모리 컨트롤러가 초기화 되지 않았다면? 디스크에서 `U-boot` 바이너리를 읽었지만, 대신 RAM에 로드가 불가능하다. 즉, 위의 2가지 과정이 반드시 선행되어야 한다.

     " 그리고 중요한 작업이 `인터럽트 비활성화`다. 정확히 말하면 `외부 인터럽트 비활성화`다(CPU 내부 인터럽트는 비활성화하면 안된다. 사실 할 수 있는 방법도 없다). 왜 비활성화할까? 외부 인터럽트를 처리하려면 인터럽트 핸들러를 등록해야 한다. 근데 시스템 초기에는 인터럽트 핸들러가 없기 때문에 비활성화 하는 것이다. 그렇면, 인터럽트 핸들러가 있으면 활성화 시켜도 되나? 가능하다. 그런데, 부팅 과정에서는 거의하지 않는다. 왜? 2가지 이유가 있을 수 있다.

    0" BSP만 동작한다. 
    1" 하드웨어 초기화는 타이밍이 있다. 

    " 멀티 프로세서환경에서 부팅 시점에는 하나의 CPU만 동작하도록 한다. 왜? 부팅 과정의 대부분의 작업은 `I/O 디바이스 초기화`가 주를 이룬다. 그런데, 시스템에 존재하는 대부분의 디바이스는 하나이고 디바이스들의 초기화는 리셋이 발생하는 시점에만 딱 한번만 이루어 지면 된다. 멀티 프로세서에서 전원이 인가되면, 모든 프로세서들이 동일한 소스를 동일한 시점에 실행하게 된다. 하드웨어를 여러 번 초기화할 수 도 있고, 다수의 프로세서가 하나의 디바이스와 통신하기 위한 병목현상이 발생할 수 도 있다. 그리고 무엇보다 이 시점에는 동기화 메커니즘이 구현되어 있지 않을 것이다. 결국, 멀티 프로세서 환경에서는 부팅 시점에 하나의 CPU만 동작하도록 한다. 그런데, 이렇게 하나의 CPU만 동작하는 환경에서 외부에서 인터럽트가 인터럽트가 들어온다? 부팅 속도에 큰 영향을 미치게 될 수 있다. 그러므로, 인터럽트를 비활성화 해야 한다.

    " 하드웨어들은 초기화 시점에 타이밍 다이어그램에 맞게 특정 시간을 딜레이하고 그 다음 커맨드를 보내고, 또 딜레이하고를 반복하는 과정들이 있을 수 있다. 그런데, 인터럽트를 활성화하면 정확한 시점에 데이터를 주고 받기가 힘들어진다. 그러므로, 그러므로, 인터럽트를 비활성화 해야 한다.

    2 ~ 3)" 코드 재배치 진행 : 주로 RAM이 장착되어 있는지 확인하고, RAM 사이즈를 확인한다. 그리고, System MMIO 영역 또한 확인해야 한다. 그리고 자신이 사용할 수 있는 RAM 영역이 어느 정도 인지를 확보한다. 즉, `pre-loader`는 현재 시스템의 메모리 맵을 알고 있다. `시스템 메모리-맵`은 굉장히 중요한 내용이다. 그러므로, `U-boot`가 실행되는 시점에는 `pre-lodaer`를 통해 시스템 메모리 맵에 대한 정보를 전달받아야 한다.

    " 대개 stage 2 boot loader는 함수를 사용하는 C 언어로 작성되어 있어서 이 시점에서는 스택 영역에 대한 설정을 해야 한다. 함수를 사용하기 때문에 스택을 설정한다? 무슨 말일까? 함수는 스택을 사용하는 프로시저다. 지역 변수, 리턴 주소, 파라미터 등 함수에서 사용되는 모든 리소스들은 스택에 저장된다. 이건 컴파일러 및 어셈블러에 의해서 정해진 스펙이다. 컴파일러 및 어셈블러는 함수 및 프로시저를 만나면, 각 CPU 아키텍처에 맞는 ABI에 기반해서 컨버팅 과정에서 스택 레지스터를 사용하게 된다. 그러므로, C와 같은 함수 기반의 언어를 사용하기 전에 반드시 각 CPU 아키텍처에 존재하는 `스택 레지스터`를 설정해놔야 한다. 

    " 앞에 과정이 모두 끝나면, pre-loader는 자기 자신을 DRAM으로 로드한다. 그리고 PC 포인터를 DRAM로 옮긴 자기 자신의 위치로 이동시켜서 하던 일을 마저 수행한다. 그리고 second stage code를 RAM에 load한다. 근데, 애 `pre-loader`는 DRAM으로 자신을 로드할까? 내 개인적인 생각이지만, ISRAM의 사이즈가 부족해서 일 수 있다. 즉, `pre-loader`를 2개로 나눠서 앞 부분은 ISRAM에 실행하고, 시간적으로 여유가 있는 부분들은 DRAM에서 실행되도록 하는 것이다. 혹은, ISRAM이 캐시이기 때문에 `pre-loader`를 DARM에 로드하는 것일 수 있다. 뭔 말이냐? 각 시스템 아키텍처들은 자신만의 시스템 자료 구조들을 가지고 있다. 이런 시스템 자료 구조들은 프로세서마다 별도로 가지고 있을 수도 있고, 시스템 전체에 하나만 존재할 수 도 있다. 그런데, 이 시스템 자료 구조의 특징이 굉장히 자주 사용된다는 것이다. 예를 들어, `인터럽트 벡터 테이블`, `페이징 테이블` 등이 있다. 그래서 이런 시스템 자료 구조들은 대개 캐시에 저장된다. 그래서 시스템 자료 구조에 밀려 `pre-loader`가 DRAM으로 밀려날 가능성이 있다. `pre-loader`가 실행되는 시점에 시스템 자료 구조를 사용해야 하나? 즉, 스테이지 2 부트 로더를 사용하기 전에 시스템 자료 구조를 만들 일이 있을까? 그럴 일은 없어보인다. 그래서 시스템 자료 구조 관련 추측은 틀릴 가능성이 높다.
    4 ~ 6) pre-loader는 DRAM을 초기화하고, `스테이지 2 부트 로더`를 ROM(nand/emmc)에서 DRAM으로 load하여 execute한다. ROM에서 RAM으로 `스테이지 2 부트 로더`를 복사할 때, 2 가지 고려해야 할 사항이 있다.

    1) 스테이지 2 부트 로더가 ROM의 어디에 위치해 있는지
    2) 스테이지 2 부트 로더를 RAM의 어디에 로드할지

    - `스테이지 2 부트 로더`로 점프하는 것은 U-boot에서 PC 레지스터를 적절한 주소로 수정하면 된다. 
    ldr pc, _start_armboot
    - `start_armboot`는 stage 2에서 C 프로그램으로 작성된 entry point 다. `start_armboot`은 U-Boot에 의해 실행되는 첫 번째 C language function이며, 시스템 초기화 작업을 완료하고 메인 루프에 진입하여 사용자가 입력한 명령을 처리합니다.

    - second stage (stage 2) bootloader의 workflow 다음과 같다.
    1) Initialize the hardware devices to be used in this stage(stage 2)
    2) Check system memory map 
    3) Load kernel image and root file system image
    4) Set kernel startup parameters
    5) Boot kernel
    7 ~ 8) bootimage를 ramdisk 및 kernel로 압축 해제한다. 그리고 DRAM으로 load하고 dtb를 초기화합니다. u-boot를 통해 부팅된 kernel을 uImage라고 한다. 이 kernel은 두 부분으로 구성되어 있다. 하나는 header이고, 다른 하나는 real kernel이다. 이건 `uImage = u-boot header  + zImage` 로 구성되어 있다는 것을 의미한다. 

     

     

    " u-boot heaer 구성을 좀 보자.

    /uboot/include/image.h
    typedef struct image_header {
        uint32_t    ih_magic;    /* Image Header Magic Number    */
        uint32_t    ih_hcrc;    /* Image Header CRC Checksum    */
        uint32_t    ih_time;    /* Image Creation Timestamp    */
        uint32_t    ih_size;    /* Image Data Size        */
        uint32_t    ih_load;    /* Data     Load  Address        */
        uint32_t    ih_ep;        /* Entry Point Address        */
        uint32_t    ih_dcrc;    /* Image Data CRC Checksum    */
        uint8_t        ih_os;        /* Operating System        */
        uint8_t        ih_arch;    /* CPU architecture        */
        uint8_t        ih_type;    /* Image Type            */
        uint8_t        ih_comp;    /* Compression Type        */
        uint8_t        ih_name[IH_NMLEN];    /* Image Name        */
    } image_header_t;

     

     

    " 우리가 볼 부분은 아래의 변수 2개다.

    uint32_t    ih_load;    /* Data Load Address */
    uint32_t    ih_ep;      /* Entry Point Address */
    ih_load : kernel load addresss
    " 리눅스 커널이 RAM에 어디에 로드해야 하는지를 나타내는 변수다. 

    ih_ep : kernel entry address

     

     

    " 참고로, kernel load address와 kernel entry address는 같을 수 있다. 이런 경우는, 대개 리눅스 커널의 헤더가 없는 경우다. 즉, 리눅스 커널이 RAW 바이너리 포맷이면 가능하다. Uboot가 커널을 부팅하는 과정은 환경 변수 env에서 bootcmd를 읽어 커널을 어떻게 시작할지 결정하는 것입니다. For example, uboot wants to read the kernel partition from the nand flash to the memory address 0x30007FC0 and start the kernel. 우리는 아래의 명령어를 사용해서 커널을 시작시킬 수 있다.

    bootm 0x30007FC0.

     

     

    " 커널 시작의 핵심은 bootm 명령이다. bootm 명령의 구현은 uboot의 do_bootm() 함수에 있다. 그래서 위의 명령어를 치면, argv[0] = `bootm`, argv[1] = `0x30007FC0`가 된다. 조건문은 `addr = simple_strtoul(argv[1], NULL, 16)`로 진입하고, addr = `0x30007FC0`이 된다. 아래의 `load_addr`는 전역 변수로 `bootm` 커맨드를 통해 커널이 로드될 주소가 전달되지 않을 경우, default로 사용되는 주소다.

    Source file: cmd_bootm.c
    
    
    int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
    {
    .....
     if (argc < 2) {
                 	addr = load_addr; 
     	} else {
                 	addr = simple_strtoul(argv[1], NULL, 16);
    }
    
     / * Read the uboot header from the load address and parse it */
    .....
    .....
    
       switch (hdr->ih_comp) { 
            case IH_COMP_NONE: 
                     if(ntohl(hdr->ih_load) == addr) { /
                           printf ("   XIP %s ... ", name);
                     } else {//
                           memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
                      }
    ....
    ....
    
    }
    mkimage -A arm -O linux -C none -a 0x30008000 -e 0x30008000 -d zImage uImage
    -A: CPU type
     -O: operating system
     -C: The compression method used
     -a: kernel load address
     -e: kernel entry address
    int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
    {
    	/* No need for those on ARM */
    	if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
    		return -1;
    
    
    	if (flag & BOOTM_STATE_OS_PREP) {
    		boot_prep_linux(images);
    		return 0;
    	}
    
    
    	if (flag & BOOTM_STATE_OS_GO) {
    		boot_jump_linux(images);
    		return 0;
    	}
    
    
    	boot_prep_linux(images);
    	boot_jump_linux(images);
    	return 0;
    }

     

    static void boot_jump_linux(bootm_headers_t *images, int flag)
    {
        unsigned long machid = gd->bd->bi_arch_number;      //Get the machine id (set in board/samsung/jz2440/jz2440.c, MACH_TYPE_SMDK2410(193))
        char *s;
        void (*kernel_entry)(int zero, int arch, uint params);
        unsigned long r2;
        int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
    
    
        kernel_entry = (void (*)(int, int, uint))images->ep;    //Get the entry address of the kernel, here should be 30000000
    
    
        s = getenv("machid");        //Get the machine id from the environment variable (In this example, the machine id has not been set in the environment variable)
    
        if (s) {            //Determine whether the machine id is set in the environment variable
            strict_strtoul(s, 16, &machid);    //If set, use the machine id in the environment variable
            printf("Using machid 0x%lx from environment\n", machid);
        }
    
    
        debug("## Transferring control to Linux (at address %08lx)" \
            "...\n", (ulong) kernel_entry);
        bootstage_mark(BOOTSTAGE_ID_RUN_OS);
        announce_and_cleanup(fake);
    
    
        if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
            r2 = (unsigned long)images->ft_addr;
        else
            r2 = gd->bd->bi_boot_params;    //Get the tag parameter address, gd->bd->bi_boot_params is set in the setup_start_tag function
    if (!fake) kernel_entry(0, machid, r2); }  //Enter the kernel

     

    " 커널 압축 이미지 관련 내용은 이 글을 참고하면 좋다. 그리고, ATAG는 이 글을 참고하자.

     

    - Decompress

    " 커널 이미지를 압축은 왜 할까? 당연히 이미지 사이즈가 커서 압축했을 것이다. 그런데, 압축을 한 이미지는 곧 바로 실행이 불가능하다. 즉, 결국 다시 압축을 해제해야 한다. 압축을 한 이미지가 압축하지 않은 이미지보다 뭔가 장점이 있을 것이다. 부트 로더에서 커널을 메모리에 로드할 때, 압축이미지를 풀어서 메모리에 로드한다. 차라리 압축하지 않은 이미지를 커널에 로딩하는게 더 빠르지 않을까? 예를 들어, 압축하지 않은 이미지가 10MB 이고, 압축한 이미지가 4.6MB 일 때 어떤 게 더 빨리 메모리에 로딩되는지를 따져야 한다. 일반적으로, 압축한 이미지를 풀면서 메모리에 로딩하는것이 더 빠르다고 한다.

     

     

    - Initrd / Initramfs

    " 커널 부팅과정에서 램 디스크를 사용한다. 여기서 그 이유를 알아보자. 리눅스 커널에서 사용하는 램 디스크를 `initrd / initramfs`라고 한다. 왜 사용할까? 첫 번째로, 커널 사이즈를 줄이기 위해서 사용한다. 리눅스 커널 소스의 80%는 디바이스 드라이버가 차지한다. 이 드라이버들을 컴파일 시점에 한 꺼번에 빌드하면 어떻게 될까? 커널 이미지는 엄청 커지게 된다. 커널 이미지가 커지면 뭐가 안좋을까? 커널은 메모리에 항상 상주하고 있는 소프트웨어다. 즉, 커널 이미지에 사용되지도 않은 드라이버 코드가 있을 경우, 메모리 낭비를 불러올 수 있게 된다. 그래서, 커널 이미지는 최소한의 필수적인 디바이스 드라이버만 있어야 한다. 그리고, 커널 이미지가 크면 부팅 속도에도 영향을 준다. 앞에서 말했지만, 커널은 메모리에 항시 상주해있다. 최초의 부트 로더가 커널을 로딩할 때, 당연히 커널 사이즈가 작을 수 록, 메모리에 로딩하는 속도도 빨라질 것 이다. 여기서 `initrd` 가 등장한다.

    Many Linux distributions ship a single, generic Linux kernel image – one that the distribution's developers create specifically to boot on a wide variety of hardware. The device drivers for this generic kernel image are included as loadable kernel modules because statically compiling many drivers into one kernel causes the kernel image to be much larger, perhaps too large to boot on computers with limited memory, or in some cases to cause boot-time crashes or other problems due to probing for nonexistent or conflicting hardware. This static-compiled kernel approach also leaves modules in kernel memory which are no longer used or needed, and raises the problem of detecting and loading the modules necessary to mount the root file system at boot time, or for that matter, deducing where or what the root file system is.
    ...

    In the `initrd` scheme, the image may be a file system image (optionally compressed), which is made available in a special block device (/dev/ram) that is then mounted as the initial root file system.The driver for that file system must be compiled statically into the kernel. Many distributions originally used compressed ext2 file system images, while the others (including Debian 3.1) used cramfs in order to boot on memory-limited systems, since the cramfs image can be mounted in-place without requiring extra space for decompression. Once the initial root file system is up, the kernel executes `/linuxrc` as its first process; when it exits, the kernel assumes that the real root file system has been mounted and executes `/sbin/init` to begin the normal user-space boot process

    - 참고 : https://en.wikipedia.org/wiki/Initial_ramdisk

    `start_kernel` executes a wide range of initialization functions. It sets up interrupt handling (IRQs), further configures memory, starts the Init process (the first user-space process), and then starts the idle task via `cpu_idle()`. Notably, the kernel startup process also mounts the initial RAM disk (`initrd`) that was loaded previously as the temporary root file system during the boot phase. The initrd allows driver modules to be loaded directly from memory, without reliance upon other devices (e.g. a hard disk) and the drivers that are needed to access them (e.g. a SATA driver). This split of some drivers statically compiled into the kernel and other drivers loaded from initrd allows for a smaller kernel. The root file system is later switched via a call to `pivot_root()` which unmounts the temporary root file system and replaces it with the use of the real one, once the latter is accessible. The memory used by the temporary root file system is then reclaimed.

    - 참고 : https://en.wikipedia.org/wiki/Booting_process_of_Linux

     

     

    " 부트 로더는 커널과 초기 `initrd`를 메모리에 로딩한 뒤, 커널에게 제어권을 넘긴다. 이 때, `initrd`의 주소도 함께 넘긴다. 이제 커널은 `initrd` 주소를 확인하고, 부팅에 필요한 데이터를 디스크가 아닌, `initrd`에서 불러오게 된다. 왜냐면, `initrd(초기 램 디스크)`는 리눅스 커널이 부팅 시점에 필요한 거의 모든 정보를 가지고 있기 때문이다. 커널 동적 모듈도 여기에 포함되어 있다. 예를 들어, 디스크 드라이버가 여기에 포함된다. `initrd` 이미지는 `/bin/, /etc/, /lib/ 등`과 같은 루프 파일 시스템 구조이기 때문에, 커널이 `initrd` 구조를 해석하려면, 반드시 정적으로 `initrd` 파일 시스템을 해석할 수 있는 드라이버 정도는 가지고 있어야 한다. `initrd`는 임시 파일 시스템이므로, 구조가 복잡할 필요가 없다. 그래서 `EXT2`나 `FAT` 파일 시스템을 주로 사용한다. 

     

    `start_kernel` executes a wide range of initialization functions. It sets up interrupt handling (IRQs), further configures memory, starts the Init process (the first user-space process), and then starts the idle task via `cpu_idle()`. Notably, the kernel startup process also mounts the initial RAM disk (`initrd`) that was loaded previously as the temporary root file system during the boot phase. The initrd allows driver modules to be loaded directly from memory, without reliance upon other devices (e.g. a hard disk) and the drivers that are needed to access them (e.g. a SATA driver). This split of some drivers statically compiled into the kernel and other drivers loaded from initrd allows for a smaller kernel. The root file system is later switched via a call to `pivot_root()` which unmounts the temporary root file system and replaces it with the use of the real one, once the latter is accessible. The memory used by the temporary root file system is then reclaimed.

    Thus, the kernel initializes devices, mounts the root filesystem specified by the boot loader as read only, and runs Init (`/sbin/init`) which is designated as the first process run by the system (PID = 1). A message is printed by the kernel upon mounting the file system, and by Init upon starting the Init process. It may also optionally run Initrd[clarification needed] to allow setup and device related matters (RAM disk or similar) to be handled before the root file system is mounted.

    - 참고 : https://en.wikipedia.org/wiki/Booting_process_of_Linux

     

     

    " `initrd`는 수정이 가능할까? 이 말은 `부팅 시점에 동작들을 런타임에 바뀔 수 있는가 ?`와 같다. 대개, 부팅 시점은 상당히 민감하고 예민한 부분들이 많다. 특히나, 하드웨어 초기화는 타이밍 관련 이슈가 많다. 그래서 `initrd`는 대개 `READ-ONLY`로 설정된다. 바뀌는 경우도 있을 수 있다. `initrd`는 커널 빌드 시점에 커널 이미지와 같이 생성되기 때문에, 커널이 변경되면 `initrd`로 새로 생성될 수 있다.

    An image of this initial root file system (along with the kernel image) must be stored somewhere accessible by the Linux bootloader or the boot firmware of the computer. This can be the root file system itself, a boot image on an optical disc, a small partition on a local disk (a boot partition, usually using ext2 or FAT file systems), or a TFTP server (on systems that can boot from Ethernet).

    The bootloader will load the kernel and initial root file system image into memory and then start the kernel, passing in the memory address of the image. At the end of its boot sequence, the kernel tries to determine the format of the image from its first few blocks of data, which can lead either to the initrd or initramfs scheme.
    ...

    Most initial root file systems implement `/linuxrc` or `/init` as a shell script and thus include a minimal shell (usually /bin/ash) along with some essential user-space utilities (usually the BusyBox toolkit). To further save space, the shell, utilities and their supporting libraries are typically compiled with space optimizations enabled (such as with gcc's "-Os" flag) and linked against klibc, a minimal version of the C library written specifically for this purpose.

    - 참고 : https://en.wikipedia.org/wiki/Initial_ramdisk

     

     

    - Root File System

    " 부팅 시점에 루트 파일 시스템에 접근한다고 하는데, 왜 접근할까? 그리고, 루프 파일 시스템이 뭘까? 먼저, 루트 파일 시스템은 스펙은 없지만, 레퍼런스가 존재한다. `Filesystem Hierarchy Standard(FHS)`라는 레퍼런스가 있다. 루프 파일 시스템은 이 레퍼런스를 따르고 있다. 유닉스 계열 운영체제는 모두 파일 시스템 구조를 `FHS` 를 따른다.

    The `Filesystem Hierarchy Standard`(FHS) is a reference describing the conventions used for the layout of a UNIX system. It has been made popular by its use in Linux distributions, but it is used by other UNIX variants as well. It is maintained by the Linux Foundation. The latest version is 3.0, released on 3 June 2015.

    In the FHS, all files and directories appear under the root directory `/`, even if they are stored on different physical or virtual devices. Some of these directories only exist in a particular system if certain subsystems, such as the X Window System, are installed.

    - 참고 : https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard

     

    " 루트 파일 시스템이 따르는 FHS는 EXT, FAT 파일 시스템과 다르다. `FHS` 는 파일 시스템이 아니다. 주로 리눅스 배포판의 파일 및 폴더 구조를 어떤 식으로 보이게 할 것이냐에 초점이 되어있다. 즉, 레이아웃과 관련이 있다. 예를 들어, "`/` 아래에는 `/bin/`, `/boot/`, `/dev/` 등의 폴더가 있어야 한다" 같은 내용이 있다. 리눅스 배포판들은 FHS 가이드 라인을 지킴으로써, 폴더 및 파일 구조에 있어 호환성을 지킬 수 있다. 그런데, EXT, FAT 은 파일 시스템이다. 즉, 전체 레이아웃 보다는 파일 및 폴더의 바이너리 포맷을 정의하고, 파일이 디스크에 어떻게 저장되어야 하는가, 어떻게 참조해야 하는가 등을 정의한다. 

     

    " 루트 파일 시스템은 FHS와 관련이 있다. 즉, 윈도우에서 사용하는 FAT, NTFS 도 FHS 구조를 따를 수 있다. 본론으로 돌아가자. 리눅스 부팅 시점에 루트 파일 시스템이 반드시 필요할까? 반대로 생각해보자. 루트 파일 시스템이 없으면, 부팅을 못하나? 못하진 않지만, 힘들다. 파일 시스템과 FHS가 없으면, 부팅 시점에 `하드 디스크`나 `램 디스크`에 접근할 때, 부팅에 필요한 자료들에 모든 주소를 다 알고 있어야 한다. 심지어, 부팅 시점에 필요한 정보가 디스크에 있는지 메모리에 있는지 까지도 알고 있어야 한다. 왜? `initrd` 에서 `/sbin/init`을 실행시키고 싶다. 어떻게 할것인가? 저 프로그램의 엔트리 포인트 주소를 알고 있어야 한다. 그리고, 거기로 점프하면 된다. 말이 쉽지 코드로 하면 상당한 많은 양이 된다. 파일 시스템이 방금 말한 위의 복잡한 과정들을 몇 개의 간단한 함수로 제공해준다. 이제, 최초에 질문에 답해보자. 왜 부팅 시점에 루프 파일 시스템이 접근하는 걸까? 루프 파일 시스템을 이용해서 부팅에 필요한 데이터에 접근하기 위해서다. 굳이, 루프 파일 시스템을 사용하는 이유는? 루프 파일 시스템은 FHS 가이드를 따른다. 그리고, 유닉스 계열의 운영체제들은 자기네들 끼리의 호환성을 지키기 위해 FHS 가이드를 따른다.

     

     

     

    - Where is RAM ?

    " RAM 은 SoC 내부에 존재할까? 대개는 그렇지 않다. RAM 은 물리적으로 차지하는 공간이 많다. raspberry pi 3 B+ 모델같은 경우, RAM 이 SoC 에 포함되어 있는 것은 아니지만, 1GB RAM 을 사용하기 때문에(사이즈가 작음), 하나의 shield cap 안에 SoC 와 함께 들어갈 수 있다고 쳐도 raspberry pi 4 같은 경우는 RAM 이 4GB 라서 하나의 shield cap 안에 SoC 와 함께 가려질 크기가 절대 아니다.

    'Linux' 카테고리의 다른 글

    [리눅스] - !!${변수} 의미  (0) 2023.08.03
    [리눅스] 루프백 디바이스(/dev/loopXX)  (0) 2023.04.25
Designed by Tistory.