← Todas las materias

Arquitectura Cliente-Servidor

Mtro. Carlos Alberto Roman Zamitiz · Grupo 2 · carlos.roman@ingenieria.unam.edu · Martes y jueves · 07:00–08:30 · Salón A305

Evaluación

Criterios y tareas

Material

Diapositivas, manual, guiones del profesor

Entregas

PDFs que ya entregaste

Programas

Código y salida (Tema 1)

Plan de estudios

Clave FI: 2946

Objetivo: el alumno aplicará los conocimientos de protocolos, criptografía y seguridad para desarrollar programas bajo la arquitectura cliente/servidor mediante un lenguaje de programación.

Núm.TemaHoras
1Conceptos básicos6.0
2Creación de socket servidor y cliente10.0
3Servidores y clientes sincronizados10.0
4Sockets broadcasting y multicasting8.0
5Implantación de servidores con criptografía y código seguro8.0
6Herramientas para la comunicación entre servidores6.0
Bibliografía básica

Todos los temas y subtemas del plan. En azul, lo que ya se vio en clase (enlaza a la clase). En gris, lo que falta.

Tema 1 — Conceptos básicos · Clases 01–06

Las 6 primeras clases fueron fundamentos de procesos en Linux (repaso de sistemas operativos; base para 1.6 y 1.7): imagen del proceso · system()/fork()/exec() · permisos y bit SetUID · errno · procesos huérfanos · estados de proceso, señales, wait() y terminación de procesos · manejo de señales con signal() y control fg/bg. Resúmenes: Clase 01 · 02 · 03 · 04 · 05 (no asistida, reconstruida del sitio del profesor) · 06. Código del profesor en Programas.

Teoría y ejemplos del Tema 1 (procesos en Linux — apuntes de clase + PDFs del profesor)

En corto

  • Tema 1 = Linux y procesos: qué es un proceso y su “imagen” en memoria (text / data / BSS / heap / stack), y cómo se crean con system(), fork() y exec().
  • Herramientas para inspeccionar procesos: size, pmap, /proc/<PID>/maps, ps / ps aux / ps -eo, top.
  • fork() crea una copia del padre (salvo PID/PPID): devuelve el PID del hijo al padre y 0 al hijo.
  • Permisos octales (r=4, w=2, x=1); el bit setuid (chmod 4xxx) hace que el proceso tome el UID del dueño del archivo.
  • Incluye 25 programas en C del profesor con su salida, 5 diagramas y 3 PDFs de teoría.

🎯 Para el examen

  • BCP / PCB (bloque de control de proceso): estructura del kernel con los metadatos del proceso.
  • size no muestra heap ni stack porque son dinámicos (solo existen en RAM con el proceso vivo).
  • pmap: la memoria se asigna por páginas de 4 KB; las zonas dinámicas (stack/heap) nunca tienen permiso X (anti-inyección de código).
  • ps -eo pid,ppid,…: las columnas van separadas por comas sin espacios o hay error de sintaxis.
  • ID real vs. efectivo; con setuid el EUID pasa a ser el del dueño del archivo (Tarea 2).
  • fork(): retorno pid_t (PID del hijo al padre / 0 al hijo); patrón if(id_hijo!=0); riesgo de deadlock.
Objetivo

Entender los conceptos relacionados con los procesos, además de cómo identificarlos en un entorno Linux y las funciones system(), fork() y exec() para su creación.

Clase 1 — Linux y procesos: la imagen de un proceso

Encuadre: la primera parte de la materia es "una continuación de sistemas operativos": primero procesos, después desarrollo de servidores. El curso se enfoca en un solo procesador (uniprocesador): no se ve programación multiprocesador ni multinúcleo. "Distribuido" aquí significa servidor en un host y cliente en otro, conectados por red.

Entorno de trabajo del curso
  • Códigos del semestre → repositorio GitHub público (se puede hacer fork o rama en tu cuenta).
  • Apuntes oficiales → GitHub Pages (HTTPS, alta disponibilidad). El sitio viejo profesores.fi-b.unam.mx/~carlos/acs es HTTP y está obsoleto.
  • GitHub Codespaces: contenedor en la nube; el README.md del Tema 1 explica cómo levantarlo. No hace falta traer computadora — compila y ejecuta incluso desde el celular. Dentro tienes root (sudo o sudo su). Si rompes el sistema (rm -rf), destruyes el Codespace y creas otro; los programas vuelven intactos. Cerrar la pestaña lo suspende; se reactiva con el botón verde.
  • En equipo propio: cualquier distro de Linux sirve. En Windows → WSL (Linux dentro de Windows) o SSH a un servidor Linux remoto.
  • Red UNAM: los rangos públicos 132.247.*.* y 132.248.*.* son de la UNAM a nivel mundial. Las IP privadas (p. ej. 192.168.x.x) son internas y aisladas. El profesor se conectó por SSH a 132.248.77.168 [inaudible: también se transcribe como 132.247.248.7.168].
  • Documentación formal de funciones C en Linux: The Open Group. Buscar "Open Group <función>" (ej. Open Group getpid).
Imagen de un proceso

Definición del profesor: "una fotografía o instantánea". Un ejecutable en ejecución crea un proceso que vive mientras la ejecución esté viva; al terminar o cerrar, el proceso muere.

Procesos en segundo plano (background) o demonios (daemon): no son visibles ni interactúas con ellos en la terminal. Cerrar la interfaz gráfica de un programa no siempre mata su proceso.

Segmentos de la imagen en memoria

Al ejecutar un binario, el SO carga en una zona de memoria independiente (espacio de direcciones propio) las áreas de código y datos.

Segmentos estáticos:

  • text (texto / código): solo lectura; código máquina + constantes de tipo string (p. ej. las que se pasan a printf).
  • data: variables globales inicializadas.
  • BSS (Block Started by Symbol; el profesor lo dictó como "block started by single"): variables globales no inicializadas.

Segmentos dinámicos:

  • heap (montículo): memoria dinámica en ejecución (malloc).
  • stack (pila): variables locales y parámetros de las llamadas a función.
  • heap y stack crecen en sentidos opuestos ("uno crece a la izquierda y el otro a la derecha") para aprovechar el espacio.

