Wie genau funktionieren fopen(), fclose()?

Lesezeit: 7 Minuten

Fabians Benutzeravatar
Fabian

Ich habe mich nur über die Funktionen fopen, fclose, socket und closesocket gewundert. Was genau passiert beim Aufruf von fopen oder beim Öffnen eines Sockets (insbesondere in Bezug auf den Speicher)?

Kann das Öffnen von Dateien/Sockets ohne Schließen zu Speicherverlusten führen?

Und drittens: Wie werden Sockets erstellt und wie sehen sie in Bezug auf den Speicher aus?

Mich interessiert auch die Rolle des Betriebssystems (Windows) beim Lesen der Sockets und beim Senden der Daten.

  • Diese Funktionen neigen dazu, in Interrupts zu geraten, die vom Kernel abgefangen werden.

    – Alternative

    26. Februar 2011 um 23:26

  • @mathepic: Du meinst „Systemaufrufe“, oder? Der Aufruf des Betriebssystems ist nicht mehr so ​​teuer wie die Interrupt-Verarbeitung. Natürlich gibt es physische Hardware-Interrupts, wenn die Netzwerkkarte Daten empfängt, aber das ist ziemlich weit von den aufgeführten Funktionen entfernt.

    – Ben Voigt

    26. Februar 2011 um 23:55 Uhr

Stefan Monovs Benutzeravatar
Stefan Monov

Haftungsausschluss: Ich bin größtenteils nicht qualifiziert, darüber zu sprechen. Es wäre toll, wenn auch jemand mit mehr Sachkenntnis posten würde.

Dateien

Die Details, wie Dinge wie fopen() implementiert werden, hängen stark vom Betriebssystem ab (UNIX verfügt beispielsweise auch über fopen()). Sogar Windows-Versionen können sich stark voneinander unterscheiden.

Ich werde Ihnen meine Vorstellung davon geben, wie es funktioniert, aber im Grunde ist es Spekulation.

  • Beim Aufruf weist fopen a zu DATEI Objekt auf dem Heap. Beachten Sie, dass die Daten in einem FILE-Objekt undokumentiert sind – FILE ist ein undurchsichtige Strukturkönnen Sie nur Zeiger auf FILE aus Ihrem Code verwenden.
  • Das FILE-Objekt wird initialisiert. Zum Beispiel so etwas wie fillLevel = 0 Dabei ist fillLevel die Menge der gepufferten Daten, die noch nicht geleert wurden.
  • Ein Aufruf des Dateisystemtreibers (FS-Treiber) öffnet die Datei und stellt ein Handle dafür bereit, das irgendwo in der FILE-Struktur abgelegt wird.
    • Zu diesem Zweck ermittelt der FS-Treiber die Festplattenadresse, die dem angeforderten Pfad entspricht, und merkt sich diese Festplattenadresse intern, sodass er später Aufrufe von fread usw. ausführen kann.
      • Der FS-Treiber verwendet eine Art Indextabelle (auf der Festplatte gespeichert), um die Festplattenadresse zu ermitteln, die dem angeforderten Pfad entspricht. Dies unterscheidet sich stark je nach Dateisystemtyp – FAT32, NTFS usw.
      • Der FS-Treiber verlässt sich auf den Festplattentreiber, um die eigentlichen Lese- und Schreibvorgänge auf der Festplatte durchzuführen.
  • Möglicherweise wird der Datei ein Cache im RAM zugewiesen. Auf diese Weise kann C++ für alle Fälle ein KB lesen, wenn der Benutzer das Lesen eines Bytes anfordert, sodass spätere Lesevorgänge sofort erfolgen.
  • Von fopen wird ein Zeiger auf die zugewiesene DATEI zurückgegeben.

Wenn Sie eine Datei öffnen und nie schließen, können einige Dinge durchsickern, ja. Die FILE-Struktur wird auslaufen, die internen Daten des FS-Treibers werden auslaufen, und auch der Cache (sofern vorhanden) wird auslaufen.

Aber die Erinnerung ist nicht das Einzige, was verloren geht. Der Datei selbst wird undicht sein, weil das Betriebssystem denkt, dass es geöffnet ist, obwohl dies nicht der Fall ist. Dies kann beispielsweise unter Windows zu einem Problem werden, wenn eine im Schreibmodus geöffnete Datei erst wieder im Schreibmodus geöffnet werden kann, wenn sie geschlossen wird.

Wenn Ihre App beendet wird, ohne eine Datei zu schließen, werden die meisten Betriebssysteme danach bereinigen. Aber das nützt nicht viel, da Ihre App wahrscheinlich lange laufen wird, bevor sie beendet wird, und während dieser Zeit alle Dateien ordnungsgemäß schließen müssen. Außerdem können Sie sich nicht vollständig darauf verlassen, dass das Betriebssystem nach Ihnen aufräumt – dies wird im C-Standard nicht garantiert.

Steckdosen

Die Implementierung eines Sockets hängt von der Art des Sockets ab – Netzwerk-Listen-Socket, Netzwerk-Client-Socket, Interprozess-Socket usw.

Eine vollständige Diskussion aller Arten von Sockets und ihrer möglichen Implementierungen würde hier nicht passen.

Zusamenfassend:

  • Genau wie eine Datei speichert ein Socket einige Informationen im RAM, die für seinen Betrieb relevante Dinge beschreiben, beispielsweise die IP des Remote-Hosts.
  • Aus Leistungsgründen kann es auch Caches im RAM haben
  • Es kann begrenzte Betriebssystemressourcen wie offene Ports festhalten, sodass diese für die Verwendung durch andere Apps nicht verfügbar sind

All diese Dinge werden auslaufen, wenn Sie die Steckdose nicht schließen.

