2021/08/27

Notas del curso Linux Device Drivers

En el marco de silenciar el cooler de una Raspberry Pi, vengo capacitándome tal como cuento en el curso anterior de linux "avanzado". Este, Linux Device Drivers , del mismo autor es de... drivers.

Los drivers en forma de módulos son la manera de no tener que estar recompilando y rebooteando al sistema operativo ante agregados y cambios.

El curso me ha servido mucho para comprender la arquitectura, estaba teniendo una idea equivocada del alcance de las responsabilidades del driver, cuando lo haga concretamente entraré en más detalles.

La parte de gestión está un poco repetida respecto al curso anterior, pero se vé con mayor profundidad, por ejemplo la parte de configuración para modprobe, el blacklisting, que en /sys/module/$MODULE/parameters están los parametros y serán legibles o incluso escribibles según los permisos de module_param().

Respecto a escribir módulos muy básicos el curso está ok, pero hay muchos temas muy interesantes que sólo menciona de modo muy superficial, por ejemplo:

  • debugfs
  • tracing
  • crash

Amerita cada uno de estos temas un buen tiempo, hoy no va a ser...

Me hubiera gustado que en el ejercicio de char devices, que es un dispositivo donde guardar un string, como parte del enunciado hubiera pedido que el tamaño del buffer se determinara en el momento de la carga en lugar del momento de compilación, pero eso hubiese significado usar memoria dinámica.

Tambien que se pudiera lidiar con >>, esto es, poder agregar información, el comportamiento actual es:

$ echo "hola" > /dev/charDevice

$ cat /dev/charDevice

hola

$ echo "chau" >> /dev/charDevice

$ cat /dev/charDevice

chau 

 

Y lo que deseo es:


$ echo "hola" > /dev/charDevice

$ cat /dev/charDevice

hola

$ echo "chau" >> /dev/charDevice

$ cat /dev/charDevice

hola

chau

 

Hagamos mi deseo realidad


La parte del append tiene dos aspectos, detectarlo y lidiar correctamente con el buffer.

Tras una caótica exploración de las estructuras en uso (léase, no recuerdo que hice) parecida a pensar que en el momento del write debería haber algún indicio de si estamos en el contexto de un > o un >>, hallé que file provee unos flags donde dice el modo de apertura. el campo se llama f_flags. y está en la struct file en fs.h y


struct file {
...
unsigned int f_flags;
...
}


con un if (filep->f_flags &O_APPEND) bien posicionado, ya podemos elegir el comportamiento.


Este es el código base:

 

ssize_t charDevice_write( struct file *filep,
                          const char *buf,
                          size_t nbytes, loff_t *ppos){
  int failed, transferred;
  if (debug)
    printk("WRITE : %d flags: %x\n",
           (int)nbytes, filep->f_flags);

  if (!nbytes)
    return 0;

  if (nbytes >= BUFFER_SIZE)
    nbytes =  BUFFER_SIZE-1;

  if (filep->f_flags & O_APPEND)
    printk("WRITE : Appending %ld bytes\n",nbytes);
  else
    printk("WRITE : Saving %ld bytes\n",nbytes);
    
  failed = copy_from_user(char_buffer, buf, nbytes);
  transferred = nbytes - failed;

  if (!transferred)
    return -EFAULT;

  char_buffer[transferred] = '\0';

  buffer_length = transferred;
  return transferred;

}

 

Este es el código intermedio ya funcionando:

 

ssize_t charDevice_write( struct file *filep,
                          const char *buf,
                          size_t nbytes, loff_t *ppos){
  int failed, transferred;
  if (debug)
    printk("WRITE : %d flags: %x\n",
           (int)nbytes, filep->f_flags);

  if (!nbytes)
    return 0;

  if (filep->f_flags & O_APPEND) {
    printk("WRITE : Appending %ld bytes\n",nbytes);
    if (nbytes >= BUFFER_SIZE - buffer_length)
      nbytes =  BUFFER_SIZE - buffer_length -1;

    failed = copy_from_user(
              char_buffer + buffer_length, buf, nbytes);
    transferred = nbytes - failed;

    if (!transferred)
      return -EFAULT;

    buffer_length += transferred;

    char_buffer[ buffer_length] = '\0';
  } else {
    printk("WRITE : Saving %ld bytes\n",nbytes);
    if (nbytes >= BUFFER_SIZE )
      nbytes =  BUFFER_SIZE-1;

    failed = copy_from_user(char_buffer, buf, nbytes);
    transferred = nbytes - failed;

    if (!transferred)
      return -EFAULT;

    buffer_length = transferred;
    char_buffer[transferred] = '\0';
  }
  return transferred;
}


Este es el código final refactorizado, notando que todo se trata de buffer_length:

 