Scope (ámbito) de una variable local = los límites de su función.

Tarea futura (todavía no): investigar file descriptors y la asignación dinámica de memoria (malloc).

⚠️ Examen — BCP / PCB (Process Control Block, "bloque de control de proceso"): estructura administrativa del kernel donde se guardan los metadatos del proceso. El profesor lo marcó como muy importante.

Ejercicio: servidor sencillo en C (dos terminales)
  • Objetivo: entender la estructura de la imagen (código, datos inicializados, BSS, montículo y pila) y diferenciarla del contexto del SO.
  • Compilar dando al binario el mismo nombre que el .c (con -o); así ls -l agrupa fuente y binario. Sin -o se genera a.out (mala práctica: el nombre no dice nada).
  • Terminal 1: corre el servidor; usa getchar() para pausar y que el proceso no muera al instante — libera la memoria y muere hasta que pulsas Enter.
  • Terminal 2: monitorea el proceso en RAM.
  • Ubicación de las variables: global inicializada → data; global no inicializada → BSS; local → stack; reservada con mallocheap; strings literales de printftext.
Comandos de análisis de procesos

size servidoranálisis estático (mira el archivo en disco). Columnas: text, data, bss, dec (suma en decimal), hex (suma en hexadecimal), filename. Ejemplo: 1767 + 592 + 8 = 2367 dec = 0x93F [inaudible: la transcripción decía "3F"].

⚠️ Examen: ¿por qué size no muestra heap ni stack? Porque son dinámicos: solo existen en RAM cuando el proceso está vivo; size solo analiza el archivo estático en disco.

pmap <PID> (Process Map) — análisis dinámico: consulta las estructuras del kernel e imprime el mapa completo del proceso en RAM (stack, heap, librerías .so, direcciones físicas). El PID se obtiene con getpid() (de <unistd.h>).

  • Los valores de pmap no cuadran con size porque el SO asigna memoria por páginas de 4 KB; toda sección se redondea a página completa → todos los valores son múltiplos de 4.
  • ⚠️ Seguridad: las zonas dinámicas (stack, heap) tienen permisos R / RW pero nunca X — para que no se pueda inyectar ni ejecutar código malicioso en el proceso.

cat /proc/<PID>/maps — mientras el proceso vive, el SO mantiene el directorio /proc/<PID>/; el archivo maps muestra direcciones hexadecimales, permisos de cada segmento y librerías dinámicas cargadas (información del PCB). Al pulsar Enter en la Terminal 1 termina getchar(), se libera el heap y el proceso muere.

Árbol de procesos · PID y PPID
  • Todos los procesos forman un árbol jerárquico. La raíz tiene PID = 1: init en Unix clásico / distros viejas, systemd en distros modernas. De la raíz se derivan todos los demás procesos.
  • PID = identificador único del proceso. PPID = PID del proceso padre.
  • Principio: todo comando lanzado en una terminal tiene como padre al proceso de esa terminal (bash). El PID de bash no cambia mientras sea la misma terminal; el de ps cambia cada vez, porque nace y muere en el instante.
Monitoreo: top y ps

top — dinámico, se actualiza en pantalla; se sale con q.

Tiempo compartido (time-sharing): un proceso no acapara la RAM de principio a fin (sería una cola secuencial ineficiente). El planificador (scheduler) reparte pequeños lapsos de CPU entre los procesos según su prioridad, alternando tan rápido que da la ilusión de paralelismo.

psestático (no se actualiza). Sin opciones: solo los procesos de la terminal actual (normalmente bash + el propio ps).

ps aux — todos los procesos del sistema, ordenados por PID ascendente. Columnas: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND.

  • Demonios: sin terminal asociada → ? en la columna TTY; por convención su nombre termina en d (systemd, kthreadd, watchdogd).
  • VSZ = Virtual Memory Size, RSS = Resident Set Size (el profesor lo aclarará la próxima clase).

ps -e -o pid,ppid,user,stat,cmd-e lista todos los procesos; -o elige las columnas a mostrar.

⚠️ Examen — sintaxis: las columnas tras -o van separadas por comas y SIN espacios; un espacio hace que la terminal devuelva error de sintaxis. Los estados de la columna STAT (S, R, R+, Ss…) se ven en clases posteriores.

El comando ls para administración y seguridad
  • ls a secas no da información útil. ls -l = formato largo (permisos, tamaño, dueño, fecha). ls -la = incluye archivos ocultos (los que empiezan con .).
  • Recomendado: ls -ltrl largo, t ordena por fecha de modificación (más reciente arriba), r invierte el orden (más reciente abajo, junto al prompt).
  • Uso en seguridad: en un incidente de intrusión ves de inmediato el último archivo creado o modificado por un atacante, sin hacer scroll en un directorio con cientos de archivos.
contextoContexto — origen de "TTY"

TTY viene de los teletipos (teletype) de los años 1940-50: máquina de escribir mecánica + transmisión de datos a una computadora. Con los sistemas de tiempo compartido de los 60, las terminales lógicas (pantallas, ventanas) heredaron las siglas. Dato histórico que remarcó el profesor: las primeras programadoras fueron mujeres.

Sincronización: race condition y deadlock
  • Los procesos compiten de forma concurrente por la CPU → condición de carrera (race condition).
  • Deadlock (bloqueo mutuo / "candado mortal"): "el proceso 1 dice: para continuar necesito que termine el proceso 2; y el proceso 2 dice: para continuar necesito que termine el proceso 1". No se debe estructurar un programa donde dos procesos dependen circularmente uno del otro.
Para repasar (tarea moral de la Clase 1)
  • No hay tarea para entregar. Tarea moral: repasar el sitio web del curso y el PDF 2 del Tema 1 ("ID de procesos") — es extenso, no verlo todo de golpe.
  • La próxima clase: teoría de fork() en C para crear procesos.
Clase 2 — permisos, bit setuid, /etc/passwd y fork()
Permisos UGO y chmod (notación decimal)