Die Rolle des Betriebssystems in Sockets

Das Betriebssystem implementiert den TCP/IP-Standard, Ethernet und andere Protokolle, die zum Planen/Versenden/Annehmen von Verbindungen erforderlich sind und sie dem Benutzercode über eine API wie Berkeley Sockets zur Verfügung stellen.

Das Betriebssystem delegiert Netzwerk-E/A (Kommunikation mit der Netzwerkkarte) an den Netzwerktreiber.

  • Gute Antwort. Ich möchte nur hinzufügen, dass die Bibliothek in den meisten modernen Betriebssystemen den Treiber nicht direkt, sondern über einen Systemaufruf aufruft.

    – Chinmay Kanchi

    27. Februar 2011 um 1:15

  • Ich würde das auch hinzufügen, obwohl es nicht gelingt, Dateideskriptoren zu schließen Ist Bei einer Art Speicherleck verlieren Sie auch eine Ressource, die wahrscheinlich knapper ist als der Speicher, nämlich die Anzahl möglicher gleichzeitig geöffneter Dateideskriptoren. Erwarten Sie also einen Misserfolg mit EMFILE oder was auch immer, lange bevor es Ihnen tatsächlich gelingt, eine lähmende Menge Speicher zu verschwenden.

    – Steve Jessop

    27. Februar 2011 um 3:24


Mit VS2017 unter Windows 10 können Sie den internen Aufrufstack sehen:

ntdll.dll!NtCreateFile()   Unknown
KernelBase.dll!CreateFileInternal() Unknown
KernelBase.dll!CreateFileW()   Unknown
ucrtbased.dll!create_file(const wchar_t * const path, _SECURITY_ATTRIBUTES * const security_attributes, const `anonymous-namespace'::file_options options) Line 234 C++
ucrtbased.dll!_wsopen_nolock(int * punlock_flag, int * pfh, const wchar_t * path, int oflag, int shflag, int pmode, int secure) Line 702    C++
ucrtbased.dll!_sopen_nolock(int * punlock_flag, int * pfh, const char * path, int oflag, int shflag, int pmode, int secure) Line 852    C++
ucrtbased.dll!__crt_char_traits<char>::tsopen_nolock<int * __ptr64,int * __ptr64,char const * __ptr64 const & __ptr64,int const & __ptr64,int,int const & __ptr64,int>(int * && <args_0>, int * && <args_1>, const char * const & <args_2>, const int & <args_3>, int && <args_4>, const int & <args_5>, int && <args_6>) Line 109  C++
ucrtbased.dll!common_sopen_dispatch<char>(const char * const path, const int oflag, const int shflag, const int pmode, int * const pfh, const int secure) Line 172  C++
ucrtbased.dll!_sopen_dispatch(const char * path, int oflag, int shflag, int pmode, int * pfh, int secure) Line 204  C++
ucrtbased.dll!_sopen_s(int * pfh, const char * path, int oflag, int shflag, int pmode) Line 895 C++
ucrtbased.dll!__crt_char_traits<char>::tsopen_s<int * __ptr64,char const * __ptr64 const & __ptr64,int const & __ptr64,int const & __ptr64,int>(int * && <args_0>, const char * const & <args_1>, const int & <args_2>, const int & <args_3>, int && <args_4>) Line 109 C++
ucrtbased.dll!common_openfile<char>(const char * const file_name, const char * const mode, const int share_flag, const __crt_stdio_stream stream) Line 38   C++
ucrtbased.dll!_openfile(const char * file_name, const char * mode, int share_flag, _iobuf * public_stream) Line 67  C++
ucrtbased.dll!__crt_char_traits<char>::open_file<char const * __ptr64 const & __ptr64,char const * __ptr64 const & __ptr64,int const & __ptr64,_iobuf * __ptr64>(const char * const & <args_0>, const char * const & <args_1>, const int & <args_2>, _iobuf * && <args_3>) Line 109 C++
ucrtbased.dll!common_fsopen<char>(const char * const file_name, const char * const mode, const int share_flag) Line 54  C++
ucrtbased.dll!fopen(const char * file, const char * mode) Line 104  C++

Der meiste Code befindet sich in:

C:\Program Files (x86)\Windows Kits\10\Source\10.0.17763.0\ucrt\stdio\fopen.cpp
C:\Program Files (x86)\Windows Kits\10\Source\10.0.17763.0\ucrt\stdio\openfile.cpp
C:\Program Files (x86)\Windows Kits\10\Source\10.0.17763.0\ucrt\lowio\open.cpp

In _wsopen_nolock in open.cpp gibt es:

// Allocate the CRT file handle.  Note that if a handle is allocated, it is
// locked when it is returned by the allocation function.  It is our caller's
// responsibility to unlock the file handle (we do not unlock it before
// returning).
*pfh = _alloc_osfhnd();

Schließlich ruft es die Windows-API CreateFileW auf, die die versteckte API „NtCreateFile“ aufruft, deren Assemblycode lautet:

NtCreateFile:
00007FFFD81A0120 mov         r10,rcx  
00007FFFD81A0123 mov         eax,55h  
00007FFFD81A0128 test        byte ptr[7FFE0308h],1  
00007FFFD81A0130 jne         NtCreateFile+15h(07FFFD81A0135h)
00007FFFD81A0132 syscall
00007FFFD81A0134 ret
00007FFFD81A0135 int         2Eh  
00007FFFD81A0137 ret
00007FFFD81A0138 nop         dword ptr[rax + rax]

Schließlich führt es die Syscall-Anweisung aus, die in den Kernel-Code eingeht.

1452320cookie-checkWie genau funktionieren fopen(), fclose()?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy