Linux RamDisk

Saudações.

Nesse artigo vou ensinar o conceito de disco em RAM e suas aplicações no kernel. Em seguida vou ensinar como usá-lo para conseguir I/O infinito (ou quase) para suas aplicações.

O objetivo é criar uma maneira de trabalhar I/O intenso em RAM sem onerar o disco, principalmente discos que perdem vida útil e ambientes cloud que cobram por I/O.

Pré-requisitos (constam em outros artigos aqui do blog):

  • Instalação do Linux (Debian);

1 – Conceito de RamDisk (RD)

O sistema operacional (Linux nesse caso) precisa de arquivos para ler e executar, a parte mais crítica do boot e da inicialização do Linux é saber onde estão os arquivos vitais para execução do kernel:

  • Os drivers (módulos do kernel, /lib/modules/);
  • O INIT (/sbin/init): o processo responsável por iniciar todos os programas (SystemD no caso do Debian).

O processo de boot do Linux tem a seguinte ordem:

  • O arquivo vmlinuz (kernel) é copiado para a RAM pelo bootloader;
  • O arquivo initrd.gz é copiado para a RAM pelo bootloader;
  • O bootloader passa a execução para o kernel;
  • O kernel descompacta o arquivo initrd.gz em um espaço de RAM – isso cria um RAMDISK (initrd = initramdisk = Initial RAM Disk = disco inicial na RAM);
  • O diretório / (rootfs) é montado nesse espaço da RAM do initrd;
  • O kernel procura o binário ELF /init ou /sbin/init e o executa, tornando-o responsável pelo PID 1;
  • O init presente dentro do initrd não é o processo 1 do SystemD, ele é responsável por garantir um ambiente mínimo para que o disco principal seja reconhecido. O disco principal pode estar num simples disco SATA ou sobre várias outras camadas de softwares, como:
    • LVM2 (comum em Ubuntu, Proxmox PVE);
    • RAID;
    • DM-Crypt (disco criptografado);
    • iSCSI (disco na rede);
  • Caberá ao init do initrd resolver todos os problemas para alcançar e montar o disco oficial do sistema e remontar a pasta / (rootfs) no disco do sistema, o que libera o disco inicial na RAM (initrd é apagado);
  • Com o disco do sistema pronto, o processo init antigo roda um exec sobre o software init presente no disco do sistema, que no caso do Debian é o SystemD.
  • Desse ponto em diante o SystemD assume e sobe todos os serviços;
  • Seu Linux estará rodando.

Esse processo moderno substituiu o antigo método de kernel inflado, onde para rodar o kernel linux o arquivo vmlinuz precisava ter todos os drivers para reconhecer o hardware.

Agora, o arquivo vmlinuz pode ser pequeno, ultra minimalista e deixar todos os drivers na pasta /lib/modules do sistema.

Para que seu Linux reconheça os hardware durante o boot, todos os drivers precisarão ser copiados para o arquivo initrd.gz, normalmente hospedado em /boot ou /boot/efi.

Destruir o initrd.gz ou deixar um driver de fora é a forma mais eficiente de impedir que o Linux seja carregado durante o boot!

2 – Motivação para uso de RamDisk

Embora o uso do RD no INIT seja temporário e efêmero para o Linux, ele pode ser usado intencionalmente como uma forma de trabalhar arquivos com alta intensidade de leitura e escrita sem sair do bloco CPU/Memória, onde a largura de banda é superior a 60 GB por segundo e a latência é de nano segundos.

Eu descobri esse recurso lendo a documentação do Kernel a 20 anos atras e passei a usá-lo de maneira simples com o RAMFS (sistema de arquivos na RAM).

Quando surgiu a necessidade de criar um FS na RAM com suporte a overlay, o RAMFS não suportou e migrei para o RAMDISK (dispositivo de blocos na RAM).

Minha principal surpresa foi ao usá-lo para compilação de programas: ao compilar o kernel ou sistemas em nodeJS (dependências infinitas) sobre um disco SATA, SAS ou NVME a demora de I/O para cada arquivo pequeno que precisava ser lido e escrito era muito grande.

Ao mover essa demanda de I/O temporário para o RAMDISK o sistema compilou 8x mais rápido. O gargalo passou a ser na CPU e na rede!

Uso comum do RD:

  • Treinamento: aprender e praticar tecnologias de disco e sistema de arquivos (LVM2, RAID, DM-CRYPT, SDS em geral, etc);
  • Criação de discos template para máquinas virtuais;
  • Rodar sistemas que exigem I/O rápido para performar bem (PostgreSQL, etc);
  • Compilar sistemas com grande quantidade de arquivos;
  • Armazenar e criar sistemas de containers;

É importante tomar cuidado, o RD não persiste nada. Ao desligar ou reiniciar o Linux TUDO É PERDIDO.

Se quiser persistir você deverá usar um sistema de sincronia e backup.

3 – Parâmetros e configurações de RamDisk

O RD é provido pelo módulo “brd” (Ram backed block device driver) do kernel Linux.

Bash
# Informações sobre o "brd":
modinfo brd;
   # filename:       /lib/modules/.../kernel/drivers/block/brd.ko.xz
   # alias:          rd
   # alias:          block-major-1-*
   # license:        GPL
   # description:    Ram backed block device driver
   # depends:        
   # intree:         Y
   # name:           brd
   # retpoline:      Y
   # vermagic:       6.12.57+deb13-amd64 SMP preempt mod_unload modversions 
   # parm:           rd_nr:Maximum number of brd devices (int)
   # parm:           rd_size:Size of each RAM disk in kbytes. (ulong)
   # parm:           max_part:Num Minors to reserve between devices (int)