Regla mnemotécnica: "ugo" (como "Hugo" sin H) → Usuario (dueño), Grupo, Otros. chmod = change mode.

  r  w  x
  4  2  1        rwx = 4+2+1 = 7      rw- = 6      r-x = 5
  • chmod 777 archivo — todos los permisos para u, g y o.
  • chmod 644 archivo — archivo editable no ejecutable: dueño rw (6), grupo y otros r (4).
  • chmod 755 archivo — ejecutable típico: dueño rwx (7), grupo y otros r-x (5).
Bit SetUID (chmod con 4 dígitos)

Al ejecutar un binario, el proceso normalmente pertenece a quien lo ejecuta. Con el bit SetUID activo, quien lo ejecuta toma temporalmente los privilegios del dueño del archivo mientras dura la ejecución.

Se activa con un cuarto dígito a la izquierda en chmod, que indica sobre qué bloque:

4 = SetUID   (bloque de usuario)   ← el más usado
2 = SetGID   (bloque de grupo)
1 = sticky   (bloque de otros)
ej.  chmod 4511 archivo

⚠️ Riesgo de seguridad: si root pone SetUID a un ejecutable, cualquier usuario sin privilegios se vuelve root durante esa ejecución. Manejar con extrema precaución.

Usuarios en Linux · /etc/passwd · ID real vs. efectivo
  • ID real → dueño legítimo del proceso / archivo; es el que asigna /etc/passwd. Se obtiene con getuid() (igual que getpid() / getppid()).
  • ID efectivo → el usuario que está corriendo el programa en ese instante. Se obtiene con geteuid() (la "e" = effective).

cat /etc/passwd — campos separados por :

username : x : UID : GID : nombre_real : /home/... : shell
   |       |    |     |        |            |          |
 el 1º   contraseña  id    id de     GECOS (si no    dir.   tipo de
 es root  oculta   usuario  grupo    se puso, =user) home   shell

Usuarios de sistema con shell nologin: no pueden iniciar sesión interactiva (seguridad). [inaudible: la ruta se oye como "etc/al passworden"]

Tarea 2 — probar el bit SetUID

Metodología: dos terminales (una como root, otra como usuario sin privilegios). Las instrucciones de pantalla dividida en Codespaces están en la carpeta del Tema 3 del repo, aunque se usan desde el Tema 1. whoami muestra el usuario activo.

fdisk = comando solo-root para manipular la tabla de particiones (man fdisk). Sirve cualquier comando que pida privilegios. Pasos:

# 1) como usuario NO root:
fdisk -l                     # -> "permiso denegado"
                             #    (en Fedora puede solo regresar el prompt vacío)

# 2) como root, ver permisos actuales:
ls -l /sbin/fdisk            # -> 755  (rwxr-xr-x)

# 3) como root, activar SetUID:
chmod 4511 /sbin/fdisk       # -> r-s--x--x  (la 's' en vez de 'x' = SetUID activo;
                             #    el nombre se ve en rojo si hay color)

# 4) como usuario NO root otra vez:
fdisk -l                     # -> AHORA SÍ funciona: lista las particiones

# 5) OBLIGATORIO: revertir de inmediato
chmod 755 /sbin/fdisk        # (o los permisos que tuviera antes)

[inaudible: la ruta se dicta rápido como "diagonal se bin diagonal fis" → /sbin/fdisk]

Actividad 2: programa en C (programa02_ids.c en el repo) que imprime getuid() y geteuid(). Compilar gcc programa02_ids.c -o programa02_ids. Ejecutado normal → ID real == ID efectivo. Aplicando chmod 4511 al binario y ejecutándolo con otro usuario → salen distintos.

⚠️ Para sacar 10: las capturas deben mostrar ID real ≠ ID efectivo.

⚠️ No entregar la Tarea 2 todavía — el profesor no pudo hacer la demo en el laboratorio; reactivará la fecha de entrega después. (Recomienda clonar el repo localmente con git en vez de usar Codespaces.)

fork() — creación de procesos
  • Mecanismo nativo del kernel de Linux para crear procesos.
  • Es costoso: clona / duplica toda la estructura de memoria del proceso que lo llama.
  • El proceso hijo es copia idéntica del padre en datos y variables, salvo su PID y su PPID.
  • Tras fork(), padre e hijo corren en paralelo y asíncronos (ráfagas de CPU alternadas).

Retorno (tipo pid_t, encapsula un entero; se imprime con %d / %i):

al PADRE  ->  el PID del hijo   (entero != 0)
al HIJO   ->  0

La variable donde guardas el retorno se duplica: en el padre vale el PID del hijo, en el hijo vale 0. Se separa el código con:

if (id_hijo != 0) {
    // código exclusivo del padre
} else {
    // código exclusivo del hijo
}

Sin if, todo lo que va después de fork() se ejecuta en ambos → sale duplicado. En programa05_fork_v1.c: la línea de antes del fork() se imprime una vez; las dos de después, dos veces cada una.

Nota semántica del profesor: antes del fork() lo llama "proceso main / principal"; "padre" solo cuando ya hay hijo.

examen⚠️ Deadlock (candado / abrazo mortal)

Si el padre espera a que termine el hijo para continuar y a la vez el hijo espera a que termine el padre → bloqueo indefinido. Única salida: Ctrl + C.

Ejemplo de ejecución — programa05_fork_v2.c

Valores de esa corrida (programa05_fork_v2.c):

DatoValor
PID del padre30405
PID del hijo30406 (el entero inmediato siguiente)
id_hijo en el padre30406
id_hijo en el hijo0
getpid() en el hijo30406
getppid() en el padrePID de la shell (bash) que lanzó el programa
getppid() en el hijo30405 (su creador es el padre)

El if puede ser != 0 (padre primero) o == 0 (hijo primero), a gusto del programador.

Para repasar (Clase 2)

Tarea moral (sin entrega): leer con calma el PDF 2 del Tema 1 (procesos).

Clase 3 — Codespaces y ajuste de la Tarea 2

Contenedores en Codespaces: si sales del navegador sin destruir el contenedor, queda pausado pero vivo y sigue consumiendo la cuota gratis. Puedes tener varios (uno por materia); se pausan al salir y se retoman.

# iniciar un Codespace:
1. botón verde "Code" en el repo
2. pestaña "Codespaces"   (solo aparece si estás logueado en GitHub)
3. "Create codespace on main"
4. si pregunta por seguridad -> "Confiar y continuar"
# abre VS Code en el navegador; terminal abajo; se puede dividir (split terminal)

