Differenza tra oggetti condivisi (.so), librerie statiche (.a) e DLL (.so)?


273

Sono stato coinvolto in alcuni dibattiti riguardo alle biblioteche di Linux e vorrei confermare alcune cose.

È mia comprensione (per favore correggimi se sbaglio e modificherò il mio post in seguito), ci sono due modi per usare le librerie quando crei un'applicazione:

  1. Librerie statiche (file .a): al momento del collegamento, una copia dell'intera libreria viene inserita nell'applicazione finale in modo che le funzioni all'interno della libreria siano sempre disponibili per l'applicazione chiamante
  2. Oggetti condivisi (file .so): al momento del collegamento, l'oggetto viene appena verificato rispetto alla sua API tramite il file di intestazione (.h) corrispondente. La libreria non viene effettivamente utilizzata fino al runtime, dove è necessaria.

L'ovvio vantaggio delle librerie statiche è che consentono all'intera applicazione di essere autonoma, mentre il vantaggio delle librerie dinamiche è che il file ".so" può essere sostituito (cioè: nel caso in cui debba essere aggiornato a causa di una sicurezza bug) senza richiedere la ricompilazione dell'applicazione di base.

Ho sentito alcune persone distinguere tra oggetti condivisi e librerie dinamiche (DLL), anche se sono entrambi file ".so". Esiste una distinzione tra oggetti condivisi e DLL quando si tratta di sviluppo C / C ++ su Linux o qualsiasi altro sistema operativo compatibile POSIX (es. MINIX, UNIX, QNX, ecc.)? Mi è stato detto che una differenza chiave (finora) è che gli oggetti condivisi vengono utilizzati solo in fase di esecuzione, mentre le DLL devono essere aperte prima utilizzando la chiamata dlopen () all'interno dell'applicazione.

Infine, ho anche sentito alcuni sviluppatori menzionare "archivi condivisi", che, a mio avviso, sono anche librerie statiche, ma che non vengono mai utilizzate direttamente da un'applicazione. Al contrario, altre librerie statiche si collegheranno agli "archivi condivisi" per estrarre alcune (ma non tutte) funzioni / risorse dall'archivio condiviso nella libreria statica in fase di creazione.

Grazie in anticipo per il vostro aiuto.

Aggiornare


Nel contesto in cui mi sono stati forniti questi termini, sono stati effettivamente termini errati utilizzati da un team di sviluppatori Windows che hanno dovuto imparare Linux. Ho provato a correggerli, ma le norme linguistiche (errate) sono rimaste bloccate.

  1. Oggetto condiviso: una libreria che viene automaticamente collegata a un programma all'avvio del programma ed esiste come file autonomo. La libreria è inclusa nell'elenco dei collegamenti al momento della compilazione (ovvero: LDOPTS+=-lmylibper un file di libreria denominato mylib.so). La libreria deve essere presente al momento della compilazione e all'avvio dell'applicazione.
  2. Libreria statica: una libreria che viene unita al programma vero e proprio al momento della compilazione per una singola (più grande) applicazione contenente il codice dell'applicazione e il codice della libreria che viene automaticamente collegato in un programma quando il programma viene creato e il file binario finale contenente entrambi il programma principale e la libreria stessa esistono come un singolo file binario autonomo. La libreria è inclusa nell'elenco dei collegamenti al momento della compilazione (ovvero: LDOPTS+=-lmylibper un file di libreria denominato mylib.a). La libreria deve essere presente al momento della compilazione.
  3. DLL: essenzialmente lo stesso di un oggetto condiviso, ma anziché essere incluso nell'elenco dei collegamenti al momento della compilazione, la libreria viene caricata tramite dlopen()/ dlsym()comandi in modo che la libreria non debba essere presente al momento della compilazione per la compilazione del programma. Inoltre, non è necessario che la libreria sia presente (necessariamente) all'avvio dell'applicazione o al momento della compilazione , poiché è necessaria solo al momento in cui vengono effettuate le dlopen/ dlsymchiamate.
  4. Archivio condiviso: essenzialmente come una libreria statica, ma è compilato con i flag "export-shared" e "-fPIC". La libreria è inclusa nell'elenco dei collegamenti al momento della compilazione (ovvero: LDOPTS+=-lmylibSper un file di libreria denominato mylibS.a). La distinzione tra i due è che questo flag aggiuntivo è richiesto se un oggetto condiviso o una DLL desidera collegare staticamente l'archivio condiviso nel proprio codice ed essere in grado di rendere le funzioni nell'oggetto condiviso disponibili per altri programmi, anziché semplicemente utilizzarle interno alla DLL. Ciò è utile nel caso in cui qualcuno ti fornisca una libreria statica e desideri riconfezionarla come SO. La libreria deve essere presente al momento della compilazione.