Podemos ver os seguintes detalhes no comando acima:

  • Nome do módulo: brd ou rd, para uso no modprobe;
  • Parâmetros aceitos no setup do módulo:
    • rd_nr: número de discos de RAM a inicializar, padrão: 16 discos;
    • rd_size: tamanho em kbytes do disco de RAM, padrão 4 MB (4096 KB);
      • Esse valor pode ser guiado pela variável ramdisk_size do cmdline do kernel durante o boot;
    • max_part: quantidade de partições por RD, padrão 1 (não é um parâmetro relevante).

Características do RamDISK:

  • BlockDevice: dispositivos de blocos são criados nos endereços /dev/ramN, onde N é o numero de discos desejados ao subir o módulo (parâmetro rd_nr, padrão 16). Apenas subir o módulo resulta na criação de 16 discos RD: /dev/ram0 até /dev/ram15;
  • Static: o módulo brd não aceita reconfiguração a quente. Se você criou o RD com 1G e deseja aumentar para 2G, terá que descarregar o modulo apagando todos os RDs para então subir novamente o módulo com os novos parâmetros;
  • Overcommit: integra a natureza overcommit de RAM do Linux (Lazy/on-demand), ou seja, ao criar um RD de 2G isso não resulta em uso imediato desses 2G. Zero bytes serão alocados inicialmente e a medida que você grava blocos a memória é realmente alocada para eles.
  • OOM: erros de out-of-memory (acabou a memória) são resolvidos negando a escrita em novos blocos com o erro input/output error;
  • SWAP: o RD faz uso da memória virtual, o que significa que todo o pool de memória real (DDR) e memória virtual (swap) estarão disponíveis para alocação de blocos;
    • Configure o swappness para zero ou próximo de zero;
  • Overhead: infelizmente o Linux aplica o buffer de escrita (escrita atrasada) e o cache de leitura para cache de blocos mesmo em operações no RD, o que significa um uso duplo de memória (ele grava na memória do pipeline do buffer para então escrever no RD, que também está na memória, fazendo a escrita duas vezes).
    • Isso é possível de reduzir orientando softwares e sistemas de arquivos a fazer uso do parâmetro de I/O chamado odirect, que envia os bytes direto para o dispositivo de blocos sem passar pelo buffer;

4 – Criando um disco na RAM

4.1 – Instalar programas

Alguns comandos necessários para operar discos e partições:

Bash
# Instalar ferramentas:
apt -y install e2fsprogs;
apt -y install debugfs;
apt -y install util-linux;
apt -y install fdisk;
apt -y install fio;
apt -y install hdparm;
apt -y install ioping;

4.2 – Suporte no kernel e driver

É necessário carregar o módulo brd.

Bash
# Colocar "brd" nos modulos iniciais:
echo 'brd' > /etc/modules-load.d/brd.conf;

# Caso deseje carregar o brd ainda na fase de initrd:
mkdir -p /etc/initramfs;
echo 'brd rd_nr=1 rd_size=8388608' > /etc/initramfs/modules;

# Configurando os parâmetros iniciais do "brd" manualmente:
# - Tamanhos (especificar em KB):
#  Gigas | Kbytes
#------------------------
#    1 G | 1048576
#    2 G | 2097152
#    4 G | 4194304
#    8 G | 8388608
#   16 G | 16777216
#------------------------
# Criar apenas 1 disco de 16G:
(
    echo 'options brd rd_nr=1 max_part=3 rd_size=16777216';
) > /etc/modprobe.d/brd.conf;

# Carregar módulo:
modprobe brd;

Conferindo ambiente e criação do RamDISK:

Bash
# Verificar informacoes no kernel:
grep ram /proc/devices;
grep ram /proc/diskstats;
grep brd /proc/modules;
grep ram /proc/mounts;
grep ram /proc/partitions;

# Conferir dispositivo de blocos /dev/ram0
fdisk -l /dev/ram0;
    # Disk /dev/ram0: 16 GiB, 17179869184 bytes, 33554432 sectors
    # Units: sectors of 1 * 512 = 512 bytes
    # Sector size (logical/physical): 512 bytes / 4096 bytes
    # I/O size (minimum/optimal): 4096 bytes / 4096 bytes

4.3 – Testar velocidade de I/O na RAM

Testando capacidade de escrita

Bash
# Escrita com zeros:
# - Escrever 512 blocos de 4M (2Gbytes)
dd if=/dev/zero of=/dev/ram0 count=512 bs=4194304 status=progress;
    # 2130706432 bytes (2.1 GB, 2.0 GiB) copied, 1 s, 2.1 GB/s
    # 512+0 records in
    # 512+0 records out
    # 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 2.76339 s, 777 MB/s

# - Escrever 1024 blocos de 2M (2Gbytes)
dd if=/dev/zero of=/dev/ram0 count=1024 bs=2097152 status=progress;
    # 1759510528 bytes (1.8 GB, 1.6 GiB) copied, 1 s, 1.8 GB/s
    # 1024+0 records in
    # 1024+0 records out
    # 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 2.85374 s, 753 MB/s

# - Escrever 2048 blocos de 1M (2Gbytes)
dd if=/dev/zero of=/dev/ram0 count=2048 bs=1048576 status=progress;
    # 2107637760 bytes (2.1 GB, 2.0 GiB) copied, 1 s, 2.1 GB/s
    # 2048+0 records in
    # 2048+0 records out
    # 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 2.64519 s, 812 MB/s

Testar capacidade de escrita sem buffer do kernel

