read(fd, buf, SSIZE_MAX + 1) (was: Kommata)

4 views
Skip to first unread message

Helmut Waitzmann

unread,
Apr 5, 2019, 11:44:21 AM4/5/19
to
Helmut Schellong <r...@schellong.biz>:
>On 04/04/2019 17:47, Stefan Reuther wrote:
>> Am 04.04.2019 um 14:26 schrieb Helmut Schellong:
>>> On 04/04/2019 13:39, Claus Reibenstein wrote:
>>>>> On 04/03/2019 16:34, Rainer Weikusat wrote:
>>>>>
>>>>>> while ((rc = read(fd, buf, len)), rc == -1 && errno == EAGAIN) {
>>>>>
>>>>> [...] ich formuliere stets
>>>>> 'rc < 0', weil jeder Wert <0 eine Fehlersituation ist.
>>>>
>>>> Wobei es laut Doku nur genau diesen einen negativen Wert gibt bei
>>>> read(). Von daher ist das wurscht.
>>>
>>> Das weiß ich, dennoch ist jeder Wert <0 ein Wert, mit dem man
>>> außer ihn als Fehler anzusehen, nichts anfangen kann.
>>
>> Der Größenparameter ist ein size_t. Es ist vielleicht nicht sonderlich
>> relevant, auf einem 32-Bit-System 3 Gigabyte am Stück zu lesen, aber auf
>> einem 16-Bitter 40 kByte find ich jetzt nicht ungewöhnlich. Dann wäre
>> der Erfolgswert -24576.
>>
>> Nicht, dass sich MS-DOS darum scheren würde, aber POSIX (SUS) lässt
>> diesen Fall implementation-defined.
>
>http://osr507doc.xinuos.com/en/man/html.S/read.S.html
>
>|Return values
>|On success, read and readv return a non-negative integer
>|indicating the number of bytes actually read.
>|On failure, read and readv return -1
>|and set errno to identify the error.
>
>'Success' erfordert unbedingt Werte >= 0.

POSIX The Open Group Base Specifications Issue 7, 2018 edition
IEEE Std 1003.1™-2017 (Revision of IEEE Std 1003.1-2008):

<http://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html#tag_16_474_02>:

SYNOPSIS

#include <unistd.h>

[…]
ssize_t read(int fildes, void *buf, size_t nbyte);


<http://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html#tag_16_474_03>:

DESCRIPTION

The read() function shall attempt to read nbyte bytes from
the file associated with the open file descriptor, fildes,
into the buffer pointed to by buf.

[…]

If the value of nbyte is greater than {SSIZE_MAX}, the
result is implementation-defined.

<http://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html#tag_16_474_04>:

RETURN VALUE

Upon successful completion, these functions shall return a
non-negative integer indicating the number of bytes actually
read. Otherwise, the functions shall return -1 and set errno
to indicate the error.

=> Über Funktionswerte <-1 wird im POSIX‐Standard keine Aussage
gemacht, außer der, dass für den Fall, dass mehr als SSIZE_MAX
Bytes gelesen werden sollen, die Implementierung festlegen muss,
was dann geschehen soll. Der C‐Standard macht überhaupt keine
Aussage, weil die Funktion »read()« nicht darin enthalten ist.

>Das war auch schon so, als beide Typen 'int' waren.
>Daß die Typen heute ssize_t und size_t sind, ändert nichts daran.
>
>Wenn heute ein Wert 'len' übergeben wird, der größer ist als
>der positive Wertbereich von ssize_t, werden eben weniger Bytes
>in 'buf' kopiert als per 'len' angegeben, oder es wird
>der Fehler EOVERFLOW oder ein anderer gegeben.
>Mit ersterem muß sowieso gerechnet werden.

Das geben weder der POSIX‐ noch der C‐Standard her.

Fazit: Wer will, dass sein Programm auf jedem POSIX‐System
funktioniert, muss sich darauf beschränken, nicht mehr als
SSIZE_MAX Bytes lesen zu wollen. Hält er sich an die
Beschränkung, können Funktionswerte <-1 (bisher) nicht vorkommen.
Tut er es nicht, erklärt er sich mit dem einverstanden, was seine
Implementierung festgelegt hat.

Will man also wasserdicht programmieren, sind Funktionswerte >=0
als Anzahl gelesener Bytes, der Funktionswert =-1 als im Standard
definierter Fehlerfall mit im Standard definierten
Fehlerbehandlungsmöglichkeiten und Funktionswerte < -1 als
verbotene Werte zu behandeln. »Verboten« heißt: Das Programm
kann überhaupt keine Annahmen mehr über den Zustand des
File‐Descriptors machen. Es bleibt ihm nicht viel anderes übrig,
als (nach Belieben) dem Anwender eine Fehlermeldung über einen den
Standard sprengenden Fehler zu geben und danach diesen
File‐Descriptor nicht mehr anzufassen (allenfalls noch, ihn zu
schließen).

Siehe auch
<http://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html#tag_16_474_08_01>.

Wenn ich das richtig verstanden habe, ist die Funktion »read()« im
C‐Standard nicht definiert, jedoch im POSIX‐Standard. Deshalb
schlage ich ein

Crosspost & Followup-To: de.comp.os.unix.programming

vor. Notfalls bitte passend abändern.

Michael Bäuerle

unread,
Apr 5, 2019, 1:47:05 PM4/5/19
to
Helmut Waitzmann wrote:
>
> [...]
> <http://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html#tag_16_474_04>:
>
> RETURN VALUE
>
> Upon successful completion, these functions shall return a
> non-negative integer indicating the number of bytes actually
> read. Otherwise, the functions shall return -1 and set errno
> to indicate the error.
>
> => Über Funktionswerte <-1 wird im POSIX‐Standard keine Aussage
> gemacht, außer der, dass für den Fall, dass mehr als SSIZE_MAX
> Bytes gelesen werden sollen, die Implementierung festlegen muss,
> was dann geschehen soll.
> [...]
> Fazit: Wer will, dass sein Programm auf jedem POSIX‐System
> funktioniert, muss sich darauf beschränken, nicht mehr als
> SSIZE_MAX Bytes lesen zu wollen. Hält er sich an die
> Beschränkung, können Funktionswerte <-1 (bisher) nicht vorkommen.
> Tut er es nicht, erklärt er sich mit dem einverstanden, was seine
> Implementierung festgelegt hat.

Da read() auch EINTR liefern darf, sollte das Programm es sowieso
mehrmals aufrufen können. Für die Schleife muss man dann nur noch
die Länge <=SSIZE_MAX pro Aufruf einbauen.

Helmut Schellong

unread,
Apr 5, 2019, 2:01:14 PM4/5/19
to
Es ist vollkommen unklar, was 'result' umfaßt.
'return value' kann u.a. damit gemeint sein, muß aber nicht.
Es muß auch kein Wert gemeint sein, sondern eine andere
Operationsweise, etc.

In der Rationale steht noch folgendes:

|This volume of POSIX.1-2017 also limits the range further
|by requiring that the byte count be limited so
|that a signed return value remains meaningful.
|Since the return type is also a (signed) abstract type, the
|byte count can be defined by the implementation
|to be larger than an int can hold.

> <http://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html#tag_16_474_04>:
>
> RETURN VALUE
>
> Upon successful completion, these functions shall return a
> non-negative integer indicating the number of bytes actually
> read. Otherwise, the functions shall return -1 and set errno
> to indicate the error.
>
> => Über Funktionswerte <-1 wird im POSIX‐Standard keine Aussage
> gemacht, außer der, dass für den Fall, dass mehr als SSIZE_MAX
> Bytes gelesen werden sollen, die Implementierung festlegen muss,
> was dann geschehen soll. Der C‐Standard macht überhaupt keine
> Aussage, weil die Funktion »read()« nicht darin enthalten ist.

POSIX legt nicht fest, daß nbyte>SSIZE_MAX eine Wirkung
auf den return-value haben soll.

>> Das war auch schon so, als beide Typen 'int' waren.
>> Daß die Typen heute ssize_t und size_t sind, ändert nichts daran.
>>
>> Wenn heute ein Wert 'len' übergeben wird, der größer ist als
>> der positive Wertbereich von ssize_t, werden eben weniger Bytes
>> in 'buf' kopiert als per 'len' angegeben, oder es wird
>> der Fehler EOVERFLOW oder ein anderer gegeben.
>> Mit ersterem muß sowieso gerechnet werden.
>
> Das geben weder der POSIX‐ noch der C‐Standard her.

Ich habe 'z.B.' oder ähnlich vergessen.
Aber weniger Bytes als per nbyte angegeben kann passieren.

> Fazit: Wer will, dass sein Programm auf jedem POSIX‐System
> funktioniert, muss sich darauf beschränken, nicht mehr als
> SSIZE_MAX Bytes lesen zu wollen. Hält er sich an die
> Beschränkung, können Funktionswerte <-1 (bisher) nicht vorkommen.
> Tut er es nicht, erklärt er sich mit dem einverstanden, was seine
> Implementierung festgelegt hat.
>
> Will man also wasserdicht programmieren, sind Funktionswerte >=0
> als Anzahl gelesener Bytes, der Funktionswert =-1 als im Standard
> definierter Fehlerfall mit im Standard definierten
> Fehlerbehandlungsmöglichkeiten und Funktionswerte < -1 als
> verbotene Werte zu behandeln. »Verboten« heißt: Das Programm
> kann überhaupt keine Annahmen mehr über den Zustand des
> File‐Descriptors machen. Es bleibt ihm nicht viel anderes übrig,
> als (nach Belieben) dem Anwender eine Fehlermeldung über einen den
> Standard sprengenden Fehler zu geben und danach diesen
> File‐Descriptor nicht mehr anzufassen (allenfalls noch, ihn zu
> schließen).

Ja, deshalb programmiere ich seit ca. 1987 so oder ähnl.:

for (max=2048; (nr= read(fd, buf, max), nr>0); N+=nr) {
//...
}
if (!nr) /*...*/;
else ERROR();

Seit langem verwende ich Wrapper-Funktionen wie z.B. readE().
Darin ist dann die ganze Fehlerbehandlung.
Standard-Maßnahme ist Abbruch des Programms.

> Siehe auch
> <http://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html#tag_16_474_08_01>.
>
> Wenn ich das richtig verstanden habe, ist die Funktion »read()« im
> C‐Standard nicht definiert, jedoch im POSIX‐Standard.
fread() gibt es dafür, mit FILE*, wie üblich.


--
Mit freundlichen Grüßen
Helmut Schellong v...@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm

Helmut Waitzmann

unread,
Apr 8, 2019, 3:07:43 AM4/8/19
to
Helmut Schellong <r...@schellong.biz>:
Ein Hinweis, was »result« umfasst, könnten folgende Sätze aus dem
oben genannten Abschnitt »DESCRIPTION« sein:

Before any action described below is taken, and if nbyte is
zero, the read() function may detect and return errors as
described below. In the absence of errors, or if error
detection is not performed, the read() function shall return
zero and have no other results.

»shall return zero and have no other results« legt nahe, dass
»shall return zero« ein Teil dessen ist, was »results« umfasst,
denn anderenfalls wäre das Wort »other« fehl am Platz.

»no other results« legt nahe, dass »results« nicht nur den
Funktionswert sondern die ganze Wirkung, die der Funktionsaufruf
hat, umfasst.

Das im Hinterkopf behaltend, nochmal zitiert:

>> If the value of nbyte is greater than {SSIZE_MAX}, the
>> result is implementation-defined.

=> Wenn man read mit einem Größenparameter > SSIZE_MAX aufruft,
ist alles – Funktionswert und Wirkung – implementation‐defined.

<http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap01.html#tag_01_05_02>
erklärt »implementation‐defined«:

implementation-defined

Describes a value or behavior that is not defined by
POSIX.1-2017 but is selected by an implementor. The value or
behavior may vary among implementations that conform to
POSIX.1-2017. An application should not rely on the existence
of the value or behavior. An application that relies on such a
value or behavior cannot be assured to be portable across
conforming implementations.

The implementor shall document such a value or behavior so that
it can be used correctly by an application.

=> Wer will, dass sein Programm unter allen POSIX‐konformen
Umgebungen das gleiche Verhalten hat, muss den Größenparameter auf
Werte zwischen 0 und SSIZE_MAX beschränken.

Wer größere Werte nutzen will, dem garantiert der POSIX‐Standard,
dass er in der Dokumentation der Implementierung, die er nutzt,
nachlesen kann, was »read()« dann tut.

Die Implementierung ist völlig frei darin, festzulegen, was sie
dann tut. Angefangen von Stefans Beispiel einer Implementierung
auf einer 16‐Bit‐Maschine, bei der SSIZE_MAX = 32768 und
SIZE_MAX = 65536 ist und jeder negative Funktionswert < -1 nicht
als Fehleranzeiger sondern als Funktionswert + 65536 zu verstehen
ist, über eine Implementierung, die keine Daten liest und -1 als
Funktionswert liefert (also einen Fehler anzeigt), bis zu einer
Implementierung, die den aufrufenden Prozess mit einem KILL‐Signal
erschlägt, ist alles möglich. Es muss nur dokumentiert sein.

(In der »read«‐Handbuchseite meines Rechners heißt es:

SYNOPSIS
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

[…]

If count is greater than SSIZE_MAX, the result is
unspecified.

Auf gut Deutsch: »Wir wollen oder können nicht sagen, was dann
geschieht«.)

>
>In der Rationale steht noch folgendes:
>
>|This volume of POSIX.1-2017 also limits the range further
>|by requiring that the byte count be limited so
>|that a signed return value remains meaningful.
>|Since the return type is also a (signed) abstract type, the
>|byte count can be defined by the implementation
>|to be larger than an int can hold.

Da kommt mir noch eine Idee:

<http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_types.h.html#tag_13_65_03>:

DESCRIPTION

The <sys/types.h> header shall define at least the following
types:

[…]

size_t
Used for sizes of objects.
ssize_t
Used for a count of bytes or an error indication.

Dass »an error indication« tatsächlich der Wert -1 sein muss,
steht da nicht. Könnte es nicht auch ein Wert sein, der beim
Vergleich mit -1 den Wahrheitswert »wahr« liefert, wie im
folgenden?

Wenn ssize_t ein unsigned Integer‐Typ wäre, würde der
C‐Compiler im Code‐Schnipsel

ssize_t rc=read(…);

if (rc == (ssize_t) -1)
// Fehlerfall hier behandeln

den Fehlertest »rc == (ssize_t) -1« in

»rc == SSIZE_MAX«

umsetzen.

Wenn ssize_t dabei mindestens die Größe des Integer‐Typs unsigned
int hätte, könnte man den Typecast »(ssize_t)« sparen:

ssize_t rc=read(…);

if (rc == -1)
// Fehlerfall hier behandeln

Das ist quellcode‐kompatibel zu dem Fall, dass ssize_t ein signed
Typ ist.

Es funktioniert nur, wenn die Maschine negative Zahlen im
Zweierkomplement darstellt: Bei Einerkomplementdarstellung gibt
es keine negative Zahl, die beim Umwandeln per Type‐Cast die
positive Zahl SSIZE_MAX + 1 ergibt.

Scheitern würde allerdings

»if (rc < 0)«, weil »rc« keine Werte < 0 annehmen könnte.

Ist irgendwo festgelegt, dass »ssize_t« ein signed Integer‐Typ
sein muss? Falls nicht, spräche das sehr dafür, den Fehlertest am
Funktionswert der Funktion »read()« nicht auf < 0 sondern auf
== -1 zu machen.

>
>> <http://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html#tag_16_474_04>:
>>
>> RETURN VALUE
>>
>> Upon successful completion, these functions shall return a
>> non-negative integer indicating the number of bytes actually
>> read. Otherwise, the functions shall return -1 and set errno
>> to indicate the error.
>>
>> => Über Funktionswerte <-1 wird im POSIX‐Standard keine Aussage
>> gemacht, außer der, dass für den Fall, dass mehr als SSIZE_MAX
>> Bytes gelesen werden sollen, die Implementierung festlegen muss,
>> was dann geschehen soll. Der C‐Standard macht überhaupt keine
>> Aussage, weil die Funktion »read()« nicht darin enthalten ist.
>
>POSIX legt nicht fest, daß nbyte>SSIZE_MAX eine Wirkung
>auf den return-value haben soll.

POSIX legt fest, dass nbyte > SSIZE_MAX die Wirkung hat, die von
der Implementierung definiert ist. Diese Wirkung kann (s. o.)
auch den Funktionswert umfassen.

=> Die Implementierung ist völlig frei darin, was sie im Fall
nbyte > SSIZE_MAX macht – den Funktionswert eingeschlossen. Sie
muss es allerdings festlegen.

Helmut Schellong

unread,
Apr 8, 2019, 7:03:06 AM4/8/19
to
On 04/08/2019 09:05, Helmut Waitzmann wrote:
> Helmut Schellong <r...@schellong.biz>:
>> On 04/05/2019 17:44, Helmut Waitzmann wrote:

[... ...]

> Die Implementierung ist völlig frei darin, festzulegen, was sie
> dann tut. Angefangen von Stefans Beispiel einer Implementierung
> auf einer 16‐Bit‐Maschine, bei der SSIZE_MAX = 32768 und
> SIZE_MAX = 65536 ist und jeder negative Funktionswert < -1 nicht
> als Fehleranzeiger sondern als Funktionswert + 65536 zu verstehen
> ist, über eine Implementierung, die keine Daten liest und -1 als
> Funktionswert liefert (also einen Fehler anzeigt), bis zu einer
> Implementierung, die den aufrufenden Prozess mit einem KILL‐Signal
> erschlägt, ist alles möglich. Es muss nur dokumentiert sein.
>
> (In der »read«‐Handbuchseite meines Rechners heißt es:
>
> SYNOPSIS
> #include <unistd.h>
>
> ssize_t read(int fd, void *buf, size_t count);
>
> […]
>
> If count is greater than SSIZE_MAX, the result is
> unspecified.
>
> Auf gut Deutsch: »Wir wollen oder können nicht sagen, was dann
> geschieht«.)

Bei mir steht:
[EINVAL] The value nbytes is greater than INT_MAX.

So (oder ähnlich) kenne ich das schon immer.
Also: vernünftige, logische Werte.

Ich rate Stefan Reuther, mal zu prüfen, ob _wirklich_
40000 Bytes eingefüllt werden!
Ich vermute, das impl.def. Verhalten bei der 16bit-Implementation
ist nicht dokumentiert.

>> In der Rationale steht noch folgendes:
>>
>> |This volume of POSIX.1-2017 also limits the range further
>> |by requiring that the byte count be limited so
>> |that a signed return value remains meaningful.
>> |Since the return type is also a (signed) abstract type, the
>> |byte count can be defined by the implementation
>> |to be larger than an int can hold.
>
> Da kommt mir noch eine Idee:

[...]

> Ist irgendwo festgelegt, dass »ssize_t« ein signed Integer‐Typ
> sein muss? Falls nicht, spräche das sehr dafür, den Fehlertest am
> Funktionswert der Funktion »read()« nicht auf < 0 sondern auf
> == -1 zu machen.

/usr/include/x86/_limits.h:76:#define __SSIZE_MAX __LONG_MAX
/* max value for a ssize_t */
/usr/include/x86/_types.h:111:typedef __int64_t __ssize_t;
/* byte count or error */
/usr/include/x86/_types.h:120:typedef __int32_t __ssize_t;

/usr/include/limits.h:45:#define _POSIX_SSIZE_MAX 32767
/usr/include/x86/_limits.h:76:#define __SSIZE_MAX __LONG_MAX
/* max value for a ssize_t */
/usr/include/x86/_limits.h:86:#define __SSIZE_MAX __INT_MAX
/usr/include/sys/limits.h:72:#define SSIZE_MAX __SSIZE_MAX
/* max value for an ssize_t */

Ich sehe ssize_t seit Jahrzehnten ausnahmslos als signed:
ssize_t ist sprachlich signed size_t

[...]
>> POSIX legt nicht fest, daß nbyte>SSIZE_MAX eine Wirkung
>> auf den return-value haben soll.
>
> POSIX legt fest, dass nbyte > SSIZE_MAX die Wirkung hat, die von
> der Implementierung definiert ist. Diese Wirkung kann (s. o.)
> auch den Funktionswert umfassen.
>
> => Die Implementierung ist völlig frei darin, was sie im Fall
> nbyte > SSIZE_MAX macht – den Funktionswert eingeschlossen. Sie
> muss es allerdings festlegen.

Ich sehe das alles auch so, auch weiter oben.

Für mich steht allerdings fest, daß ssize_t unbedingt signed ist.
ssize_t ist der signed-Bruder von size_t.

Michael Bäuerle

unread,
Apr 8, 2019, 7:17:16 AM4/8/19
to
Helmut Waitzmann wrote:
>
> [...]
> Ist irgendwo festgelegt, dass »ssize_t« ein signed Integer‐Typ
> sein muss?

Ja, siehe hier:
<https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_types.h.html#tag_13_65_03>
|
| • blksize_t, pid_t, and ssize_t shall be signed integer types.
^^^^^^^ ^^^^^^

Stefan Reuther

unread,
Apr 8, 2019, 12:53:14 PM4/8/19
to
Am 08.04.2019 um 13:03 schrieb Helmut Schellong:
> On 04/08/2019 09:05, Helmut Waitzmann wrote:
>> If count is greater than SSIZE_MAX, the result is
>> unspecified.
>>
>> Auf gut Deutsch: »Wir wollen oder können nicht sagen, was dann
>> geschieht«.)
>
> Bei mir steht:
> [EINVAL] The value nbytes is greater than INT_MAX.
>
> So (oder ähnlich) kenne ich das schon immer.
> Also: vernünftige, logische Werte.
>
> Ich rate Stefan Reuther, mal zu prüfen, ob _wirklich_
> 40000 Bytes eingefüllt werden!