ssize_t charDevice_write( struct file *filep,
                          const char *buf,
                          size_t nbytes, loff_t *ppos){
  int failed, transferred;
  if (debug)
    printk("WRITE : %d flags: %x\n",
            (int)nbytes, filep->f_flags);

  if (!nbytes)
    return 0;

  if (filep->f_flags & O_APPEND) {
    printk("WRITE : Appending %ld bytes\n",nbytes);
  } else {
    printk("WRITE : Saving %ld bytes\n",nbytes);
    buffer_length = 0;
  }

  if (nbytes >= BUFFER_SIZE - buffer_length)
    nbytes =  BUFFER_SIZE - buffer_length -1;

  failed = copy_from_user(
            char_buffer + buffer_length, buf, nbytes);
  transferred = nbytes - failed;

  if (!transferred)
    return -EFAULT;

  buffer_length += transferred;

  char_buffer[ buffer_length] = '\0';
  return transferred;
}

Este es mi script de prueba de mi script de prueba:

 

sudo rmmod charDevice
sudo insmod charDevice.ko debug=1 major=197
echo "one" > charDevice2
cat charDevice2
echo "two" >> charDevice2
cat charDevice2
dmesg | tail


Y esta es la traza de la ejecución:

 ./go.sh
[sudo] password for carlos:             
one
one
two
[971144.117183] WRITE : 4 flags: 8001
[971144.117187] WRITE : Saving 4 bytes
[971144.119726] READ     : 131072 bytes  ppos  : 0
[971144.119958] READ     : 131072 bytes  ppos  : 4
[971144.119959] READ     : 1
[971144.120169] WRITE : 4 flags: 8401
[971144.120171] WRITE : Appending 4 bytes
[971144.120987] READ     : 131072 bytes  ppos  : 0
[971144.125827] READ     : 131072 bytes  ppos  : 8
[971144.125831] READ     : 1


El código completo en github.

Conclusiones

El curso está bien, empieza mejor de lo que termina, en el tercer tema que es network devices como que pierde el foco, pues explica algo muy específico, quizás ya estaba cansado y la limitación fué mía.







2021/08/16

Notas del curso Advanced Linux: The Linux Kernel

Max CPUs de s390 y powerpc
Max CPUs de s390 y powerpc

 

Preparándome para regular la velocidad de un cooler en una Raspberry PI para reducir el ruido, tema recurrente en mi vida, gracias a mi trabajo que me dá LinkedIn Learning, tomé algunos cursos, también como parte de mi campaña de recertificación y reaprendizaje continuo.

En el caso de Advanced Linux: The Linux Kernel, buena parte del curso ya la sé, pero siempre hay detalles nuevos y necesarios refrescos.

Comparto algunas cositas que me parecieron interesantes y algunas de mis pruebas.

 

El operador |&

 

Este no lo conocía, te junta stderr en stdout, fantástico.

 

view

 

Es una versión de vim en modo readonly, ¡que estúpido! llevo como veinticinco años mirando archivos con less que no tiene highlight, vos quizás pienses que lo estúpido es usar vim/view, cuestión de gustos y origen de varias flame wars.

 

initramfs


En el curso dice que el initramfs se abre con cpio y quizás gunzip, pero no, escarbando un poco en man cpio:

$ cpio --no-absolute-filenames -i < initrd.img-5.4.0-58-generic

 

sólo trae esto:

tree kernel/
kernel/
└── x86
    └── microcode
        └── AuthenticAMD.bin

   

Recordando mis fracasos con el router dañando por un rayo, usé binwalk:

$ sudo apt install binwalk

En particular en Mint Mate 20.1 no resuelve en las dependencias lzop, así que te enterás en el medio que falta, no importa, rm -r,

$ sudo apt install lzop

$ binwalk -e initrd.img-5.4.0-58-generic

Esto es más compeletito:


_initrd.img-5.4.0-58-generic.extracted/
├── 0.cpio
├── 17D6ECC
├── ....
├── 1DCEE71
├── 1DCEE71.zlib
├── 1E2C581
├── 1E2C581.zlib
├── 220582A.zip
├── 26DCC87.gz
├── ...
├── 3E9AA31.gz
├── 3FE8E9D.gz
├── 43CD22.gz
├── 4A69999.gz
├── 4B60465.xz
├── 4D6447.lzo
├── 4D64CC.xz
├── 51D939A
├── 51D939A.zlib
├── 7C00.cpio
├── cpio-root
│   └── kernel
│       └── x86
│           └── microcode
│               └── AuthenticAMD.bin
├── cpio-root-0
│   └── kernel
│       └── x86
│           └── microcode
│               └── GenuineIntel.bin
└── T1:X3_101115_1_8_1_expROM_FW_uni_template_eeprom0.bin

 