Bash
# Escrita sem buffer do kernel (mais rápido):
# - Escrever 512 blocos de 4M (2Gbytes)
dd if=/dev/zero of=/dev/ram0 count=512 bs=4194304 oflag=direct status=progress;
    # 512+0 records in
    # 512+0 records out
    # 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 0.574772 s, 3.7 GB/s

# - Escrever 1024 blocos de 2M (2Gbytes)
dd if=/dev/zero of=/dev/ram0 count=1024 bs=2097152 oflag=direct status=progress;
    # 1024+0 records in
    # 1024+0 records out
    # 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 0.560641 s, 3.8 GB/s

# - Escrever 2048 blocos de 1M (2Gbytes)
dd if=/dev/zero of=/dev/ram0 count=2048 bs=1048576 oflag=direct status=progress;
    # 2048+0 records in
    # 2048+0 records out
    # 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 0.558049 s, 3.8 GB/s

Podemos observar velocidades muito superiores quando o buffer de escrita do kernel é ignorado (oflag=direct) e os blocos vão direto para o dispositivos de blocos /dev/ram0.

Teste de capacidade de leitura

Bash
# Teste de leitura
# - Ler 512 blocos de 4M (2Gbytes)
dd if=/dev/ram0 of=/dev/null count=512 bs=4194304 status=progress;
    # 2134900736 bytes (2.1 GB, 2.0 GiB) copied, 1 s, 2.1 GB/s
    # 512+0 records in
    # 512+0 records out
    # 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 1.19463 s, 1.8 GB/s

# - Ler 1024 blocos de 2M (2Gbytes)
dd if=/dev/ram0 of=/dev/null count=1024 bs=2097152 status=progress;
    # 1024+0 records in
    # 1024+0 records out
    # 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 1.1758 s, 1.8 GB/s

# - Ler 2048 blocos de 1M (2Gbytes)
dd if=/dev/ram0 of=/dev/null count=2048 bs=1048576 status=progress;
    # 2126512128 bytes (2.1 GB, 2.0 GiB) copied, 1 s, 2.1 GB/s
    # 2048+0 records in
    # 2048+0 records out
    # 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 1.19769 s, 1.8 GB/s

Teste de IOPs

Os testes de IOPs ajudam a medir quantas operações de IO (leitura+escrita) o dispositivo é capaz de suportar.

Bash
# Teste sequencial de leitura
fio \
    --name=seq-read \
    --rw=read \
    --bs=1M \
    --size=1G \
    --direct=1 \
    --filename=/dev/ram0;

# Teste sequencial de escrita
fio \
    --name=seq-write \
    --rw=write \
    --bs=1M \
    --size=1G \
    --direct=1 \
    --filename=/dev/ram0;

# Teste aleatório 4K (IOPS)
fio \
    --name=rand-4k \
    --rw=randread \
    --bs=4K \
    --size=1G \
    --direct=1 \
    --filename=/dev/ram0;

# Teste misto leitura/escrita
fio \
    --name=mixed \
    --rw=randrw \
    --rwmixread=70 \
    --bs=4K \
    --size=1G \
    --direct=1 \
    --filename=/dev/ram0;

# Teste completo com múltiplos parâmetros
fio \
    --name=ramdisk-test \
    --filename=/dev/ram0 \
    --size=2G \
    --direct=1 \
    --rw=randrw \
    --bs=4k \
    --ioengine=libaio \
    --iodepth=32 \
    --numjobs=4 \
    --runtime=60 \
    --group_reporting;

Testes com HDPARM

Bash
# Teste de leitura
hdparm -t /dev/ram0;
    # /dev/ram0:
    # Timing buffered disk reads: 6184 MB in  3.00 seconds = 2061.00 MB/sec

hdparm -T /dev/ram0;
    # /dev/ram0:
    #  Timing cached reads:   17848 MB in  1.98 seconds = 8994.84 MB/sec

# Teste com cache
hdparm --direct -t /dev/ram0;
    # /dev/ram0:
    #  Timing O_DIRECT disk reads: 16384 MB in  1.44 seconds = 11367.67 MB/sec

Testes com IOPING

Bash
# Teste de latência
ioping -c 10 /dev/ram0;
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=1 time=17.7 us (warmup)
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=2 time=33.6 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=3 time=27.9 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=4 time=31.7 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=5 time=31.8 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=6 time=28.8 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=7 time=31.9 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=8 time=31.9 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=9 time=29.9 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=10 time=31.5 us

# Teste de taxa de transferência
ioping -R /dev/ram0;
    # --- /dev/ram0 (block device 16 GiB) ioping statistics ---
    # 689.6 k requests completed in 2.02 s, 2.63 GiB read, 341.6 k iops, 1.30 GiB/s
    # generated 689.6 k requests in 3.00 s, 2.63 GiB, 229.9 k iops, 897.9 MiB/s
    # min/avg/max/mdev = 2.43 us / 2.93 us / 1.19 ms / 4.94 us

# Teste com I/O direto
ioping -c 10 -D /dev/ram0;
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=1 time=13.0 us (warmup)
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=2 time=21.1 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=3 time=21.3 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=4 time=25.7 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=5 time=21.3 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=6 time=20.4 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=7 time=20.9 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=8 time=18.5 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=9 time=21.2 us
    # 4 KiB <<< /dev/ram0 (block device 16 GiB): request=10 time=25.9 us
    # 
    # --- /dev/ram0 (block device 16 GiB) ioping statistics ---
    # 9 requests completed in 196.3 us, 36 KiB read, 45.8 k iops, 179.1 MiB/s
    # generated 10 requests in 9.00 s, 40 KiB, 1 iops, 4.44 KiB/s
    # min/avg/max/mdev = 18.5 us / 21.8 us / 25.9 us / 2.28 us

4.4 – Particionando o RamDISK

Não é necessário particioná-lo, embora recomendável. O bd /dev/ram0 pode ser formatado diretamente e montado sem problemas.

Particionar é o comportamento padrão para que o RD possa ser convertido em disco para máquinas virtuais (RAW, QCOW2, VMDK). Nada impede o uso para qualquer outra finalidade e ainda deixa o disco de RAM em estado de arte:

Bash
# Criar partição padrão GPT no RAMDISK
# - Tipos de particoes
GUID_BIOS="21686148-6449-6E6F-744E-656564454649";  # bios boot
GUID_ESP="C12A7328-F81F-11D2-BA4B-00A0C93EC93B";   # esp efi/uefi
GUID_LINUX="0FC63DAF-8483-4772-8E79-3D69D8477DE4"; # linux (root)

# Apagar primeiros setores (capricho):
dd if=/dev/zero of=/dev/ram0 bs=512 count=2048;

# 1. Particionar GPT:
parted /dev/ram0 --script -- mklabel gpt;

# 2. Criar partição 1 - BIOS boot (setores 34 a 2047)
parted /dev/ram0 --script -- mkpart primary 34s 2047s;
parted /dev/ram0 --script -- type 1 "$GUID_BIOS";

# 3. Criar partição 2 - EFI System (setores 2048 a 2099199 = 1GB)
parted /dev/ram0 --script -- mkpart primary fat32 2048s 2099199s;
parted /dev/ram0 --script -- type 2 "$GUID_ESP";

# 4. Criar partição 3 - Linux LVM (setores 2099200 a 20971486 = 9.5GB)
parted /dev/ram0 --script -- mkpart primary 2099200s '100%';
parted /dev/ram0 --script -- type 3 "$GUID_LINUX";

# 5. Verificar o resultado
parted /dev/ram0 --script -- print;
    # Model: RAM Drive (brd)
    # Disk /dev/ram0: 17.2GB
    # Sector size (logical/physical): 512B/4096B
    # Partition Table: gpt
    # Disk Flags: 
    # 
    # Number  Start   End     Size    File system  Name     Flags
    #  1      17.4kB  1049kB  1031kB               primary  bios_grub
    #  2      1049kB  1075MB  1074MB               primary  boot, esp
    #  3      1075MB  17.2GB  16.1GB               primary

Com esse esquema de partição, vamos operar no /dev/ram0p3 (tipo Linux):

4.5 – Formatando partição na RAM

O melhor sistema de arquivos para uso em blocos na RAM é o Ext2 pois ele não tem journal (estágio de escrita em área de trabalho do disco para em seguida gravar na localização final, dupla escrita).

Como ele é um sistema muito antigo, a melhor opção é o Ext4 sem journal, motivos:

  • Ext4 sem journal se comporta como o ext2;
  • É compatível com tudo que é moderno;
  • O Ext4 pode ser convertido para suportar journal caso deseje exportar o RD para uso externo (disco de VM);

Recursos dispensáveis no ext4 pois são desnecessários sobre o RD: has_journal, metadata_csum, resize_inode, 64bit, huge_file, dir_nlink, bigalloc, quota, encrypt.

Assim, para RD que será exportado para disco virtual, apenas alternar o journal, já para RD temporário podemos depenar o Ext4 em busca da performance máxima!

Criando sistema de arquivos Ext4 exportável:

Bash
# Formatar com ext4 sem journal:
echo 'y' | mkfs.ext4 -O '^has_journal' /dev/ram0p3;
    # mke2fs 1.47.2 (1-Jan-2025)
    # Discarding device blocks: done                            
    # Creating filesystem with 3931648 4k blocks and 983040 inodes
    # Filesystem UUID: 4caf42a2-016c-4666-b66f-d1dd92858c70
    # Superblock backups stored on blocks: 
    #     32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208
    # 
    # Allocating group tables: done                            
    # Writing inode tables: done                            
    # Writing superblocks and filesystem accounting information: done   

# Ativar journal:
tune2fs -O 'has_journal' /dev/ram0p3;

# Desativar journal:
tune2fs -O '^has_journal' /dev/ram0p3;

# Conferindo features do ext4:
tune2fs -l /dev/ram0p3 | grep features;
tune2fs -l /dev/ram0p3 | grep has_journal && echo "# Journal ATIVADO";
tune2fs -l /dev/ram0p3 | grep has_journal || echo "# Journal DESATIVADO";

4.6 – Montando sistema de arquivos

Ao montar um FS sobre a RAM não precisamos nos preocupar com muito detalhes dado a natureza fulgaz e dispensável dos dados. Opções de montagem para máxima performance:

  • noatime e relatime: não atualizar inode quando o arquivo for acessado;
  • nodiratime: não atualizar inode quando o diretório for acessado;
  • nobarrier: destivar barreiras de escrita;
  • commit=3600: fazer commit longo (sync), em vez do padrão 5s;
  • data=writeback: modo journal rápido, caso tenha jornal;
  • dioread_nolock: desativar looks de paralelismo;
  • nodiscard: não fazer trim (esse recurso só é util em SSD);
  • nodelalloc: desativa delayed allocation (menos overhead);
  • noinit_itable: não inicializa tabela de inodes em background;
  • dax: Direct Access Mode, desativa completamente o page cache mas pode não estar disponível em todos os sistemas;

Montando:

Bash
# Pasta para montagem: /ram
RAMDIR="/ram";
mkdir -p $RAMDIR;

# Montar em modo ultra-rapido:
RAMDEV="/dev/ram0p3";

# Desmontar caso esteja montado:
umount /ram 2>/dev/null;

# Montar modo simples:
mount \
    -t ext4 \
    -o 'noatime,nodiratime,relatime,nodiscard' \
    $RAMDEV \
    $RAMDIR;

# Tentar ativar opcoes na montagem:
mount -o remount,rw,noatime        $RAMDIR;
mount -o remount,rw,nodiratime     $RAMDIR;
mount -o remount,rw,relatime       $RAMDIR;
mount -o remount,rw,nobarrier      $RAMDIR;
mount -o remount,rw,commit=3600    $RAMDIR;
mount -o remount,rw,dioread_nolock $RAMDIR;
mount -o remount,rw,nodelalloc     $RAMDIR;
mount -o remount,rw,noinit_itable  $RAMDIR;
mount -o remount,rw,nodiscard      $RAMDIR;
mount -o remount,rw,dax            $RAMDIR;

# Conferindo atributos de montagem:
grep /ram /proc/mounts;

Montagem concluída. A pasta /ram já pode ser usada para trabalhar com arquivos.

4.6 – Testando velocidade do sistema de arquivos na RAM

Vamos verificar qual a capacidade de escrita e leitura de arquivos. É bom comparar os testes no RD e comparar com os testes no disco para ver se há vantagem significante para seu ambiente (se você tem NVME de ultima geração).

Bash
# Escrita sem buffer do kernel (mais rápido):
# - Escrever 512 blocos de 4M (2Gbytes)
dd if=/dev/zero of=/ram/dd.bin count=512 bs=4194304 oflag=direct status=progress;
    # 512+0 records in
    # 512+0 records out
    # 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 0.656324 s, 3.3 GB/s

# - Escrever 1024 blocos de 2M (2Gbytes)
dd if=/dev/zero of=/ram/dd.bin count=1024 bs=2097152 oflag=direct status=progress;
    # 1024+0 records in
    # 1024+0 records out
    # 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 0.659488 s, 3.3 GB/s

# - Escrever 2048 blocos de 1M (2Gbytes)
dd if=/dev/zero of=/ram/dd.bin count=2048 bs=1048576 oflag=direct status=progress;
    # 2048+0 records in
    # 2048+0 records out
    # 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 0.639682 s, 3.4 GB/s

# - Apagar arquivo gerado no teste:
rm -f /ram/dd.bin;

# Teste sequencial de leitura
fio \
    --name=seq-read \
    --rw=read \
    --bs=1M \
    --size=1G \
    --direct=1 \
    --filename=/ram/fio-test.bin;

# Teste sequencial de escrita
fio \
    --name=seq-write \
    --rw=write \
    --bs=1M \
    --size=1G \
    --direct=1 \
    --filename=/ram/fio-test.bin;

# Teste aleatório 4K (IOPS)
fio \
    --name=rand-4k \
    --rw=randread \
    --bs=4K \
    --size=1G \
    --direct=1 \
    --filename=/ram/fio-test.bin;

# Teste misto leitura/escrita
fio \
    --name=mixed \
    --rw=randrw \
    --rwmixread=70 \
    --bs=4K \
    --size=1G \
    --direct=1 \
    --filename=/ram/fio-test.bin;

# Teste completo com múltiplos parâmetros
fio \
    --name=ramdisk-test \
    --filename=/ram/fio-test.bin \
    --size=2G \
    --direct=1 \
    --rw=randrw \
    --bs=4k \
    --ioengine=libaio \
    --iodepth=32 \
    --numjobs=4 \
    --runtime=60 \
    --group_reporting;

# - Apagar arquivo gerado no teste:
rm -f /ram/fio-test.bin;

5 – Serviço RamDISK

Agora vem a parte boa: transformar a montagem do RamDisk em um serviço a ser executado durante o boot do Debian e criar scripts para gerir eventos para que outros sistemas possam colocar, tirar e fazer backup de arquivos sendo trabalhados na RAM.

Variáveis definidas em /etc/default/ramdisk:

  • RAMDISK_SIZE: tamanho em gigabytes do RD;
  • RAMDISK_MOUNT: montar RD em diretório? Valores: yes (padrão) ou no;
  • RAMDISK_FS: tipo de sistema de arquivos a formatar, padrão ext4;
  • RAMDISK_DIR: diretório para montar RD, se montagem ativa, padrão /ram;
  • RAMDISK_OPTIONS: opções do ponto de montagem, por padrão tentar todas as opções aceleradas de montagem;
Bash
# Criar arquivos com variáveis de ambiente padrão:
(
    echo 'RAMDISK_SIZE=8';
    echo 'RAMDISK_MOUNT=yes';
    echo 'RAMDISK_FS=ext4';
    echo 'RAMDISK_DIR=/ram';
    echo 'RAMDISK_OPTIONS=""';
) > /etc/default/ramdisk;

Arquivos e diretórios do serviço RamDisk:

  • /etc/systemd/system/ramdisk.service: configuração do serviço no SystemD;
  • /etc/default/ramdisk: manifesto de variáveis de ambiente;
  • /usr/local/sbin/ramdisk-env: script para gerar variáveis de ambiente;
  • /usr/local/sbin/ramdisk-run-scripts: script para rodar eventos;
  • /usr/local/sbin/ramdisk-part-gpt: script para particionar RD;
  • /usr/local/sbin/ramdisk-fs-setup: script para formatar e montar FS;
  • /usr/local/sbin/ramdisk-start: script para iniciar RD;
  • /usr/local/sbin/ramdisk-stop: script para desligar RD;