Aggiornamento aggiuntivo

La distinzione tra " DLL" e " shared library" era solo un colloquialismo (pigro, impreciso) nella società in cui lavoravo all'epoca (gli sviluppatori di Windows erano costretti a passare allo sviluppo di Linux e il termine bloccato), aderendo alle descrizioni annotate sopra.

Inoltre, il " S" letterale finale dopo il nome della biblioteca, nel caso di "archivi condivisi" era solo una convenzione usata in quella società, e non nel settore in generale.


14
Per i .afile, la "a" in realtà sta per "archove", ed è semplicemente un archivio di file oggetto. I linker moderni dovrebbero essere abbastanza buoni da non dover includere la libreria while, solo i file degli oggetti nell'archivio necessari e potrebbero anche usare solo le sezioni di codice / dati nei file degli oggetti a cui si fa riferimento.
Qualche programmatore, amico,

4
DLL è solo una terminologia di Windows. Non è utilizzato su unices.
R .. GitHub smette di aiutare ICE



2
@DevNull "arch i ve" ovviamente. :)
Un tizio programmatore il

Risposte:


94

Ho sempre pensato che DLL e oggetti condivisi siano termini diversi per la stessa cosa - Windows li chiama DLL, mentre sui sistemi UNIX sono oggetti condivisi, con il termine generale - libreria collegata dinamicamente - che copre entrambi (anche la funzione di aprire un .so su UNIX viene chiamato dlopen()dopo 'libreria dinamica').

Sono infatti collegati solo all'avvio dell'applicazione, tuttavia la tua nozione di verifica rispetto al file di intestazione non è corretta. Il file di intestazione definisce i prototipi necessari per compilare il codice che utilizza la libreria, ma al momento del collegamento il linker guarda all'interno della libreria stessa per assicurarsi che le funzioni necessarie siano effettivamente presenti. Il linker deve trovare i corpi funzione da qualche parte al momento del collegamento o genererà un errore. Lo fa ANCHE in fase di esecuzione, perché come giustamente fai notare la libreria stessa potrebbe essere cambiata da quando è stato compilato il programma. Questo è il motivo per cui la stabilità ABI è così importante nelle librerie di piattaforme, poiché il cambiamento ABI è ciò che rompe i programmi esistenti compilati rispetto alle versioni precedenti.

Le librerie statiche sono solo fasci di file oggetto direttamente dal compilatore, proprio come quelli che ti stai costruendo come parte della compilazione del tuo progetto, quindi vengono inseriti e inviati al linker esattamente allo stesso modo, e i bit non utilizzati sono lasciato cadere esattamente allo stesso modo.


1
Perché alcuni progetti che vedo su Linux devono usare la chiamata dlopen () per accedere alle funzioni all'interno di un file ".so" e alcuni non lo devono fare affatto? Grazie, a proposito!
Cloud

9
Quelli che non lo fanno ottengono le funzioni loro assegnate dal caricatore di processi, ovvero il caricatore di elfi di Linux. dlopen esiste se l'applicazione desidera aprire e utilizzare un file .so o .dll che non era presente al momento della compilazione o semplicemente aggiungere funzionalità extra, come i plug-in.
rapadura,

Ma l'applicazione non verrà compilata affatto se .so non è presente al momento della compilazione? È possibile forzare il linker a creare il programma finale senza il .so presente? Grazie.
Cloud

