Ocak 14, 2021

Bir .exe Uygulamasının Anatomisi (CLR, CIL, JIT, CTS Kavramları)

Okuma süresi: 7 dakika

Herkese merhaba,

Uzunca bir süre yazmaya ara vermiştim, bu süreçte eski yazılarımla ilgili o kadar güzel feedbackler aldım ki geri döneceğim zaman aslında sadece kendime notlar alırım diye çıktığım bu yolun farklı bir sorumluluk yüklediğini de böylece fark etmiş oldum. Pandemi, karantina, evden çalışma derken hepimizin uzunca saatlerini evde bilgisayar başında geçirdiği şu günlerde kod yazarken en büyük destekçilerimden olan Spotify Desktop versiyonu ile müzik dinlerken bir ışık yandı zihnimde. .NET Framework’te çalışan bir exe programını ameliyat masasına yatırıp tüm süreci baştan sona kısaca incelemek. Bu sebeple bu yazımda zaman zaman benim de karıştırdığım/detaylarını hatırlamakta güçlük çektiğim CLR, CIL, JIT, CTS gibi kavramları masaya yatıracağız. Yolu desktop programlarıyla kesişen herkesin bilmesinde fayda olan bu kavramları inceleyeceğiz.

Sözü fazla uzatmadan en güzel öğrenme şeklinin diyagramlar/tablolar/görsellerle desteklendiğine olan sonsuz inancım sebebiyle bir .NET uygulamasının çalışması için bir araya gelen bileşenleri içeren şu görselle başlayalım.

Bir exe uygulamasını Compile Time ve Run Time’da çalıştıran bileşenler

Bu kavramları daha iyi idrak edebilmek adına iki kavramın üstünden hızlıca geçmemiz faydalı olacaktır. Diyagramda yer alan her bir bileşeni masaya yatırıp çok derinlerine dalmadan fakat suya da yazı yazmadan inceleyeceğiz 🙂

Compile Time ve Run Time Nedir?

Compile Time (Derleme Zamanı)

Basitçe compile işlemi yüksek seviyeli bir dilin daha düşük seviyeli bir dile dönüştürülmesi işlemidir

Compile işlemi; oldukça basit ve akılda kalıcı bir izahla “yüksek seviyeli bir dilin daha düşük seviyeli bir dile dönüştürülmesi işlemidir“. Yani biz yazılım geliştiriciler tarafından yazılmış kod, compile işleminden sonra makinelerin seviyesine yaklaşmaktadır. İşte bu işlemi gerçekleştiren mekanizmaya Compiler(Derleyici) derken, bu süreçte yaşanan tüm işlemleri zaman açısından anlatmak için de Compile Time (Derleme Zamanı) kavramını kullanmaktayız. Yukarıdaki compiler diyagramında Compilation Error kısmı ise, yüksek seviyeli bir dilin compile edilme işlemi sırasında oluşan hataları anlatmaktadır. Compile Time hatalarının en güzel tarafı .exe dosyası daha oluşturulmadan derleme işlemi sırasında hata alınmasıdır. ‘Syntax Error (Sözdizimi Hatası)’ hatasını Compilation Error’a örnek olarak verebiliriz.

Run Time (Çalışma Zamanı)

Programın çalıştırıldığı andan sonlandırılıncaya kadar geçen süreci anlatmak için kullanılan kavramdır. Compile Time’da olduğu gibi Run Time’da da hatalar almak pek mümkündür ki aslında kıyaslandığında en acı senaryo Run Time errorlardır. Bazen bir run time hatasını bulmak için çok fazla efor sarfetmek gerekebilir. Basitçe yine neleri Run Time Error’a örnek olarak verebiliriz diye düşündüğümüzde aşağıdaki örnek maddeler en azından bu kavramı daha iyi idrak etmeye yardımcı olacaktır;

  • Bellek yetersiz hatası
  • Programda bir dosya için işlem yapacağımızı düşünelim ve bu dosyanın yolunu kod içerisinden sabit bir şekilde verdiğimizi düşünelim. Kod burada compile işlemini başarıyla geçecektir ve program çalıştırıldığında eğer kodda belirttiğimiz dosya yolunda ilgili dosya bulunamazsa hata oluşacaktır. Bu hata örneği compilation error ve run time error arasındaki farkı güzel bir şekilde idrak etmeye yardımcı olacaktır.

 

Compile Time ve Run Time kavramlarına da neşter vurarak damarlarında gezindiğimize göre ana konumuza tekrar geri dönelim. Bir .exe uzantılı programın .NET Framework’te çalışması için yazdığımız kod öncelikle compile işlemiyle daha düşük seviyeli bir dile çevriliyor. Yukarıdaki görselde de görüleceği üzere VB.NET ile yazılmış bir kod .NET Framework içerisinde yer alan VB.NET compiler ile, C# ile yazılmış bir kod C# Compiler ile ve F#/C++ gibi dillerle yazılmış kodlar da yine .NET Framework içerisinde yer alan o dile özgü compilerlar ile compile edilmektedir. Bu kısımdan itibaren önemli olan şey tüm bu farklı dillerde yazılan kodlar compilation sonrası CIL (Common Intermediate Language) denilen bir ara dile çevrilmektedir.