Diretórios para scripts que desejam se integrar ao uso do RamDisk:

  • /etc/ramdisk-pre-up.d: Pasta com scripts a executar antes do RD ser construído (/dev/ram0 já existe nesse ponto mas não foi particionado);
  • /etc/ramdisk-up.d: Pasta com scripts a executar quando o RD estiver pronto para uso (/dev/ram0 montado no diretório). Momento ideal para instalar arquivos e programas no RD (SETUP);
  • /etc/ramdisk-pre-down.d: Pasta com scripts a executar antes do RD ser desativado (/dev/ram0 ainda montado no diretório). Momento ideal para BACKUP;
  • /etc/ramdisk-down.d: Pasta com scripts a serem executados após o RD ser desativado;

Criando serviço:

Bash
# Criar diretorios de scripts:
mkdir -p /etc/ramdisk-pre-up.d;   chmod 755 /etc/ramdisk-pre-up.d;
mkdir -p /etc/ramdisk-up.d;       chmod 755 /etc/ramdisk-up.d;
mkdir -p /etc/ramdisk-pre-down.d; chmod 755 /etc/ramdisk-pre-down.d;
mkdir -p /etc/ramdisk-down.d;     chmod 755 /etc/ramdisk-down.d;

# Script para obter variáveis de ambiente com valores default
(
    echo '#!/bin/bash';
    echo;
    echo '[ -f /etc/default/ramdisk ] && . /etc/default/ramdisk;';
    echo;
    echo '# Carregar defautls';
    echo '[ "x$RAMDISK_SIZE"    = "x" ] && RAMDISK_SIZE=8;';
    echo '[ "x$RAMDISK_MOUNT"   = "x" ] && RAMDISK_MOUNT=yes;';
    echo '[ "x$RAMDISK_FS"      = "x" ] && RAMDISK_FS=ext4;';
    echo '[ "x$RAMDISK_DIR"     = "x" ] && RAMDISK_DIR="/ram";';
    echo '[ "x$RAMDISK_OPTIONS" = "x" ] && RAMDISK_OPTIONS="fast";';
    echo 'RAMDISK_BSIZE=$((RAMDISK_SIZE * 1024 * 1024));';
    echo 'RAMDISK_KARG="options brd rd_nr=1 max_part=3 rd_size=$RAMDISK_BSIZE";';
    echo;
    echo '# Exportar para ambiente';
    echo 'export RAMDISK_SIZE="$RAMDISK_SIZE";';
    echo 'export RAMDISK_MOUNT="$RAMDISK_MOUNT";';
    echo 'export RAMDISK_DIR="$RAMDISK_DIR";';
    echo 'export RAMDISK_FS="$RAMDISK_FS";';
    echo 'export RAMDISK_OPTIONS="$RAMDISK_OPTIONS";';
    echo 'export RAMDISK_BSIZE="$RAMDISK_BSIZE";';
    echo 'export RAMDISK_KARG="$RAMDISK_KARG";';
    echo;
) > /usr/local/sbin/ramdisk-env;
chmod +x /usr/local/sbin/ramdisk-env;

# Script para executar os scripts do evento
(
    echo '#!/bin/bash';
    echo;
    echo '. /usr/local/sbin/ramdisk-env;';
    echo;
    echo 'EVDIR="/etc/ramdisk-$1.d";';
    echo '[ -d "$EVDIR" ] || exit 1;';
    echo 'cd "$EVDIR" || exit 2;';
    echo;
    echo 'for script in $EVDIR/*; do';
    echo '    if [ -x "$script" ]; then';
    echo '        "$script" || true;';
    echo '    fi;';
    echo 'done;';
    echo;
) > /usr/local/sbin/ramdisk-run-scripts;
chmod +x /usr/local/sbin/ramdisk-run-scripts;

# Script para particionar ramdisk
(
    echo '#!/bin/bash';
    echo;
    echo '# Criar partição padrão GPT no RAMDISK';
    echo '# - Tipos de particoes';
    echo 'GUID_BIOS="21686148-6449-6E6F-744E-656564454649";  # bios boot';
    echo 'GUID_ESP="C12A7328-F81F-11D2-BA4B-00A0C93EC93B";   # esp efi/uefi';
    echo 'GUID_LINUX="0FC63DAF-8483-4772-8E79-3D69D8477DE4"; # linux (root)';
    echo;
    echo '# Apagar primeiros setores (capricho):';
    echo 'dd if=/dev/zero of=/dev/ram0 bs=512 count=2048 oflag=direct 2>/dev/null;';
    echo;
    echo '# 1. Particionar GPT:';
    echo 'parted /dev/ram0 --script -- mklabel gpt 2>/dev/null;';
    echo;
    echo '# 2. Criar partição 1 - BIOS boot (setores 34 a 2047)';
    echo 'parted /dev/ram0 --script -- mkpart primary 34s 2047s 2>/dev/null;';
    echo 'parted /dev/ram0 --script -- type 1 "$GUID_BIOS";';
    echo;
    echo '# 3. Criar partição 2 - EFI System (setores 2048 a 2099199 = 1GB)';
    echo 'parted /dev/ram0 --script -- mkpart primary fat32 2048s 2099199s;';
    echo 'parted /dev/ram0 --script -- type 2 "$GUID_ESP";';
    echo;
    echo '# 4. Criar partição 3 - Linux LVM (setores 2099200 a 20971486 = 9.5GB)';
    echo 'parted /dev/ram0 --script -- mkpart primary 2099200s '100%';';
    echo 'parted /dev/ram0 --script -- type 3 "$GUID_LINUX";';
    echo;
) > /usr/local/sbin/ramdisk-part-gpt;
chmod +x /usr/local/sbin/ramdisk-part-gpt;