1
Credo che dipende da come usi le funzioni di .so, ma qui la mia conoscenza di questo si ferma: / Buone domande.
rapadura,

1
Per quanto riguarda dlopen () e la sua famiglia di funzioni, ho capito che questo è usato per aprire / chiudere a livello di programmazione una dll in modo che non debba essere caricata in memoria per l'intera esecuzione dell'applicazione. Altrimenti, devi dire al linker nei suoi argomenti della riga di comando (aka il tuo makefile) che vuoi caricare la libreria. Verrà caricato in fase di esecuzione e rimarrà caricato in memoria fino alla chiusura dell'applicazione. Ci sono più cose che possono accadere a livello di sistema operativo, ma questo è più o meno ciò che accade per quanto riguarda l'applicazione.
Taylor Price,

198

Una libreria statica (.a) è una libreria che può essere collegata direttamente all'eseguibile finale prodotto dal linker, è contenuta al suo interno e non è necessario avere la libreria nel sistema in cui verrà distribuito l'eseguibile.

Una libreria condivisa (.so) è una libreria collegata ma non incorporata nell'eseguibile finale, quindi verrà caricata all'avvio dell'eseguibile e dovrà essere presente nel sistema in cui è distribuito l'eseguibile.

Una libreria a collegamento dinamico su Windows (.dll) è come una libreria condivisa (.so) su Linux, ma ci sono alcune differenze tra le due implementazioni correlate al sistema operativo (Windows vs Linux):

Una DLL può definire due tipi di funzioni: esportata e interna. Le funzioni esportate devono essere richiamate da altri moduli, nonché dall'interno della DLL in cui sono definite. Le funzioni interne sono in genere destinate a essere chiamate solo dall'interno della DLL in cui sono definite.

Una libreria SO su Linux non richiede istruzioni di esportazione speciali per indicare simboli esportabili, poiché tutti i simboli sono disponibili per un processo di interrogazione.


1
+1 bella spiegazione semplice. Se una funzione viene dichiarata "interna" in una DLL, ciò significa che non può essere chiamata dall'esterno della libreria?
Mike

23
Non è necessariamente vero che tutti i simboli sono disponibili in una libreria SO. I simboli nascosti sono possibili e consigliati perché non vi è alcuna buona ragione per gli utenti della biblioteca di vedere tutti i tuoi simboli.
Zan Lynx,

3
A proposito: g ++ ha una __attribute__sintassi per i simboli 'esportazione' selettivi:#define DLLEXPORT __attribute__ ((visibility("default"))) #define DLLLOCAL __attribute__ ((visibility("hidden")))
Brian Haak,

33

Posso approfondire i dettagli delle DLL in Windows per aiutare a chiarire quei misteri ai miei amici qui in * NIX-land ...

Una DLL è come un file di oggetti condivisi. Entrambe sono immagini, pronte per essere caricate in memoria dal programma di caricamento del rispettivo sistema operativo. Le immagini sono accompagnate da vari bit di metadati per aiutare i linker e i caricatori a creare le associazioni necessarie e utilizzare la libreria di codice.

Le DLL di Windows hanno una tabella di esportazione. Le esportazioni possono essere per nome o per posizione della tabella (numerico). Quest'ultimo metodo è considerato "vecchia scuola" ed è molto più fragile: la ricostruzione della DLL e la modifica della posizione di una funzione nella tabella finiranno in un disastro, mentre non vi è alcun reale problema se il collegamento dei punti di ingresso è per nome. Quindi, dimentica quello come un problema, ma tieni presente che è lì se lavori con codice "dinosauro" come librerie di fornitori di terze parti.

Le DLL di Windows vengono create compilando e collegando, proprio come si farebbe con una EXE (applicazione eseguibile), ma la DLL non è pensata come indipendente, proprio come una SO deve essere utilizzata da un'applicazione, tramite caricamento dinamico, oppure tramite link-time binding (il riferimento al SO è incorporato nei metadati del file binario dell'applicazione e il caricatore del programma del sistema operativo caricherà automaticamente i SO di riferimento). Le DLL possono fare riferimento ad altre DLL, così come le SO possono fare riferimento ad altre SO.

