|
G A C E T A
D E L I N U X |
|
select()
sobre message queue Traducción al español por Miguel
Campos
|
Cuando se usa message queue o cualquier otra utileria de Unix basada en File descriptor, lo más inconveniente es que message queue no soporta llamadas del sistema a select() . Así que usualmente los programadores Unix resuelven el problema de muliplexeo de I/O en una simple pero fea forma parecida a esto:
while(1)
{
select on socket with timeout;
...
wait on a message queue with IPC_NOWAIT
}
Ciertamente la implementación es fea. No me agrada. Otra solución podria ser adoptar multi-threading. Pero en este artículo, me gustaría mostrar un enfoque divertido, esto es, implementando una nueva llamada al sistema denominada msgqToFd(). No es mi intención el proporcionarles una implementación flexible terminada, ya en el kernel y libre de errores. Solo quiero mostrarles mi experimento. Este artículo le puede resultar interesante a los lectores que gusten de jugar con El código fuente del Kernel de GNU/Linux
Aqui esta su declaración.
int msgqToFd(int msgq_id)
Regresa un file descriptor correspondiente a una message queue, que puede ser usada con select().
Si sucede cualquier error regresa -1.
Una aplicación puede usar la llamada como sigue
...
q_fd = msgqToFd(msgq_id);
while(1)
{
FD_ZERO(&rset);
FD_SET(0, &rset);
FD_SET(q_fd, &rset);
select(q_fd + 1, &rset, NULL, NULL, NULL);
if(FD_ISSET(0, &rset))
{
...
}
if(FD_ISSET(q_fd, &rset))
{
r = msgrcv(msgq_id, &msg, sizeof(msg.buffer), 0, 0);
...
}
}
Un file descriptor se asocia con una estructura de archivos. En esta, hay una serie de operaciones soportadas por este tipo de archivo llamadas file_operations. En la estructura file_operations hay una entrada llamada poll. Lo que la llamada genérica a select() hace es llamar esta función poll() para obtener el estado de el archivo (o de un socket o lo que sea) tal como el nombre lo sugiere.
En general el select() funciona así:
while(1)
{
for each file descriptor in the set
{
call file's poll() to get mask.
if(mask & can_read or mask & can_write or mask & exception)
{
coloca la bandera para este fd de tal forma que el archivo sea de lectura/escritura o si se dio una
excepcion.
retval++;
}
}
if(retval != 0)
break;
schedule_timeout(__timeout);
}
Para una implementación detallada de select(),por favor dale un vistazo a sys_select() y do_select() en fs/select.c. del fuente estandar del kernel.
Otra cosa que se requiere entender espoll_wait(). Lo que hace es poner el proceso actual en una cola de espera provista por las utilerias del kernel como archivo ,pipe socket o como en este caso , message queue.
Cabe destacar que el proceso actual puede esperar en varias colas de espera llamando select()
La llamada al sistema debe regresar un file descriptor correspondiente a una message queue. El file descriptor debe apuntar a una estructura de archivos que contenga file_operations para la message queue.
Para hacer esto, sys_msgqToFd() hace lo siguiente:
Con msqid, localiza la correspondiente struct msg_queue
obtiene un nuevo inode llamando a get_msgq_inode()
obtiene un nuevo file descriptor con get_unused_fd()
obtiene una nueva estructura de archivos con get_empty_filp()
initialize inode, file structure
Prepara file_operations del archivo con msgq_file_ops
Prepara private_data con msq->q_perm.key
instala fd y la estructura de archivos con fd_install()
regresa el nuevo fd
Por favor denle un vistazo a msg.c y a msg.h que se adjuntan a este archivo. También vea sys_i386.c
La implementación de msgq_poll() es bastante simple.
Lo que hace es
Con file->private_data, que es una llave para un message queue, localiza la message queue correspondiente
Coloca el proceso actual en lacola de espera de la message queue llamando poll_wait()
Si la message queue esta vacia (msq->q_qnum == 0), Coloca la máscara para escritura ( esto puede generar algunos argumento pero olvidemonos de eso por ahora). Si no, coloca la máscara para lectura
Regresa la máscara
Para soportar poll() en una message queue, necesitaremos modificar el código fuente de la message queue existente.
La modificación incluye:
Agregar un encabezado de cola de espera a struct msg_queue, que será usado para colocar ahi un proceso para select(). También el encabezado para la cola de espara deberá ser inicializado cada que la message queue se cree. Por favor observa struct msg_queue y newque() en msg.c.
Cada que un nuevo mensaje se cree en la message queue ,el proceso esperando en la message queue ( llamando a select()) deberá ser "despertado". Vea sys_msgsnd() en msg.c.
Cuando una message queue es borrada o sus propiedades cambian, todos los procesos que esperan en la message queue (llamando a select()) deberán ser despertados . Vease sys_msgctl() y freeque() en msg.c.
Para crear un nuevo inode y una estructura de archivos , necesitaremos inicializar ciertas cosas del sistema de archivos
s para VFS para que opere adecuadamente. Para esto necesitaremos código adicional para registrar un nuevo sistema de archivos e inicializar algo. Vease msg_init() en msg.c.
Todos los cambios son definidos con MSGQ_POLL_SUPPORT. Asi que será fácil ubicar los cambios.
Para obtener una estructura de archivos , necesitaremos inicializar las propiedades f_vfsmnt y f_dentry del archivo de forma adecuada. De otra forma apareceran mensajes de OOPS en la consola. Para que VFS trabaje correctamente con esta nueva estructura de archivos , necesitaremos inicializaciones adicionales, que ya se explicaron anteriormente.
Desde que soportamos solamente poll() para file_operations, no necesitamos encargarnos de cada detalle del código de inicialización del sistema de archivos. Todo lo que necesitamos es inicializar adecuadamente f_dentry y f_vfsmnt. Casi todo el código fue copiado de pipe.c.
Para agregar la nueva llamada al sistema necesitamos hacer 2 cosas.
El primer paso es agregar la nueva llamada en el nivel kernel,
cosa que ya hicimos (sys_msgqToFd()).
en el kernel
GNU/Linux , todos las llamadas relacionadas al
sistema V IPC son despachadas por sys_ipc()
en arch/i386/kernel/sys_i386.c. sys_ipc()
usa un número de llamada para identificar una llamada al
sistema solicitada. Para despachar la nueva llamada , tenemos que
definir un nuevo número de llamada (la cual es 25) para
sys_msgqToFd() y modificar sys_ipc()
para llamar sys_msgqToFd(). Solo por referencia ,
vease arch/i386/kernel/entry.S in the standard kernel
source and sys_ipc() en sys_i386.c
adjunto en este artículo.
El segundo paso es agregar una función"stub" para las aplicaciones a nivel de usuario. Actualmente todas funciones "stub" de las llamadas a sistema son provistas por la GLIBC.Y para agregar una nueva llamada a sistema, se tiene que modificar GLIBC compilarla e instalarla. Oh demonios, NO GRACIAS!!!. No quiero hacerlo y no quiero que lo hagan ustedes. Para resolver este problema hice un poco de "copy & paste" deGLIBC. Si ves en user/syscall_stuff.c adjunta a este articulo, hay una funcion llamada msgqToFd(), que es la "stub" para la llamada al sistema msgqToFd() .
Lo que hace es simplemente
return INLINE_SYSCALL(ipc, 5, 25, key, 0, 0, NULL);
He aqui una corta descripción para la macro.
ipc : numero de llamada al sistema para sys_ipc(). ipc es expandida a __NR_ipc, que es 117.
5 : Numero de argumentos para la macro.
25 : numero de llamada para sys_msgqToFd()
key : argumento para sys_msgqToFd()
INLINE_SYSCALL inicializa los argumentos adecuadamente e invoca la interrupción 0x80 para cambiar a modo kernel para invocar una llamada a sistema.
No estoy seguro acerca del uso práctico de esta modificación. Sólo queria ver si este tipo de modificaciones era posible o no.
Ademas de eso , me gustaría hablar de un par de cosas que necesitan ser comentadas.
Si dos o mas procesos accesan a un message queue y un proceso esta esperando en la message queue con msgrcv() y otro esta esperando con select(), el proceso o thread que formo la message queue recibirá el nuevo mensaje. vease pipelined_send() en msg.c.
Para probar que se pueda escribir, msgq_poll() coloca la bandera o máscara en solo lectura si la message queue esta vacia. Realmente podriamos colocar esta máscara como solo escritura si la message queue no esta llena y no habría mucha diferencia. Pero escogi esta implementación por simple.
Pensemos acerca de este escenario.
Una cola es creada
Un file descriptor para la cola es creado
La cola es eliminada
En este caso que debemos hacer? Una buena solución sería cerrar el fd cuando la cola sea eliminada. pero esto es imposible si se toma en cuenta que una message queue puede ser eliminada por cualquier proceso con los derechos adecuados. Esto significa que un proceso que elimine la cola puede no tener un descriptor de archivos (file descriptor) asociado con la message queue aun si esta message queue esta mapeada a un file descriptor por cualquier otro proceso.
Adicionalmente, si la misma cola (con la misma llave) se crea nuevamente, el mapeo se mantendría arriba.
Problema de eficiencia. Todos los procesos esperando en la cola de espera que esten llamando select() se despertarán cuando llegue un nuevo mensaje. Eventualmente solo un proceso recibira el mensaje y los demas procesos volveran a dormir.
No hay soporte para el tipo de mensaje. Si importar el tipo de mensaje,si existe cualquiera , select() terminará.
DIY :-)
msg.c Implementacion
modificada de la message queue
msg.h Archivo
de cabecera del message queue
sys_i386.c
Modificado para la nueva llamada a
sistema
user/Makefile
Makefile para probar el
programa (renombrar de Makefile.txt a
Makefile)
user/syscall_stuff.c Función
de Stub para msgqToFd()
user/msg_test.h
Encabezado para msgqToFd()
user/msgq.c Programa
de prueba
user/msgq2.c Otro
programa de prueba
Recomiendo usar GNU/Linux kernel 2-4-20 sobre x86 para este
experimento.
Para construir un nuevo kernel con esta modificación,
Sugiero que se debería
copiar
msg.c a ipc/msg.c
msg.h a include/linux/msg.h
sys_i386.c a arch/i386/kernel/sys_i386.c
contruirlo
e instalarlo!!!!
Antes de correr los programas de prueba debes
estar seguro de hacer los archivos llave:
touch
.msgq_key1
touch .msgq_key2
Copyright © 2003, Hyouck
"Hawk" Kim. Licencia de Copia
http://www.linuxgazette.com/copying.html
Publicado
en el número 92 de Linux Gazette, Julio 2003