O halde ana diyagramımızdan nerede olduğumuza geri dönüp bir bakacak olursak şu anda yazdığımız kodu compile ettik ve CIL (Common Intermediate Language) oluştu. Derleme işlemi sırasında bir hata oluşmadıysa artık Compile Time’ı sorunsuz bir şekilde aşmış bulunuyoruz. Şimdi CIL (Common Intermediate Language) nedir, biraz da bu kavramı inceleyelim.

CIL (Common Intermediate Language) Nedir?

CIL veya .NET Framework için MSIL olarak adlandırılır. Türkçe’ye Ortak Ara Dil olarak çevrilmektedir ve ara dil olarak nitelendirilmesinin sebebi; C# kadar yüksek seviyeli yani bir insanın rahatça anlayabileceği kadar kolay bir dil olmamasına karşın, makine dili kadar da insandan uzak bir dil değildir.

Compilation işlemi sonrası CIL’e dönüştürülen kodlarımız exe dosyası olarak kaydedilir. Bu aşamadan itibaren artık çalıştırılabilir bir executable file’ımız mevcuttur. Burada dikkat edersek .exe içerisinde direkt olarak makine kodları yer almamaktadır, yazdığımız koddan CIL’e dönüştürülmüş kodlar yer almaktadır. Bu sebeple exe dosyasının çalışması için makinede muhakkak .NET Framework’ün yer alması gerekmektedir. Çünkü çalıştırma işlemini .NET Framework içerisindeki CLR mekanizması gerçekleştirecektir.

Not: Bir exe dosyasını compile edildikten sonra direkt olarak başka birisine verdiğimizde aslında bir bakıma projenin kaynak kodlarını da vermiş oluyoruz. Decompiler’lar aracılığı ile eğer herhangi bir şifreleme algoritması veya güvenlik aracı ile exe’mizi şifrelemezsek kolayca kaynak koduna erişilebileceğini unutmayalım.

CIL içerisinde Metadata olarak adlandırılan bir yapı daha vardır. Metadata programda kullanılan veri tiplerini, oluşturduğumuz sınıfları, sınıfların methodlarını, özelliklerini ve diğer bilgilerini de içerir. Yani CIL’e biraz daha üstten genel bir bakış atarsak, CIL içerisinde değişken tanımları, değişkenlerin nasıl saklanacağı, methodların nasıl çalıştırılacağı, aritmetik ve lojik işlemler, bellek kullanımı gibi birçok işin nasıl yapılacağı açıklanır.

Sihir gibi değil mi 🙂 Bir “Hello World” uygulamasının CIL kodunu inceleyelim dilerseniz. Visual Studio ile basit bir Hello World console application projesi oluşturup, konsola “Hello World” yazdıran kodu yazalım.

Kodu derledikten sonra, CIL kodunu görüntüleyebilmek için IL DASM tool’unu kullanabiliriz. Bu toola’a Developer Command Prompt’a ildasm yazarak kolay bir şekilde ulaşabiliyoruz. IL DASM ile exe dosyasını açtığımızda ilk olarak aşağıdaki görüntü bizi karşılamaktadır.

Burada programın manifesto bilgisine, methodlarına ve classlarının özelliklerine erişebiliyoruz. Gelin şimdi bir de Main methodunun CIL kodunun nasıl olduğunu görüntüleyelim.

İşte sihirli kısım. CIL kodunu anlamak aslında çok da zor değil. Örneğin;  ldstr “Hello World” satırında ‘Hello World’ stringinin load edileceği bilgisi yer alıyor. Hemen bir altındaki call çağrısıyla hangi methodun çağrılacağı belirtilmiş. Görüldüğü gibi, CIL ne makine dili kadar insandan uzak ne de C# kadar insana yakın.

Artık kodumuz derlendi, exe dosyası oluşturuldu ve çalıştırılmaya hazır. Run Time’daki operasyonlara geçerek, exe dosyasının .NET Framework’te nasıl çalıştırıldığını inceleyelim.

CLR (Common Language Runtime) Nedir?

CLR (Common Language Runtime) .NET Framework’te programların çalışmasını kontrol eden ve işletim sistemi ile program arasında yer alan bir arabirimdir. CIL kodlarını çalıştıran ana mekanizmadır. CIL kodları daha önce de incelediğimiz gibi makine dili değil bir ara dildir. Bu sebeple programın çalıştırılabilmesi için bu ara dili alıp makine diline çevirecek ve makinede işletecek bir mekanizmaya ihtiyaç duymaktaydık. Bu ihtiyacı CLR karşılamaktadır.

