Încărcare dinamică - Dynamic loading
Încărcarea dinamică este un mecanism prin care un program de computer poate, în timpul rulării , încărca o bibliotecă (sau alt binar ) în memorie, prelua adresele funcțiilor și variabilelor conținute în bibliotecă, poate executa acele funcții sau accesa aceste variabile și descărca bibliotecă din memorie. Este unul dintre cele 3 mecanisme prin care un program de calculator poate folosi un alt software; celelalte două sunt legături statice și legături dinamice . Spre deosebire de legătura statică și legarea dinamică, încărcarea dinamică permite unui program de computer să pornească în absența acestor biblioteci, să descopere bibliotecile disponibile și să câștige potențial funcționalitate suplimentară.
Istorie
Încărcarea dinamică a fost o tehnică obișnuită pentru sistemele de operare IBM pentru System / 360, cum ar fi OS / 360 , în special pentru subrutinele I / O și pentru bibliotecile de rulare COBOL și PL / I și continuă să fie folosită în sistemele de operare IBM pentru z / Architecture , cum ar fi z / OS . În ceea ce privește programatorul de aplicații, încărcarea este în mare măsură transparentă, deoarece este gestionată în cea mai mare parte de sistemul de operare (sau subsistemul I / O). Principalele avantaje sunt:
- Corecții ( patch-uri ) la subsisteme au remediat toate programele simultan, fără a fi nevoie să le reconectați
- Bibliotecile ar putea fi protejate împotriva modificărilor neautorizate
Sistemul strategic de procesare a tranzacțiilor IBM , CICS (începând cu anii 1970) folosește extensiv încărcarea dinamică atât pentru nucleul său, cât și pentru încărcarea normală a programului de aplicație . Corecțiile la programele de aplicații ar putea fi făcute offline și copiile noi ale programelor modificate pot fi încărcate dinamic, fără a fi nevoie să reporniți CICS (care poate, și se întâmplă frecvent, să ruleze 24/7 ).
Bibliotecile partajate au fost adăugate la Unix în anii 1980, dar inițial fără posibilitatea de a permite unui program să încarce biblioteci suplimentare după pornire.
Utilizări
Încărcarea dinamică este cea mai frecvent utilizată la implementarea pluginurilor software . De exemplu, fișierele de pluginuri „obiect partajat dinamic” ale serverului web Apache *.dso sunt biblioteci care sunt încărcate în timpul rulării cu încărcare dinamică. Încărcarea dinamică este, de asemenea, utilizată în implementarea programelor de calculator în care mai multe biblioteci diferite pot furniza funcționalitatea necesară și unde utilizatorul are opțiunea de a selecta ce bibliotecă sau biblioteci să furnizeze.
În C / C ++
Nu toate sistemele acceptă încărcarea dinamică. Sistemele de operare de tip UNIX, cum ar fi macOS , Linux și Solaris, asigură încărcare dinamică cu biblioteca de limbaj de programare C "dl". Sistemul de operare Windows oferă încărcare dinamică prin intermediul API-ului Windows .
rezumat
| Nume | API POSIX / UNIX standard | API Microsoft Windows |
|---|---|---|
| Includerea fișierului antet |
#include <dlfcn.h>
|
#include <windows.h>
|
| Definiții pentru antet |
dl
( |
kernel32.dll
|
| Se încarcă biblioteca |
dlopen
|
LoadLibraryLoadLibraryEx |
| Extragerea conținutului |
dlsym
|
GetProcAddress
|
| Descărcarea bibliotecii |
dlclose
|
FreeLibrary
|
Se încarcă biblioteca
Încărcarea bibliotecii este realizată cu LoadLibrarysau LoadLibraryExpe pentru Windows și dlopenpe UNIX , cum ar fi de operare sisteme . Urmează exemple:
Majoritatea sistemelor de operare similare UNIX (Solaris, Linux, * BSD etc.)
void* sdl_library = dlopen("libSDL.so", RTLD_LAZY);
if (sdl_library == NULL) {
// report error ...
} else {
// use the result in a call to dlsym
}
macOS
Ca bibliotecă UNIX :
void* sdl_library = dlopen("libSDL.dylib", RTLD_LAZY);
if (sdl_library == NULL) {
// report error ...
} else {
// use the result in a call to dlsym
}
Ca macOS Framework :
void* sdl_library = dlopen("/Library/Frameworks/SDL.framework/SDL", RTLD_LAZY);
if (sdl_library == NULL) {
// report error ...
} else {
// use the result in a call to dlsym
}
Sau dacă cadrul sau pachetul conține cod Objective-C:
NSBundle *bundle = [NSBundle bundleWithPath:@"/Library/Plugins/Plugin.bundle"];
NSError *err = nil;
if ([bundle loadAndReturnError:&err])
{
// Use the classes and functions in the bundle.
}
else
{
// Handle error.
}
Windows
HMODULE sdl_library = LoadLibrary(TEXT("SDL.dll"));
if (sdl_library == NULL) {
// report error ...
} else {
// use the result in a call to GetProcAddress
}
Extragerea conținutului bibliotecii
Extragerea conținutului unei biblioteci încărcate dinamic se realizează cu GetProcAddressprivire pentru Windows și dlsympe UNIX -cum ar fi sistemele de operare .
Sisteme de operare asemănătoare UNIX (Solaris, Linux, * BSD, macOS etc.)
void* initializer = dlsym(sdl_library,"SDL_Init");
if (initializer == NULL) {
// report error ...
} else {
// cast initializer to its proper type and use
}
Pe macOS, atunci când utilizați pachete Objective-C, puteți, de asemenea:
Class rootClass = [bundle principalClass]; // Alternatively, NSClassFromString() can be used to obtain a class by name.
if (rootClass)
{
id object = [[rootClass alloc] init]; // Use the object.
}
else
{
// Report error.
}
Windows
FARPROC initializer = GetProcAddress(sdl_library,"SDL_Init");
if (initializer == NULL) {
// report error ...
} else {
// cast initializer to its proper type and use
}
Conversia unui pointer de funcție de bibliotecă
Rezultatul dlsym()sau GetProcAddress()trebuie convertit la un pointer de tipul adecvat înainte de a putea fi utilizat.
Windows
În Windows, conversia este simplă, deoarece FARPROC este în esență deja un indicator de funcție :
typedef INT_PTR (*FARPROC)(void);
Acest lucru poate fi problematic atunci când adresa unui obiect trebuie recuperată, mai degrabă decât o funcție. Cu toate acestea, de obicei, cineva dorește să extragă funcții oricum, deci acest lucru nu este în mod normal o problemă.
typedef void (*sdl_init_function_type)(void);
sdl_init_function_type init_func = (sdl_init_function_type) initializer;
UNIX (POSIX)
Conform specificațiilor POSIX, rezultatul dlsym()este un voidindicator. Cu toate acestea, un indicator de funcție nu este necesar să aibă nici măcar aceeași dimensiune ca un indicator de obiect de date și, prin urmare, o conversie validă între tip void*și un indicator într-o funcție poate să nu fie ușor de implementat pe toate platformele.
Pe majoritatea sistemelor utilizate astăzi, indicatoarele de funcții și de obiecte sunt de facto convertibile. Următorul fragment de cod demonstrează o soluție care permite conversia oricum pe multe sisteme:
typedef void (*sdl_init_function_type)(void);
sdl_init_function_type init_func = (sdl_init_function_type)initializer;
Fragmentul de mai sus va da un avertisment privind unele compilatoare: warning: dereferencing type-punned pointer will break strict-aliasing rules. O altă soluție este:
typedef void (*sdl_init_function_type)(void);
union { sdl_init_function_type func; void * obj; } alias;
alias.obj = initializer;
sdl_init_function_type init_func = alias.func;
care dezactivează avertismentul chiar dacă aliasarea strictă este în vigoare. Acest lucru folosește faptul că citirea de la un membru al uniunii diferit de cel scris în cel mai recent (numit „ tip punning ”) este comună și este permisă în mod explicit chiar dacă aliasarea strictă este în vigoare, cu condiția ca memoria să fie accesată prin intermediul tipului de uniune direct. Cu toate acestea, acest lucru nu este strict cazul aici, deoarece indicatorul funcției este copiat pentru a fi utilizat în afara uniunii. Rețineți că acest truc poate să nu funcționeze pe platforme unde dimensiunea indicatorilor de date și dimensiunea indicatorilor de funcție nu sunt aceleași.
Rezolvarea problemei indicatorului funcțional pe sistemele POSIX
Rămâne faptul că orice conversie între funcția și indicatorii obiectelor de date trebuie considerată ca o extensie de implementare (inerent neportabilă) și că nu există un mod „corect” pentru o conversie directă, deoarece în acest sens standardele POSIX și ISO sunt în contradicție fiecare.
Din cauza acestei probleme, documentația POSIX dlsym()pentru problema învechită 6 a declarat că „o versiune viitoare poate fie să adauge o nouă funcție pentru a returna indicatorii de funcții, fie interfața actuală poate fi depreciată în favoarea a două funcții noi: una care returnează indicatori de date iar celălalt care returnează pointerele funcției ".
Pentru versiunea ulterioară a standardului (ediția 7, 2008), problema a fost discutată și concluzia a fost că indicatorii de funcție trebuie să fie convertibili în void*pentru conformitatea POSIX. Acest lucru impune producătorilor de compilatoare să implementeze o distribuție de lucru pentru acest caz.
Dacă conținutul bibliotecii poate fi schimbat (adică în cazul unei biblioteci personalizate), pe lângă funcția în sine poate fi exportat un indicator către aceasta. Deoarece un pointer către un pointer funcțional este el însuși un pointer obiect, acest pointer poate fi întotdeauna recuperat legal prin apelare dlsym()și conversie ulterioară. Cu toate acestea, această abordare necesită menținerea unor indicații separate pentru toate funcțiile care urmează să fie utilizate extern, iar beneficiile sunt de obicei mici.
Descărcarea bibliotecii
Încărcarea unei biblioteci determină alocarea memoriei; biblioteca trebuie alocată pentru a evita scurgerile de memorie . În plus, eșecul descărcării unei biblioteci poate împiedica operațiunile sistemului de fișiere pe fișierul care conține biblioteca. Descarcarea biblioteca este realizată cu FreeLibraryprivire pentru Windows și dlclosepe UNIX , cum ar fi de operare sisteme . Cu toate acestea, descărcarea unui DLL poate duce la blocarea programului dacă obiectele din aplicația principală se referă la memoria alocată în DLL. De exemplu, dacă o DLL introduce o nouă clasă și DLL este închisă, operațiunile ulterioare pe instanțe din acea clasă din aplicația principală vor provoca probabil o încălcare a accesului la memorie. La fel, dacă DLL introduce o funcție din fabrică pentru instanțierea claselor încărcate dinamic, apelarea sau dereferențierea acelei funcții după închiderea DLL duce la un comportament nedefinit.
Sisteme de operare asemănătoare UNIX (Solaris, Linux, * BSD, macOS etc.)
dlclose(sdl_library);
Windows
FreeLibrary(sdl_library);
Biblioteca specială
Implementările încărcării dinamice pe sistemele de operare de tip UNIX și Windows permit programatorilor să extragă simboluri din procesul de executare curent.
Sistemele de operare de tipul UNIX permit programatorilor să acceseze tabelul global de simboluri, care include atât bibliotecile dinamice executabile principale, cât și cele încărcate ulterior.
Windows permite programatorilor să acceseze simboluri exportate de executabilul principal. Windows nu folosește un tabel global de simboluri și nu are API pentru a căuta în mai multe module pentru a găsi un simbol după nume.
Sisteme de operare asemănătoare UNIX (Solaris, Linux, * BSD, macOS etc.)
void* this_process = dlopen(NULL,0);
Windows
HMODULE this_process = GetModuleHandle(NULL);
HMODULE this_process_again;
GetModuleHandleEx(0,0,&this_process_again);
În Java
În limbajul de programare Java , clasele pot fi încărcate dinamic folosind ClassLoaderobiectul. De exemplu:
Class type = ClassLoader.getSystemClassLoader().loadClass(name);
Object obj = type.newInstance();
Mecanismul de reflecție oferă, de asemenea, un mijloc de încărcare a unei clase dacă nu este deja încărcată. Acesta folosește clasificatorul clasei curente:
Class type = Class.forName(name);
Object obj = type.newInstance();
Cu toate acestea, nu există o modalitate simplă de a descărca o clasă într-un mod controlat. Clasele încărcate pot fi descărcate doar într-un mod controlat, adică atunci când programatorul dorește să se întâmple acest lucru, dacă încărcătorul de clasă folosit pentru a încărca clasa nu este încărcătorul de clase de sistem și este el însuși descărcat. Când faceți acest lucru, trebuie respectate diferite detalii pentru a vă asigura că clasa este într-adevăr descărcată. Acest lucru face ca descărcarea cursurilor să fie plictisitoare.
Descărcarea implicită a claselor, adică într-un mod necontrolat de către colectorul de gunoi, sa schimbat de câteva ori în Java. Până la Java 1.2. colectorul de gunoi putea descărca o clasă ori de câte ori simțea că are nevoie de spațiu, independent de care încărcător de clasă a fost folosit pentru a încărca clasa. Începând cu Java 1.2, clasele încărcate prin sistemul de încărcare a claselor nu au fost niciodată descărcate și clasele încărcate prin alte încărcătoare de clasă numai când acest alt încărcător de clase a fost descărcat. Începând cu Java 6, clasele pot conține un marker intern care indică colectorului de gunoi că pot fi descărcate dacă colectorul de gunoi dorește să facă acest lucru, independent de încărcătorul de clase folosit pentru a încărca clasa. Colectorul de gunoi este liber să ignore acest indiciu.
În mod similar, bibliotecile care implementează metode native sunt încărcate dinamic folosind System.loadLibrarymetoda. Nu există nicio System.unloadLibrarymetodă.
Platforme fără încărcare dinamică
În ciuda promulgării sale în anii 1980 prin UNIX și Windows, unele sisteme au ales în continuare să nu adauge - sau chiar să elimine - încărcarea dinamică. De exemplu, Planul 9 de la Bell Labs și succesorul său 9front evită intenționat legătura dinamică, deoarece consideră că este „dăunător”. Limbajul de programare Du - te , de unii dintre aceiași dezvoltatori ca Plan 9, de asemenea , nu suporta legarea dinamica, dar plugin de încărcare este disponibil din Go 1.8 (februarie 2017). Runtime-ul Go și orice funcții ale bibliotecii sunt legate static în binarul compilat.
Vezi si
- Compilați și mergeți sistemul
- DLL Hell
- Legare directă
- Legare dinamică (calcul)
- Expediere dinamică
- Bibliotecă dinamică
- Linker dinamic
- Bibliotecă de legături dinamice
- FlexOS
- Linker GNU
- aur (linker)
- Bibliotecă (calcul)
- Linker (calcul)
- Încărcător (calcul)
- Decorați numele
- Preobligatoriu
- Prelinking
- Relocare (informatică)
- Tabel de relocare
- Extensie sistem rezident (RSX)
- Biblioteca statică
- Terminați și rămâneți rezident (TSR)
Referințe
Lecturi suplimentare
- Silberschatz, Abraham; Galvin, Peter Baer; Gagne, Greg (2005). „Capitolul 8.1.4„ Încărcare dinamică ”și Capitolul 8.1.5„ Conectare dinamică și biblioteci partajate ” ”. Concepte de sistem de operare . J. Wiley & Sons . ISBN 978-0-471-69466-3.
linkuri externe
- Legături generale
- Încărcare dinamică pe Linux4U
- Suport pentru obiectul partajat dinamic (DSO) de către Apache
- C ++ Dynamic Linking Prin exemplu
- Exemplu de încărcare dinamică a bibliotecii (exemplu de lucru complet, dar concis)
- Subiecte de programare a bibliotecii dinamice de la Apple Developer Connection (direcționat către macOS)
- API UNIX C / C ++:
- API Windows C / C ++:
- API Java: