> UNIX/Linux Sistemlerinde Kütüphane İşlemleri: Kütüphane terimi "hazır kodların bulunduğu topluluklar" için kullanılan bir terimdir. Ancak aşağı seviyeli dünyada kütüphane kavramı daha farklı bir biçimde kullanılmaktadır. Aşağı seviyeli dünyada, "içerisinde derlenmiş bir biçimde fonksiyonların bulunduğu dosyalara kütüphane (library)" denilmektedir. Aslında kütüphaneler yalnızca fonksiyon değil, global nesneler de içerebilmektedir. Kütüphaneler "statik" ve "dinamik" olmak üzere ikiye ayrılmaktadır. Statik kütüphane dosyalarının uzantıları UNIX/Linux sistemlerinde ".a (archive)" biçiminde, Windows sistemlerinde ".lib (library)" biçimindedir. Dinamik kütüphane dosyalarının uzantıları ise UNIX/Linux sistemlerinde ".so (shared object), Windows sistemlerinde ".dll (dynamic link library)" biçimindedir. UNIX/Linux dünyasında kütüphane dosyaları geleneksel olarak başında "lib" öneki olacak biçimde isimlendirilmektedir. * Örnek 1, "x" isimli bir statik kütüphane dosyası UNIX/Linux sistemlerinde genellikle "libx.a" biçiminde, "x" isimli bir dinamik kütüphane dosyası ise UNIX/Linux sistemlerinde genellikle "libx.so" biçiminde isimlendirilmektedir. Şimdi de statik ve dinamik kütüphaneleri kendi içerisinde irdeleyelim: >> "Static Kütüphaneler" : Statik kütüphaneler aslında "object modules (yani .o dosyalarını)" tutan birer kap gibidir. Yani statik kütüphaneler, uzantısı ".o" olan dosyalardan, oluşmaktadır. Statik kütüphanelere "link" aşamasında "linker" tarafından bakılır. Bir program, statik kütüphane dosyasından bir çağırma yaptıysa (ya da o kütüphaneden bir global değişkeni kullandıysa), "linker" program o statik kütüphane içerisinde ilgili fonksiyonun bulunduğu "object" modülü link aşamasında statik kütüphane dosyasından çekerek çalıştırılabilir dosyaya yazar. (Yani statik kütüphaneden bir tek fonksiyon çağırsak bile aslında o fonksiyonun bulunduğu object modülün tamamı çalıştırılabilen dosyaya yazılmaktadır.) Statik kütüphaneleri kullanan programlar artık o statik kütüphaneler olmadan çalıştırılabilirler. Statik kütüphane kullanımının şu dezavantajları vardır: -> Kütüphaneyi kullanan farklı programlar aynı fonksiyonun (onun bulunduğu "object" modülün) bir kopyasını çalıştırılabilir dosya içerisinde bulundururlar. Yani örneğin "printf" fonksiyonu statik kütüphanede ise her "printf" kullanan C programı aslında "printf" fonksiyonunun bir kopyasını da barındırıyor durumda olur. Bu da programların fazla yer kaplamasına yol açacaktır. -> Aynı statik kütüphaneyi kullanan programlar belleğe yüklenirken, işletim sistemi aynı kütüphane kodlarınını yeniden fiziksel belleğe yükleyecektir. İşletim sistemi bu kodların ortak olarak kullanıldığını anlayamamaktadır. -> Statik kütüphanede bir değişiklik yapıldığında onu kullanan programların yeniden link edilmesi gerekir. Statik kütüphane kullanımının şu avantajları vardır: -> Kolay konuşlandırılabilirler. Statik kütüphane kullanan bir programın yüklenmesi için başka dosyalara gereksinim duyulmamaktadır. -> Statik kütüphanelerin kullanımları kolaydır, statik kütüphane kullanan programlar için daha kolay "build" ya da "make" işlemi yapılabilmektedir. -> Statik kütüphane kullanan programların yüklenmesi dinamik kütüphane kullanan programların yüklenmesinden çoğu kez daha hızlı yapılmaktadır. Ancak bu durum çeşitli koşullara göre tam ters bir hale de gelebilmektedir. UNIX/Linux sistemlerinde statik kütüphane dosyaları üzerinde işlemler "ar" isimli utility program yoluyla yapılmaktadır. "ar" programına önce bir seçenek, sonra statik kütüphane dosyasının ismi, sonra da bir ya da birden fazla "object" modül ismi komut satırı argümanı olarak verilir. Örneğin: "ar r libmyutil.a x.o y.o" Buradaki "r" seçeneğini belirtmektedir. "ar" eski bir komut olduğu için burada seçenekler '-' ile başlatılarak verilmemektedir. Komuttaki "libmyutil.a" işlemden etkilenecek statik kütüphane dosyasını "x.o" ve "y.o" argümanları ise "object" modülleri belirtmektedir. Tipik "ar" seçenekleri ve yaptıkları işler şunlardır: -> "r (replace)" seçeneği (yanında "-" olmadığına dikkat ediniz) ilgili "object" modüllerin kütüphaneye yerleştirilmesini sağlar. Eğer kütüphane dosyası yoksa komut aynı zamanda onu yaratmaktadır. Örneğin: "ar r libmyutil.a x.o y.o" Burada "libmyutil.a" statik kütüphane dosyasına "x.o" ve "y.o" isimli "object" modülleri yerleştirilmiştir. Eğer "libmyutil.a" dosyası yoksa aynı zamanda bu dosya yaratılacaktır. -> "t" seçeneği kütüphane içerisindeki "object" modüllerin listesini almakta kullanılır. Örneğin: "ar t libsample.a" "d (delete)" seçeneği kütüphaneden bir "object" modülü silmekte kullanılır. Örneğin: "ar d libmyutil.a x.o" "x (extract)" seçeneği kütüphane içerisindeki "object" modülü bir dosya biçiminde diske save etmekte kullanılır. Ancak bu "object" modül kütüphane dosyasından silinmeyecektir. Örneğin: "ar x libmyutil.a x.o" -> "m (modify)" seçeneği de bir "object" modülün yeni versiyonunu eski versiyonla değiştirmekte kullanılır. O halde "x.c" ve "y.c" dosyalarının içerisindeki fonksiyonları statik kütüphane dosyasına eklemek için sırasıyla şunlar yapılmalıdır: "gcc -c x.c" "gcc -c y.c" "ar r libmyutil.a x.o y.o" Statik kütüphane kullanan programları derlerken statik kütüphane dosyaları komut satırında belirtilebilir. Bu durumda "gcc" ve "clang" derleyicileri o dosyayı link işleminde kullanmaktadır. Örneğin: "gcc -o app app.c libmyutil.a" Burada "libmyutil.a" dosyasına C derleyicisi bakmamaktadır. "gcc" aslında bu dosyayı "linker" a iletmektedir. Biz bu işlemi iki adımda da yapabilirdik: "gcc -c app.c" "gcc -o app app.o libmyutil.a" Her ne kadar GNU'nun "linker" programı aslında "ld" isimli programsa da genellikle programcılar bu "ld" "linker" ını doğrudan değil yukarıdaki gibi "gcc" yoluyla kullanırlar. Çünkü "ld" "linker" ı kullanılırken "libc" kütüphanesi gibi "start-up object" modüller gibi modüllerin de programcı tarafından "ld linker" ına verilmesi gerekmektedir. Bu da oldukça sıkıcı bir işlemdir. Halbuki biz "ld linker" ını "gcc" yoluyla çalıştırdığımızda "ld linker" ına bu dosyalar zaten "default" kütüphaneler ve "object" modüller "gcc" tarafından verilmektedir. Komut satırında kütüphane dosyalarının komut satırı argümanlarının sonunda belirtilmesi uygundur. Çünkü "gcc" programı kütüphane dosyalarının solundaki dosyalar "link" edilirken ilgili kütüphane dosyasını bu işleme dahil ederler. Örneğin: "gcc -o app app1.o libmyutil.a app2.o" Böylesi bir kullanımda "libmyutil.a" komut satırı argümanının solunda yalnızca "app1.o" dosyası vardır. Dolayısıyla yalnızca bu modül için bu kütüphaneye bakılacaktır, "app2.o" bu kütüphaneye bakılmayacaktır. Şüphesiz statik kütüphane kullanmak yerine aslında "object" modülleri de doğrudan "link" işlemine sokabiliriz. Örneğin: "gcc -o sample sample.c x.o y.o" Ancak çok sayıda "object" modül söz konusu olduğunda bu işlemin zorlaşacağına dikkat ediniz. Yani, "object modüller dosyalara benzetilirse statik kütüphane dosyaları dizinler gibi" düşünülebilir. Derleme işlemi sırasında kütüphane dosyası "-l" biçiminde de belirtilebilir. Bu durumda arama sırasında "lib" öneki ve ".a" uzantısı aramaya dahil edilmektedir. Yani örneğin: "gcc -o sample sample.c -lmyutil" işleminde aslında "libmyutil.a" (ya da "libmyutil.so") dosyaları aranmaktadır. Arama işlemi sırasıyla bazı dizinlerde yapılmaktadır. Örneğin, "/lib" dizini, "/usr/lib" dizini, "/usr/local/lib" vb. dizinlere bakılmaktadır. Ancak "bulunulan dizine" bakılmamaktadır. İşte "-l" seçeneği ile "belli bir dizine de bakılması isteniyorsa" "-L" seçeneği ile ilgili dizin belirtilebilir. Örneğin: "gcc -o sample sample.c -lmyutil -L." Buradaki "." çalışma dizinini temsil etmektedir. Artık "libmyutil.a" kütüphanesi için bulunulan dizine de ("current working directory") bakılacaktır. Birden fazla dizin için "-L" seçeneğinin yinelenmesi gerekmektedir. Örneğin: "gcc -o sample sample.c -lmyutil -L. -L/home/csd" Geleneksel olarak "-l" ve "-L" seçeneklerinden sonra boşluk bırakılmamaktadır. Ancak boşluk bırakılmasında bir sakınca yoktur. Bir statik kütüphane başka bir statik kütüphaneye bağımlı olabilir. Örneğin biz, "liby.a" kütüphanesindeki kodda "libx.a" kütüphanesindeki fonksiyonları kullanmış olabiliriz. Bu durumda "liby.a" kütüphanesini kullanan program "libx.a" kütüphanesini de komut satırında belirtmek zorundadır. Örneğin: "gcc -o sample sample.c libx.a liby.a" >> "Dynamic Libraries" : Dinamik kütüphane dosyalarının UNIX/Linux sistemlerinde uzantıları ".so" ("shared object" ten kısaltma), Windows sistemlerinde ise ".dll (Dynamic Link Library)" biçimindedir. Bir dinamik kütüphaneden bir fonksiyon çağrıldığında "linker", statik kütüphanede olduğu gibi gidip fonksiyonun kodunu (fonksiyonun bulunduğu "object" modülü) çalıştırılabilen dosyaya yazmaz. Bunun yerine çalıştırılabilen dosyaya çağrılan fonksiyonun hangi dinamik kütüphanede olduğu bilgisini yazar. Çalıştırılabilen dosyayı yükleyen işletim sistemi o dosyanın çalışması için gerekli olan dinamik kütüphaneleri çalıştırılabilen dosyayla birlikte bütünsel olarak sanal bellek alanına yüklemektedir. Böylece birtakım ayarlamalar yapıldıktan sonra artık çağrılan fonksiyon için gerçekten o anda sanal belleğe yüklü olan dinamik kütüphane kodlarına gidilmektedir. * Örnek 1, Biz "app" programımızda "libmyutil.so" dinamik kütüphanesinden "foo" isimli fonksiyonu çağırmış olalım. Bu "foo" fonksiyonunun kodları dinamik kütüphaneden alınıp "app" dosyasına yazılmayacaktır. Bu "app" dosyası çalıştırıldığında işletim sistemi bu "app" dosyası ile birlikte "libmyutil.so" dosyasını da sanal belleğe yükleyecektir. Programın akışı "foo" çağrısına geldiğinde akış "libmyutil.so" dosyası içerisindeki "foo" fonksiyonunun kodlarına aktarılacaktır. Dinamik kütüphane dosyalarının bir kısmının değil, hepsinin prosesin adres alanına yüklendiğine dikkat ediniz. Tabii işletim sisteminin sanal bellek mekanizması aslında yalnızca bazı sayfaları fiziksel belleğe yükleyebilecektir. Dinamik kütüphane kullanımının avantajları şunlardır: -> Çalıştırılabilen dosyalar fonksiyon kodlarını içermezler. Dolayısıyla önemli bir disk hacmi kazanılmış olur. Oysa statik kütüphanelerde statik kütüphanelerden çağrılan fonksiyonlar çalıştırılabilen dosyalara yazılmaktadır. -> Dinamik kütüphaneler birden fazla program proses tarafından fiziksel belleğe tekrar tekrar yüklenmeden kullanılabilmektedir. Yani işletim sistemi arka planda aslında aynı dinamik kütüphaneyi kullanan programlarda bu kütüphaneyi tekrar tekrar fiziksel belleğe yüklememektedir. Bu da statik kütüphanelere göre önemli bir bellek kullanım avantaj oluşturmaktadır. Bu durumda eğer dinamik kütüphanenin ilgili kısmı daha önce fiziksel yüklenmişse bu durum dinamik kütüphane kullanan programın daha hızlı yüklemesine de yol açabilmektedir. * Örnek 1, "Process1" ve "Process2" biçiminde iki programın çalıştığını düşünelim. Bunlar aynı dinamik kütüphaneyi kullanıyor olsun. İşletim sistemi bu dinamik kütüphaneyi bu proseslerin sanal bellek alanlarının farklı yerlerine yükleyebilir. Ancak aslında işletim sistemi sayfa tablolarını kullanarak mümkün olduğunca aslında bu iki dinamik kütüphaneyi aynı fiziksel sayfaya eşlemeye çalışacaktır. Tabii bu durumda proseslerden biri dinamik kütüphane içerisindeki bir statik nesneyi değiştirdiğinde artık "copy on write" mekanizması devreye girecek ve dinamik kütüphanenin o sayfasının yeni bir kopyası oluşturulacaktır. Aslında bu durum "fork" fonksiyonu ile yeni bir prosesin yaratılması durumuna çok benzemektedir. -> Dinamik kütüphaneleri kullanan programlar bu dinamik kütüphanelerdeki değişikliklerden etkilenmezler. Yani biz dinamik kütüphanenin yeni bir versiyonunu oluşturduğumuzda bunu kullanan programları derlemek ya da "link" etmek zorunda kalmayız. * Örnek 1, Bir dinamik kütüphaneden "foo" fonksiyonunu çağırmış olalım. Bu "foo" fonksiyonunun kodları bizim çalıştırılabilir dosyamızın içerisinde değil de dinamik kütüphanede olduğuna göre dinamik kütüphanedeki "foo" fonksiyonu değiştirildiğinde bizim programımız artık değişmiş olan "foo" fonksiyonunu çağıracaktır. Dinamik kütüphanelerin gerçekleştiriminde ve kullanımında önemli bir sorun vardır. Dinamik kütüphanelerin tam olarak sanal belleğin neresine yükleneceği baştan belli değildir. Halbuki çalıştırılabilen dosyanın sanal belleğin neresine yükleneceği baştan bilinebilmektedir. Yani çalıştırılabilen dosyanın tüm kodları aslında derleyici ve bağlayıcı tarafından zaten "onun sanal bellekte yükleneceği yere göre" oluşturulmaktadır. Fakat dinamik kütüphanelerin birden fazlası prosesin sanal adres alanına yüklenebildiğinden bunlar için yükleme adresinin önceden tespit edilmesi mümkün değildir. İşte bu sorunu giderebilmek için işletim sistemlerinde değişik teknikler kullanılmaktadır. >>> Windows sistemlerinde, "import-export tablosu ve relocation tablosu" denilen yöntem tercih edilmiştir. Bu sistemlerde dinamik kütüphane belli bir adrese yüklendiğinde işletim sistemi o dinamik kütüphanenin "relocation" tablosuna bakarak gerekli makine komutlarını düzeltmektedir. Dinamik kütüphane fonksiyonlarının çağrımı için de "import" tablosu ve "export" tablosu denilen tablolar kullanılmaktadır. >>> UNIX/Linux dünyasında dinamik kütüphanelerin herhangi bir yere yüklenebilmesi için ise "Konumdan Bağımsız Kod (Position Independent Code)" denilen teknik kullanılmaktadır. Konumdan bağımsız kod "nereye yüklenirse yüklenilsin çalışabilen kod" anlamına gelmektedir. Konumdan bağımsız kod oluşturabilmek derleyicinin yapabileceği bir işlemdir. Konumdan bağımsız kod oluşturabilmek için "gcc" ve "clang" derleyicilerinde derleme sırasında "-fPIC" seçeneğinin bulundurulması gerekmektedir. Biz kursumuzda konumdan bağımsız kodun nasıl oluşturulabildiği üzerinde durmayacağız. Bu konuyu anlayabilmek için sembolik makine dilleri ve "ELF" formatı hakkında bilgi sahibi olmak gerekir. Pekiyi Windows sistemlerinin kullandığı "relocation" tekniği ile UNIX/Linux sistemlerinde kullanılan "konumdan bağımsız kod tekniği" arasında performans bakımından ne farklılıklar vardır? İşte bu tekniklerin kendi aralarında bazı avantaj ve dezavantajları bulunmaktadır. -> Windows'taki teknikte "relocation" işlemi bir zaman kaybı oluşturabilmektedir. Ancak bir "relocation" işlemi yapıldığında kodlar daha hızlı çalışma eğilimindedir. -> Konumdan bağımsız kod tekniğinde ise "relocation" işlemine gerek kalmaz. Ancak dinamik kütüphanelerdeki fonksiyonlar çağrılırken göreli biçimde daha fazla zaman kaybedilmektedir. Aynı zamanda bu teknikte kodlar biraz daha fazla yer kaplamaktadır. Linux sistemlerinde aslında dinamik kütüphaneler ismine "dinamik linker (dynamic linker)" denilen bir dinamik kütüphane tarafından yüklenmektedir. Bu dinamik kütüphane "ld.so" ya da "ld-linux.so" ismiyle bulunmaktadır. Programın yüklenmesinin "execve" sistem fonksiyonu tarafından yapıldığını anımsayınız. Bu sistem fonksiyonu ayrıntılı birtakım işlemler yaparak tüm yüklemeyi gerçekleştirmektedir. Bu sürecin ayrıntıları olmakla birlikte kabaca "execve" süreci bu bağlamda şöyle yürütülmektedir: -> "execve" fonksiyonu önce işletim sistemi için gereken çeşitli veri yapılarını oluşturur sonra çalıştırılabilen dosyayı belleğe yükler. -> Sonra da dinamik "linker" kütüphanesini belleğe yükler. -> Bundan sonra akış dinamik "linker" daki koda aktarılır. Dinamik "linker" da çalıştırılabilir dosyada belirtilen dinamik kütüphaneleri yükler. -> Sonra da akışı çalıştırılabilen dosyada belirtilen gerçek başlangıç adresine "(entry point)" aktarır. Dinamik "linker" kodları aslında "user-mod" da "mmap" sistem fonksiyonunu kullanarak diğer dinamik kütüphaneleri yüklemektedir. UNIX/Linux sistemlerinde bir dinamik kütüphane oluşturma işlemi şöyle yapılır: -> Önce dinamik kütüphaneye yerleştirilecek object modüllerin "-fPIC" seçeneği ile "Konumdan Bağımsız Kod (Position Independent Code)" tekniği kullanılarak derlenmesi gerekir. "-fPIC" seçeneğinde "-f" ten sonra boşluk bırakılmamalıdır. -> Link işleminde "çalıştırılabilir (executable)" değil de "dinamik kütüphane" dosyasının oluşturulması için "-shared" seçeneğinin kullanılması gerekir. "-shared" seçeneği kullanılmazsa linker dinamik kütüphane değil, normal çalıştırılabilir dosya oluşturmaya çalışmaktadır. Zaten bu durumda "main" fonksiyonu olmadığı için linker error mesajı verecektir. "gcc -fPIC a.c b.c c.c" "gcc -o libmyutil.so -shared a.o b.o c.o" -> Dinamik kütüphanelere daha sonra dosya eklenip çıkartılamaz. Onların her defasında yeniden bütünsel biçimde oluşturulmaları gerekmektedir. Yukarıdaki işlem aslında tek hamlede de aşağıdaki gibi yapılabilmektedir: "gcc -o libmyutil.so -shared -fPIC a.c b.c c.c" Biz yukarıda dinamik kütüphanelerin nasıl oluşturulduğunu gördük. Pekiyi dinamik kütüphaneler nasıl kullanılmaktadır? Dinamik kütüphane kullanan bir program link edilirken kullanılan dinamik kütüphanenin komut satırında belirtilmesi gerekir. Örneğin: "gcc -o app app.c libmyutil.so" Tabii bu işlem yine -l seçeneği ile de yapılabilirdi: "gcc -o app app.c -lmyutil -L." Standart C fonksiyonlarının ve POSIX fonksiyonlarının bulunduğu "glibc" kütüphanesi link aşamasında otomatik olarak devreye sokulmaktadır. Yani biz standart fonksiyonları ve POSIX fonksiyonları için link aşamasında kütüphane belirtmek zorunda değiliz. Default durumda "gcc" (ve tabii "clang") derleyicileri standart C fonksiyonlarını ve POSIX fonksiyonlarını ("glibc" kütüphanesi) dinamik kütüphaneden alarak kullanır. Ancak programcı isterse "-static" seçeneği ile statik "link" işlemi de yapabilir. Bu durumda bu fonksiyonlar statik kütüphanelerden alınarak çalıştırılabilen dosyalara yazılacaktır. Örneğin: "gcc -o app -static app.c" Tabii bu biçimde statik "link" işlemi yapıldığında çalıştırılabilen dosyanın boyutu çok büyüyecektir. Eğer "glibc" kütüphanesinin varsayılan olarak devreye sokulması istenmiyorsa "nodefaultlibs" seçeneğinin kullanılması gerekmektedir. Örneğin: "gcc -nodefaultlibs -o app app.c" Burada "glibc" kütüphanesi devreye sokulmadığı için "link" aşamasında hata oluşacaktır. Bu durumda kütüphanenin açıkça belirtilmesi gerekir. Örneğin: "gcc -nodefaultlibs -o app app.c -lc" Bir kütüphanenin statik ve dinamik biçimi aynı anda bulunuyorsa ve biz bu kütüphaneyi "-l" seçeneği ile belirtiyorsak bu durumda varsayılan olarak kütüphanenin dinamik versiyonu devreye sokulmaktadır. Eğer kütüphanelerin statik versiyonlarının devreye sokulması isteniyorsa "-static" komut satırı argümanının kullanılması gerekmektedir. Örneğin: "gcc -o app app.c -lmyutil -L." Burada eğer "libmyutil.so" ve "libmyutil.a" dosyaları varsa "libmyutil.so" dosyası kullanılacaktır. Yani dinamik link yapılacaktır. Tabii biz açıkça statik kütüphanenin ya da dinamik kütüphanenin kullanılmasını isteyebiliriz: "gcc -o app app.c libmyutil.a" ya da "gcc -static -o app app.c -lmyutil -L." Burada "glibc" kütüphanesinin dinamik biçimi devreye sokulacaktır. Ancak "libmyutil" kütüphanesi statik biçimde "link" edilmiştir. Eğer "-static" komut satırı argümanı kullanılırsa bu durumda tüm kütüphanelerin statik versiyonları devreye sokulmaktadır. Tabii bu durumda biz açıkça dinamik kütüphanelerin "link" işlemine sokulmasını isteyemeyiz. Örneğin: "gcc -static -o app app.c libmyutil.so" Bu işlem başarısız olacaktır. Çünkü "-static" argümanı zaten "tüm kütüphanelerin statik olarak link edileceği" anlamına gelmektedir. Bir programın kullandığı dinamik kütüphaneler "ldd" isimli "utility" program ile basit bir biçimde görüntülenebilir. Örneğin: $ ldd sample linux-vdso.so.1 (0x00007fff38162000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7ec0b5c000) /lib64/ld-linux-x86-64.so.2 (0x00007f7ec114f000) "ldd" programı dinamik kütüphanelerin kullandığı dinamik kütüphaneleri de görüntülemektedir. Programın doğrudan kullandığı dinamik kütüphanelerin listesi "readelf" komutuyla aşağıdaki gibi de elde edilebilir: $ readelf -d sample | grep "NEEDED" 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] Pekiyi bizim programımız örneğin "libmyutil.so" isimli bir dinamik kütüphaneden çağrı yapıyor olsun. Bu "libmyutil.so" dosyasının program çalıştırılırken nerede bulundurulması gerekir? İşte program çalıştırılırken ilgili dinamik kütüphane dosyasının özel bazı dizinlerde bulunuyor olması gerekmektedir. Yükleme sırasında hangi dizinlere bakıldığı "man ld.so" sayfasından elde edilebilir. Bu dokümanda dinamik kütüphanenin bulunabilmesi için sırasıyla tek tek hangi dizinlere bakıldığı belirtilmiştir. Dinamik "linker" ın dinamik kütüphaneleri nerede ve nasıl aradığının bazı ayrıntıları vardır. Ancak konuyu basitleştirirsek dinamik "linker", "LD_LIBRARY_PATH" isimli çevre değişkeni ile belirtilen dizinlerde, dinamik kütüphane dosyalarını aramaktadır. "LD_LIBRARY_PATH" çevre değişkeni normal olarak çevre değişken listesinde bulunmaz. Programcının bunu eklemesi gerekebilmektedir. Bu çevre değişkeninin değeri ":" karakterleriyle ayrılmış olan dizinlerden oluşmalıdır. İşte dinamik "linker" tek tek bu dizinlere bakmaktadır. Örneğin: "export LD_LIBRARY_PATH=/home/kaan:/home/kaan/Study/Unix-Linux-SysProg" Burada artık dinamik kütüphaneler "/home/kaan" dizininde ve "/home/kaan/Unix-Linux-SysProg" dizininde aranacaktır. Eğer biz "LD_LIBRARY_PATH" çevre değişkenine "." biçiminde dizin eklersek buradaki "." o anda bulunulan dizin anlamına gelmektedir. Örneğin: "export LD_LIBRARY_PATH=/home/kaan:/home/kaan/Study/Unix-Linux-SysProg:." Dinamik "linker" eğer dinamik kütüphaneyi "LD_LIBRARY_PATH" ile belirtilen dizinde bulamazsa bu durumda "/lib", "/usr/lib", "/lib64", "/usr/lib64" dizinlerine de bakmaktadır. (Bu 64'lü dizinlere "32-bit" sistemlerde ve bazı "64-bit" sistemlerde bakılmamaktadır.) O halde dinamik kütüphanemizin dinamik "linker" tarafından bulunmasını istiyorsak onu doğrudan "/lib" ya da "/usr/lib" dizinlerinin içerisine de yerleştirebiliriz. "/lib" dizini sistemin yüklenmesi için de gereken aşağı seviyeli kütüphanelerin bulunduğu dizindir. Programcının bu tür durumlarda "/usr/lib" dizinini tercih etmesi uygundur. Dinamik kütüphanelerin aranacağı yer aslında doğrudan çalıştırılabilen dosyanın içerisine "(.dynamic isimli bölüme ('section'))" de yazılabilir. Bunun için "-rpath " linker seçeneği kullanılmalıdır. Buradaki yol ifadesinin mutlak olması gerekir. Ancak eskiden bunun ELF formatında için "DT_RPATH" isimli tag kullanılırken daha sonra bu tag "deprecated" yapılmış ve "DT_RUNPATH" tag'ı kullanılmaya başlanmıştır. "-rpath" seçeneğini "linker" a geçirebilmek için "gcc" de "-Wl" seçeneğini kullanmak gerekir. "-Wl" seçeneği bitişik yazılan virgüllü alanlardan oluşmaktadır. "gcc" bunu "ld linker" ına virgüller yerine boşluklar koyarak geçirmektedir. Örneğin: "gcc -o app app.c -Wl,-rpath,/home/csd/Study/Unix-Linux-SysProg libmyutil.so" Burada ELF formatının "DT_RUNPATH" tag'ına yerleştirme yapılmaktadır. Çalıştırılabilir dosyaya iliştirilen "rpath" bilgisi "readelf" programı ile aşağıdaki gibi görüntülenebilir: $ readelf -d app | grep "RUNPATH" 0x000000000000001d (RUNPATH) Library runpath: [/home/csd/Study/Unix-Linux-SysPro Biz bu tag'a birden fazla dizin de yerleştirebiliriz. Bu durumda yine dizinleri ':' ile ayırmamız gerekir. Örneğin: "gcc -o app app.c -Wl,-rpath,/home/csd/Study/Unix-Linux-SysProg:/home/kaan libmyutil.so" Birden fazla kez "-rpath" seçeneği kullanıldığında bu seçenekler tek bir "DT_RUNPATH" tag'ına aralarına ':' karakteri getirilerek yerleştirilmektedir. Yani aşağıdaki işlem yukarıdaki ile eşdeğerdir: "gcc -o app app.c -Wl,-rpath,/home/csd/Study/Unix-Linux-SysProg,-rpath,/home/kaan libmyutil.so" Eğer DT_RPATH tag'ına yerleştirme yapılmak isteniyorsa linker seçeneklerine ayrıca --disable-new-dtags seçeneğinin de girilmesi gerekmektedir. Örneğin: "gcc -o app app.c -Wl,-rpath,/home/csd/Study/Unix-Linux-SysProg,--disable-new-dtags libmyutil.so" DT_RPATH tag'ını da aşağıdaki gibi görüntüleyebiliriz: $ readelf -d app | grep "RPATH" 0x000000000000000f (RPATH) Library rpath: [/home/csd/Study/Unix-Linux-SysProg] Yukarıda da belirttiğimiz gibi DT_RPATH tag'ı bir süredir "deprecated" yapılmıştır. Çalıştırılabilir dosyaya "DT_RUNPATH" tag'ının mutlak yol ifadesi biçiminde girilmesi kullanım sorunlarına yol açabilmektedir. Çünkü bu durumda dinamik kütüphaneler uygulamanın kurulduğu dizine göreli biçimde konuşlandırılacağı zaman kurulum yeri değiştirildiğinde sorunlar oluşabilmektedir. Örneğin biz çalıştırılabilir dosyanın "DT_RUNPATH" tag'ına "home/kaan/test" isimli yol ifadesini yazmış olalım. Programımızı ve dinamik kütüphanemizi bu dizine yerleştirirsek bir sorun oluşmayacaktır. Ancak başka bir dizine yerleştirirsek dinamik kütüphanemiz bulunamayacaktır. İşte bunu engellemek için "-rpath" seçeneğinde '$ORIGIN' argümanı kullanılmaktadır. Buradaki '$ORIGIN' argümanı o anda uygulamanın bulunduğu dizini temsil etmektedir. Örneğin: gcc -o app app.c -Wl,-rpath,'$ORIGIN'/. libmyutil.so Burada artık çalıştırılabilen dosya nereye yerleştirilirse yerleştirilsin dinamik kütüphaneler onun yerleştirildiği dizinde aranacaktır. Aslında arama sırası bakımından "DT_RPATH" tag'ının en yukarıda olması (daha doğrusu "LD_LIBRARY_PATH" in yukarısında olması) yanlış bir tasarımdır. Geriye doğru uyumu koruyarak bu yanlış tasarım "DT_RUNPATH" tag'ı ile telafi edilmiştir. "DT_RUNPATH" tag'ına "LD_LIBRARY_PATH" çevre değişkeninden sonra başvurulmaktadır. (Bunun için "man ld.so" sayfasındaki sıralamayı gözden geçiriniz.) Dinamik kütüphanelerin aranması sırasında "/lib" ve "/usr/lib" dizinlerine bakılmadan önce özel bir dosyaya da bakılmaktadır. Bu dosya "/etc/ld.so.cache" isimli dosyadır. "/etc/ld.so.cache" dosyası aslında "binary" bir dosyadır. Bu dosya hızlı aramanın yapılabilmesi için "sözlük (dictionary)" tarzı yani algoritmik aramaya izin verecek biçimde bir içeriğe sahiptir. Bu dosya ilgili dinamik kütüphane dosyalarının hangi dizinler içerisinde olduğunu gösteren bir yapıdadır. (Yani bu dosya ".so" dosyalarının hangi dizinlerde olduğunu belirten binary bir dosyadır.) Başka bir deyişle bu dosyanın içerisinde "falanca .so dosyası filanca dizinde" biçiminde bilgiler vardır. İlgili ".so" dosyasının yerinin bu dosyada aranması dizinlerde aranmasından çok daha hızlı yapılabilmektedir. Pekiyi bu "/etc/ld.so.cache" dosyasının içerisinde hangi ".so" dosyaları vardır? Aslında bu dosyanın içerisinde "/lib" ve "/usr/lib" dizinindeki ".so" dosyalarının hepsi bulunmaktadır. Ama programcı isterse kendi dosyalarını da bu cache dosyasının içerisine yerleştirebilir. Pekiyi neden böyle bir "cache" dosyasına gereksinim duyulmuştur? İşte dinamik kütüphaneler yüklenirken "/lib" ve "/usr/lib" dizinlerinin taranması göreli olarak uzun zaman almaktadır. Bu da programın yüklenme süresini uzatabilmektedir. Halbuki bu dizinlere bakılmadan önce bu "cache" dosyasına bakılırsa ilgili dosyanın olup olmadığı varsa nerede olduğu çok daha hızlı bir biçimde elde edilebilmektedir. Burada dikkat edilmesi gereken nokta bu "cache" dosyasına "/lib" ve "/usr/lib" dizinlerinden daha önce bakıldığı ve bu dizinlerin içeriğinin de zaten bu cache dosyasının içerisinde olduğudur. O halde aslında "/lib" ve "/usr/lib" dizinlerinde arama çok nadir olarak yapılmaktadır. Bu "cache" dosyasına "LD_LIBRARY_PATH" dizininden daha sonra bakılmaktadır. O halde programcının kendi ".so" dosyalarını da eğer uzun süreliğine konuşlandıracaksa bu "cache" dosyasının içerisine yazması tavsiye edilmektedir. Pekiyi "/etc/ld.so.cache" dosyasına biz nasıl bir dosya ekleriz? Aslında programcı bunu dolaylı olarak yapmaktadır. Şöyle ki: -> "/sbin/ldconfig" isimli bir program vardır. Bu program "/etc/ld.so.conf" isimli bir text dosyasına bakar. Bu dosya dizinlerden oluşmaktadır. Bu "ldconfig" programı bu dizinlerin içerisindeki "so" dosyalarını "/etc/ld.so.cache" dosyasına eklemektedir. Şimdilerde "/etc/ld.so.conf" dosyasının içeriği şöyledir: "include /etc/ld.so.conf.d/*.conf" Bu satır "/etc/ld.so.conf.d" dizinindeki tüm ".conf" uzantılı dosyaların bu işleme dahil edileceğini belirtmektedir. Biz "ldconfig" programını çalıştırdığımızda bu program "/lib", "/usr/lib" ve "/etc/ld.so.conf" (dolayısıyla "/etc/ld.so.conf.d" dizinindeki ".conf" dosyalarına) bakarak "/etc/ld.so.cache" dosyasını yeniden oluşturmaktadır. O halde bizim bu cache'e ekleme yapmak için tek yapacağımız şey "/etc/ld.so.conf.d" dizinindeki bir ".conf" dosyasına yeni bir satır olarak bir dizinin yol ifadesini girmektir. (".conf" dosyaları her satırda bir dizinin yol ifadesinden oluşmaktadır.) Tabii programcı isterse bu dizine yeni bir ".conf" dosyası da ekleyebilir. İşte programcı bu işlemi yaptıktan sonra "/sbin/ldconfig" programını çalıştırınca artık onun eklediği dizinin içerisindeki ".so" dosyaları da "/etc/ld.so.cache" dosyasının içerisine eklenmiş olacaktır. Daha açık bir anlatımla programcı bu "cache" dosyasına ekleme işini adım adım şöyle yapar: -> Önce ".so" dosyasını bir dizine yerleştirir. -> Bu dizinin ismini "/etc/ld.so.conf.d" dizinindeki bir dosyanın sonuna ekler. Ya da bu dizinde yeni "conf" dosyası oluşturarak dizini bu dosyanın içerisine yazar. -> "/sbin/ldconfig" programını çalıştırır. "ldconfig" programının "sudo" ile çalıştırılması gerektiğine dikkat ediniz. Zaten "/sbin" dizinindeki tüm programlar "super user" için bulundurulmuştur. Programcı "/etc/ld.so.conf.d" dizinindeki herhangi bir dosyaya değil de "-f" seçeneği ile kendi belirlediği bir dosyaya da ilgili dizini yazabilmektedir. Başka bir deyişle "-f" seçeneği "şu config dosyasına da bak" anlamına gelmektedir. "ldconfig" her çalıştırıldığında sıfırdan yeniden "cache" dosyasını oluşturmaktadır. Programcı "/lib" ya da "/usr/lib" dizinine bir "so" dosyası eklediğinde "ldconfig" programını çalıştırması zorunlu olmasa da iyi bir tekniktir. Çünkü o dosya da "cache" dosyasına yazılacak ve daha hızlı bulunacaktır. "ldconfig" programında "-p" seçeneği ile "cache" dosyası içerisindeki tüm dosyalar görüntülenebilmektedir. Kütüphane dosyalarının "so" isimleri denilen bir isimleri de bulunabilmektedir. Kütüphane dosyalarının "so" isimleri "linker" tarafından kullanılan isimleridir. Kütüphane dosyası oluşturulurken "so" isimleri verilmeyebilir. Yani bir kütüphane dosyasının "so" ismi olmak zorunda değildir. Kütüphane dosyalarına "so" isimlerini vermek için "-soname " linker seçeneği kullanılmaktadır. Kütüphanelere verilen "so" isimleri ELF formatının dinamik bölümündeki "(dynamic section)" "SONAME" isimli bir tag'ına yerleştirilmektedir. "-soname" komut satırı argümanı linker'a ilişkin olduğu için "-Wl" seçeneği ile kullanılmalıdır. Örneğin biz "libx.so" isimli bir dinamik kütüphaneyi "so" ismi vererek oluşturmak isteyelim. Bu işlemi şöyle yapabiliriz: "gcc -o libx.so -fPIC -shared -Wl,-soname,liby.so libx.c" Burada "libx.so" kütüphane dosyasına "liby.so" "so" ismi verilmiştir. Kütüphane dosyalarına iliştirilen "so" isimleri "readelf" ile aşağıdaki gibi görüntülenebilir: $ readelf -d libx.so | grep "SONAME" 0x000000000000000e (SONAME) Kitaplık so_adı: [liby.so] Aynı işlem "objdump" programıyla da şöyle yapılabilir: objdump -x libx.so | grep "SONAME" SONAME liby.so Tabii yukarıda da belirttiğimiz gibi biz dinamik kütüphanelere "so" ismi vermek zorunda değiliz. "so" ismi içeren bir kütüphaneyi kullanan bir program link edilirken "linker" çalıştırılabilen dosyaya "so" ismini içeren kütüphanenin ismini değil "so" ismini yazmaktadır. Yukarıdaki örneğimizde "libx.so" kütüphanesi "so" ismi olarak "liby.so" ismini içermektedir. Şimdi "libx.so" dosyasını kullanan "app.c" dosyasını derleyip link edelim: "gcc -o app app.c libx.so" Burada link işleminde "libx.so" dosya ismi kullanılmıştır. Ancak oluşturulan "app" dosyasının içerisine linker bu ismi değil, "so" ismi olan "liby.so" ismini yazacaktır. Örneğin: $ readelf -d app | grep "NEEDED" 0x0000000000000001 (NEEDED) Paylaşımlı kitaplık: [liby.so] 0x0000000000000001 (NEEDED) Paylaşımlı kitaplık: [libc.so.6] O halde biz buradaki "app" dosyasını çalıştırmak istediğimizde yükleyici (yani dinamik "linker") artık "libx.so" dosyasını değil, "liby.so" dosyasını yüklemeye çalışacaktır. Örneğin. $ export LD_LIBRARY_PATH=. $ ./app ./app: error while loading shared libraries: liby.so: cannot open shared object file: No such file or directory Tabii yukarıda belirttiğimiz gibi eğer kütüphaneyi oluştururken ona "so" ismi vermeseydik bu durumda linker "app" dosyasına "libx.so" dosyasını yazacaktı ve yükleyici de (dynamic "linker") bu dosyası yükleyecekti. Pekiyi yukarıdaki örnekte "app" programı artık "liby.so" dosyasını kullanıyor gibi olduğuna göre ve böyle de bir dosya olmadığına göre bu işlemlerin ne anlamı vardır? İşte biz bıu örnekte "so" ismine ilişkin dosyayı bir sembolik "link" dosyası haline getirirsek ve bu sembolik link dosyası da "libx.so" dosyasını gösterir hale gelirse sorunu ortadan kaldırabiliriz. Örneğin: $ ln -s libx.so liby.so $ ls -l liby.so lrwxrwxrwx 1 kaan study 7 Şub 25 16:44 liby.so -> libx.so Şimdi artık "app" dosyasını çalıştırmak istediğimizde yükleyici "liby.so" dosyasını yüklemek isteyecektir. Ancak "liby.so" dosyası da zaten "libx.so" dosyasını belrttiği için yine "libx.so" dosyası yüklenecektir. Yani artık "app" dosyasını çalıştırabiliriz. Tabii burada tüm bunları neden yapmış olduğumuza bir anlam verememiş olabilirsiniz. İşte bunun anlamını izleyen paragraflarda dinamik kütüphanelerin versiyonlanması konusunda açıklayacağız. UNIX/Linux sistemlerinde dinamik kütüphane dosyalarına isteğe bağlı olarak birer versiyon numarası verilebilmektedir. Bu versiyon numarası dosya isminin bir parçası durumundadır. Linux sistemlerinde izlenen tipik numaralandırma "(convention)" şöyledir: .so... Örneğin: libmyutil.so.2.4.6 Majör numaralar büyük değişiklikleri, minör numaralar ise küçük değişiklikleri anlatmaktadır. Majör numara değişirse yeni dinamik kütüphane eskisiyle uyumlu olmaz. Burada "uyumlu değildir" lafı eski dinamik kütüphaneyi kullanan programların yenisini kullanamayacağı anlamına gelmektedir. Çünkü muhtemelen bu yeni versiyonda fonksiyonların isimlerinde, parametrik yapılarında değişiklikler söz konusu olmuş olabilir ya da bazı fonksiyonlar silinmiş olabilir. Fakat majör numarası aynı ancak minör numaraları farklı olan kütüphaneler birbirleriyle uyumludur. Yani alçak minör numarayı kullanan program yüksek minör numarayı kullanırsa sorun olmayacaktır. Bu durumda tabii yüksek minör numaralı kütüphanede hiçbir fonksiyonun ismi, parametrik yapısı değişmemiş ve hiçbir fonksiyon silinmemiş olmalıdır. Örneğin yüksek minör numaralarda fonksiyonlarda daha hızlı çalışacak biçimde optimizasyonlar yapılmış olabilir. Ya da örneğin yüksek minör numaralarda yeni birtakım fonksiyonlar da eklenmiş olabilir. Çünkü yeni birtakım fonksiyonlar eklendiğinde eski fonksiyonlar varlığını devam ettirmektedir. Tabii yine de bu durum dinamik kütüphanenin eski versiyonunu kullanan programların düzgün çalışacağı anlamına gelmemektedir. Çünkü programcılar kodlarına yeni birtakım şeyler eklerken istemeden eski kodların çalışmasını da bozabilmektedir. (Bu tür problemler Windows sistemlerinde eskiden ciddi sıkıntılara yol açmaktaydı. Bu probleme Windows sistemlerinde "DLL cehennemi (DLL Hell)" deniyordu.) Linux sistemlerinde versiyonlama bakımından bir dinamik kütüphanenin üç ismi bulunmaktadır: -> Gerçek ismi (real name) -> so ismi (so name) -> Linker ismi (linker name) Kütüphanenin majör ve çift minör versiyonlu ismine gerçek ismi denilmektedir. Örneğin: "libmyutil.so.2.4.6" "so" ismi ise yalnızca majör numara içeren ismidir. Örneğin yukarıdaki gerçek ismin "so" ismi şöyledir: "libmyutil.so.2" Linker ismi ise hiç versiyon numarası içermeyen ismidir. Örneğin yukarıdaki kütüphanelerin linker ismi ise şöyledir: "libmyutil.so" İşte tipik olarak "so" ismi gerçek isme sembolik link, linker ismi de en yüksek numaralı "so" ismine sembolik link yapılır. linker ismi ---> so ismi ---> gerçek ismi Örneğin: gcc -o libmyutil.so.1.0.0 -shared -fPIC libmyutil.c (gerçek isimli kütüphane dosyası oluşturuldu) ln -s libmyutil.so.1.0.0 libmyutil.so.1 (so ismi oluşturuldu) ln -s libmyutil.so.1 libmyutil.so (linker ismi oluşturuldu) Burada oluşturulan üç dosyayı "ls -l" komutu ile görüntüleyelim: lrwxrwxrwx 1 kaan study 14 Şub 25 15:45 libmyutil.so -> libmyutil.so.1 lrwxrwxrwx 1 kaan study 18 Şub 25 15:45 libmyutil.so.1 -> libmyutil.so.1.0.0 -rwxr-xr-x 1 kaan study 15736 Şub 25 15:45 libmyutil.so.1.0.0 Dinamik kütüphanelerin "linker" isimleri o kütüphaneyi kullanan programlar "link" edilirlen "link" aşamasında ("link" ederken) kullanılan isimlerdir. Bu sayede "link" işlemini yapan programcıların daha az tuşa basarak genel bir isim kullanması sağlanmıştır. Bu durumda örneğin biz "libmyutil" isimli kütüphaneyi kullanan programı link etmek istersek şöyle yapabiliriz: "gcc -o app app.c libmyutil.so" Ya da şöyle yapabiliriz: "gcc -o app app.c -lmyutil -L." Burada aslında "libmyutil.so" dosyası "so ismine" "so" ismi de "gerçek isme link yapılmış" durumdadır. Yani bu komutun aslında eşdeğeri şöyledir: "gcc -o app app.c libmyutil.so.1.0.0" Yukarıda anlattıklarımızı özetlersek geldiğimiz noktayı daha iyi kavrayabiliriz: -> Bir dinamik kütüphane oluştururken ona bir versiyon numarası da atanabilmektedir. Örneğin biz oluşturduğumuz "myutil" dinamik kütüphanesine "1.0.0" versiyon numarası atamış olalım. Bu durumda kütüphanemizin gerçek ismi "libmyutil.so.1.0.0" olacaktır. Kütüphanemizi aşağıdaki gibi derlemiş olalım: $ gcc -fPIC -shared -o libmyutil.so.1.0.0 -Wl,-soname,libmyutil.so.1 libmyutil.c -> Dinamik kütüphanelerin "so ismi" kütüphanelerin içerisine yazılan ismidir. Yukarıdaki gibi bir derlemede biz "libmyutil.so.1.0.0" kütüphanesinin içerisine "so ismi" olarak "libmyutil.so" ismini yerleştirdik. "so isimleri" genel olarak yalnızca majör numara içeren isimlerdir. Bizim bu aşamada tipik olarak bir sembolik link oluşturarak "so" ismine ilişkin dosyanın gerçek kütüphane dosyasını göstermesini sağlamamız gerekir. Bunu şöyle yapabiliriz: $ ln -s libmyutil.so.1.0.0 libmyutil.so.1 Şimdi her iki dosyayı da görüntüleyelim: lrwxrwxrwx 1 kaan study 18 Mar 1 20:02 libmyutil.so.1 -> libmyutil.so.1.0.0 -rwxr-xr-x 1 kaan study 15736 Mar 1 19:57 libmyutil.so.1.0.0 -> Dinamik kütüphanenin link aşamasında kullanılmasını kolaylaştırmak için sonunda versiyon uzuntısı olmayan bir "linker ismi" oluşturabiliriz. Tabii bu "linker" ismi aslında gerçek kütüphaneye referans edecektir. Ancak bu referansın doğrudan değil de "so ismi" üzerinden yapılması daha esnek bir kullanıma yol açacaktır. Örneğin: $ ln -s libmyutil.so.1 libmyutil.so Artık kütüphanenin "linker ismi" "so ismine", "so ismi" de gerçek ismine sembolik link yapılmış durumdadır. Bu üç dosyayı aşağıda yeniden görüntüleyelim: lrwxrwxrwx 1 kaan study 14 Mar 1 20:07 libmyutil.so -> libmyutil.so.1 lrwxrwxrwx 1 kaan study 18 Mar 1 20:02 libmyutil.so.1 -> libmyutil.so.1.0.0 -rwxr-xr-x 1 kaan study 15736 Mar 1 19:57 libmyutil.so.1.0.0 Aşağıdaki gibi bir durum elde ettiğimize dikkat ediniz: Linker ismi ---> so ismi ---> gerçek isim -> Şimdi kütüphaneyi kullanan bir "app" programını derleyip link edelim: $ gcc -o app app.c libmyutil.so Şimdi "LD_LIBRARY_PATH" çevre değişkenini belirleyip programı çalıştıralım: $ LD_LIBRARY_PATH=. ./app 30.000000 -10.000000 200.000000 0.500000 Burada app programının kullandığı kütüphane ismi app dosyasının içerisinde kütüphanenin "so ismi" olarak set edilecektir. Yani burada "app" dosyası sanki "libmyutil.so.1" dosyasını kullanıyor gibi olacaktır. Örneğin: $ readelf -d app | grep "NEEDED" 0x0000000000000001 (NEEDED) Paylaşımlı kitaplık: [libmyutil.so.1] 0x0000000000000001 (NEEDED) Paylaşımlı kitaplık: [libc.so.6] İşte "app" programını yükleyecek olan dinamik linker aslında "libmyutil.so.1" dosyasını yüklemeye çalışacaktır. Bu dosyann kütüphanenin gerçek ismine sembolik "link" yapıldığını anımsayınız. Bu durumda gerçekte yüklenecek olan dosya "libmyutil.so.1" dosyası değil, "libmyutil.so.1.0.0" dosyası olacaktır. Yani çalışmada bir sorun ortaya çıkmayacaktır. Pekiyi tüm bunların amacı nedir? Bunu şöyle açıklayabiliriz: -> Örneğin kütüphanemizin libmyutil.so.1.1.0 biçiminde majör numarası aynı, minör numarası farklı öncekiyle uyumlu yeni bir versiyonunun daha oluşturulduğunu düşünelim. Şimdi biz uygulamamızı çektiğimiz dizin içerisindeki "libmyutil.so" dosyasını bu yeni versiyonu referans edecek biçimde değiştirebiliriz. Bu durumda dinamik "linker" "app" programını yüklemeye çalışırken aslında artık "libmyutil.so.1.1.0" kütüphanesini yükleyecektir. Burada biz hiç "app" dosyasının içini değiştirmeden artık "app" dosyasının kütüphanenin yeni minör versiyonunu kullamasını sağlamış olduk. -> Şimdi de kütüphanemizin "libmyutil.so.2.0.0" biçiminde yeni bir majör versiyonunun oluşturulduğunu varsayalım. "1" numaralı majör versiyonla "2" numaralı majör versiyon birbirleriyle uyumlu değildir. Biz bu "libmyutil.so.2.0.0" yeni versiyonu derlerken ona "so ismi" olarak artık "libmyutil.so.2" ismini vermeliyiz. Tabii bu durumda biz yine "libmyutil.so.2" sembolik bağlantı dosyasının "libmyutil.so.2.0.0" dosyasını göstermesini sağlamalıyız. Artık kütüphanenin 2'inci versiyonunu kullanan programlarda yüklenecek kütüphane "libmyutil.so.2" kütüphanesi olacaktır. Bu kütüphanede ikinci versiyonunun gerçek kütüphane ismine sembolik link yapılmış durumdadır. "so ismine" ilişkin sembolik link çıkartma ve "/etc/ld.so.cache" dosyasının güncellenmesi işlemi ldconfig tarafından otomatik, yapılabilmektedir. Yani aslında örneğin biz kütüphanenin gerçek isimli dosyasını "/lib" ya da "/usr/lib" içerisine yerleştirip "ldconfig" programını çalıştırdığımızda bu program zaten "so ismine" ilişkin sembolik "link" i de oluşturmaktadır. Örneğin biz "libmyutil.so.1.0.0" dosyasını "/usr/lib" dizinine kopyalayalım ve "ldconfig" programını çalıştıralım. "ldconfig" programı "libmyutil.so.1" sembolik "link" dosyasını oluşturup bu sembolik "link" dosyasının "libmyutil.so.1.0.0" dosyasına referans etmesini sağlayacaktır. Tabii cache'e de "libmyutil.so.1" dosyasını yerleştirecektir. Örneğin: $ ldconfig -p | grep "libmyutil" libmyutil.so.1 (libc6,x86-64) => /lib/libmyutil.so.1 $ ls -l /usr/lib | grep "libmyutil" lrwxrwxrwx 1 root root 18 Mar 1 21:02 libmyutil.so.1 -> libmyutil.so.1.0.0 -rwxr-xr-x 1 root root 15736 Mar 1 21:01 libmyutil.so.1.0. Özetle Dinamik kütüphane kullanırken şu konvansiyona uymak iyi bir tekniktir: -> Kütüphane ismini "lib" ile başlatarak vermek -> Kütüphane ismine majör ve minör numara vermek -> Gerçek isimli kütüphane dosyasını oluştururken "so ismi" olarak "-Wl,-soname" seçeneği ile kütüphanenin "so ismini" yazmak -> Kütüphane için "linker ismi" ve "so ismini" sembolik link biçiminde oluşturmak -> Kütüphane paylaşılacaksa onu "/lib" ya da tercihen "/usr/lib" dizinine yerleştirmek ve "ldconfig" programı çalıştırarak "/etc/ld.so.cache" dosyasının güncellenmesini sağlamak Dinamik kütüphane dosyaları program çalıştırıldıktan sonra çalışma zamanı sırasında çalışmanın belli bir aşamasında da yüklenebilir. Buna "dinamik kütüphane dosyalarının dinamik yüklenmesi" de denilmektedir. Dinamik kütüphane dosyalarının baştan "dinamik linker" tarafından değil de programın çalışma zamanı sırasında yüklenmesinin bazı avantajları şunlar olabilmektedir: -> Dinamik kütüphaneler baştan yüklenmediği için program başlangıçta daha hızlı yüklenebilir. -> Programın sanal bellek alanı gereksiz bir biçimde doldurulmayabilir. Örneğin nadiren çalışacak bir fonksiyon dinamik kütüphanede olabilir. Bu durumda o dinamik kütüphanenin işin başında yüklenmesi gereksiz bir yükleme zamanı ve bellek israfına yol açabilmektedir. Dinamik kütüphanelerin dinamik yüklenmesi dlopen, dlsym, dlerror ve dlclose fonksiyonlarıyla yapılmaktadır. Bu fonksiyonlar "libdl" kütüphanesi içerisindedir. Dolayısıyla link işlemi için "-ldl" seçeneğinin bulundurulması gerekir. -> Dinamik kütüphanelerin dinamik yüklenmesi için önce "dlopen" fonksiyonu ile dinamik kütüphanenin yüklenmesinin sağlanması gerekir. "dlopen" fonksiyonunun prototipi şöyledir: #include void *dlopen(const char *filename, int flag); Fonksiyonun birinci parametresi yüklenecek dinamik kütüphanenin yol ifadesini, ikinci parametresi seçenek belirten bayrakları almaktadır. Fonksiyon başarı durumunda kütüphaneyi temsil eden bir handle değerine, başarısızlık durumunda "NULL" adrese geri dönmektedir. "dlopen" fonksiyonunun birinci parametresindeki dinamik kütüphane isminde eğer hiç "/" karakteri yoksa bu durumda kütüphanenin aranması daha önce ele aldığımız prosedüre göre yapılmaktadır. Eğer dosya isminde en az bir "/" karakteri varsa dosya yalnızca bu mutlak ya da göreli yol ifadesinde aranmaktadır. Dinamik yükleme sırasında yüklenecek kütüphanenin SONAME alanında yazılan isme hiç bakılmamaktadır. (Bu "SONAME" alanındaki isim yalnızca "link" aşamasında "linker" tarafından kullanılmaktadır.) Örneğin: void *dlh; if ((dlh = dlopen("libmyutil.so.1.0.0", RTLD_NOW)) == NULL) { fprintf(stderr, "dlopen: %s\n", dlerror()); exit(EXIT_FAILURE); } Burada "dlopen" fonksiyonunun ikinci parametresine "RTLD_NOW" bayrağı geçilmiştir. Bu bayrağın etkisi izleyen paragraflarda ele alınacaktır. -> Başarısızlık durumunda fonksiyon "errno" değişkenini "set" etmez. Başarısızlığa ilişkin yazı doğrudan "dlerror" fonksiyonuyla elde edilmektedir: char *dlerror(void); -> Kütüphanenin adres alanından boşaltılması ise "dlclose" fonksiyonuyla yapılmaktadır: #include int dlclose(void *handle); Aynı kütüphane dlopen fonksiyonu ile ikinci kez yüklenebilir. Bu durumda gerçek bir yükleme yapılmaz. Ancak yüklenen sayıda "close" işleminin yapılması gerekmektedir. Kütüphanenin içerisindeki fonksiyonlar ya da global nesneler adresleri elde edilerek kullanılırlar. Bunların adreslerini elde edebilmek için dlsym isimli fonksiyon kullanılmaktadır: #include void *dlsym(void *handle, const char *symbol); Fonksiyon başarı durumunda ilgili sembolün adresine, başarısızlık durumunda NULL adrese geri döner. Örneğin: double (*padd)(double, double); ... if ((padd = (double (*)(double, double))(dlsym(dlh, "add")) == NULL) { fprintf(stderr, "dlsym: %s\n", dlerror()); exit(EXIT_FAILURE); } result = padd(10, 20); printf("%f\n", result); Ancak burada C standartları bağlamında bir pürüz vardır. C'de (ve tabii C++'ta) fonksiyon adresleri ile data adresleri tür dönüştürme operatörü ile bile dönüştürülememektedir. Yani yukarıdaki tür dönüştürmesi ile atama geçersizdir. Ayrıca "void *" türü "data" adresi için anlamlıdır. Yani biz C'de de C++'ta da "void" bir adresi fonksiyon göstericisine, fonksiyon adresini de "void" bir göstericiye atayamayız. Ancak pek çok derleyici "default" durumda bu biçimdeki dönüştürmeleri kabul etmektedir. Yani yukarıdaki kod aslında C'de geçersiz olmasına karşın "gcc" ve "clang" derleyicilerinde sorunsuz derlenecektir. (Derleme sırasında "-pedantic-errors" seçeneği kullanılırsa derleyiciler standartlara uyumu daha katı bir biçimde ele almaktadır. Dolayısıyla yukarıdaki kod bu seçenek kullanılarak derlenirse "error" oluşacaktır.) Pekiyi bu durumda ne yapabiliriz? İşte bunun için bir hile vardır; -> Fonksiyon göstericisinin adresini alırsak artık o bir "data" göstericisi haline gelir. Bir daha "*" kullanırsak "data" göstericisi gibi aslında fonksiyon göstericisinin içerisine değer atayabiliriz. Örneğin: if ((*(void **)&padd = dlsym(dlh, "add")) == NULL) { fprintf(stderr, "dlsym: %s\n", dlerror()); exit(EXIT_FAILURE); } Sembol isimleri konusunda dikkat etmek gerekir. Çünkü bazı derleyiciler bazı koşullar altında isimleri farklı isim gibi "object" dosyaya yazabilmektedir. Buna "name decoration" ya da "name mangling" denilmektedir. Örneğin C++ derleyicileri fonksiyon isimlerini parametrik yapıyla kombine ederek başka bir isimle "object" dosyaya yazar. Halbuki "dlsym" fonksiyonunda sembolün dinamik kütüphanedeki dekore edilmiş isminin kullanılması gerekmektedir. Sembollerin dekore edilmiş isimlerini elde edebilmek için "nm" utility'sini kullanabilirsiniz. Örneğin: "nm libmyutil.so.1.0.0" "nm utility" si ELF formatının "string" tablosunu görüntülemektedir. Aynı işlem readelf programında -s ile de yapılabilir: "readelf -s libmyutil.so.1.0.0" Aşağıda bir dinamik kütüphane dinamik olarak yüklenmiş ve oradan bir fonksiyon ve "data" adresi alınarak kullanılmıştır. Buradaki dinamik kütüphaneyi daha önce yaptığımız gibi derleyebilirsiniz: "gcc -fPIC -shared -o libmyutil.so.1.0.0 -Wl,-soname,libmyutil.so.1 libmyutil.c" Aşağıda bu konuya ilişkin bir örnek verilmiştir. * Örnek 1, /* libmyutil.c */ #include double add(double a, double b) { return a + b; } double sub(double a, double b) { return a - b; } double multiply(double a, double b) { return a * b; } double divide(double a, double b) { return a / b; } /* app.c */ #include #include #include typedef double (*PROC)(double, double); int main(void) { void *dlh; PROC padd, psub, pmul, pdiv; double result; if ((dlh = dlopen("libmyutil.so.1.0.0", RTLD_NOW)) == NULL) { fprintf(stderr, "dlopen: %s\n", dlerror()); exit(EXIT_FAILURE); } if ((*(void **)&padd = dlsym(dlh, "add")) == NULL) { fprintf(stderr, "dlsym: %s\n", dlerror()); exit(EXIT_FAILURE); } result = padd(10, 20); printf("%f\n", result); if ((*(void **)&psub = dlsym(dlh, "sub")) == NULL) { fprintf(stderr, "dlsym: %s\n", dlerror()); exit(EXIT_FAILURE); } result = psub(10, 20); printf("%f\n", result); if ((*(void **)&pmul = dlsym(dlh, "multiply")) == NULL) { fprintf(stderr, "dlsym: %s\n", dlerror()); exit(EXIT_FAILURE); } result = pmul(10, 20); printf("%f\n", result); if ((*(void **)&pdiv = dlsym(dlh, "divide")) == NULL) { fprintf(stderr, "dlsym: %s\n", dlerror()); exit(EXIT_FAILURE); } result = pdiv(10, 20); printf("%f\n", result); dlclose(dlh); return 0; } Dinamik kütüphane "dlopen" fonksiyonuyla yüklenirken global değişkenlerin ve fonksiyonların nihai yükleme adresleri bu "dlopen" işlemi sırasında hesaplanabilir ya da onlar kullanıldıklarında hesaplanabilir. İkisi arasında kullanıcı açısından bir fark olmamakla birlikte tüm sembollerin adreslerinin yükleme sırasında hesaplanması bazen yükleme işlemini (eğer çok sembol varsa) uzatabilmektedir. Bu durumu ayarlamak için dlopen fonksiyonunun ikinci parametresi olan flags parametresi kullanılır. Bu "flags" parametresi, -> "RTLD_NOW" olarak girilirse (yukarıdaki örnekte böyle yaptık) tüm sembollerin adresleri "dlopen" sırasında; -> "RTLD_LAZY" girilirse kullanıldıkları noktada; hesaplanmaktadır. İki biçim arasında çoğu kez programcı için bir farklılık oluşmamaktadır. Ancak aşağıdaki örnekte bu iki biçimin ne anlama geldiği gösterilmektedir. * Örnek 1, Aşağıdaki örnekte "libmyutil.so.1.0.0" kütüphanesindeki "foo" fonksiyonu gerçekte olmayan bir bar fonksiyonunu çağırmıştır. Bu fonksiyonun gerçekte olmadığı "foo" fonksiyonunun sembol çözümlemesi yapıldığında anlaşılacaktır. İşte eğer bu kütüphaneyi kullanan "app.c" programı kütüphaneyi "RTLD_NOW" ile yüklerse tüm semboller o anda çözülmeye çalışılacağından dolayı "bar" fonksiyonunun bulunmuyor olması hatası da "dlopen" sırasında oluşacaktır. Eğer kütüphane "RTLD_LAZY" ile yüklenirse bu durumda sembol çözümlemesi "foo" nun kullanıldığı noktada (yani dlsym fonksiyonunda) gerçekleşecektir. Dolayısıyla hata da o noktada oluşacaktır. Bu programı "RTLD_NOW" ve "RTLD_LAZY" bayraklarıyla ayrı ayrı derleyip çalıştırınız. /* libmyutil.c */ #include void bar(void); void foo(void) { bar(); } /* app.c */ #include #include #include int main(void) { void *dlh; void (*pfoo)(void); double result; if ((dlh = dlopen("libmyutil.so.1.0.0", RTLD_LAZY)) == NULL) { fprintf(stderr, "dlopen: %s\n", dlerror()); exit(EXIT_FAILURE); } printf("dlopen called\n"); if ((*(void **)&pfoo = dlsym(dlh, "foo")) == NULL) { fprintf(stderr, "dlsym: %s\n", dlerror()); exit(EXIT_FAILURE); } pfoo(); dlclose(dlh); return 0; } Bazen bir dinamik kütüphane içerisindeki sembollerin o dinamik kütüphaneyi kullanan kodlar tarafından kullanılması istenmeyebilir. Örneğin dinamik kütüphanede "bar" isimli bir fonksiyon vardır. Bu fonksiyon bu dinamik kütüphanenin kendi içerisinden başka fonksiyonlar tarafından kullanılıyor olabilir. Ancak bu fonksiyonun dinamik kütüphanenin dışından kullanılması istenmeyebilir. (Bunun çeşitli nedenleri olabilir. Örneğin kapsülleme sağlamak için, dışarıdaki sembol çakışmalarını ortadan kaldırmak için vs.) İşte bunu sağlamak amacıyla "gcc" ve "clang" derleyicilerine özgü "__attribute__((...))" eklentisindan faydalanılmaktadır. "__attribute__((...))" eklentisi pek çok seçeneğe sahip platform spesifik bazı işlemlere yol açmaktadır. Bu eklentinin seçeneklerini gcc dokümanlarından elde edebilirsiniz. Bizim bu amaçla kullanacağımız "__attribute__((...))" seçeneği "visibility" isimli seçenektir. Aşağıdaki örnekte "bar" fonksiyonu "foo" fonksiyonu tarafından kullanılmaktadır. Ancak kütüphanenin dışından bu fonksiyonun kullanılması istenmemiştir. Eğer fonksiyon isminin soluna "__attribute__((visibility("hidden")))" yazılırsa bu durumda bu fonksiyon dinamik kütüphanenin dışından herhangi bir biçimde kullanılamaz. Örneğin: void __attribute__((visibility("hidden"))) bar(void) { // ... } Burada fonksiyon özelliğinin (yani "__attribute__" sentaksının) fonksiyon isminin hemen soluna getirildiğine ve çift parantez kullanıldığına dikkat ediniz. Burada kullanılan özellik "visibility" isimli özelliktir ve bu özelliğin değeri "hidden" biçiminde verilmiştir. * Örnek 1, Aşağıdaki örnekte "libmyutil.so.1.0.0" kütüphanesindeki "foo" fonksiyonu dışarıdan çağrılabildiği halde "bar" fonksiyonu dışarıdan çağrılamayacaktır. Tabii kütüphane içerisindeki "foo" fonksiyonu bar fonksiyonunu çağırabilmektedir. Dosyaları, gcc -shared -fPIC -Wl,-soname,libmyutil.so.1 -o libmyutil.so.1.0.0 libmyutil.c gcc -o app app.c libmyutil.so.1.0.0 -ldl gibi derleyebilirsiniz. /* libmyutil.c */ #include void __attribute__((visibility("hidden"))) bar(void) { printf("bar\n"); } void foo(void) { printf("foo\n"); bar(); } /* app.c */ #include #include #include int main(void) { void *dlh; void (*pfoo)(void); void (*pbar)(void); double result; if ((dlh = dlopen("./libmyutil.so.1.0.0", RTLD_NOW)) == NULL) { fprintf(stderr, "dlopen: %s\n", dlerror()); exit(EXIT_FAILURE); } if ((*(void **)&pfoo = dlsym(dlh, "foo")) == NULL) { fprintf(stderr, "dlsym: %s\n", dlerror()); exit(EXIT_FAILURE); } pfoo(); if ((*(void **)&pbar = dlsym(dlh, "bar")) == NULL) { fprintf(stderr, "dlsym: %s\n", dlerror()); exit(EXIT_FAILURE); } pbar(); dlclose(dlh); return 0; } * Örnek 2, Pekiyi dinamik kütüphaneyi dinamik yüklemeyip normal yöntemle kullansaydık ne olacaktı? İşte bu durumda hata, programı link ederken oluşmaktadır. Örneğin: $ gcc -shared -fPIC -Wl,-soname,libmyutil.so.1 -o libmyutil.so.1.0.0 libmyutil.c $ gcc -o app app.c libmyutil.so.1.0.0 -ldl /usr/bin/ld: /tmp/ccK2cCXC.o: in function `main': app.c:(.text+0xe): undefined reference to `bar' collect2: error: ld returned 1 exit status Bu testi yapabilmek için app.c programı şöyle olabilir: #include void __attribute__((visibility("hidden"))) bar(void) { printf("bar\n"); } void foo(void) { printf("foo\n"); bar(); } Bir dinamik kütüphane normal olarak ya da dinamik olarak yüklendiğinde birtakım ilk işlerin yapılması gerekebilir. (Örneğin kütüphane "thread-safe" olma iddiasındadır ve birtakım senkronizasyon nesnelerinin ve "thread" e özgü alanların yaratılması gerekebilir.) Bunun için "gcc" ve "clang" derleyicilerine özgü olan "__attribute__((constructor))" fonksiyon özelliği "(function attribute)" kullanılmaktadır. Benzer biçimde dinamik kütüphane programın adres alanından boşaltılırken de birtakım son işlemler için "__attribute__((destructor))" ile belirtilen fonksiyon çağrılmaktadır. (Aslında bu "constructor" ve "destructor" fonksiyonları normal programlarda da kullanılabilir. Bu durumda ilgili fonksiyonlar "main" fonksiyonundan önce ve "main" fonksiyonundan sonra çağrılmaktadır.) Dinamik kütüphane birden fazla kez yüklendiğinde yalnızca ilk yüklemede toplamda bir kez "constructor" fonksiyonu çağrılmaktadır. Benzer biçimde "destructor" fonksiyonu da yalnızca bir kez çağrılır. * Örnek 1.0, Aşağıda normal bir programda "__attribute__((constructor))" ve "__attribute__((destructor))" fonksiyon özelliklerinin kullanımına bir örnek verilmiştir. Ekranda şunları göreceksiniz: constructor foo begins... constructor foo ends... main begins... main ends... destructor bar begins... destructor bar ends... İlgili programın kodları da şöyle olabilir: #include void __attribute__((constructor)) foo(void) { printf("constructor foo begins...\n"); printf("constructor foo ends...\n"); } void __attribute__((destructor)) bar(void) { printf("destructor bar begins...\n"); printf("destructor bar ends...\n"); } int main(void) { printf("main begins...\n"); printf("main ends...\n"); return 0; } * Örnek 1.1, Aşağıda da dinamik kütüphane içerisinde "__attribute__((constructor))" ve "__attribute__((destructor))" fonksiyon özelliklerinin kullanımına bir örnek verilmiştir. Derlemeyi aşağıdaki gibi yapabilirsiniz: $ gcc -shared -fPIC -Wl,-soname,libmyutil.so.1 -o libmyutil.so.1.0.0 libmyutil.c $ gcc -o app app.c libmyutil.so.1.0.0 -ldl Kütüphaneye "so ismi" verdiğimiz için sembolik link oluşturmayı unutmayınız: $ ln -s libmyutil.so.1.0.0 libmyutil.so.1 Programı çalıştırmadan önce "LD_LIBRARY_PATH" çevre değişkenini de ayarlayınız: $ export LD_LIBRARY_PATH=. İlgili programın kodları da şöyle olabilir: /* libmyutil.c */ #include void __attribute__((constructor)) constructor(void) { printf("constructor begins...\n"); printf("constructor ends...\n"); } void __attribute__((destructor)) destructor(void) { printf("destructor begins...\n"); printf("destructor ends...\n"); } void foo(void) { printf("foo\n"); } /* app.c */ #include void foo(void); int main(void) { foo(); return 0; } Bir kütüphane oluşturmak isteyen kişi kütüphanesi için en azından bir başlık dosyasını kendisi oluşturmalıdır. Çünkü kütüphane içerisindeki fonksiyonları kullanacak kişiler en azından onların prototiplerini bulundurmak zorunda kalacaklardır. Kütüphaneler için oluşturulacak başlık dosyalarında kütüphane için anlamlı sembolik sabitler, fonksiyon prototipleri, inline fonksiyon tanımlamaları, "typedef" bildirimleri gibi "nesne yaratmayan" bildirimler bulunmalıdır. Başlık dosyalarında include korumasının yapılması unutulmamalıdır. Aşağıda kütüphane için bir başlık dosyası oluşturma örneği verilmiştir. * Örnek 1, Programımızı şağıdaki gibi derleyebilirsiniz: $ gcc -shared -fPIC -Wl,-soname,libmyutil.so.1 -o libmyutil.so.1.0.0 libmyutil.c $ gcc -o app app.c libmyutil.so.1.0.0 Sembolik bağlantı yoksa aşağıdaki gibi yaratabilirsiniz: ln -s libmyutil.so.1.0.0 libmyutil.so.1 İlgili programın kodları da şöyle olabilir: /* util.h */ #ifndef UTIL_H_ #define UTIL_H_ /* Function prototypes */ void foo(void); void bar(void); #endif /* libmyutil.c */ #include #include "util.h" void foo(void) { printf("foo\n"); } void bar(void) { printf("bar\n"); } /* app.c */ #include #include "util.h" int main(void) { foo(); bar(); return 0; } * Örnek 2, C++'ta yazılmış kodların da kütüphane biçimine getirilmesinde farklı bir durum yoktur. Sınıfların bildirimleri başlık dosyalarında bulundurulur. Bunlar yine C++ derleyicisi ile ("g++" ya da "clang++") derlenir. Aynı biçimde kullanılır. Aşağıda C++'ta yazılmış olan bir sınıfın dinamik kütüphaneye yerleştirilmesi ve oradan kullanılmasına bir örnek verilmiştir. Derleme işlemlerini şöyle yapabilirsiniz: $ g++ -shared -fPIC -Wl,-soname,libmyutil.so.1 -o libmyutil.so.1.0.0 libmyutil.cpp $ g++ -o app app.cpp libmyutil.so.1.0.0 İlgili programın kodları da şöyle olabilir: // util.hpp #ifndef UTIL_HPP_ #define UTIL_HPP_ /* Function prototypes */ namespace CSD { class Date { public: Date() = default; Date(int day, int month, int year); void disp() const; private: int m_day; int m_month; int m_year; }; } #endif // libmyutil.cpp #include #include "util.hpp" namespace CSD { Date::Date(int day, int month, int year) { m_day = day; m_month = month; m_year = year; } void Date::disp() const { std::cout << m_day << '/' << m_month << '/' << m_year << std::endl; } } // app.cpp #include #include "util.hpp" using namespace CSD; int main() { Date d{10, 12, 2009}; d.disp(); return 0; }