CLR, içerisinde yer alan JIT (Just in Time) derleyicisi ile, CIL kodunu alır makine koduna çevirir ve programın çalışmasını sağlar. Akıllara hemen ‘İyi de neden böyle bir şeye ihtiyaç duyuyoruz ki, direkt olarak neden yazdığımız kodlar makine diline çevrilmiyor da ara bir dile dönüştürülerek CLR’a ihtiyaç duyuyoruz?’ gibi bir soru gelebilir. CLR’ın bize sağladığı avantaj programlarımızı platform bağımsız çalıştırabilmemizi sağlamasından ileri geliyor. .NET Framework yüklü herhangi bir makinede CIL kodunu CLR işleteceği için kaynak kullanımları, performans ile ilgili maksimum optimizasyonu da yine JIT derleyicisi ile beraber sağlamış oluyor.

Hazır JIT kavramı geçmişken, JIT derleyicisi nedir hemen inceleyelim.

JIT (Just in Time) Compiler Nedir?

C#’ta CIL’e compile ettiğimiz program çalıştırılırken CLR aracılığı ile JIT derleyiciler devreye girer. Bu derleyiciler CIL kodunu işleyerek programın çalıştırıldığı sistemin ve işlemcinin anlayabileceği makine kodunu oluştururlar. Burada bir miktar kafa karışıklığı yaşanmış olabilir. “Bir defa kod yazıyoruz ve iki kere mi derleme işlemi uygulanıyor?” gibi bir soru akıllara gelebilir. En başta yazdığımız bir kod iki kere derleniyor evet. Önce bizim yazdığımız C# kodu C# derleyicisi ile CIL’e dönüştürülüyor ve bu oluşan CIL, CLR mekanizması içerisindeki JIT derleyicileri ile makine koduna çevriliyor. Just in Time denilmesinin sebebi, Türkçe’ye ‘O an‘ veya ‘Tam o anda‘ şeklinde çevirerek biraz daha türlerini incelediğimizde daha net ortaya çıkacaktır.

3 çeşit JIT compiler bulunmaktadır. Bunlar

  1. Normal-JIT
  2. Econo-JIT
  3. Pre-JIT

1. Normal-JIT

Normal-JIT derleyiciler; programın tamamını tek seferde derlemez. Program çalışırken parça parça derleme işlemi gerçekleştirilir. Her method çalıştırılmadan önce derlenir ve derlenen methodun makine dili kodları önbellekte tutulur. Böylece program içerisinde derlenmiş bir methodun tekrar çalıştırılması gerektiğinde yeniden derleme işlemi gerçekleştirilmez ve bu method önbellekten çekilir.

2. Econo-JIT

Econo-JIT derleyicilerde de programın tamamı tek seferde derlenmez. Yine Normal-JIT gibi program çalışırken parça parça derleme işlemi gerçekleştirilir. Her method yine çalıştırılmadan önce derlenir ancak Normal-JIT’ten farklı olarak derlenen methodun makine dili kodları önbellekte tutulmaz. Her method her çağrılışında yeniden derlenir.

3. Pre-JIT

Tüm program en baştan tek seferde makine koduna çevrilip sonra çalıştırılır. Programın daha hızlı çalışmasını sağlasa da diğer JIT derleyici türlerine göre daha fazla hafıza gerektirmektedir.

Normal-JITEcono-JITPre-JIT
Programı tek seferde derlemezProgramı tek seferde derlemezTüm program çalışmadan önce tek seferde derlenir
Program çalışırken parça parça derleme işlemi yaparProgram çalışırken parça parça derleme işlemi yaparProgram çalışmadan önce tek seferde derlendiği için program çalışırken derleme işlemi yapılmaz
Her method çalıştırılmadan önce derlenirHer method çalıştırılmadan önce derlenir
Derlenen methodun makine dili kodları önbellekte tutulurDerlenen methodun makine dili kodları önbellekte tutulmaz

 

CLR içerisinde yer alan başka bir yapı ise CTS (Common Type System) yapısıdır. Bu yapıyı da inceleyerek bir .NET programının çalışma anatomisini tamamlamış olalım.

CTS (Common Type System) Nedir?

Bütün veri tiplerinin tanımlı olduğu bir sistem olarak düşünebiliriz. CTS sayesinde .NET Framework’te farklı dillerde yazılmış programların veri tipleri arasındaki uyum sağlanmış olur.

Örneğin VB.NET ile yazılmış bir Integer değişkeni, C#’ta int olarak yazılsa da CTS karşılıkları System.Int32‘dir ve RAM’de kapladıkları alan aynıdır. Bu sayede tipler arasındaki uyum sağlanmış olur, değişen tek şey dillerdeki syntax olmaktadır.

Özet

  • Yazılan kod, o dile özgü derleyici ile CIL (Common Intermediate Language)’e çevrilir.
  • CIL içerisinde Metadata yapısını da barındırır.
  • CLR mekanizması aracılığı ile CIL kodları JIT derleyicileri ile derlenerek program çalıştırılır.
Share

Ceyhun Çözvelioğlu

Coffee Lover and Software Developer

You may also like...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir