Linux RamDisk

há 3 dias 3
ANUNCIE AQUI

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

Ler artigo completo