← Fundamentos de Sistemas Operativos Bloque 1 · Introducción · Tema 4 de 34

Tema 04

Preparar el entorno: Linux, gcc y Bash

A partir del próximo tema, cada idea se comprueba en una terminal. Aquí dejamos listo el lugar de trabajo: elegimos cómo tener Linux, instalamos el compilador y las herramientas de observación, y verificamos todo compilando un primer programa en C y escribiendo un primer script.

1. Qué necesitamos

El curso no requiere un equipo potente ni software de pago. Hace falta lo siguiente:

ComponentePara qué lo usamos
Un sistema LinuxEs el sistema operativo que vamos a observar. Tiene que ser un kernel de Linux real, no un imitador.
Una terminal con BashPara ejecutar comandos y escribir scripts. Viene con cualquier distribución.
gccEl compilador de C: convierte nuestros programas en ejecutables.
straceMuestra las llamadas al sistema que hace un programa.
Páginas manEl manual de referencia de comandos, llamadas al sistema y funciones de C.
Un editor de textoPara escribir el código.

Como distribución recomendamos Ubuntu en una versión LTS (de soporte extendido): es la más documentada y la que se instala por defecto en casi todos los caminos que veremos. Si ya usas Debian, Fedora, Arch u otra, sirve igual; solo cambia el comando para instalar paquetes.

Si ya tienes Linux

Pasa directamente a la sección 7 para instalar las herramientas y luego a la sección 9 para verificar el entorno.

2. Tres caminos hacia Linux

No es necesario reemplazar el sistema que usas a diario. Hay tres formas habituales de tener un Linux completo, y se diferencian en cuántas capas quedan entre tus programas y el hardware:

Capas de software en una instalación nativa de Linux, en una máquina virtual y en WSL 2 Nativo Tus programas Kernel de Linux Hardware Máquina virtual Tus programas Kernel de Linux VirtualBox / UTM Windows o macOS Hardware WSL 2 Tus programas Kernel de Linux Windows · VM ligera Hardware
En los tres casos tus programas hablan con un kernel de Linux auténtico. Cambia lo que hay debajo de él.
WSL 2Máquina virtualLinux nativo
Sistema anfitriónWindows 10 u 11Windows, macOS o LinuxNinguno
Esfuerzo de instalaciónMuy bajo: un comandoMedio: unos 30 minutosAlto: afecta al disco del equipo
Riesgo para tu equipoNingunoNingunoHay que respaldar antes
Programas en C, llamadas al sistema, /proc, straceSíSíSí
Proceso de arranque (bloque 5)No: no hay firmware ni GRUBSí, completoSí, completo
Hardware real (bloque 2)ParcialHardware simuladoSí
Experimentar sin miedoSe puede reinstalarInstantáneas para volver atrásUn error puede costar caro

Nuestra recomendación según tu situación:

Procesadores ARM

Los ejemplos del curso se muestran sobre la arquitectura x86_64, la de los procesadores Intel y AMD. En equipos con procesador ARM, como los Mac con Apple Silicon, Linux se identifica como aarch64. Los programas en C y los comandos funcionan igual; solo cambian detalles de bajo nivel, como los nombres de los registros de la CPU, que señalaremos cuando aparezcan.

3. Camino A: WSL 2 en Windows

WSL (Windows Subsystem for Linux) es una función de Windows que ejecuta un kernel de Linux dentro de una máquina virtual muy ligera, integrada con el sistema. No es un emulador: los programas se ejecutan sobre Linux de verdad.

Instalación

  1. Abre PowerShell como administrador: clic derecho sobre el botón de Inicio y elige «Terminal (Administrador)».
  2. Ejecuta el comando de instalación. Descarga WSL y, por defecto, la distribución Ubuntu:
PowerShell (administrador)
PS> wsl --install
  1. Reinicia el equipo cuando lo pida.
  2. Tras el reinicio se abre una ventana de Ubuntu que termina la instalación y te pide crear un usuario y una contraseña de Linux. Son independientes de los de Windows. Al escribir la contraseña no se muestra nada en pantalla, ni siquiera asteriscos; es normal.
  3. Comprueba que la distribución usa la versión 2:
PowerShell
PS> wsl --list --verbose
  NAME      STATE           VERSION
* Ubuntu    Running         2

Desde ahora, para entrar a Linux basta con abrir la aplicación Ubuntu del menú Inicio, o una pestaña de Ubuntu en Windows Terminal.

Tres cosas que conviene saber

4. Camino B: máquina virtual

Una máquina virtual es una computadora simulada por software, dentro de la cual se instala un sistema operativo completo como si fuera un equipo físico. El sistema instalado no puede afectar al equipo real, y eso la convierte en un laboratorio ideal.

Pasos generales

  1. Instala el programa de virtualización. En Windows y en Linux, VirtualBox (gratuito). En Mac con Apple Silicon, UTM (gratuito).
  2. Descarga la imagen ISO de Ubuntu desde su sitio oficial. Elige la edición Desktop si quieres escritorio gráfico. En un Mac con Apple Silicon, descarga la versión para ARM64.
  3. Crea la máquina virtual con estos recursos como mínimo: 2 procesadores, 4 GB de memoria y 25 GB de disco. El disco virtual es un archivo que crece según se usa; no reserva ese espacio de inmediato.
  4. Arranca la máquina con la ISO y sigue el instalador de Ubuntu. Las opciones por defecto son adecuadas. Elige «borrar el disco e instalar»: se refiere al disco virtual, no al de tu equipo.
  5. Reinicia, inicia sesión y abre la aplicación Terminal.
Usa las instantáneas

Los programas de virtualización permiten guardar una instantánea (snapshot) del estado completo de la máquina y volver a ella cuando quieras. Toma una cuando termines este tema. Si más adelante un experimento deja el sistema inutilizable, lo restauras en segundos.

Si el programa informa que la virtualización no está disponible, hay que activarla en la configuración del firmware del equipo (BIOS o UEFI), donde aparece como «Intel VT-x», «AMD-V» o «SVM».

5. Camino C: Linux nativo

Instalar Linux directamente en el equipo da el mejor rendimiento y acceso al hardware real. Se puede hacer de dos maneras: como único sistema, o junto al existente en lo que se llama arranque dual, donde al encender se elige cuál iniciar.

El procedimiento consiste en grabar la ISO de Ubuntu en una memoria USB, arrancar el equipo desde ella y seguir el instalador. Los detalles dependen de cada equipo y exceden este tema; el sitio de Ubuntu tiene una guía oficial paso a paso.

Antes de instalar

Una instalación nativa modifica las particiones del disco. Un paso mal elegido puede borrar el sistema y los datos que ya tienes. Haz una copia de seguridad completa antes de empezar. Si quieres probar sin comprometerte, la misma memoria USB permite arrancar Ubuntu en modo de prueba, sin instalar nada.

6. Primer contacto con la terminal

Abre la terminal. Lo que ves es el prompt, la señal de que la shell espera un comando:

terminal
diego@mi-equipo:~$ █

Se lee así: el usuario diego, en el equipo mi-equipo, está ubicado en el directorio ~ (abreviatura de su carpeta personal, /home/diego), y el signo $ indica que es un usuario común. El administrador del sistema, llamado root, ve un # en su lugar.

Convención del curso

En los ejemplos abreviamos el prompt a un solo $. Ese signo no se escribe: indica que lo que sigue es un comando. Las líneas sin $ son la respuesta del sistema, y el texto que sigue a un # es un comentario explicativo.

Estos son los comandos mínimos para moverte. El curso siguiente de la ruta, Terminal y Línea de Comandos en Linux, los trata a fondo; aquí alcanza con reconocerlos.

ComandoQué haceEjemplo
pwdMuestra en qué directorio estás.pwd
lsLista el contenido de un directorio.ls -l
cdCambia de directorio. Sin argumentos, vuelve a la carpeta personal.cd so, cd ..
mkdirCrea un directorio.mkdir tema04
catMuestra el contenido de un archivo.cat hola.c
cp, mvCopia; mueve o cambia el nombre.cp a.c b.c
rmBorra un archivo. No hay papelera.rm b.c
clearLimpia la pantalla.clear

Y estas teclas ahorran mucho tiempo:

Creemos el directorio de trabajo del curso, con una carpeta por tema:

terminal
$ mkdir -p ~/so/tema04           # -p crea también los directorios intermedios
$ cd ~/so/tema04
$ pwd
/home/diego/so/tema04

7. Instalar las herramientas

En Linux el software se instala desde los repositorios de la distribución con un gestor de paquetes. Instalar programas modifica el sistema, así que requiere permisos de administrador: se antepone sudo al comando, que pedirá tu contraseña.

Ubuntu y Debian
$ sudo apt update                # actualiza la lista de paquetes disponibles
$ sudo apt install build-essential strace gdb manpages-dev

Qué trae cada paquete:

En otras distribuciones los paquetes equivalentes son:

Fedora
$ sudo dnf install gcc make glibc-devel strace gdb man-pages
Arch Linux
$ sudo pacman -S base-devel strace gdb man-db man-pages

Comprueba que todo quedó instalado pidiendo a cada herramienta su versión. Los números serán distintos en tu equipo:

terminal
$ gcc --version | head -1
gcc (Ubuntu 13.2.0-23ubuntu4) 13.2.0
$ strace --version | head -1
strace -- version 6.8
$ bash --version | head -1
GNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu)
$ uname -srm
Linux 6.8.0-45-generic x86_64

8. Un editor de texto

Para escribir programas sirve cualquier editor de texto plano. Dos opciones razonables para empezar:

Si ya manejas vim, emacs u otro, úsalo. El curso no depende del editor.

9. El primer programa en C

En lugar del «Hola, mundo» habitual, nuestro primer programa le hace al kernel varias preguntas sobre el entorno que acabas de instalar. Sirve a la vez de verificación y de anticipo: cada dato que imprime proviene de una llamada al sistema. Escríbelo en un archivo llamado entorno.c dentro de ~/so/tema04.

entorno.c
#include <stdio.h>
#include <unistd.h>
#include <sys/utsname.h>

int main(void)
{
    struct utsname so;

    if (uname(&so) == -1) {
        perror("uname");
        return 1;
    }

    printf("Kernel:        %s %s\n", so.sysname, so.release);
    printf("Arquitectura:  %s\n", so.machine);
    printf("Equipo:        %s\n", so.nodename);
    printf("Procesadores:  %ld\n", sysconf(_SC_NPROCESSORS_ONLN));
    printf("Pagina:        %ld bytes\n", sysconf(_SC_PAGESIZE));
    printf("Puntero:       %zu bits\n", sizeof(void *) * 8);
    printf("PID:           %d (padre: %d)\n", (int)getpid(), (int)getppid());
    printf("Usuario (UID): %d\n", (int)getuid());
    return 0;
}

No hace falta entender cada línea todavía. Lo esencial:

Compilar

terminal
$ gcc -Wall -Wextra -g entorno.c -o entorno

Si no aparece ningún mensaje, la compilación fue correcta: en Unix, el silencio significa éxito. Las opciones significan:

OpciónSignificado
-Wall -WextraActiva las advertencias del compilador. Señalan código legal pero sospechoso, y casi siempre tienen razón.
-gIncluye información de depuración en el ejecutable, para que gdb pueda mostrar el código fuente.
entorno.cEl archivo fuente que se compila.
-o entornoEl nombre del ejecutable resultante. Sin esta opción se llamaría a.out.

Usaremos esta misma línea, cambiando los nombres, para compilar todos los programas del curso. Qué hace gcc por dentro para pasar del texto al ejecutable es el contenido del tema 9.

Ejecutar

terminal
$ ./entorno
Kernel:        Linux 6.8.0-45-generic
Arquitectura:  x86_64
Equipo:        mi-equipo
Procesadores:  8
Pagina:        4096 bytes
Puntero:       64 bits
PID:           3817 (padre: 3120)
Usuario (UID): 1000
$ echo $?                        # código de salida del último programa
0

Dos detalles de esa ejecución merecen explicación:

Guarda una copia de esta salida. Ejecuta el programa dos veces y compara: el PID cambia, porque cada ejecución es un proceso nuevo, mientras que el PID del padre se mantiene, porque el padre es la shell desde la que lo lanzas. Volveremos sobre cada uno de estos datos: el tamaño de página en el tema 27, los PID en el tema 26 y el UID en el tema 30.

10. Leer los mensajes del compilador

Tarde o temprano el compilador protestará, y saber leer sus mensajes ahorra mucho tiempo. Este programa tiene dos defectos a propósito:

aviso.c
#include <stdio.h>

int main(void)
{
    int sin_usar;
    printf("Hola\n")
    return 0;
}
terminal (salida abreviada)
$ gcc -Wall -Wextra -g aviso.c -o aviso
aviso.c: In function ‘main’:
aviso.c:6:21: error: expected ‘;’ before ‘return’
    6 |     printf("Hola\n")
      |                     ^
      |                     ;
aviso.c:5:9: warning: unused variable ‘sin_usar’ [-Wunused-variable]
    5 |     int sin_usar;
      |         ^~~~~~~~

Todos los mensajes siguen el formato archivo:línea:columna: tipo: descripción, y debajo se muestra la línea con una marca en el punto exacto. Hay dos tipos:

Dos consejos prácticos: corrige siempre el primer error y vuelve a compilar, porque los siguientes suelen ser consecuencia de él; y trata las advertencias como errores, sin dejar ninguna pendiente.

11. El primer script en Bash

Un script es un archivo de texto con comandos que la shell ejecuta en orden, como si los escribieras uno tras otro. El siguiente comprueba que el entorno del curso esté completo. Guárdalo como verificar.sh.

verificar.sh
#!/bin/bash
# Verifica que el entorno del curso este completo.

echo "Kernel:       $(uname -sr)"
echo "Distribucion: $(. /etc/os-release && echo "$PRETTY_NAME")"
echo "Shell:        Bash $BASH_VERSION"
echo

faltan=0
for herramienta in gcc make strace gdb man; do
    if command -v "$herramienta" > /dev/null; then
        echo "  [ok]     $herramienta"
    else
        echo "  [FALTA]  $herramienta"
        faltan=1
    fi
done

exit $faltan

Las piezas nuevas:

Un archivo recién creado no tiene permiso de ejecución. Hay que dárselo una vez con chmod:

terminal
$ chmod +x verificar.sh          # añade el permiso de ejecución
$ ./verificar.sh
Kernel:       Linux 6.8.0-45-generic
Distribucion: Ubuntu 24.04.1 LTS
Shell:        Bash 5.2.21(1)-release

  [ok]     gcc
  [ok]     make
  [ok]     strace
  [ok]     gdb
  [ok]     man

Si alguna línea dice [FALTA], vuelve a la sección 7. Observa que entorno y verificar.sh informan el mismo kernel por dos vías distintas: un programa compilado que llama al kernel directamente y un script que lo hace a través de comandos. Los scripts tienen su propio curso en la ruta, Automatización con Scripts en Bash.

12. El manual: man

Linux trae su documentación de referencia instalada, y es la fuente más confiable sobre el sistema que tienes delante. Se consulta con man seguido del nombre de lo que buscas. El manual está dividido en secciones, y tres de ellas nos interesan especialmente:

SecciónContenidoEjemplo
1Comandos de usuarioman 1 ls
2Llamadas al sistemaman 2 write
3Funciones de bibliotecaman 3 printf

La división entre las secciones 2 y 3 es exactamente la frontera entre el kernel y la biblioteca que vimos en el tema 1: write está en la sección 2 porque es una llamada al sistema, y printf en la 3 porque es una función de la biblioteca de C. Cuando un mismo nombre existe en varias secciones, el número evita la ambigüedad: man 1 printf describe el comando de la shell y man 3 printf la función de C.

terminal
$ man 2 getpid                   # abre la página; se sale con la tecla q
$ man -k directory               # busca páginas que mencionen una palabra

Dentro de una página, las flechas y la barra espaciadora desplazan el texto, /palabra busca, n salta a la siguiente coincidencia y q cierra. Las páginas de la sección 2 tienen siempre la misma estructura; las partes que más consultarás son SYNOPSIS (qué archivos incluir y qué parámetros recibe), RETURN VALUE (qué devuelve) y ERRORS (por qué puede fallar).

13. Probar strace

Para terminar, verifiquemos la herramienta que más usaremos en el bloque 3. Le pedimos a strace que ejecute nuestro programa y muestre únicamente su llamada a uname:

terminal
$ strace -e trace=uname ./entorno
uname({sysname="Linux", nodename="mi-equipo", ...}) = 0
Kernel:        Linux 6.8.0-45-generic
Arquitectura:  x86_64
…
+++ exited with 0 +++

La línea resaltada es el pedido que el programa hizo al kernel y la respuesta que recibió: los datos con que el kernel completó la estructura y el valor de retorno 0, que indica éxito. Con la opción -c, en cambio, strace muestra un resumen con la cantidad de llamadas de cada tipo:

terminal (extracto)
$ strace -c ./entorno > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
  0.00    0.000000           0         8           mmap
  0.00    0.000000           0         3           mprotect
  0.00    0.000000           0         2           openat
  0.00    0.000000           0         1           write
  0.00    0.000000           0         1           uname
…
------ ----------- ----------- --------- --------- ----------------
100.00    0.000000           0        37         1 total

Un programa de veinte líneas hizo decenas de llamadas al sistema, la mayoría antes de llegar a main: son el trabajo de cargar el ejecutable y la biblioteca de C. Las cantidades exactas varían según el sistema. Aprenderemos a interpretar esta salida en el tema 16; por ahora basta con que funcione.

14. Problemas frecuentes

MensajeCausaSolución
gcc: command not foundEl compilador no está instalado.Repite la instalación de la sección 7.
entorno: command not foundFalta el ./ delante del nombre.Ejecuta ./entorno.
./entorno: No such file or directoryEstás en otro directorio, o la compilación falló.Comprueba con pwd y ls.
./verificar.sh: Permission deniedEl script no tiene permiso de ejecución.chmod +x verificar.sh
bad interpreter: /bin/bash^MEl archivo se guardó con finales de línea de Windows.Configura el editor para usar finales de línea «LF», o conviértelo con dos2unix.
fatal error: stdio.h: No such file or directoryFaltan los archivos de la biblioteca de C.Instala build-essential (o glibc-devel).
No manual entry for write in section 2Faltan las páginas de desarrollo.Instala manpages-dev (o man-pages).
usuario is not in the sudoers fileTu usuario no tiene permiso para usar sudo.Usa la cuenta creada durante la instalación, que sí lo tiene.
WSL: error 0x80370102La virtualización está desactivada en el firmware.Actívala en la BIOS o UEFI (Intel VT-x, AMD-V o SVM).

Ante cualquier otro error, lee el mensaje completo antes de buscar ayuda: casi siempre indica el archivo, la línea o el comando exactos que fallaron.

15. Resumen y lista de control

Antes de pasar al bloque 2, confirma que puedes marcar los cinco puntos:

  1. uname -s responde Linux.
  2. entorno.c compila sin errores ni advertencias, y ./entorno muestra los datos de tu sistema.
  3. ./verificar.sh muestra [ok] en las cinco herramientas.
  4. man 2 write abre una página del manual.
  5. strace -e trace=uname ./entorno muestra la llamada a uname.

16. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. ¿Por qué la terminal de macOS no alcanza para este curso, si también es de la familia Unix?

Porque su kernel no es Linux. Comparte muchos comandos y buena parte de la interfaz de programación, pero no tiene el sistema de archivos /proc ni strace, y sus llamadas al sistema difieren en los detalles. Hace falta una máquina virtual con Linux.

2. ¿Qué parte del curso no se puede practicar en WSL 2 y por qué?

El proceso de arranque del bloque 5. En WSL, Windows inicia el kernel de Linux directamente, sin firmware propio ni cargador de arranque como GRUB. En una máquina virtual o en una instalación nativa el arranque es completo.

3. Compilaste entorno.c y al escribir entorno la shell responde «command not found». ¿Qué ocurre?

La shell solo busca los nombres sin ruta en los directorios del sistema, y el directorio actual no está entre ellos. Hay que indicar la ubicación: ./entorno.

4. ¿Qué diferencia hay entre un error y una advertencia del compilador?

Un error significa que el código no es C válido y no se genera el ejecutable. Una advertencia significa que el código es válido y se compila, pero contiene algo que probablemente sea un descuido. Conviene corregir ambos.

5. ¿Por qué write está en la sección 2 del manual y printf en la 3?

La sección 2 documenta las llamadas al sistema, es decir, los servicios del kernel. La sección 3 documenta las funciones de biblioteca, que se ejecutan en modo usuario. printf es una función de la biblioteca de C que, por dentro, termina usando write.

6. Al ejecutar ./entorno dos veces, el PID cambia pero el del padre no. ¿Por qué?

Cada ejecución crea un proceso nuevo, y el kernel le asigna un identificador distinto. El padre es en ambos casos la shell desde la que se lanza el programa, que sigue siendo el mismo proceso.

7. ¿Qué indica echo $? y qué valor esperarías tras ejecutar un programa que funcionó bien?

Muestra el código de salida del último programa ejecutado, que es el valor devuelto por su función main o pasado a exit. Por convención, un programa que termina correctamente devuelve 0.

Para practicar

  1. Compila ahora hola.c y hola_write.c del tema 1 y copiar.c, ilusion.c e invasor.c del tema 2, y repite sus experimentos.
  2. Modifica entorno.c para que termine con return 3 y comprueba el valor de $?.
  3. Abre man 2 uname y averigua qué otros campos tiene la estructura utsname. Añade uno al programa.
  4. Añade ltrace a la lista de herramientas de verificar.sh. Si aparece como faltante, instálalo y vuelve a ejecutar el script.
  5. Si usas una máquina virtual, toma una instantánea con el nombre «entorno listo».