Tarea 2 ajustada: fdisk no viene en el contenedor de Codespaces.

  • Actividad 1 (probar SetUID con fdisk): solo quien tenga un Linux local propio.
  • Actividad 2 (obligatoria para todos): compilar y analizar programa02_ids.c con getuid / geteuid bajo el bit SetUID.

get = leer / obtener; set = escribir / establecer. C y Python no son estrictos con el nombre del archivo (a diferencia de Java): en C todo se estructura desde main.

Clase 3 — Demo del bit SetUID en Codespaces, paso a paso
# en ambas terminales:
whoami                       # -> codespace   (usuario NO privilegiado por defecto)

gcc programa02_ids.c -o programa02_ids
# al autocompletar con TAB, QUITAR el .c del -o para no sobrescribir el fuente
./programa02_ids            # real == efectivo == 1000  (lo corre su propio dueño)

# --- terminal izquierda: hacerse root ---
sudo su                     # whoami -> root
                            # con root: rm -rf /  te vuela todo -> destruir y recrear el Codespace
cat /etc/passwd             # metadatos de usuarios; codespace tiene ID 1000 (uid gid = 1000 1000)
cat /etc/shadow             # en algunas distros root (ID SIEMPRE 0) no está en /etc/passwd
                            # sino "en las sombras" en /etc/shadow, junto a demonios como sys
pwd                         # ruta absoluta actual  (/workspaces/arquitectura-cliente-servidor)

chown root:root programa02_ids     # change owner  usuario:grupo archivo
ls -l programa02_ids               # ahora pertenece a  root root  (antes codespace codespace)

chmod 700 programa02_ids           # solo el dueño (root) tiene rwx; grupo y otros = 0
# --- terminal derecha (usuario codespace, cae en "otros") ---
./programa02_ids                   # -> "Permiso denegado"

# --- terminal izquierda (root): activar SetUID ---
chmod 4511 programa02_ids          # 4 = SetUID en la terna del USUARIO
ls -l programa02_ids               # la 'x' del usuario se vuelve 's'/'S'; nombre en rojo (si hay color)

# --- terminal derecha ---
./programa02_ids                   # AHORA SÍ corre:
                                   #   Real     = 1000  (codespace, quien lo lanzó)
                                   #   Efectivo = 0     (root, dueño del archivo)

Ciclo de vida: el cambio de identidad es temporal — solo mientras el proceso vive en memoria. Antes de ejecutar y una vez que termina, el usuario sigue siendo el 1000.

Clase 3 — Bits especiales (Q&A): SetUID / SetGID / sticky

El 4.º dígito de chmod:

4 = SetUID  (usuario)
2 = SetGID  (grupo: cambia grupo real y efectivo)
6 = 4+2     (ambos)
1 = sticky bit   ej. chmod 1511 -> sin error; en la terna de "otros" aparece una 'T';
                 el archivo se ve en verde/dorado

No hay funciones "get others ID" en C. Solo existen las 4 estándar: getuid() / geteuid() (usuario) y getgid() / getegid() (grupo). Por eso no se puede demostrar dinámicamente el efecto del 1 (otros). Tarea de investigación que dejó el profe para sí mismo: el comportamiento exacto de la T (sticky) en los SO modernos.

Clase 3 — Gestión de errores en Linux: errno
  • errno (siempre en minúsculas): variable global que el sistema establece automáticamente cuando un programa en Linux tiene una condición de error. No se declara ni se inicializa; la provee el entorno de Linux.
  • ~131 códigos de error estándar (enteros 1–131). Cada uno tiene una macro en C que empieza con E; quitando la E da pista del error:
    • ENOENT (No ENTry) → código 2, "No such file or directory" (error de dedo en rutas o borrado accidental).
    • EPERM (Operation Not Permitted) → falta de privilegios.
    • EIO (Input/Output) → fallo físico o lógico de E/S.
  • 0 = éxito (success), estandarizado. Cualquier entero ≠ 0 se interpreta como error. Por eso int main termina con return 0; — le devuelve 0 al proceso padre.

programa03_codigos_sistema.c: bucle for de i = 0 (éxito) a 150. Aunque el estándar dice 131, se puede iterar más sin riesgo; los índices sobrantes imprimen Unknown error. En la distro del contenedor la tabla real llega hasta el código 133. Salida: índice 0Success; índice 2No such file or directory.

Clase 3 — Red de la Facultad de Ingeniería e IPs de la UNAM
  • 132.247.x.x y 132.248.x.x = direccionamiento público global de la UNAM.
  • Tercer octeto 59 → infraestructura de la Facultad de Ingeniería.
  • Años 90, al instalar el cableado Ethernet, la red de la FI se dividió en 4 subredes por edificio:
    • FI-A — Edificio Principal
    • FI-B — Anexo
    • FI-C — Ciencias Básicas
    • FI-P — Posgrado
  • El servidor de profesores está físicamente en la red FP (Posgrado) → de ahí profesores.fi.unam.mx (subdominio .fp.). IP del servidor institucional de perfiles: 132.248.59.6.
Clase 4 — strerror/perror, flujo de fork(), huérfanos y la familia exec