Selbstverständlich werden sie das. Das ist auch zu erwarten, denn das
dazugehörige Syscall-Interface kennt nur unsigned-Werte in Prozessor-
Registern, und meldet die Fehler out-of-band (Carry-Flag).

> Ich vermute, das impl.def. Verhalten bei der 16bit-Implementation
> ist nicht dokumentiert.

Korrekt. Wobei die Implementation halt 30 Jahre alt ist und versucht,
das nachzubauen, was es damals unter *ix gab und noch nicht wirklich
POSIX hieß.

"Upon successful completion, int (sic!) returns an integer indicating
the number of bytes placed in the buffer; if the file was opened in text
mode, read does not count carriage returns or Ctrl-Z characters in the
number of bytes read. On error, it returns -1 and sets errno."


Stefan

Rainer Weikusat

unread,
Apr 8, 2019, 12:55:40 PM4/8/19
to
read kann nur dann EINTR zurueckliefern, falls waehrend einem
blockierenden read ein Signal von einer benutzerdefinierten
Signalfunktion verarbeitet wurde, die zurueckkehrte.

Bonita Montero

unread,
Apr 9, 2019, 9:19:10 AM4/9/19
to
>>        If the value of nbyte is greater than {SSIZE_MAX},
>>        the result is implementation-defined.