In Windows, le DLL renderanno disponibili solo punti di ingresso specifici. Questi sono chiamati "esportazioni". Lo sviluppatore può utilizzare una parola chiave del compilatore speciale per rendere un simbolo visibile esternamente (ad altri linker e al caricatore dinamico) oppure le esportazioni possono essere elencate in un file di definizione del modulo che viene utilizzato al momento del collegamento quando la stessa DLL è in fase di creazione. La pratica moderna è decorare la definizione della funzione con la parola chiave per esportare il nome del simbolo. È anche possibile creare file di intestazione con parole chiave che dichiareranno quel simbolo come uno da importare da una DLL all'esterno dell'unità di compilazione corrente. Cerca le parole chiave __declspec (dllexport) e __declspec (dllimport) per ulteriori informazioni.

Una delle caratteristiche interessanti delle DLL è che possono dichiarare una funzione standard di gestore "al caricamento / scaricamento". Ogni volta che la DLL viene caricata o scaricata, la DLL può eseguire alcune inizializzazioni o operazioni di pulizia, a seconda dei casi. Questo si adatta perfettamente ad avere una DLL come gestore delle risorse orientato agli oggetti, come un driver di dispositivo o un'interfaccia di oggetti condivisi.

Quando uno sviluppatore desidera utilizzare una DLL già creata, deve fare riferimento a una "libreria di esportazione" (* .LIB) creata dallo sviluppatore DLL quando ha creato la DLL oppure deve caricare esplicitamente la DLL in fase di esecuzione e richiedere indirizzo del punto di ingresso per nome tramite i meccanismi LoadLibrary () e GetProcAddress (). La maggior parte delle volte, il collegamento a un file LIB (che contiene semplicemente i metadati del linker per i punti di ingresso esportati della DLL) è il modo in cui le DLL vengono utilizzate. Il caricamento dinamico è in genere riservato per l'implementazione del "polimorfismo" o della "configurabilità di runtime" nei comportamenti del programma (accesso a componenti aggiuntivi o funzionalità definite in seguito, dette "plug-in").

Il modo in cui Windows fa le cose può causare confusione a volte; il sistema utilizza l'estensione .LIB per fare riferimento sia alle normali librerie statiche (archivi, come file POSIX * .a) sia alle librerie "export stub" necessarie per associare un'applicazione a una DLL al momento del collegamento. Quindi, si dovrebbe sempre cercare di vedere se un file * .LIB ha un file * .DLL con lo stesso nome; in caso contrario, è probabile che il file * .LIB sia un archivio di librerie statico e non esporti metadati di associazione per una DLL.


4

Hai ragione nel dire che i file statici vengono copiati nell'applicazione al momento del collegamento e che i file condivisi vengono appena verificati al momento del collegamento e caricati in fase di esecuzione.

La chiamata di dlopen non è solo per oggetti condivisi, se l'applicazione desidera farlo in fase di esecuzione per suo conto, altrimenti gli oggetti condivisi vengono caricati automaticamente all'avvio dell'applicazione. DLLS e .so sono la stessa cosa. il dlopen esiste per aggiungere capacità di caricamento dinamico ancora più dettagliate per i processi. Non è necessario utilizzare dlopen per aprire / utilizzare le DLL, ciò accade anche all'avvio dell'applicazione.


Quale sarebbe un esempio dell'uso di dlopen () per un maggiore controllo del caricamento? Se la SO / DLL viene caricata automaticamente all'avvio, dlopen () la chiude e la riapre con autorizzazioni o restrizioni diverse, ad esempio? Grazie.
Cloud

1
Credo che il dlopen sia per plugin o funzionalità simili. Le autorizzazioni / restrizioni dovrebbero essere le stesse del caricamento automatico e comunque un dlopen caricherà ricorsivamente le librerie dipendenti.
rapadura,

DLL e non.so sono esattamente la stessa cosa. Vedi questa risposta
Basile Starynkevitch il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.