Detalle completo y demos en la Clase 04. Entre esta clase y la siguiente empieza el Tema 2: Estados de proceso.

  • strerror(n) devuelve la cadena del mensaje de sistema para el código n (printf("%d: %s\n", i, strerror(i))). En Fedora/RHEL el último código con mensaje es el 133 (desde 134Unknown error); el 41 está reservado sin mensaje.
  • perror("txt"): si la instrucción anterior falló, imprime txt: <mensaje del sistema> (añade ": " solo). La P es de "personalizar".
  • Flujo de fork(): tres caminos (error -1 / hijo 0 / padre con PID del hijo). Compacto: if ((child = fork()) == -1); equivalente descompuesto: pid_t ch; ch = fork(); if (ch == -1). Está en <unistd.h>; falla (-1) sobre todo por falta de memoria para duplicar la imagen. Doc oficial: buscar "Open Group" fork.
  • exit(): EXIT_FAILUREexit(1); EXIT_SUCCESSexit(0).
  • Proceso huérfano = padre muerto, hijo vivo → lo adopta PID 1 (init/systemd). En Linux moderno hay subprocesos reapers que adoptan huérfanos, por eso el PPID de adopción a veces no es 1. Ver distro: cat /etc/os-release. Jerarquía: hijo / padre (llamó fork) / abuelo (la terminal).
  • Familia exec (6 funciones): L = lista de args terminada en NULL / (char *)0 · V = vector de punteros · E = entorno propio (envp) · P = busca en $PATH. Con path (ruta absoluta): execl/execv/execle/execve; con file (nombre): execlp/execvp.
  • Reemplazo de imagen: un exec exitoso destruye la imagen del proceso original (muere ahí); el código de abajo es inalcanzable. Si falla devuelve -1 y el proceso sigue → regla: if (execl(...) == -1) { perror("execl"); exit(EXIT_FAILURE); }. El argumento 0 debe ser el nombre del ejecutable: execl("/bin/ls", "ls", "-l", "/", (char *)0).
Contexto histórico: origen de los sistemas operativos

1944–1952: John Von Neumann propone el diseño de una computadora que almacena un programa y usa un procesador central (arquitectura de programa almacenado), junto con el sistema de numeración binaria. Esto permitió que la computadora se usara más allá de la ciencia: en economía, administración, producción, etc. 1956: se crea el primer Sistema Operativo de la historia, para un IBM 704. Solo encadenaba la ejecución de un programa cuando el anterior terminaba. Década de 1960: revolución en los SO — aparecen los conceptos de sistema multitarea, multiusuario, multiprocesador y en tiempo real. En esta década nace UNIX, base de la mayoría de los SO actuales.

Origen de Unix

Unix es un sistema operativo portable, multitarea y multiusuario, desarrollado por empleados de los laboratorios Bell de AT&T (Ken Thompson, Dennis Ritchie y Douglas McIlroy). A finales de los 60, el MIT, los laboratorios Bell y General Electric trabajaban en un SO experimental llamado Multics, pensado para mejorar la seguridad e interactividad. AT&T se desvinculó del proyecto, pero Ken Thompson siguió usando la máquina GE-645 para un juego llamado "Space Travel"; al resultar lento y caro, lo reescribió en ensamblador para un DEC PDP-7 junto con Dennis Ritchie. Esa experiencia, sumada a lo aprendido en Multics, llevó a Thompson y Ritchie (con Rudd Canaday y otros) a crear un nuevo SO con sistema de archivos y multitarea propios: UNICS (Uniplexed Information and Computing System), renombrado después a Unix. Hoy existen varias versiones desarrolladas por distintas compañías: SunOS, Ultrix, HP-UX, entre otras.

Cuatro filosofías de trabajo de Unix
  • Fue desarrollado por programadores, para programadores
  • Asume que el usuario sabe lo que hace
  • Todo lo ve y lo maneja como archivo
  • Si un comando no manda mensaje de error, se ejecutó satisfactoriamente
¿Qué es Linux?

Linux es un sistema operativo open source, diseñado y creado en 1991 por Linus Torvalds mientras estaba en la universidad, como una alternativa gratuita y libre a MINIX (que a su vez se basaba en los principios de Unix). Se lanzó bajo la Licencia Pública General (GPL) de GNU: cualquiera puede ejecutarlo, estudiarlo, compartirlo y modificarlo; el código modificado también puede redistribuirse (incluso venderse), siempre bajo la misma licencia. Esto lo distingue de sistemas propietarios como Unix o Windows, que están bloqueados y no se pueden modificar. "Linux" suele referirse al kernel de Linux junto con las herramientas, aplicaciones y servicios que lo acompañan (parte de ellos del proyecto GNU) — por eso la Free Software Foundation prefiere el nombre "GNU/Linux".

¿Qué es un proceso?

Un proceso es una instancia de un programa en ejecución. Mientras el ejecutable no se ejecute, es solamente un archivo ocupando espacio en disco. Un mismo archivo ejecutable puede generar varias instancias (varios procesos) al mismo tiempo: cada ejecución es un proceso distinto, aunque provenga del mismo programa.

Estructura de un proceso
  • Segmento de texto: contiene las instrucciones que entiende la CPU
  • Segmento de datos: contiene los datos que deben cargarse al iniciar el proceso
  • Segmento de pila: serie de marcos de pila que se apilan al llamar una función y se desapilan al retornar de ella; cada marco contiene los parámetros de la función, las variables locales y la información necesaria para restaurar el marco anterior
Procesos padre e hijo en Linux

Linux es un sistema de tiempo compartido que permite ejecutar varios procesos a la vez (multiproceso). El planificador es la parte del núcleo que gestiona la CPU y decide qué proceso la ocupa en cada instante. Todo proceso en Linux, excepto el primero (proceso init), se crea mediante una llamada a fork(). El proceso que llama a fork() es el proceso padre, y el que se crea es el proceso hijo. Un padre puede tener varios hijos, pero cada proceso tiene un único padre.

Identificación de procesos: PID y PPID

Cada proceso en Linux se identifica con un PID (ID de proceso): un número entero positivo asignado al crearse. getpid() devuelve el PID del proceso que la invoca. getppid() devuelve el PID de su proceso padre (PPID).

Ejemplo 1 — Número de proceso (print-pid.c)
#include <stdio.h>
#include <unistd.h>  // Declara funciones parte del estándar POSIX

int main () {
    printf("The process ID is %d\n", getpid());
    printf("The parent process ID is %d\n", (int) getppid());
    return 0;
}
Otros identificadores de un proceso
AtributoTipoFunción
ID de procesopid_tgetpid(void)
ID de proceso padrepid_tgetppid(void)
ID real del usuariouid_tgetuid(void)
ID efectivo del usuariouid_tgeteuid(void)
ID real de grupogid_tgetgid(void)
ID efectivo de grupogid_tgetegid(void)
ID real vs. ID efectivo