Das Problem ergibt sich doch sowieso nicht. Auf Systemen mit linearen
Adressraum ist size_t so groß wie ein Pointer. SSIZE_MAX wäre der halbe
Adressraum. Wann schreibt man schon mal einen Block mit der Größe des
halben Adressraums?

Helmut Schellong

unread,
Apr 9, 2019, 9:37:29 AM4/9/19
to
Die Performance ist am besten mit 10000-20000 Byte Blockgröße.
Ein Aufruf read(fd, buf, 2000000000); wäre idiotisch.
Selbst das liegt noch unterhalb von INT_MAX.

Bonita Montero

unread,
Apr 9, 2019, 11:49:35 AM4/9/19
to
>> Das Problem ergibt sich doch sowieso nicht. Auf Systemen mit linearen
>> Adressraum ist size_t so groß wie ein Pointer. SSIZE_MAX wäre der halbe
>> Adressraum. Wann schreibt man schon mal einen Block mit der Größe des
>> halben Adressraums?

> Die Performance ist am besten mit 10000-20000 Byte Blockgröße.

Was ist denn das für ein Unsinn? Es gibt einen gewissen fixen Overhead
der Teilweise im Kernel-Aufruf begründet ist. Es wird sicher so sein,
dass ab einer gewissen Blockgröße der nicht mehr ins Gewicht fällt so
dass es z.B. beim Schreiben von zig GB egal ist, ob man eine Blockgröße
von 100k oder einem 10MB hat. Daher muss man z.B. bei sevbuf() nicht
allzu große Buffer-Größen angeben.
Aber ein Optimum gibt es da nicht in dem Sinne, dass man da einen mitt-
leren Wert hat wo rechts *und* links davon die Performance schlechter
wird.

> Ein Aufruf  read(fd, buf, 2000000000);  wäre idiotisch.

Nicht unbedingt Es sind Fälle denkbar wo man das File komplett
im Speicher haben und nicht nur von vorne nach hinten lesen will.

Helmut Schellong

unread,
Apr 9, 2019, 1:46:47 PM4/9/19
to
On 04/09/2019 17:49, Bonita Montero wrote:
>>> Das Problem ergibt sich doch sowieso nicht. Auf Systemen mit linearen
>>> Adressraum ist size_t so groß wie ein Pointer. SSIZE_MAX wäre der halbe
>>> Adressraum. Wann schreibt man schon mal einen Block mit der Größe des
>>> halben Adressraums?
>
>> Die Performance ist am besten mit 10000-20000 Byte Blockgröße.
>
> Was ist denn das für ein Unsinn? Es gibt einen gewissen fixen Overhead
> der Teilweise im Kernel-Aufruf begründet ist. Es wird sicher so sein,
> dass ab einer gewissen Blockgröße der nicht mehr ins Gewicht fällt so
> dass es z.B. beim Schreiben von zig GB egal ist, ob man eine Blockgröße
> von 100k oder einem 10MB hat. Daher muss man z.B. bei sevbuf() nicht
> allzu große Buffer-Größen angeben.
> Aber ein Optimum gibt es da nicht in dem Sinne, dass man da einen mitt-
> leren Wert hat wo rechts *und* links davon die Performance schlechter
> wird.

Das ist kein Unsinn, sondern eine getestete Tatsache.
Es gibt in der Tat einen für Performance optimalen Bereich.
In der realen Praxis, wo die Pufferdaten auch gelesen werden.

setvbuf() braucht man nicht, wenn read() einen Puffer füllt.
U.a. deshalb sind read()/write() grundsätzlich schneller
als die C-Standard-Funktionen.

Wenn der Puffer sehr klein ist, muß eine Schleife mit dem
read()-Aufruf entsprechend öfter durchlaufen werden:
Je kleiner der Puffer, desto langsamer die Arbeit mit read().

Je größer der Puffer, desto weniger optimal wird der Prozessor
benutzt. Das Cache-System eines Prozessors arbeitet in einem
bestimmten Puffer-Größenbereich optimal.

>> Ein Aufruf  read(fd, buf, 2000000000);  wäre idiotisch.
>
> Nicht unbedingt Es sind Fälle denkbar wo man das File komplett
> im Speicher haben und nicht nur von vorne nach hinten lesen will.

In 99,99% aller Fälle sind Puffergrößen im GB-Bereich idiotisch,
wie ich es schrieb.
Dem Prozeß muß immerhin diese Speichermenge zugeordnet werden.
Und die kann kaum im Stack Platz finden, was optimal wäre.

Es kann dann auch oft vorkommen, daß das System eine erforderliche
Speichermenge dem Prozeß nicht zur Verfügung stellen kann.
Eine weitere Problematisierung entsteht, wenn mehrere solche
Prozesse parallel laufen sollen.
Beispielsweise, wenn vier Verzeichnisse gleichzeitig bearbeitet
werden sollen - man hat ja schließlich 8 Kerne (bis 28).

Es wird dann auch vorkommen, daß viel mehr als 2 GB Puffer
gebraucht werden.
Ab einer gewissen Grenze müssen andere Konzepte verfolgt werden,
wenn die Forderung ist, daß eine Datei unbedingt komplett in den
Arbeitsspeicher passen muß.
Und diese anderen Konzepte sind dann wesentlich weniger performant.

Bonita Montero

unread,
Apr 9, 2019, 2:29:08 PM4/9/19
to
> Das ist kein Unsinn, sondern eine getestete Tatsache.
> Es gibt in der Tat einen für Performance optimalen Bereich.

Blödsinn. Es gibt einen Bereich *ab* dem es keine wesentliche
Performance-Steigerung gibt.

> setvbuf() braucht man nicht, wenn read() einen Puffer füllt.
> U.a. deshalb sind read()/write() grundsätzlich schneller
> als die C-Standard-Funktionen.

Du hast mich nicht verstanden. setvbuf() setzt ja eben die
Buffer-Größe für das dem fread() zugrundeliegende read().

> Je größer der Puffer, desto weniger optimal wird der Prozessor
> benutzt. Das Cache-System eines Prozessors arbeitet in einem
> bestimmten Puffer-Größenbereich optimal.

Wenn man nicht gerade "Speicher-Benchmarks" auf solchen Puffern
schreibt, sondern die Daten noch interpretiert, dann wird das
nicht so ins Gewicht fallen.

> In 99,99% aller Fälle sind Puffergrößen im GB-Bereich idiotisch,
> wie ich es schrieb.

Es ist aber eben oft ziemlich unhandlich, in einer Schleife im Puffer
zu pointern und wenn der an das Ende stößst einen Block nachzulesen.
Einfach den ganzen Krempel en-bloc einlesen und einfach nur den
Pointer fortschiebnen ist wesentlich komfortabler.
Ein weiteres Problem ist, dass wenn ich nicht gerade chars lese,
sondern Datentypen die mehrere Wörter haben ich nicht weiß ob
Wortgrenzen über den Puffer-Rand hinausgehen.

> Es kann dann auch oft vorkommen, daß das System eine erforderliche
> Speichermenge dem Prozeß nicht zur Verfügung stellen kann.

Ist meistens nicht realistisch.

> Ab einer gewissen Grenze müssen andere Konzepte verfolgt werden,
> wenn die Forderung ist, daß eine Datei unbedingt komplett in den
> Arbeitsspeicher passen muß.
> Und diese anderen Konzepte sind dann wesentlich weniger performant.

Memory-mapping ist z.B. nicht inperformant. Die Größe der Blöcke
die vom Kernel kommen entsprechen einer Page und da die OSe alle
die sequentiellen Zugriffe erkennen führen die auch ein entspre-
chendes Read-Ahead durch. Und nutzt man noch zusätzlich madvise(),
dann werden alte Pages auch früzeitig aus dem RAM geworfen, dass
die gemappten Pages nichts wichtiges aus dem Speicher werfen.

Helmut Schellong

unread,
Apr 9, 2019, 3:08:17 PM4/9/19
to
On 04/09/2019 20:29, Bonita Montero wrote:
>> Das ist kein Unsinn, sondern eine getestete Tatsache.
>> Es gibt in der Tat einen für Performance optimalen Bereich.
>
> Blödsinn. Es gibt einen Bereich *ab* dem es keine wesentliche
> Performance-Steigerung gibt.

Nein.
Es gibt einen Größenbereich für den Puffer, in dem
die Performance am höchsten ist.
Das ist einfach so und es wurde durch Messung bestätigt.

>> setvbuf() braucht man nicht, wenn read() einen Puffer füllt.
>> U.a. deshalb sind read()/write() grundsätzlich schneller
>> als die C-Standard-Funktionen.
>
> Du hast mich nicht verstanden. setvbuf() setzt ja eben die
> Buffer-Größe für das dem fread() zugrundeliegende read().

Wird so sein.
Aber dieser Thread heißt: "read(.....)".

>> Je größer der Puffer, desto weniger optimal wird der Prozessor
>> benutzt. Das Cache-System eines Prozessors arbeitet in einem
>> bestimmten Puffer-Größenbereich optimal.
>
> Wenn man nicht gerade "Speicher-Benchmarks" auf solchen Puffern
> schreibt, sondern die Daten noch interpretiert, dann wird das
> nicht so ins Gewicht fallen.

Die Performance geht zurück.
Bei 30 KB war sie geringer als bei 15 KB.
Es war keine Rede davon, um wieviel geringer.

>> In 99,99% aller Fälle sind Puffergrößen im GB-Bereich idiotisch,
>> wie ich es schrieb.
>
> Es ist aber eben oft ziemlich unhandlich, in einer Schleife im Puffer
> zu pointern und wenn der an das Ende stößst einen Block nachzulesen.
> Einfach den ganzen Krempel en-bloc einlesen und einfach nur den
> Pointer fortschiebnen ist wesentlich komfortabler.
> Ein weiteres Problem ist, dass wenn ich nicht gerade chars lese,
> sondern Datentypen die mehrere Wörter haben ich nicht weiß ob
> Wortgrenzen über den Puffer-Rand hinausgehen.

Verstehe ich; es ist etwas einfacher zu programmieren, wenn
alles auf einen Schlag eingelesen wird.
Ein besserer Algorithmus liegt aber vor, wenn das nicht getan wird,
sondern man sich mehr Mühe gibt.

>> Es kann dann auch oft vorkommen, daß das System eine erforderliche
>> Speichermenge dem Prozeß nicht zur Verfügung stellen kann.
>
> Ist meistens nicht realistisch.

Kommt drauf an, wieviel RAM man in seinem PC hat.
Ich habe nur 4 GB - es wird Zeit, daß ich mir einen neuen PC baue.
Denn ich erhalte von diversen Video-Programmen unter Windows
oft Fehlermeldungen wegen zu wenig RAM.

Ich habe schon festgestellt, daß diese Video-Programme die Videos
komplett ins RAM speichern wollen - was jedoch nicht geht.
Dadurch versagen diese Programme und sind nicht nutzbar.

>> Ab einer gewissen Grenze müssen andere Konzepte verfolgt werden,
>> wenn die Forderung ist, daß eine Datei unbedingt komplett in den
>> Arbeitsspeicher passen muß.
>> Und diese anderen Konzepte sind dann wesentlich weniger performant.
>
> Memory-mapping ist z.B. nicht inperformant. Die Größe der Blöcke
> die vom Kernel kommen entsprechen einer Page und da die OSe alle
> die sequentiellen Zugriffe erkennen führen die auch ein entspre-
> chendes Read-Ahead durch. Und nutzt man noch zusätzlich madvise(),
> dann werden alte Pages auch früzeitig aus dem RAM geworfen, dass
> die gemappten Pages nichts wichtiges aus dem Speicher werfen.

Um solche Konzepte geht es aber nicht.
Es geht darum, _beliebig_ große Dateien bearbeiten zu können.
Die erforderlichen Konzepte sind wesentlich komplexer
und daher stark unterschiedlich.

Ein PC kann heute maximal 128 GB RAM haben, nicht aber 10 TB.

Bonita Montero

unread,
Apr 9, 2019, 3:08:32 PM4/9/19
to
>> Je größer der Puffer, desto weniger optimal wird der Prozessor
>> benutzt. Das Cache-System eines Prozessors arbeitet in einem
>> bestimmten Puffer-Größenbereich optimal.

> Wenn man nicht gerade "Speicher-Benchmarks" auf solchen Puffern
> schreibt, sondern die Daten noch interpretiert, dann wird das
> nicht so ins Gewicht fallen.

Und nochwas: hier ist eigentlich immer die Gewschwindigkeit der
Festplatte oder SSD maßgeblich. Da ist das bisschen Unterschied
an CPU-Last völlig egal.

Bonita Montero

unread,
Apr 9, 2019, 3:14:10 PM4/9/19
to
>> Blödsinn. Es gibt einen Bereich *ab* dem es keine wesentliche
>> Performance-Steigerung gibt.

> Nein.
> Es gibt einen Größenbereich für den Puffer, in dem
> die Performance am höchsten ist.

Wo schrob ich das? Die Performance nähert sich eben mit größerer
Puffergröße asymptotisch einem Maximum; irgendwo gibt es also eine
Größe wo es keinen wesentlichen Unterschied in der CPU-last gibt.

>>> setvbuf() braucht man nicht, wenn read() einen Puffer füllt.
>>> U.a. deshalb sind read()/write() grundsätzlich schneller
>>> als die C-Standard-Funktionen.

