¿Qué sucede con los datos auxiliares de flujo de Unix en lecturas parciales?

18

Entonces, he leído mucha información sobre datos auxiliares de flujo de Unix, pero una cosa que falta en toda la documentación es ¿qué se supone que sucederá cuando hay una lectura parcial?

Supongamos que estoy recibiendo los siguientes mensajes en un búfer de 24 bytes

msg1 [20 byes]   (no ancillary data)
msg2 [7 bytes]   (2 file descriptors)
msg3 [7 bytes]   (1 file descriptor)
msg4 [10 bytes]  (no ancillary data)
msg5 [7 bytes]   (5 file descriptors)

La primera llamada a recvmsg, obtengo todo msg1 (¿y parte de msg2? ¿El sistema operativo lo hará alguna vez?) Si obtengo parte de msg2, ¿obtengo los datos auxiliares de inmediato y necesito guardarlos para la próxima lectura? cuando sé lo que el mensaje realmente me decía que hiciera con los datos? Si libero los 20 bytes de msg1 y luego llamo a recvmsg nuevamente, ¿entregará alguna vez msg3 y msg4 al mismo tiempo? ¿Los datos auxiliares de msg3 y msg4 se concatenan en la estructura del mensaje de control?

Si bien podría escribir programas de prueba para descubrir esto experimentalmente, estoy buscando documentación sobre cómo se comportan los datos auxiliares en un contexto de transmisión. Parece extraño que no pueda encontrar nada oficial en él.


Voy a agregar mis hallazgos experimentales aquí, que obtuve de este programa de prueba:

https://github.com/nrdvana/daemonproxy/blob/master/src/ancillary_test.c

Linux 3.2.59, 3.17.6

Parece que Linux agregará porciones de mensajes con soporte auxiliar al final de otros mensajes siempre y cuando no sea necesario entregar una carga útil auxiliar anterior durante esta llamada a recvmsg. Una vez que se entregan los datos auxiliares de un mensaje, devolverá una lectura breve en lugar de comenzar el siguiente mensaje de datos auxiliares. Entonces, en el ejemplo anterior, las lecturas que obtengo son:

recv1: [24 bytes] (msg1 + partial msg2 with msg2's 2 file descriptors)
recv2: [10 bytes] (remainder of msg2 + msg3 with msg3's 1 file descriptor)
recv3: [17 bytes] (msg4 + msg5 with msg5's 5 file descriptors)
recv4: [0 bytes]

BSD 4.4, 10.0

BSD proporciona más alineación que Linux y ofrece una lectura breve inmediatamente antes del inicio de un mensaje con datos auxiliares. Pero, felizmente agregará un mensaje no auxiliar al final de un mensaje auxiliar. Entonces, para BSD, parece que si su búfer es más grande que el mensaje de soporte auxiliar, obtendrá un comportamiento casi similar al de un paquete. Las lecturas que obtengo son:

recv1: [20 bytes] (msg1)
recv2: [7 bytes]  (msg2, with msg2's 2 file descriptors)
recv3: [17 bytes] (msg3, and msg4, with msg3's 1 file descriptor)
recv4: [7 bytes]  (msg5 with 5 file descriptors)
recv5: [0 bytes]

QUE HACER:

Todavía me gustaría saber cómo sucede en Linux, iOS, Solaris, etc., y cómo se podría esperar que suceda en el futuro.

M Conrad
fuente
No confunda flujos y paquetes, en un flujo no hay garantía de que los datos se entreguen en los mismos fragmentos que se enviaron, para esto necesitaría un protocolo basado en paquetes, no un flujo basado.
ctrl-alt-delor
precisamente por eso estoy haciendo esta pregunta
M Conrad
El orden debe ser preservado. Eso es lo que hacen las corrientes. Si una lectura de bloqueo devuelve 0, entonces es el final de la secuencia. Si devuelve otro número, entonces puede haber más, debe hacer al menos una lectura más para averiguarlo. No hay tal cosa como mensaje1, mensaje2, etc. No se transmite ningún delimitador de mensaje. Debe agregar esto a su protocolo, si lo necesita.
ctrl-alt-delor
1
Específicamente, tengo un protocolo de secuencia de texto y estoy agregando un comando que pasa un descriptor de archivo con una línea de texto. Necesito saber en qué orden se reciben estos datos auxiliares en relación con el texto del mensaje para poder escribir el código correctamente.
M Conrad
1
@MConrad: intentaría obtener una copia de la especificación POSIX.1g. Si no está escrito explícitamente allí, entonces puede esperar un comportamiento específico de implementación.
Laszlo Valko

Respuestas:

1

Los datos auxiliares se reciben como si estuvieran en cola junto con el primer octeto de datos normal en el segmento (si lo hay).

- POSIX.1-2017

Para el resto de su pregunta, las cosas se ponen un poco complicadas.

... Para los fines de esta sección, un datagrama se considera un segmento de datos que termina un registro y que incluye una dirección de origen como un tipo especial de datos auxiliares.

Los segmentos de datos se colocan en la cola a medida que el protocolo entrega los datos al socket. Los segmentos de datos normales se colocan al final de la cola a medida que se entregan. Si un nuevo segmento contiene el mismo tipo de datos que el segmento anterior y no incluye datos auxiliares, y si el segmento anterior no termina un registro, los segmentos se fusionan lógicamente en un solo segmento ...

Una operación de recepción nunca devolverá datos o datos auxiliares de más de un segmento.

Por lo tanto, los enchufes BSD modernos coinciden exactamente con este extracto. Esto no es sorprendente :-).

Recuerde que el estándar POSIX se escribió después de UNIX, y después de divisiones como BSD vs Sistema V. Uno de los objetivos principales era ayudar a comprender el rango de comportamiento existente y evitar aún más divisiones en las características existentes.

Linux se implementó sin referencia al código BSD. Parece comportarse de manera diferente aquí.

  1. Si has leído bien, parece que Linux es, además, fusionando "segmentos" cuando un nuevo segmento hace incluir datos auxiliares, pero el segmento anterior no lo hace.

  2. Su punto de vista de que "Linux agregará porciones de mensajes auxiliares al final de otros mensajes siempre que no sea necesario entregar una carga útil previa durante esta llamada a recvmsg", no parece explicarse completamente por el estándar. Una posible explicación implicaría una condición de carrera. Si lee parte de un "segmento", recibirá los datos auxiliares. ¡Quizás Linux interpretó esto como que significa que el resto del segmento ya no cuenta como la inclusión de datos auxiliares! Entonces, cuando se recibe un nuevo segmento, se fusiona, ya sea según el estándar o según la diferencia 1 anterior.

Si desea escribir un programa máximamente portátil, debe evitar esta área por completo. Cuando se usan datos auxiliares, es mucho más común usar sockets de datagramas . Si desea trabajar en todas las plataformas extrañas que técnicamente aspiran a proporcionar algo como POSIX, su pregunta parece aventurarse en un rincón oscuro y no probado.


Se podría argumentar que Linux aún sigue varios principios importantes:

  1. "Los datos auxiliares se reciben como si estuvieran en cola junto con el primer octeto de datos normal en el segmento".
  2. Los datos auxiliares nunca se "concatenan", como usted dice.

Sin embargo, no estoy convencido de que el comportamiento de Linux sea particularmente útil cuando lo comparas con el comportamiento de BSD. Parece que el programa que describe necesitaría agregar una solución específica para Linux. Y no sé una justificación de por qué Linux esperaría que hicieras eso.

Podría haber sido sensato al escribir el código del kernel de Linux, pero sin haber sido probado o ejercido por ningún programa.

O podría ser ejercido por algún código de programa que funciona principalmente bajo este subconjunto, pero en principio podría tener "errores" o condiciones de carrera.

Si no puede entender el comportamiento de Linux y su uso previsto, creo que se trata de tratar esto como un "rincón oscuro y no probado" en Linux.

sourcejedi
fuente
Gracias por la revisión en profundidad! Creo que la conclusión aquí es que puedo manejar esto con seguridad con dos buffers (cada uno con una porción de datos y una porción auxiliar); Si recibo descriptores de archivos en la primera lectura y no pertenecen al mensaje, pero comienza otro mensaje, entonces si la siguiente lectura también contiene datos auxiliares, significa que definitivamente encontraré el final de mi mensaje de datos que posee la primera carga útil auxiliar en esa segunda lectura. Alternando de un lado a otro, siempre debería poder hacer coincidir el mensaje con la carga útil en función de la ubicación del primer byte.
M Conrad