Cada proceso tiene dos IDs de usuario y dos de grupo, usados principalmente por seguridad (permisos de acceso a archivos). ID real de usuario/grupo: identifica al usuario real, tal como aparece en /etc/passwd. ID efectivo de usuario/grupo: se usa para acceder a archivos de otros usuarios, enviar señales a procesos o ejecutar programas "setuid". Cuando un proceso ejecuta un programa "setuid", el núcleo asigna al EUID del proceso el propietario de ese programa, y al EGID el grupo del propietario.

Ejemplo 2 — Identificadores de proceso (ids.c)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void) {
    printf("Real user ID: %d\n", getuid());
    printf("Effective user ID: %d\n", geteuid());
    printf("Real group ID: %d\n", getgid());
    printf("Effective group ID: %d\n", getegid());
    return 0;
}
Comando ps de Linux

ps despliega los procesos en ejecución en el sistema. Sin opciones, muestra los del shell actual (la primera columna es el PID; normalmente aparecen el propio shell y el proceso ps). ps -e -o pid,ppid,command muestra todos los procesos del sistema (-e) especificando qué columnas mostrar (-o): PID, PPID y el comando.

Tres formas de crear procesos en Linux

La primera es con system(), parte de las librerías estándar de C. Las otras dos son fork() y exec(), que es la manera nativa en que Linux crea procesos.

system()

Ejecuta un comando desde un programa, como si se invocara desde un Shell. Invocar un programa con privilegios de root usando system() puede dar resultados distintos entre sistemas Linux, ya que depende de la versión del Shell utilizado. Sintaxis: int system(const char *string)

Ejemplo 3 — Uso de system() (system.c)
#include <stdlib.h>

int main () {
    int return_value;
    return_value = system("ls -l /");
    return return_value;
}
fork()

Crea un nuevo proceso (el hijo) como copia del proceso padre, excepto por el PID y el PPID. Al llamar fork(), el núcleo: · Busca una entrada libre en la tabla de procesos y la reserva para el hijo. · Le asigna un PID único e invariable durante toda su vida. · Copia el contexto de nivel de usuario del padre al hijo. · Copia las tablas de control de archivos locales del padre al hijo. · Devuelve al padre el PID del hijo, y al hijo le devuelve 0. Mientras se crea el hijo, el padre sigue ejecutándose desde el punto donde se invocó fork(); el hijo también ejecuta el programa desde ese mismo punto. Sintaxis: pid_t fork(void)

Ejemplo 4 — Creación de procesos (fork.c)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main () {
    pid_t child_pid;
    printf("the main program process ID is %d\n", (int) getpid());
    child_pid = fork();
    if (child_pid != 0) {
        printf("this is the parent process, with id %d\n", (int) getpid());
        printf("the child's process ID is %d\n", (int) child_pid);
    } else {
        printf("this is the child process, with id %d\n", (int) getpid());
    }
    return 0;
}
examen⚠️ Condición de carrera (race condition)

No se puede predecir si el proceso padre seguirá ejecutándose antes o después de que se cree el hijo (o viceversa), ya que ambos corren de forma asíncrona tras fork(). Por eso no debe escribirse, en el proceso hijo, código que dependa del padre (o viceversa): hacerlo genera una condición de carrera con comportamiento impredecible en el programa.

exec()

A diferencia de fork(), exec() reemplaza el programa que se está ejecutando en un proceso por otro programa. Cuando se llama exec(), el proceso invocador termina inmediatamente y comienza a ejecutarse el nuevo programa desde el inicio (si exec() no encontró error). Si exec() tiene éxito, no regresa al proceso que lo llamó (fue reemplazado por completo). Si falla, regresa -1.

Ejemplo 6 — Uso de exec (execs.c)
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(void) {
    char *args[] = {"/bin/ls", NULL};
    if (execve("/bin/ls", args, NULL) == -1) {
        perror("execve");
        exit(EXIT_FAILURE);
    }
    puts("shouldn't get here");
    exit(EXIT_SUCCESS);
}
Familia de funciones exec()
  • execl(), execv(), execle(), execve() reciben una ruta (path) como primer argumento
  • execlp(), execvp() reciben el nombre de un archivo; si no contiene "/", lo buscan en $PATH
  • Las funciones con "l" (execl, execle, execlp) reciben una lista de argumentos terminada en un apuntador NULL
  • Las funciones con "v" (execv, execve, execvp) reciben un arreglo de apuntadores terminado en una cadena NULL
  • execve() y execle() además permiten definir un ambiente propio (envp); las demás heredan el ambiente a través de la variable environ
Sintaxis de la familia exec
int execl (char *path, char *arg0, char *arg1,... char *argN, (char *)0);
int execv (char *path, char *argv[ ]);
int execle(char *path, char *arg0, char *arg1,... char *argN, (char *)0, char *envp[ ]);
int execve(char *path, char *argv[ ], char *envp[ ]);
int execlp(char *file, char *arg0, char *arg1,... char *argN, (char *)0);
int execvp(char *file, char *argv[ ]);
Variables de ambiente

putenv(const char *string) agrega o modifica una variable de ambiente. getenv(const char *name) consulta el valor de una variable de ambiente. Se usan típicamente junto con execve()/execle() para construir el ambiente (envp) que recibirá el nuevo programa.

Ejemplo 7 — Variables de ambiente (testenv.c)
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(void) {
    char envval[] = {"MYPATH=/user/local/someapp/bin"};
    if (putenv(envval))
        puts("putenv failed");
    else
        puts("putenv succeeded");

    if (getenv("MYPATH"))
        printf("MYPATH=%s\n", getenv("MYPATH"));
    else
        puts("MYPATH unassigned");

    if (getenv("YOURPATH"))
        printf("YOURPATH=%s\n", getenv("YOURPATH"));
    else
        puts("YOURPATH unassigned");
    exit(EXIT_SUCCESS);
}
Compilación de los ejemplos

cc myecho.c –o myecho cc execve.c –o execve ./execve ./myecho (Analizar el resultado: execve reemplaza el proceso actual por myecho, pasando su propio nombre como argumento.)

Cuestionario — código a analizar (fork-exec.c)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int spawn (char* program, char** arg_list) {
    pid_t child_pid;
    child_pid = fork ();
    if (child_pid != 0)
        return child_pid;
    else {
        execvp (program, arg_list);
        fprintf (stderr, "an error occurred in execvp\n");
        abort ();
    }
}

