Guía completa de Linux embebido basada en el curso de Wei Dongshan

Enlaces de recursos

book@100ask:~$ git clone https://e.coding.net/codebug8/repo.git
book@100ask:~$ mkdir -p 100ask_imx6ull-qemu && cd 100ask_imx6ull-qemu
book@100ask:~/100ask_imx6ull-qemu$ ../repo/repo init -u https://e.coding.net/weidongshan/manifests.git -b linux-sdk -m imx6ull/100ask-imx6ull_qemu_release_v1.0.xml --no-repo-verify
book@100ask:~/100ask_imx6ull-qemu$ ../repo/repo sync -j2

Si todo funciona correctamente, se creará un directorio 100ask_imx6ull-qemu en /home/book.

Comandos básicos

Formato: comando [opciones] [argumentos]. Ejemplo: ls -l /home

  • pwd: Muestra la ruta actual.
  • ls: Lista archivos.
  • cd: Cambia de directorio. cd .. retrocede un nivel.
  • mkdir: Crea un nuevo directorio.
  • touch: Crea un archivo vacío.
  • cp test1.txt test2.txt: Copia el archivo1 en archivo2.
  • gedit: Editor de texto gráfico.
  • cat: Muestra el contenido de un archivo en la terminal.
  • rm: Elimina archivos.
  • rmdir: Elimina directorios vacíos.
  • clear: Limpia la pantalla de la terminal.
  • man + comando: Muestra el manual de ayuda.
  • mv: Renombra o mueve archivos/directorios.

Atajos de directorio:

  • cd ~: Va al directorio home.
  • cd .: Directorio actual.
  • cd ..: Directorio padre.
  • cd ../..: Sube dos niveles.

El intérprete Shell

Shell es una aplicación que recibe comandos desde el teclado o puerto serie, los analiza y ejecuta los programas correspondientes. Busca los ejecutables en las rutas definidas por la variable de entorno PATH.

Cómo modificar PATH

  1. Cambio permanente (método 1): Editar /etc/environment con privilegios de superusuario y añadir la ruta deseada separada por dos puntos (:).
  2. Cambio permanente (método 2): Usar el comando export PATH=$PATH:/ruta/deseada en el archivo de configuración del shell (ej. ~/.bashrc).
  3. Cambio temporal: Ejecutar el comando export directamente en la terminal; solo afecta a la sesión actual.

Rutas

  • Ruta absoluta: Ejemplo: /home/solo
  • Ruta relativa: Desde la ubicación actual. Ejemplo: desde /bin, ./pwd ejecuta /bin/pwd. cd ../video desde /home/solo/aaa lleva a /home/solo/video.

Editor vi

Configuración básica

cd /etc/vim
cp vimrc ~/.vimrc
cd ~
gedit .vimrc

Añadir al final del archivo .vimrc:

set nocompatible
set number
set backspace=2
set tabstop=4
set shiftwidth=4
set ignorecase
set hlsearch

Modos de operación

  • Modo normal: Navegación, copiar, pegar, eliminar. Teclas i, a, o para entrar en modo inserción.
  • Modo inserción: Edición de texto.
  • Modo comandos: Para buscar (/) o guardar/salir (:wq, :q!).

Comandos avanzados

  • find /ruta -name "archivo.txt": Busca archivos.
  • grep -rn "cadena" archivo: Busca cadenas de texto en archivos.
  • file archivo: Identifica el tipo de archivo.
  • which comando y whereis comando: Localizan la ubicación de un comando.
  • Compresión: gzip, bzip2 para un solo archivo; tar para múltiples archivos.

Configuración de red NAT

  1. Configurar Ubuntu en modo NAT en VMware.
  2. Obtener la IP de Ubuntu.
  3. En Windows, crear una conexión de red virtual con una IP en el mismo segmento que Ubuntu.
  4. Usar Mobaxterm para conectar con Ubuntu.

Para que la placa de desarrollo acceda a Ubuntu vía NFS en modo NAT, se debe modificar el puerto mountd de Ubuntu:

sudo gedit /etc/services
# Añadir las líneas:
mountd 9999/tcp
mountd 9999/udp

Reiniciar el servicio NFS y verificar los puertos:

sudo /etc/init.d/nfs-kernel-server restart
sudo rpcinfo -p