# Fazer setup do sistema de arquivos no ramdisk
(
    echo '#!/bin/bash';
    echo;
    echo '. /usr/local/sbin/ramdisk-env;';
    echo;
    echo '# Dispositivo de blocos alvo';
    echo 'BDEV="/dev/ram0p3";';
    echo '[ -e "$BDEV" ] || { echo "$BDEV not found"; BDEV="/dev/ram0"; };';
    echo '[ -e "$BDEV" ] || { echo "$BDEV not found"; exit 11; };';
    echo;
    echo '# Montagem desativa, deixar normal';
    echo '[ "$RAMDISK_MOUNT" = "yes" ] || { echo "skip mount"; exit 0; };';
    echo;
    echo '# Programa formatador:';
    echo '[ -x "/usr/sbin/mkfs.$RAMDISK_FS" ] || {';
    echo '    echo "/usr/sbin/mkfs.$RAMDISK_FS not found";';
    echo '    exit 12;';
    echo '};';
    echo;
    echo '# Opcoes de formatacao';
    echo 'MKFS_OPTION="";';
    echo 'if [ "$RAMDISK_OPTIONS" = "fast" -a "$RAMDISK_FS" = "ext4" ]; then';
    echo '    MKFS_OPTION="-O ^has_journal";';
    echo 'fi;';
    echo;
    echo '# Criar diretorio';
    echo '[ -d "$RAMDISK_DIR" ] || mkdir -p "$RAMDISK_DIR";';
    echo;
    echo '# Se ja estiver montado, ignorar, nao montar overlay';
    echo 'egrep -q "$RAMDISK_DIR" /proc/mounts && {';
    echo '    echo "pre-mounted $RAMDISK_DIR";';
    echo '    exit 0;';
    echo '};';
    echo;
    echo '# Formatar:';
    echo 'echo "Format: mkfs.$RAMDISK_FS $MKFS_OPTION $BDEV";';
    echo 'echo "y" | /usr/sbin/mkfs.$RAMDISK_FS $MKFS_OPTION $BDEV || {';
    echo '    echo "Format: failure mkfs.$RAMDISK_FS $MKFS_OPTION $BDEV";';
    echo '    # Tentar formatar novamente:';
    echo '    echo "Format: mkfs.$RAMDISK_FS $BDEV";';
    echo '    echo "y" | /usr/sbin/mkfs.$RAMDISK_FS $BDEV || {';
    echo '        echo "Format: failure mkfs.$RAMDISK_FS $BDEV";';
    echo '        exit 13;';
    echo '    };';
    echo '};';
    echo;
    echo '# Montar RD no diretorio';
    echo 'WORKS=0;';
    echo 'MOUNT="mount -t ext4 $BDEV $RAMDISK_DIR";';
    echo 'echo "Mounting: $BDEV on $RAMDISK_DIR";';
    echo 'if [ "$RAMDISK_OPTIONS" = "fast" -a "$RAMDISK_FS" = "ext4" ]; then';
    echo '    # Modo optimizado';
    echo '    OPT_BASIC="noatime,nodiratime,relatime";';
    echo '    OPT_TRY01="nodiscard,noinit_itable,commit=3600,nobarrier";';
    echo '    OPT_TRY02="nodiscard,noinit_itable,commit=3600";';
    echo '    OPT_TRY03="nodiscard,noinit_itable,nobarrier";';
    echo '    OPT_TRY04="nodiscard,noinit_itable,nodelalloc";';
    echo '    OPT_TRY05="nodiscard,nodelalloc";';
    echo '    OPT_TRY06="nodiscard,noinit_itable";';
    echo '    OPT_TRY07="nodiscard,nobarrier";';
    echo '    OPT_TRY08="nodiscard";';
    echo '    # Tentar opcoes de montagem';
    echo '    [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY01 2>/dev/null;';
    echo '    [ "$?" = "0" ] && WORKS="1";';
    echo '    [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY01 2>/dev/null;';
    echo '    [ "$?" = "0" ] && WORKS="2";';
    echo '    [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY02 2>/dev/null;';
    echo '    [ "$?" = "0" ] && WORKS="3";';
    echo '    [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY03 2>/dev/null;';
    echo '    [ "$?" = "0" ] && WORKS="4";';
    echo '    [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY04 2>/dev/null;';
    echo '    [ "$?" = "0" ] && WORKS="5";';
    echo '    [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY05 2>/dev/null;';
    echo '    [ "$?" = "0" ] && WORKS="6";';
    echo '    [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY06 2>/dev/null;';
    echo '    [ "$?" = "0" ] && WORKS="7";';
    echo '    [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY07 2>/dev/null;';
    echo '    [ "$?" = "0" ] && WORKS="8";';
    echo '    [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY08 2>/dev/null;';
    echo '    [ "$?" = "0" ] && WORKS="9";';
    echo '    [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC 2>/dev/null;';
    echo '    [ "$?" = "0" ] && WORKS="10";';
    echo '    [ "$WORKS" = "0" ] && $MOUNT 2>/dev/null;';
    echo '    [ "$?" = "0" ] && WORKS="11";';
    echo;
    echo 'else';
    echo '    # Modo normal';
    echo '    [ "$WORKS" = "0" ] && $MOUNT 2>/dev/null;';
    echo '    [ "$?" = "0" ] && WORKS="11";';
    echo 'fi;';
    echo;
    echo '# Ajuste de permissao';
    echo 'chmod 1777 "$RAMDISK_DIR"';
    echo;
    echo '# Testar se a montagem base funcionou';
    echo 'if [ "$WORKS" = "0" ]; then';
    echo '    echo "Mounting: failure on $MOUNT";';
    echo '    exit 14;';
    echo 'fi;';
    echo;
) > /usr/local/sbin/ramdisk-fs-setup;
chmod +x /usr/local/sbin/ramdisk-fs-setup;