>> Du hast mich nicht verstanden. setvbuf() setzt ja eben die
>> Buffer-Größe für das dem fread() zugrundeliegende read().

> Wird so sein.
> Aber dieser Thread heißt: "read(.....)".

Ja und? fread() setzt darauf auf.

>> Wenn man nicht gerade "Speicher-Benchmarks" auf solchen Puffern
>> schreibt, sondern die Daten noch interpretiert, dann wird das
>> nicht so ins Gewicht fallen.

> Die Performance geht zurück.

Eigentlich nicht, denn hier ist die Performance der Platte, SSD oder
des Netzwerk-Interfaces maßgeblich. Der Unterschied an CPU-Zeit ist
da zu vernachlässigen bzw. ändert auch nichts am Durchsatz.

>> Es ist aber eben oft ziemlich unhandlich, in einer Schleife im Puffer
>> zu pointern und wenn der an das Ende stößst einen Block nachzulesen.
>> Einfach den ganzen Krempel en-bloc einlesen und einfach nur den
>> Pointer fortschiebnen ist wesentlich komfortabler.
>> Ein weiteres Problem ist, dass wenn ich nicht gerade chars lese,
>> sondern Datentypen die mehrere Wörter haben ich nicht weiß ob
>> Wortgrenzen über den Puffer-Rand hinausgehen.

> Verstehe ich; es ist etwas einfacher zu programmieren, wenn
> alles auf einen Schlag eingelesen wird.
> Ein besserer Algorithmus liegt aber vor, wenn das nicht getan
> wird, sondern man sich mehr Mühe gibt.

Wie oben beschrieben drehst Du nur ein bisschen an der CPU-Last, aber
nicht am Durchsatz.

>> Memory-mapping ist z.B. nicht inperformant. Die Größe der Blöcke
>> die vom Kernel kommen entsprechen einer Page und da die OSe alle
>> die sequentiellen Zugriffe erkennen führen die auch ein entspre-
>> chendes Read-Ahead durch. Und nutzt man noch zusätzlich madvise(),
>> dann werden alte Pages auch früzeitig aus dem RAM geworfen, dass
>> die gemappten Pages nichts wichtiges aus dem Speicher werfen.

> Um solche Konzepte geht es aber nicht.
> Es geht darum, _beliebig_ große Dateien bearbeiten zu können.

Beliebig große Dateien hast Du fast nur bei Datenbanken, und da
wird völlig anders gearbeitet, also mit random-access-I/O, eigenem
Caching und asynchronem I/O.

Helmut Schellong

unread,
Apr 9, 2019, 3:41:15 PM4/9/19
to
Ein Betriebssystem ist sehr komplex.

CPU: 2.7% user, 0.0% nice, 0.2% system, 0.0% interrupt, 97.1% idle
Mem: 813M Active, 1032M Inact, 888M Laundry, 643M Wired, 394M Buf, 541M Free
Swap: 5120M Total, 300M Used, 4819M Free, 5% Inuse

Einfache Überlegungen führen da nicht zu korrekten Antworten.

Bonita Montero

unread,
Apr 9, 2019, 3:51:43 PM4/9/19
to
>> Und nochwas: hier ist eigentlich immer die Gewschwindigkeit der
>> Festplatte oder SSD maßgeblich. Da ist das bisschen Unterschied
>> an CPU-Last völlig egal.

> Ein Betriebssystem ist sehr komplex.

An der Stelle gibt's nichts komplexes. Der Unterschied ist
hier ein bisschen CPU-Last und fast gar nicht der Durchsatz.

Helmut Schellong

unread,
Apr 9, 2019, 4:07:28 PM4/9/19
to
On 04/09/2019 21:14, Bonita Montero wrote:
[...]
>> Um solche Konzepte geht es aber nicht.
>> Es geht darum, _beliebig_ große Dateien bearbeiten zu können.
>
> Beliebig große Dateien hast Du fast nur bei Datenbanken, und da
> wird völlig anders gearbeitet, also mit random-access-I/O, eigenem
> Caching und asynchronem I/O.

Beispiel:
Ich habe ein Kommando, das sucht Byte-Folgen innerhalb von Dateien.
Ich verwende einen Puffer[10*1024] und die maximale Länge der
gesuchten Byte-Folge beträgt 4*1024.

Das ist sehr einfacher hochperformanter Code, der beliebig
große Dateien durchsuchen kann - wirklich so groß, wie das
Dateisystem es zuläßt!

Bonita Montero

unread,
Apr 9, 2019, 4:31:24 PM4/9/19
to
>> Beliebig große Dateien hast Du fast nur bei Datenbanken, und da
>> wird völlig anders gearbeitet, also mit random-access-I/O, eigenem
>> Caching und asynchronem I/O.

> Beispiel:
> Ich habe ein Kommando, das sucht Byte-Folgen innerhalb von Dateien.
> Ich verwende einen Puffer[10*1024] und die maximale Länge der
> gesuchten Byte-Folge beträgt 4*1024.
> Das ist sehr einfacher hochperformanter Code, der beliebig
> große Dateien durchsuchen kann - wirklich so groß, wie das
> Dateisystem es zuläßt!

Trotzdem ist die ganze Sache durch die SSD / Platte begrenzt.

Bonita Montero

unread,
Apr 9, 2019, 4:36:04 PM4/9/19
to
> Das ist sehr einfacher hochperformanter Code, der beliebig
> große Dateien durchsuchen kann - wirklich so groß, wie das
> Dateisystem es zuläßt!

In einen 64-Bit-Adressraum lässt sich das bequem mmap()en.
Der Durchsatz ist dank Read-Ahead so gut wie der selbe.
Und der Code ist schneller geschrieben und wartbarer.
Das bissen CPU-Last mehr - interessiert keinen.

Helmut Schellong

unread,
Apr 10, 2019, 5:32:51 AM4/10/19
to
Ich habe festgestellt, daß Betriebssyteme bei großen
Festplattenzugriffen am Stück oft für 1 bis 3 s
unbedienbar sind.
Das ist verwunderlich - haben Betriebssysteme doch
Scheduler und Prozessoren mehrere Kerne.

Es ist nach meiner Erfahrung günstig, wenn alle Arbeiten
gemischt in optimal großen Häppchen abgearbeitet werden.