El protocolo NFS permite que la placa acceda a un directorio de Ubuntu como si fuera local, ideal para depuración rápida de aplicaciones.

Montaje en modo NAT (ejemplo con IP de Windows 192.168.1.17):

mount -t nfs -o nolock,vers=3,port=2049,mountport=9999 192.168.1.17:/home/book/nfs_rootfs /mnt

Montaje en QEMU (la IP de Ubuntu es 10.0.2.2):

mount -t nfs -o nolock,vers=3 10.0.2.2:/home/book/nfs_rootfs /mnt

Preparación para programar

Para compilar para la placa de desarrollo se necesita una cadana de herramientas de compilación cruzada. Ejemplo de compilación para la placa:

arm-linux-gnueabihf-gcc -o hello hello.c

Configuración de la cadena de herramientas

Se recomienda la configuración permanente en ~/.bashrc:

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/ruta/a/toolchain/bin

Para activar los cambios: source ~/.bashrc.

Verificación de la instalación

echo $ARCH
echo $CROSS_COMPILE
arm-buildroot-linux-gnueabihf-gcc -v

Primera aplicación en la placa

  1. Compilar el programa con la cadena cruzada: arm-linux-gnueabihf-gcc -o hello hello.c.
  2. Copiar el ejecutable al directorio NFS compartido: cp hello /home/book/nfs_rootfs.
  3. En la placa, montar el NFS y copiar el archivo: cp /mnt/hello ./hello.
  4. Ejecutar: chmod +x hello && ./hello.

Primer controlador (driver)

Compilar el kernel

cd Linux-5.4
make 100ask_stm32mp157_pro_defconfig
make uImage LOADADDR=0xC2000040 -j4
make dtbs
cp arch/arm/boot/uImage ~/nfs_rootfs
cp arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb ~/nfs_rootfs

Compilar e instalar módulos del kernel

make modules -j4
sudo make INSTALL_MOD_PATH=/home/book/nfs_rootfs modules_install

Reemplazar kernel y módulos en la placa

Montar NFS y copiar los archivos necesarios:

mount -t nfs -o nolock,vers=3,port=2049,mountport=9999 192.168.1.100:/home/book/nfs_rootfs /mnt
cp /mnt/uImage /boot
cp /mnt/*.dtb /boot
cp /mnt/lib/modules /lib -rfd
sync
reboot

Probar el driver hello_drv

  1. Modificar el Makefile para que KERN_DIR apunte al directorio del kernel.
  2. Compilar con make.
  3. Copiar hello_drv.ko y hello_drv_test a la placa.
  4. Cargar el driver: insmod hello_drv.ko.
  5. Ejecutar el programa de prueba: ./hello_drv_test -w www.100ask.net && ./hello_drv_test -r.

Números de inodo

Cada archivo tiene un número de inodo que almacena sus metadatos.

Enlace duro

Similar a una copia, pero sin ocupar espacio adicional. Ambos archivos comparten el mismo inodo. Solo funciona dentro del mismo sistema de archivos y no admite directorios.

ln archivo1.c archivo2.c  # Crea un enlace duro

Enlace simbólico (blando)

Similar a un acceso directo. Tiene su propio inodo y no incrementa el contador de enlaces. Si se elimina el archivo original, el enlace queda roto.

ln -s /ruta/absoluta/archivo1.c enlace.c

Colores de archivos en Ubuntu

Compilación

Un archivo C/C++ pasa por cuatro etapas: preprocesamiento, compilación, ensamblado y enlace.

  • Compilar todo junto: gcc -o test main.c sub.c
  • Compilar por separado y enlazar: gcc -c -o main.o main.c; gcc -c -o sub.o sub.c; gcc -o test main.o sub.o

Uso de Makefile

Un Makefile contiene reglas con el formato:

objetivo: dependencias
<tabulador>comandos

Los comandos se ejecutan si las dependencias son más recientes que el objetivo o si el objetivo no existe.

Funciones útiles en Makefile

  • $(foreach var,list,text): Itera sobre una lista.
  • $(wildcard pattern): Expande patrones de archivos existentes.

Mejora progresiva del Makefile

Versión 1: Simple pero ineficiente.

test : main.c sub.c sub.h
	gcc -o test main.c sub.c

Versión 2: Eficiente pero verbosa y sin detección de cambios en cabeceras.

test : main.o sub.o
	gcc -o test main.o sub.o
main.o : main.c
	gcc -c -o main.o main.c
sub.o : sub.c
	gcc -c -o sub.o sub.c
clean:
	rm *.o test -f

Versión 3: Más concisa, pero sin detección de cabeceras.

test : main.o sub.o
	gcc -o test main.o sub.o
%.o : %.c
	gcc -c -o $@ $<
clean:
	rm *.o test -f

Versión 4: Con soporte manual para cabeceras.

test : main.o sub.o
	gcc -o test main.o sub.o
%.o : %.c
	gcc -c -o $@ $<
sub.o : sub.h
clean:
	rm *.o test -f

Versión 5: Con detección automática de dependencias de cabeceras.

objs := main.o sub.o
test : $(objs)
	gcc -o test $^
dep_files := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files))
ifneq ($(dep_files),)
	include $(dep_files)
endif
%.o : %.c
	gcc -Wp,-MD,.$@.d -c -o $@ $<
clean:
	rm *.o test -f
distclean:
	rm $(dep_files) *.o test -f

Variables en Makefile

  • A = xxx: Varible de asignación diferida (se evalúa al usarla).
  • B ?= xxx: Asignación condicional (solo si no está definida).
  • C := xxx: Variable de asignación inmediata.
  • D += yyy: Asignación adicional (hereda el tipo de la variable D).

Se pueden exportar variables con export para que estén disponibles en sub-Makefiles. Ejemplo: export CC = $(CROSS_COMPILE)gcc.

Objetivos ficticios (.PHONY)

.PHONY: clean
clean:
	rm *.o test -f

Entrada/Salida de archivos (File I/O)

En Linux, todo es un archivo. Las operaciones se realizan a través de las funciones estándar de I/O.

Función open

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

flags puede ser: O_RDWR, O_RDONLY, O_WRONLY, O_APPEND, O_TRUNC, O_CREAT. El parámetro mode solo es necesario si se usa O_CREAT. Devuelve un descriptor de archivo o -1 en caso de error.

Framebuffer

Principio de operación de una pantalla LCD

El controlador de Framebufffer en Linux reserva una memoria (Framebuffer) que contiene los datos de la imagen. El programa de usuario puede acceder a ella mediante mmap.

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);

Se usa para interactuar con el controlador (por ejemplo, obtener la resolución de la pantalla).

Función mmap

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

Mapea un archivo o dispositivo en la memoria del proceso. Parámetros clave: prot (protección: PROT_READ, PROT_WRITE) y flags (MAP_SHARED para escritura compartida).

Ejemplo de uso del Framebuffer

int fb_fd = open("/dev/fb0", O_RDWR);
if (fb_fd < 0) { /* error */ }

struct fb_var_screeninfo var;
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &var)) { /* error */ }

int screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
unsigned char *fb_base = (unsigned char *)mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
if (fb_base == MAP_FAILED) { /* error */ }

Codificación UNICODE (UTF-8)

UTF-8 usa codificación de longitud variable. Los caracteres ASCII (0-127) se representan con un byte. Los caracteres no ASCII usan de 2 a 4 bytes, donde el primer byte indica la longitud total en sus bits más significativos.

Marco de trabajo del sistema de entrada

Estructura input_event

  • type: Tipo de evento (ej. EV_KEY para teclas, EV_ABS para posición absoluta).
  • code: Código específico del evento (ej. KEY_A para la tecla A).
  • value: Valor del evento (0 para liberar, 1 para presionar en teclas; coordenadas en pantallas táctiles).
  • Evento de sincronización: Un evento con type, code y value igual a 0, que indica que todos los datos de una transacción han sido enviados.