# Script para iniciar ramdisk
(
    echo '#!/bin/bash';
    echo;
    echo '. /usr/local/sbin/ramdisk-env;';
    echo;
    echo '# Setup';
    echo '[ -f /etc/modules-load.d/brd.conf ] || {';
    echo '    echo "brd" > /etc/modules-load.d/brd.conf;';
    echo '};';
    echo 'CURRENT_KARG=$(head -1 /etc/modprobe.d/brd.conf);';
    echo '[ "$RAMDISK_KARG" = "$CURRENT_KARG" ] || {';
    echo '    echo "$RAMDISK_KARG" > /etc/modprobe.d/brd.conf;';
    echo '};';
    echo;
    echo '# Carregar modulo';
    echo '_modprobe(){';
    echo '    echo "kernel: load brd";';
    echo '    modprobe brd;';
    echo '};';
    echo 'egrep -q '^brd' /proc/modules || _modprobe;';
    echo;
    echo '# Detectar RD';
    echo 'BDFOUND=0;';
    echo 'for i in 1 2 3 4 5 6 7 8 9 10; do';
    echo '    [ -e /dev/ram0 ] && { BDFOUND=1; break; };';
    echo '    echo "kernel: wait brd";';
    echo '    sleep 0.1;';
    echo 'done;';
    echo 'if [ "$BDFOUND" = "0" ]; then';
    echo '    echo "kernel: RamDisk /dev/ram0 not found";';
    echo '    exit 11;';
    echo 'fi;';
    echo;
    echo '# Executar pre-up scripts';
    echo '/usr/local/sbin/ramdisk-run-scripts "pre-up";';
    echo;
    echo '# Particionar ramdisk';
    echo '/usr/local/sbin/ramdisk-part-gpt;';
    echo;
    echo '# Formatar e montar';
    echo '/usr/local/sbin/ramdisk-fs-setup || exit 11;';
    echo;
    echo '# Executar up scripts';
    echo '/usr/local/sbin/ramdisk-run-scripts "up";';
    echo;
) > /usr/local/sbin/ramdisk-start;
chmod +x /usr/local/sbin/ramdisk-start;

# Script para parar ramdisk
(
    echo '#!/bin/bash';
    echo;
    echo '. /usr/local/sbin/ramdisk-env;';
    echo;
    echo '# Desmontar apenas se estiver montado';
    echo 'egrep -q "$RAMDISK_DIR" /proc/mounts && {';
    echo '    # Executar pre-down scripts';
    echo '    echo "scripts: pre-down";';
    echo '    /usr/local/sbin/ramdisk-run-scripts "pre-down";';
    echo;
    echo '    # Desmontar';
    echo '    echo "umount $RAMDISK_DIR";';
    echo '    sync;';
    echo '    umount /dev/ram0p3 2>/dev/null; sn="$?";';
    echo '    [ "$sn" = "0" ] || {';
    echo '        for t in 1 2 3; do';
    echo '            echo "umount $RAMDISK_DIR - try $t";';
    echo '            umount    /dev/ram0p3 2>/dev/null && break;';
    echo '            umount -f /dev/ram0p3 2>/dev/null && break;';
    echo '            sleep 0.1;';
    echo '        done;';
    echo '    };';
    echo;
    echo '    # Executar down scripts';
    echo '    echo "scripts: down";';
    echo '    /usr/local/sbin/ramdisk-run-scripts "down";';
    echo;
    echo '};';
    echo;
    echo '# Descarregar ramdisk do kernel para novos parametros';
    echo 'CURRENT_KARG=$(head -1 /etc/modprobe.d/brd.conf);';
    echo '[ "$RAMDISK_KARG" = "$CURRENT_KARG" ] || {';
    echo '    egrep -q "brd" /proc/modules && {';
    echo '        echo "kernel: unload brd";';
    echo '        rmmod brd 2>/dev/null;';
    echo '    };';
    echo '};';
    echo;
) > /usr/local/sbin/ramdisk-stop;
chmod +x /usr/local/sbin/ramdisk-stop;

# Unity de servico no SystemD
(
    echo '[Unit]';
    echo 'Description=RAM Disk Service';
    echo 'DefaultDependencies=no';
    echo 'After=local-fs-pre.target';
    echo 'Before=local-fs.target';
    echo 'Before=NetworkManager.service';
    echo 'Before=network-pre.target';
    echo '';
    echo '[Service]';
    echo 'Type=oneshot';
    echo 'RemainAfterExit=yes';
    echo 'EnvironmentFile=/etc/default/ramdisk';
    echo 'ExecStart=/usr/local/sbin/ramdisk-start';
    echo 'ExecStop=/usr/local/sbin/ramdisk-stop';
    echo 'TimeoutSec=60';
    echo '';
    echo '[Install]';
    echo 'WantedBy=local-fs.target';
) > /etc/systemd/system/ramdisk.service;

# Atualizar systemd:
systemctl daemon-reload;

Após instalar com o script acima, edite o /etc/default/ramdisk e preencha suas opções personalizadas, se desejar.

Iniciando RamDisk:

Bash
# Ativar o servico:
systemctl enable ramdisk.service;
systemctl start  ramdisk.service;
systemctl status ramdisk.service;

# Parar ramdisk:
#- systemctl stop  ramdisk.service;

Tudo pronto, agora você pode abusar de operações de alto I/O, e sincronizar o backup com outros sistemas para não perder dados!

Terminamos por hoje!

Patrick Brandão, patrickbrandao@gmail.com