Bonita Montero

unread,
Apr 10, 2019, 6:56:27 AM4/10/19
to
>> In einen 64-Bit-Adressraum lässt sich das bequem mmap()en.
>> Der Durchsatz ist dank Read-Ahead so gut wie der selbe.
>> Und der Code ist schneller geschrieben und wartbarer.
>> Das bissen CPU-Last mehr - interessiert keinen.

> Ich habe festgestellt, daß Betriebssyteme bei großen
> Festplattenzugriffen am Stück oft für 1 bis 3 s
> unbedienbar sind.

Das hängt davon ab, ob durch das Einlesen oder Einmappen der Datei
anderes aus dem Speicher verdrängt wird. Mappt man aber, dann kann
man mit madvise() nach dem Bearbeiten der Daten Bereiche die man
nicht mehr bracht so markieren, dass die für andere Pages die hi-
neinkommen bevorzugt aus dem RAM geworfen werden.
Ansonsten: wenn man durch das Lesen längerer Dateien keine Swapping
-Problmatik hat, dann kann man sich das aus sparen und das von dir
genannte Problem tritt nicht audf.

Bonita Montero

unread,
Apr 10, 2019, 7:09:56 AM4/10/19
to
> Ich habe festgestellt, daß Betriebssyteme bei großen
> Festplattenzugriffen am Stück oft für 1 bis 3 s
> unbedienbar sind.

Achso, nochwas: das kann dir auch beim stückweisen Einlesen passieren.
Und zwar dadurch, dass Pages des OS-Caches zunehmend andere Pages ver-
drängen. Also Pages die ohnehin discardable sind weil die nicht zum
Working-Set irgendeines Prozesses oder des Kernels gehören.

Helmut Schellong

unread,
Apr 10, 2019, 8:38:01 AM4/10/19
to
Ich muß feststellen, daß ich die Aufgaben eines Betriebssystems
im Mikrokontroller-Bereich besser implementierte als dies
in jedem von mir genutztem Betriebssystem der Fall ist.

Das Verhalten einer Steuerungs- und Monitoring-Software
für Industrieanlagen (z.B. 300000 €) war so, als ob
alle Operationen gleichzeitig bearbeitet wurden.

Dabei werkelte lediglich ein einziger Mikrokontroller
16 Bit, 16 MHz Takt, 6 KB RAM.
Zu einer Zeit wurde also immer nur eine Operation bearbeitet.

Das Gerät hatte ~20000 Konfigurations-Parameter und steuerte
und überwachte bis zu 130 CanBus-Geräte, die jeweils bis
zu 8 Meßwerte sandten.
Etwa 30 Meßwerte hatte die Unit selbst per ADC gemessen.
Es gab Display und Tasten und etwa 70 Bedienungsmenüs.
Über eine Remote-Windows-Software konnte alternativ und
parallel alles gesteuert und konfiguriert werden, über
zwei Schnittstellen gleichzeitig.

Bonita Montero

unread,
Apr 10, 2019, 9:41:14 AM4/10/19
to
> Ich muß feststellen, daß ich die Aufgaben eines Betriebssystems
> im Mikrokontroller-Bereich besser implementierte als dies
> in jedem von mir genutztem Betriebssystem der Fall ist.
> ...
> Dabei werkelte lediglich ein einziger Mikrokontroller
> 16 Bit, 16 MHz Takt, 6 KB RAM.
> Zu einer Zeit wurde also immer nur eine Operation bearbeitet.

Das kann man doch nicht vergleichen. Die Anforderungen sind da
völlig andere und auf einem Mikrokcontroller mit 6kB RAM kriegst
Du nichts was sich Betriebssystem nennen kann.

Helmut Schellong

unread,
Apr 10, 2019, 10:56:40 AM4/10/19
to
Die Funktionen kann man vergleichen.
Ich habe auf dem Mikrokontroller quasi ein Betriebssystem
entwickelt und implementiert.
Es gibt z.B. mehrere Scheduler.
Eine Art Dateisystem gibt es sogar, in einem externen EPROM.

Rainer Weikusat

unread,
Apr 10, 2019, 12:01:20 PM4/10/19
to
Helmut Schellong <r...@schellong.biz> writes:
> On 04/10/2019 15:41, Bonita Montero wrote:
>>> Ich muß feststellen, daß ich die Aufgaben eines Betriebssystems
>>> im Mikrokontroller-Bereich besser implementierte als dies
>>> in jedem von mir genutztem Betriebssystem der Fall ist.
>>> ...
>>> Dabei werkelte lediglich ein einziger Mikrokontroller
>>> 16 Bit, 16 MHz Takt, 6 KB RAM.
>>> Zu einer Zeit wurde also immer nur eine Operation bearbeitet.
>>
>> Das kann man doch nicht vergleichen. Die Anforderungen sind da
>> völlig andere und auf einem Mikrokcontroller mit 6kB RAM kriegst
>> Du nichts was sich Betriebssystem nennen kann.
>
> Die Funktionen kann man vergleichen.
> Ich habe auf dem Mikrokontroller quasi ein Betriebssystem
> entwickelt und implementiert.
> Es gibt z.B. mehrere Scheduler.
> Eine Art Dateisystem gibt es sogar, in einem externen EPROM.

https://en.wikipedia.org/wiki/PDP-7

So ungefaehr dieselbe Groessenordnung. Aber Leute, die Angst davor
haben, dass sich ihr 2.x Ghz Laptop mit Shellscripts einen Bruch hebt,
koennen sich wohl gar nicht vorstellen, dass man sowas ueberhaupt
programmieren kann.

Bonita Montero

unread,
Apr 10, 2019, 12:31:00 PM4/10/19
to
> https://en.wikipedia.org/wiki/PDP-7
> So ungefaehr dieselbe Groessenordnung. Aber Leute, die Angst davor
> haben, dass sich ihr 2.x Ghz Laptop mit Shellscripts einen Bruch hebt,
> koennen sich wohl gar nicht vorstellen, dass man sowas ueberhaupt
> programmieren kann.

Ich bin froh, dass wir so ein primitives Technik-Niveau überwunden
haben.

Claus Reibenstein

unread,
Apr 10, 2019, 1:46:15 PM4/10/19