int main () {
    char* arg_list[] = { "ls", "-l", "/", NULL };
    spawn ("ls", arg_list);
    printf ("done with main program\n");
    return 0;
}
Cuestionario

Describe qué hace el código anterior (fork-exec.c) y qué resultados arroja. Pista: spawn() crea un proceso hijo con fork() y reemplaza ese hijo con el programa "ls" usando execvp(); el proceso padre continúa su ejecución normal e imprime "done with main program" — el orden entre esa impresión y la salida de ls no está garantizado.

Para repasar
  • ¿Qué diferencia hay entre fork() y exec()?
  • ¿Por qué el uso de fork() puede generar una condición de carrera (race condition)?
  • ¿Qué representa el PID y qué el PPID de un proceso?
  • ¿Cuál es la diferencia entre el ID real y el ID efectivo de usuario/grupo, y cuándo se usa cada uno?
  • ¿Qué hace el comando ps -e -o pid,ppid,command y qué muestra cada columna?
  • ¿Qué ocurre si execve() tiene éxito? ¿Y si falla?
Referencias multimedia
Bibliografía
  • Francisco Manuel Márquez García — "Unix: programación avanzada". Editorial RA-MA
  • Richard Stevens — "Advanced Unix Programming". Editorial Addison Wesley
  • Kurt Wall — "Linux Programming by Example". Editorial QUE
Programas del profesor — Tema 1

Código del sitio del profesor, guardado como archivo real en files/prof-tema1/. Cada nombre abre el código y su salida en la página de Programas.

  • system()
  • programa04_system_v1.c — Ejemplo mínimo de system("ls -l /"): el shell ejecuta el comando y return_value recibe el estado devuelto por el shell (0 si todo salió bien).
  • programa04_system_v2.c — Igual que el anterior pero además interpreta el valor devuelto con WIFEXITED / WEXITSTATUS (tema de wait(), se ve más adelante) y repite con un comando que falla (ls -l no_existe).
  • fork()
  • programa05_fork_v1.c — Primer contacto con fork(): después de la llamada hay dos procesos ejecutando el mismo código, por eso los printf 2 y 3 salen duplicados. id_hijo vale 0 en el hijo y el PID del hijo en el padre.
  • programa05_fork_v2.c — El if (id_hijo != 0) separa el código que ejecuta el padre del que ejecuta el hijo. El orden en que aparecen los bloques PADRE/HIJO no está garantizado (condición de carrera).
  • programa08_child.cfork() con manejo de error: si devuelve -1 hace perror("fork") y exit. El hijo imprime in child y sus pid/ppid y termina con exit; el padre imprime in parent.
  • Errores del sistema (errno / perror)
  • programa03_codigos_sistema.c — Recorre strerror(0..150) e imprime el mensaje de cada código de error de Linux. Es la versión «en C» de la tabla de los 131 códigos.
  • programa06_error.c — Provoca un error real: rename("antes.txt","despues.txt") cuando antes.txt no existe. Muestra errno, strerror(errno), perror() y un switch que traduce el tipo de error.
  • programa07_error.c — Versión mínima del anterior: solo errno + perror(). Sirve para ver que perror("texto") imprime texto: <mensaje del errno actual>.
  • programa09No es C, es un script de shell. Lista las macros de error (EPERM, ENOENT, …) con su número, leyéndolas de /usr/include/asm-generic/errno*.h. Solo funciona en Linux.
  • Familia exec()
  • programa10_execl.cexecl(path, arg0, …, NULL): lista de argumentos y ruta absoluta. A propósito llama a /bin/lsa (no existe) para mostrar el caso de error: execl devuelve -1. Si tuviera éxito, el proceso se reemplaza y las líneas siguientes nunca se ejecutan.
  • programa11_execv.cexecv(path, argv[]): igual que execl pero los argumentos van en un arreglo. Aquí sí usa /bin/ls, así que reemplaza el proceso y muestra el listado; la última línea no se imprime.
  • programa18_execlp.cexeclp(file, …): como execl pero recibe el nombre del comando (ls, no /bin/ls) y lo busca en $PATH — de ahí la «p».
  • programa19_execvp.cexecvp(file, argv[]): versión con arreglo de argumentos de execlp. Mismo resultado.
  • programa12_getenv_user1.c — Programa «hijo» que invocan los ejemplos de exec: imprime su PID y el valor de la variable de entorno USER con getenv.
  • programa13_getenv_user2.c — Igual, pero también lee MI_VARIABLE. Si no está definida, getenv devuelve NULL y se imprime (null).
  • programa14_execle1.cexecle(path, …, NULL, envp[]): lista de argumentos más un entorno propio (envp) que solo vale para el programa ejecutado. El getenv("USER") de antes del execle todavía muestra tu USER real.
  • programa15_execle2.cexecle invocando a nuestro programa12; el envp le pasa USER=jose solo para esa ejecución. El PID no cambia: exec reemplaza el programa, no crea proceso nuevo.
  • programa16_execle3.c — Igual que el anterior con dos variables (USER, MI_VARIABLE) e invocando programa13.
  • programa17_execve1.cexecve(path, argv[], envp[]): equivale a execle pero todo en arreglos (argumentos y entorno). Es la llamada «base» del sistema; las demás exec* son envoltorios de ésta.
  • programa20_myecho.c — Programa auxiliar: imprime argv[0..argc-1], uno por línea. Sirve para ver exactamente qué argumentos recibe un programa lanzado con exec.
  • programa21_execve.c — Recibe un ejecutable en argv[1] y lo lanza con execve, pasándole newargv (con newargv[0] = argv[1]) y entorno NULL. Ejercicio: explicar qué hace newargv[0] = argv[1] y modificar el bloque final para que, si execve falla, el printf no se ejecute.
  • Patrón fork() + exec()
  • programa22_fork-exec.c — El patrón completo con trazas: spawn() hace fork(); el hijo llama execvp("./programa01_print-pid") (se reemplaza), el padre regresa de spawn y sigue. El orden exacto de las líneas varía.
  • programa23_fork-exec.c — Versión limpia del patrón (sin trazas): el hijo ejecuta ls -l /, el padre imprime Termina el proceso padre. El diagrama programa23_fork-exec.png ilustra este flujo.
  • Identificadores del proceso
  • programa01_print-pid.c — Imprime el PID del proceso (getpid) y el PID de su padre (getppid).
  • programa02_ids.c — Imprime los IDs reales y efectivos de usuario y grupo. En un ejecutable con el bit setuid, el UID efectivo sería el del dueño del archivo, no el tuyo.
Diagramas (Tema 1)
system() — el shell hijo ejecuta el comando y devuelve su estado al proceso que llamó.
system() — el shell hijo ejecuta el comando y devuelve su estado al proceso que llamó.
fork() — se crea un proceso hijo, copia del padre; la ejecución continúa en ambos desde el punto de la llamada.
fork() — se crea un proceso hijo, copia del padre; la ejecución continúa en ambos desde el punto de la llamada.
fork() — qué devuelve la llamada a cada proceso: 0 al hijo, el PID del hijo al padre, -1 si falla.
fork() — qué devuelve la llamada a cada proceso: 0 al hijo, el PID del hijo al padre, -1 si falla.
exec() — reemplaza la imagen del proceso actual por otro programa; no crea un proceso nuevo.
exec() — reemplaza la imagen del proceso actual por otro programa; no crea un proceso nuevo.
fork() + exec() — flujo combinado: el padre hace fork, el hijo se reemplaza con exec, el padre continúa.
fork() + exec() — flujo combinado: el padre hace fork, el hijo se reemplaza con exec, el padre continúa.
Referencias del profesor (Tema 1)
Tema 2 — Creación de socket servidor y cliente · sin ver
  • 2.1 Socket en TCP.
    • 2.1.1 Servidor eco.
    • 2.1.2 Cliente eco.
  • 2.2 Socket en UDP.
    • 2.2.1 Servidor eco.
    • 2.2.2 Cliente eco.
  • 2.3 Concepto de hilos.
    • 2.3.1 Creación de servidor concurrente.
  • 2.4 Definición de DAEMON.
Tema 3 — Servidores y clientes sincronizados · sin ver
  • 3.1 Procesos.
  • 3.2 Semáforos y sincronización.
  • 3.3 Lectura y escritura de archivos.
  • 3.4 Servidores orientados a conexión.
    • 3.4.1 HTTP.
    • 3.4.2 FTP.
    • 3.4.3 Otros servidores.
  • 3.5 Servidores no orientados a conexión.
    • 3.5.1 Servidores P2P.
    • 3.5.2 Otros servidores.
  • 3.6 Desarrollo de aplicaciones.
    • 3.6.1 Generación de protocolos de la capa de aplicación.
Tema 4 — Sockets broadcasting y multicasting · sin ver
  • 4.1 Broadcast.
    • 4.1.1 Definición de broadcast.
    • 4.1.2 Funcionamiento de broadcast.
    • 4.1.3 Creación del socket broadcast.
  • 4.2 Multicast.
    • 4.2.1 Definición de multicast.
    • 4.2.2 Funcionamiento multicast.
    • 4.2.3 Creación del socket multicast.
    • 4.2.4 Ruteo multicast.
Tema 5 — Implantación de servidores con criptografía y código seguro · sin ver
  • 5.1 SSL y TLS.
  • 5.2 Servidores con criptografía.
    • 5.2.1 Servidor HTTP.
    • 5.2.2 Servidor FTP.
    • 5.2.3 Servidor Secure Shell.
  • 5.3 Clientes con criptografía.
    • 5.3.1 Cliente HTTP.
    • 5.3.2 Cliente FTP.
    • 5.3.3 Cliente Secure Shell.
  • 5.4 Servidores y clientes implantando código seguro.
Tema 6 — Herramientas para la comunicación entre servidores · sin ver
  • 6.1 Servidores Web.
    • 6.1.1 HTML.
    • 6.1.2 Programación desde cliente.
    • 6.1.3 Instalación.
    • 6.1.4 Configuración.
  • 6.2 Servidores de bases de datos.
    • 6.2.1 SQL.
  • 6.3 Servidores de correos.
  • 6.4 Conceptos.
    • 6.4.1 Frameworks.
    • 6.4.2 Middleware.
    • 6.4.3 Servidores de aplicaciones.
    • 6.4.4 Servicios web.
  • 6.5 Utilización de un framework en un servidor de aplicaciones.

Clases grabadas, en orden. Cada tarjeta abre la página completa de esa clase.

Clase 0125 ago 2026 · Tema 1Linux y procesos: imagen de un proceso, segmentos de memoria (text/data/BSS/heap/stack), PCB, comandos size/pmap/ps/top, árbol PID/PPID, time-sharing, race condition y deadlock. Clase 0227 ago 2026 · Tema 1Permisos UGO / chmod decimal, bit SetUID (chmod 4511), /etc/passwd, ID real vs. efectivo, Tarea 2 y fork() (retorno pid_t, PID del hijo al padre / 0 al hijo, deadlock). Clase 031 sep 2026 · Tema 1Demo del bit SetUID paso a paso en Codespaces, bits especiales (SetGID / sticky), /etc/shadow, errno y códigos de error, y la red de la Facultad de Ingeniería (subredes e IPs de la UNAM). Clase 043 sep 2026 · Tema 1strerror() y perror(), control de flujo de fork(), procesos huérfanos y reapers (adopción por PID 1), y la familia de funciones exec (L/V/E/P, reemplazo de imagen, argumento 0, código inalcanzable). Clase 058 sep 2026 · Tema 1 (no asistida)⚠️ No asistí por una entrevista — contenido reconstruido del sitio del profesor (Práctica 3): 9 estados de un proceso, las 19 señales de UNIX System V, función wait() y sus macros, procesos zombie, terminación con kill()/abort(). Clase 0610 sep 2026 · Tema 1Repaso confirmado de Clase 05 (huérfanos, SIGCHLD=17 en X86/ARM). Manejo de señales con signal(): 3 escenarios (ignorar, tratar, inalterable), handlers void, analogía Avada Kedavra para SIGKILL. Control fg/bg: Ctrl+Z, bg, jobs, fg %N.