Formas de acceso a hardware desde una APP

  1. Modo consulta (polling): Se abre el dispositivo en modo no bloqueante (O_NONBLOCK). La función read retorna inmediatamente, con datos o con error.
  2. Modo dormir-despertar: Se abre en modo bloqueante. La función read duerme el proceso hasta que lleguen datos.
  3. Modo POLL/SELECT: Se usa poll() o select() con un tiempo de espera. Si hay datos, retornan inmediatamente; de lo contrario, duermen hasta que lleguen datos o se cumpla el tiempo de espera.
  4. Modo de notificación asíncrona: La aplicación se registra para recibir una señal (ej. SIGIO) cuando el dispositivo tenga datos. Pasos:
    • Definir un manejador de señal: signal(SIGIO, mi_funcion);
    • Indicar al controlador el PID del proceso: fcntl(fd, F_SETOWN, getpid());
    • Habilitar la notificación asíncrona: fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | FASYNC);

Desarrollo de controladores en Linux

Estructura file y file_operations

Cuando una aplicación abre un archivo, el kernel crea una estructura struct file que contiene información como los flags y la posición actual. La estructura file_operations es un vector de funciones que mapea las llamadas del sistema de la aplicación (open, read, write, close) a las funciones del controlador.

  1. Determinar el número de dispositivo mayor o pedir uno al kernel.
  2. Definir una estructura file_operations.
  3. Implementar las funciones necesarias (drv_open, drv_read, etc.) y asignarlas a la estructura.
  4. Registrar el controlador con register_chrdev en la función de entrada (module_init).
  5. Proveer una función de salida (module_exit) que llame a unregister_chrdev.
  6. Opcionalmente, crear clases y dispositivos automáticos con class_create y device_create.

Filosofía de diseño de controladores

  1. Orientado a objetos: Todos los controladores implementan file_operations.
  2. Separación en capas: La capa superior maneja la lógica independiente del hardware (registro del controlador), mientras que la capa inferior es específica del hardware.
  3. Separación izquierda-derecha: La capa inferior se divide para manejar diferentes periféricos, permitiendo elegir funciones según los recursos disponibles.

Tutorial de la placa virtual QEMU IMX6ULL

Preparación

  1. Instalar VMware y una imagen de Ubuntu 18.04 o 16.04.
  2. Configurar la máquina virtual en modo NAT.
  3. Instalar KVM para acelerar QEMU: sudo apt-get install qemu qemu-kvm libvirt-bin bridge-utils virt-manager.
  4. Clonar el repositorio de la imagen QEMU: git clone https://e.coding.net/weidongshan/ubuntu-18.04_imx6ul_qemu_system.git.

Ejecución

./install_sdl.sh  # Solo la primera vez
./qemu-imx6ull-gui.sh  # Inicia la placa simulada (usuario: root, sin contraseña)

Operaciones básicas en QEMU

  • LCD: fb-test o myfb-test /dev/fb0.
  • LEDs: Cargar el driver y usar ledtest /dev/100ask_led0 on.
  • Botones y LEDs: Cargar los drivers correspondientes y ejecutar button_led_test.
  • I2C (AT24C02): Usar i2c_usr_test para leer y escribir en la memoria EEPROM.

Obtención del código fuente del kernel

Usar la herramienta repo como se indicó al inicio del artículo.

Compilación del kernel para QEMU

cd linux-4.9.88
make mrproper
make 100ask_imx6ull_qemu_defconfig
make zImage -j2
make dtbs

Los archivos resultantes son arch/arm/boot/zImage y arch/arm/boot/dts/100ask_imx6ull_qemu.dtb.

Reemplazo del kernel en QEMU

Copiar los archivos anteriores al directorio de la imagen QEMU (imx6ull-system-image).

Modificación del sistema de archivos

Montar la imagen rootfs.img:

sudo mount -o loop rootfs.img /mnt
# Realizar modificaciones en /mnt
sudo umount /mnt

Uso de NFS con QEMU

  1. Instalar y configurar el servidor NFS en Ubuntu:
sudo apt-get install nfs-kernel-server
sudo gedit /etc/exports
# Añadir: /home/book/nfs_rootfs *(rw,nohide,insecure,no_subtree_check,async,no_root_squash)
sudo /etc/init.d/nfs-kernel-server restart

  1. En QEMU, montar el directorio NFS:
ifconfig eth0 10.0.2.15
mount -t nfs -o nolock,vers=3 10.0.2.2:/home/book/nfs_rootfs /mnt

Nota: QEMU no hace ping a Ubuntu, pero el montaje NFS funciona correctamente.

Etiquetas: Linux Embebido qemu controladores Linux Framebuffer Makefile

Publicado el 7-6 16:57