No me gusta para nada, estoy seguro que falta de todo, pero bueno, no voy a analizar esto con profundidad, ya que excede el alcance del curso, sólo completando...

Pero, si te aguantás dos minutos más, presenta el comando unmkinitramfs que hace exactamente lo que hace falta

 $ tree -L 2
.
├── early
│   └── kernel
├── early2
│   └── kernel
└── main
    ├── bin -> usr/bin
    ├── conf
    ├── etc
    ├── init
    ├── lib -> usr/lib
    ├── lib32 -> usr/lib32
    ├── lib64 -> usr/lib64
    ├── libx32 -> usr/libx32
    ├── run
    ├── sbin -> usr/sbin
    ├── scripts
    ├── usr
    └── var

 

¡Qué falta de paciencia y qué acertado no haber intentado profundizar! Me hubiese llevado semanas o para siempre, mirá el contenido de unmkinitramfs que es bash.

 

Modules

 

Busqué rápido y no encontré como identificar de los 5000 módulos disponibles cuáles están en uso (lsmod) y cuáles han sido usados (supongo que en dmesg) para eliminarlos del disco como medida de hardening y recuperación de espacio. La medida de hardening es blacklist. Queda para investigar algún  día...

 

Kernel Source

 

Si te falla make ctags con 

xargs: ctags: No such file or directory
sed: can't read tags: No such file or directory

Es por que te falta ctags:

$ sudo apt install   universal-ctags

 

Building

 

Si al hacer make xconfig te dice:

* Could not find Qt via pkg-config.
* Please install either Qt 4.8 or 5.x. and make sure it's in PKG_CONFIG_PATH

Dale :

$ apt install qtbase5-dev

Si querés ver las opciones para otras arquitecturas, por ejemplo ARM:


$ make ARCH=arm xconfig


Igual no te olvides que necesitás toolchain y setear la variable de cross compilation, ya volveré sobre este tema en otra entrada a futuro.

 

Detalles ejercicios


Para entrar en GRUB en Linux Mint (Ubutnu) es con shift, recordá si estás en virtual box que Right Ctrl te hace recuperar el punteroo del mouse. Recordá que el layout del teclado de GRUB no necesariamente corresponde con lo que tengas.


En el ejercicio propone comparar dmesg con apic=debug, pero por algún motivo mi cerebro lo convirtió en acpi=debug.

La mejor táctica es ir probando con las diversas opciones (apic, acpi, nada) y luego tomar de /var/log/dmesg.X.gz los resultados, renombrar acordemente y ahí comparar con:

for FILE in dmesg.*; do
  for DEBUG in ACPI APIC; do
    echo -n "$FILE $DEBUG "
    grep "$DEBUG" "$FILE" | wc -l
  done
done

dmesg.acpi ACPI 50
dmesg.acpi APIC 7
dmesg.apic ACPI 50
dmesg.apic APIC 58
dmesg.original ACPI 50
dmesg.original APIC 7


Hay un ejercicio de agregar una entrada a grub.cfg, que se hace con update-grub. Como nunca lo hice le desconfío, así que comencé por regenerar grub.cfg al lado como referencia.

$ sudo grub-mkconfig -o grub.cfg.original

Si la salida de diff te parece muy rústica y  no querés instalar kdiff3:

$ diff --side-by-side grub.cfg.original /boot/grub/grub.cfg -W160 | less

Lo importante es que agregó $vt_handoff a los parámetros del kernel y tocó un par de etiquetas, ok, esto indica que hay un cierto desfasaje entre la configuración y la realidad.

Luego fuí mirando cada archivo en  /etc/grub.d, su stdout va a parar a /boot/grub.cfg

Estaría bueno que fuera generando comentarios para poder mapear de dónde sale cada cosa, ah, me adelanté, hay unos separadores en /boot/grub.cfg que indican cuál fué el archivo generador.

Para hacer el ejercicio tenemos dos caminos, agregar una custom entry en /etc/grub.d y ejecutar e proceso de conversión o editar directamente grub.cfg, hice esto último pues el ejercicio no pide que persista.

No te voy a contar lo que ocurre pero si que si agregás rdinit=/bin/sh te tira un busybox, interesante...

 

Curiosidades

 

Estos son algunos de los números de CPUs que soportan algunas arquitecturas:

arm: 32

x86: 64 

s390: 512

powerpc: 8192

 

Conclusiones

 

El curso en general está bueno y me ha servido, pero no me parece tan advanced, quizás me perdí los intro/basic/intermediate.

Los ejercicios han sido útiles, pero el tiempo previsto no me corresponde a lo que le invertí, tipo que el primero lo hice en 15 minutos y decía 60 y el segundo decía 45 y estuve un par de horas.

Algunos ejercicios tienen leves discrepancias que me han ayudado a que los haga de modo no automático, sacando ventaja de los errores...