Versiyon Yönetimi
Paylaşımlı kütüphanelerin kodu zaman içerisinde değişebilmekte ve neticesinde yeni versiyonları çıkmaktadır. Yapılan değişikliğin derececesine göre versiyonları iki gruba ayırabiliriz.
Major, önceki versiyonlarla uyumluluğun (compatibility) korunmadığı, genel olarak kapsamlı değişikliklerin yapıldığı versiyonlardır.
Minör, önceki versiyonlarla uyumluluğun korunduğu versiyonlardır.
Örneğin, kütüphanenin arayüzünden bir fonksiyonun çıkarılması durumunda, kütüphanenin eski versiyonlarında bu fonksiyonu kullanan uygulamalar artık yeni halini kullanamayacak ve ABI (Application Binary Interface) uyumluluğu kırılacaktır. Benzer şekilde var olan fonksiyonların ürettikleri sonuçların veya parametrik yapılarının değiştirilmesi durumunda da geçmişe dönük uyumluluk kırılacaktır. Buna karşın, çoğu durumda bu tip değişikliklere ihtiyaç duyulmamaktadır. Kütüphaneye yeni fonksiyonlar eklenmesi veya var olanların parametrik yapıları korunarak iyileştirilmeleri durumunda geçmişe uyumluluğun korunduğu yeni versiyonları çıkarılmaktadır.
Paylaşımlı kütüphanelerin versiyonlarının kontrolünde iki yöntem kullanılabilmektedir. Sırasıyla bu yöntemlere bakalım.
İsimlendirme Geleneği (Naming Conventions)
Kütüphaneler, versiyonlarını da gösterecek biçimde libisim.so.[major].[minör].[revizyon] şeklinde isimlendirilmektedir. Revizyon numarası her durumda kullanılmayabilir. Genel yaklaşım versiyon arttıkça versiyon numaralarının değerini 1 arttırmak şeklindedir. Örneğin, sistemimizdeki Qt kütüphanesinin aşağıdaki gibi isimlendirildiğini görmekteyiz.
Bir uygulamanın, bağımlı olduğu kütüphanenin tüm minör versiyonlarıyla çalışması istenmektedir. Uygulamanın bağımlılık listesine kütüphanenin gerçek isminin yazılması durumunda, uygulama kütüphanenin o versiyonuna katı bir şekilde bağlanacak ve başka uyumlu versiyonlarıyla çalışamayacaktır. Bir örnek üzerinden bu durumu inceleyelim.
driver.c:
test.c:
Kütüphanemizi isimlendirme kurallarına uygun isimlendirip, ardından uygulamamızı derleyip çalıştırabiliriz.
Uygulamımızın bağımlılık listesinde beklediğimiz üzere libtest.so.1.0.0 kütüphanesini görmekteyiz.
Kütüphane kodunda, uyumluluğu gözeterek, bir iyileştirme yaptığımızı ve kütüphanenin revizyon numarasını 1 arttırarak, libtest.so.1.0.1 adıyla kütüphaneyi yeniden derlediğimizi düşünelim
Bu durumda driver uygulamasının yeni kütüphaneyi kullanamayacağı açıktır. Uygulamanın yeni kütüphaneye statik olarak yeniden bağlanması gerekmektedir. Bu problemi gidermek için kütüphanelere soname denilen mantıksal isimler verilmektedir. Mantıksal isimler kütüphane ismiyle beraber yalnız major versiyon numarasını içermektedir. Bu sayede birbiriyle uyumlu olan tüm versiyonlar aynı mantıksal ismi paylaşmakta ve bu şekilde ortak bir isim alanı oluşturulmaktadır. Bu mantıksal isimler kütüphaneler oluşturulurken, bağlayıcı tarafından kütüphaneye dosyasına yerleştirilmektedir. Bu amaçla, ELF formatında DT_SONAME alanı bulunmaktadır. Bir uygulamanın kütüphaneye bağlanması aşamasında, uygulamanın bağımlılık listesine, kütüphanenin adı değil mantıksal ismi (soname) yazılmaktadır. Bu kez kütüphanemize mantıksal bir isim atayarak yeniden derleyelim.
Kütüphanenin mantıksal ismini aşağıdaki gibi öğrenebiliriz.
Uygulamayı çalıştırmayı denediğimizde, bağlayıcının libtest.so.1.0.0 dosyasını değil libtest.so.1 dosyasını aradığını görmekteyiz.
Bu durumda gerçek kütüpnane dosyasına, kütüphanenin mantıksal isminde bir sembolink link oluşturabiliriz.
Bu kez uygulamanın çalıştığını görmekteyiz. Şimdi daha önce hedeflediğimiz gibi, uyumluluğu koruyarak, kütüphanede bir değişiklik yapıp revizyon numarasını 1 arttıralım.
test.c:
Uygulama üzerinde herhangi bir işlem yapmaksızın yalnız sembolik bağlantıyı yeni kütüphaneyi gösterecek şekilde değiştirelim.
Uygulamayı yeniden çalıştırdığımızda kütüphanenin yeni versiyonunu kullandığını görmekteyiz.
Ayrıca derleme zamanında kütüphanenin tam ismini yazmak yerine versiyon bilgisi içermeyen bir bağlayıcı adı (linker name) kullanılmaktadır. Bu sebeple kütüphanenin yalnız adını içeren bir sembolik link daha oluşturulmaktadır. Bu link gerçek kütüphaneyi gösterebilmesine karşın, mantıksal isim linkini göstermesi daha kullanışlıdır. Gerekli sembolik bağlantıyı aşağıdaki gibi oluşturup, derleme zamanında kütüphanenin gerçek adı yerine kullanabiliriz.
Örnek kütüphanemiz için isimlendirmeye ilişkin durumu özetleyecek olursak:
İsimlendir
Dosya Adı
Gerçek İsim (Real Name)
libtest.so.1.0.0
Mantıksal İsim (Soname)
libtest.so.1
Bağlayıcı İsmi (Linker Name)
libtest.so
İlgili dosyalar ise aşağıdaki gibidir.
Bu yöntemde, uygulamayı yeniden oluşturmak zorunda kalmamamıza karşın kütüphaneye olan sembolik bağlantıyı yönetmek zorundayız. Yeni bir uyumlu versiyon yüklendiğinde soname bağlantısı yeni kütüphaneyi göstermeli, yeni bir major versiyon yüklendiğinde ise gerekli soname bağlantısı oluşturulmalı. Bu işlemler için daha önce de bahsettiğimiz ldconfig aracını kullanabiliriz. Örneğimiz üzerinden bu durumu inceleyelim.
Kütüphanemizin ilk versiyonunu, libtest.so.1.0.0, /usr/lib altına kopyalayım ve ardından ldconfig aracını çalıştırıp neler olduğuna bakalım.
ldconfig tarafından /usr/lib altında soname bağlantısının oluşturulduğunu ve /etc/ld.so.cache dosyasına kütüphanemizle ilgili bir girişin eklendiğini görüyoruz.
Bu durumda uygulamamızı, LD_LIBRARY_PATH değişkenini kullanmadan, çalıştırıdığımızda kütüphanenin eski versiyonuyla çalışacaktır.
Şimdi kütüphanenin yeni versiyonunun yine /usr/lib altına atalım ve ldconfig uygulamasını çalıştıralım.
soname bağlantısının bu kez yeni versiyonu gösterdiğini görüyoruz.
Uygulamayı çalıştırdığımızda kütüphanenin yeni versiyonunun kullanıldığını görmekteyiz.
Sembollere Versiyon Atanması (Symbol Versioning)
Sembollere versiyon atayarak, aynı kütüphane içinde bir fonksiyonun birden çok versiyonunun barındırılması hedeflenmektedir. Kütüphane içinde uyumluluğu bozacak değişiklikler yapılmasına karşın, uygulamalar kütüphane içinde bağlandıkları fonksiyonları kullanmaya devam edebilmektedir. Bu sayede bir önceki yöntemde gördüğümüz gibi kütüphanenin yeni versiyonlarını çıkarmaya gerek kalmamaktadır.
Bu amaçla, bağlayıcı versiyon betikleri (Linker Version Scripts) kullanılmaktadır. Versiyon betikleri statik bağlayıcı, ld, tarafından kullanılan, genellikle .map uzantılı, yazı dosyalarıdır. Temel olarak süslü parantezlerle gruplanmış ve başında versiyon etiketi olan düğümlerden oluşmaktadır. Basit bir örneği aşağıdaki gibidir.
global ve local anahtar kelimeleri kütüphane içinde sembollerin görünürlüğünü belirlemektedir. local olarak belirtilen semboller kütüphane dışından kullanılamamaktadır. *
, açık bir şekilde global olarak belirtilen sembollerin dışındaki tüm sembolleri göstermektedir.
Bir önceki örneğimizi bu kez versiyon betiği kullanarak yapalım. İlk olarak, kütüphanemizin ilk halini yukarıda gösterdiğimiz örnek versiyon betiğini kullanarak oluşturalım. Versiyon betiğini ver1.map olarak isimlendirebiliriz.
Kütüphane içindeki foo ile ilgili semboller aşağıdaki gibidir.
nm çıktısında yalnız foo sembolünü görmemize karşın, readelf ile bir de foo@@VER_1 sembolünün bulunduğunu görmekteyiz. Paylaşımlı kütüphaneler ve bu kütüphanelere bağımlılığı olan çalışabilir dosyalar içinde .symtab ve .dynsym olmak üzere 2 tane sembol tablosu tutulmaktadır. .symtab kütüphane içinde tüm sembolleri içermekte ve statik bağlanım sırasında kullanılmaktadır. .dynsym ise yalnız global sembolleri içeren daha küçük bir tablodur ve dinamik bağlayıcı tarafından kullanılmaktadır. readelf çıktısındaki foo@@VER_1 sembolü .dynsym tablosunda bulunmaktadır. Normalde @ karakterinin sembol isimlerinde kullanılmasına izin verilmez. Bu sayede bu sembolün versiyonu olduğunu anlaşılmaktadır.
Şimdi uygulamamızı libtest.so ile, ara dosyaları da saklayacak şekilde, derleyelim.
Amaç koda baktığımızda foo sembolünün beklediğimiz isimde saklandığını görmekteyiz.
Uygulama içinde ise foo sembolünün sırasıyla .dynsym ve .symtab bölümlerinde versiyon bilgisiyle beraber oluşturulduğunu görüyoruz. Bağlayıcı uygulama içinde çağrılan foo sembolünün tanımını ararken kütüphane içinde foo@@VER_1 sembolüne ulaşmış ve amaç kod içindeki .dynsym tablosundaki foo sembolünü foo@VER_1 olarak değiştirmiş. Sembollerdeki @@ sembolün versiyonunun diğerlerine tercih edilmesini sağlamaktadır. Neden hem @@ hem de @ karakterlerinin kullanıldığını birden çok versiyon olduğunda daha iyi anlayacağız.
Uygulamanın beklediğimiz gibi çalıştığını görmekteyiz.
Şimdi foo fonksiyonunun eski halini koruyarak yeni bir versiyonunu nasıl ekleyebileceğimize örnek üzerinden bakalım. Ayrıca kütüphaneye bar isimli yeni bir fonksiyon da ekledik. Kütüphane kodunun yeni hali aşağıdaki gibidir.
Kütüphane içinde sembolik makina direktifleri görmekteyiz. .symver ile bir sembolün başka isimle bir kopyası (alias) oluşturulur. Bu takma isim normalde izin verilmeyen @ karakterini içerebilmektedir. Genel şekli aşağıdaki gibidir.
name2 sembolün gerçek adını, nodename ise versiyon bilgisini göstermektedir. Kütüphanenin bir önceki versiyonunda uygulamamanın foo@VER_1 sembolüne bağlandığını hatırlayınız. Bu sembol şimdi foo_old fonksiyonunu göstermektedir. Kütüphanenin yeni halini kullanacak uygulamalar ise foo@@VER_2 sembolünü dolayısıyla foo_new fonksiyonunu kullanacaklardır. @@ karakterleri versiyonun öncelikli olduğunu göstermektedir. Bu aşamada versiyon betiğine VER_2 düğümünü eklemeliyiz. Aşağıdaki versiyon betiğini ver2.map adıyla saklayıp kullanabiliriz. VER_2 düğümünün sonundaki VER_1, iki versiyon arasında ilişki kurmakta ve ilk versiyondaki global ve local bildirimlerinin ikinci versiyonda da geçerli olmasını sağlamaktadır.
Kütüphaneyi yeniden derleyelim ve foo'ya ilişkin sembollere bakalım.
foo_old ve foo_new sembollerinin LOCAL yani dışsal bağlanıma kapalı olduğunu, bunun yerine GLOBAL düzeydeki takma isimlerinin kullanıldığını görüyoruz.
Uygulamamıza bu kez driver2 ismini vererek, kütüphanenin bu haliyle derleyelim ve foo çağrısının hangi sembol ile temsil edildiğine bakalım.
Uygulamamızı çalıştırdığımızda foo fonksiyonunun yeni versiyonunun kullanıldığını görmekteyiz.
Kütüphanenin eski versiyonuna bağımlı uygulamamız da beklediğimiz gibi çalışmakta.
Sembol versiyonları kullanılarak, kütüphane kodunda kapsamlı değişlikler yapılmasına karşın, tek bir kütüphane dosyası ile tüm versiyonlar kullanılabilmektedir. glibc 2.1 versiyonundan beri kütüphane içinde versiyon sembolleri kullanılmakta ve tek bir major kütüphane dosyası, libc.so.6, bulunmaktadır.
Last updated
Was this helpful?