Linux Memory — Kernel APIs-

Kerim Fırat
5 min readAug 16, 2021

Linux’da kullanıcı belleği ve kernel belleği bağımsızdır ve ayrı adres alanlarında uygulanır. Adres alanları sanallaştırılmıştır,yani fiziksel bellekten soyutlanmıştır. Kernel’in kendisi de bir adres alanında yer alır ve her işlem(process) kendi adres alanında yer alır.Bu adres alanları sanal bellek adreslerinden oluşur ve bağımsız adres alanlarına sahip birçok işlemin önemli ölçüde daha küçük bir fiziksel adres alanına(makinedeki fiziksel bellek) başvurmasına izin verir.Bu yalnızca kullanışlı olmakla kalmaz,aynı zamanda güvenlidir.Çünkü her bir adres alanı bağımsız ve yalıtılmıştır ve dolayısıyla güvenlidir.

Ancak bu güvenliğin bir maliyeti var.Her process fiziksel belleğin farklı bölgelerine atıfta bulunan aynı adreslere sahip olabileceğinden,bu gibi durumlarda belleği paylaşmak hemen mümkün değildir.
Neyse ki “Portable Operating System Interface(POSIX)” buna çözüm getiriyor. Kullanıcı process’leri POSIX paylaşımlı bellek mekanizması(shmem) aracılığıyla belleği paylaşabilir. Süreçler(Process) arası iletişim için aşağıda verilen linkteki makalemi inceleyebilirsiniz.

Süreçler(Process) arası iletişim.

Sanal belleğin fiziksel belleğe eşlenmesi,temel donanımda uygulanan sayfa tabloları aracılığıyla gerçekleşir(aşağıdaki görsel).Donanımın kendisi eşlemeyi sağlar ancak linux kernel bu tabloları ve konfigürasyonlarını yönetir.Burada gösterildiği gibi,bir işlemin büyük bir alana sahip olabileceğini,ancak bunun seyrek olduğunu,yani adres alanlarının küçük bölgelerinin(sayfalarının) sayfa tabloları aracılığıyla fiziksel belleğe atıfta bulunduğuna dikkat edin.

Görsel 1:Sayfa tabloları(Page tables) sanal adreslerden fiziksel adreslere eşleme sağlar.

developer.ibm.com

Sayfalama adı verilen bir işlem aracılığıyla(swap),daha az kullanılan sayfalar,erişilmesi gereken diğer sayfaları barındırmak için dinamik olarak daha yavaş bir depolama aygıtına(disk gibi) taşınır(aşağıdaki görsel).Bu taşıma işlemi ile güçlü bir bellek yönetimi(memory management) hedeflenmiştir.Çünkü bu yöntem daha az ihtiyaç duyulan sayfaları diske taşır ve fiziksel belleğin daha iyi kullanılmasını sağlar.

Görsel 2: Swap işlemi daha az kullanılan sayfaları daha yavaş ve düşük maliyetli depolamaya geçirerek fiziksel bellek alanının daha iyi kullanılmasını sağlar.

developer.ibm.com

Linux bazı yararlı özellikler sunan ilginç bir takas uygulaması sağlar.Linux takas sistemi,farklı performans özellikleri sağlayan depolama aygıtları üzerinde bir takas hiyerarşisine izin veren birden çok takas bölümünün ve önceliğinin oluşturulmasına ve kullanılmasına izin verir.

Görsel 3: Adres alanları ve sanaldan fiziksele adres eşleme öğreleri.

developer.ibm.com

Tüm sayfalar takas için aday sayfalar değildir,yani tüm sayfalar takas için kullanılmaz. Kesmelere(interrupts) yanıt veren kernel kodunu veya sayfa tablolarını ve takas mantığını yöneten kodu düşünün.Bunlar,hiçbir zaman değiştirilmemesi gereken ve bu nedenle sabitlenen veya kalıcı olarak bellekte bulunan bariz sayfalardır.Kernel sayfaları takas için aday sayfalar olmasalar da,kullanıcı alanı sayfaları öyledir.Ancak yine de bir sayfayı kilitlemek için “mlock” veya “mlockall” fonksiyonları aracılığıyla sabitlenebilirler.Bu,kullanıcı alanı bellek erişim işlevlerinin arkasındaki amaçtır.Eğer Kernel,bir kullanıcının ilettiği adresin geçerli ve erişilebilir olduğunu varsayarsa,sonunda bir kernel panic meydana gelir(örneğin kullanıcı sayfası değiştirildiği için kernel’da bir sayfa hatasına neden olur). Kernel’da bu gibi durumların düzgün bir şekilde ele alınması ve yönetilmesi için API sağlanmıştır.

-Kernel APIs-

Şimdi kullanıcı belleğini işlemek için Kernel’in sağladığı API’leri inceleyelim. Bu apilerin Kernel ve kullanıcı alanı arabirimini kapsadığını unutmayalım.İnceleyeceğimiz kullanıcı alanı memory erişim fonksiyonları aşağıda verilmiştir.

Kullanıcı(User) alanı belleği erişim Api fonksiyonları:

-access_ok //Kullanıcı alanı bellek işaretçisinin geçerliliğini kontrol eder.
-get_user //Kullanıcı alanından basit bir değişken alır.
-put_user //Kullanıcı alanına basit bir değişken koyar.
-clear_user //Kullanıcı alanındaki bir bloğu temizler veya sıfırlar.
-copy_to_user //Kernel’dan kullanıcı alanına bir veri bloğu kopyalar.
-copy_from_user //Kullanıcı alanından Kernel’e bir bir veri bloğu kopyalar.
-strnlen_user //Kullanıcı alanındaki bir string buffer arabelleğinin boyutunu alır.
-strncpy_from_user //Kullanıcı alanından Kernel’e bir string kopyalar.

Bu fonksiyonların uygulanması mimariye bağlı olabilir. Bu fonksiyonların tanımlamaları 32(x86) bit mimari için tanımlama dosyası yolu “/kernel/arch/x86/include/asm/uaccess.h”,kaynak kod dosyası yolu “/kernel/arch/x86/lib/usercopy_32.c” şeklindedir.
Arm için tanımlama dosyası yolu “/kernel/arch/arm/include/asm/uaccess.h” şeklindedir.
Yukarıda incelediğimiz fonksiyonların veri hareketlerini gösteren görsel aşağıda yer almaktadır.

developer.ibm.com

-access_ok Fonksiyonu:
Erişim sağlamayı düşündüğümüz kullanıcı alanındaki işaretçinin(pointer) geçerliliğini kontrol etmek için “access_ok” fonksiyonu kullanırız. Fonksiyonu çağıran,veri bloğunun başlangıcını,bloğun boyutunu ve erişim türünü gösteren bir pointeri parametre olarak verir. Eğer erişime izin var ise true(1),aksi halde false(0) döndürür. Fonksiyonun ilk parametresinde verilecek type “VERIFY_READ” veya “VERIFY_WRITE” olmalıdır.İkinci parametre olarak verilecek addr ise,test edilecek bellek bloğunun ilk baytına yönelik bir işaretçidir.Son parametre olan size ise test edilecek bellek bloğunun byte cinsinden boyutudur.
Fonksiyon:

access_ok(type, addr, size );

Örnek:

if (!access_ok(VERIFY_READ, ptr, sizeof(double)))
return -1;

-get_user Fonksiyonu:
Kullanıcı alanından basit bir değişken okumak için “get_user” fonksiyonunu kullanırız. Bu fonksiyon “char” ve “int” gibi basit türler için kullanılır. Ancak veri türleri daha büyük ise,bunun yerine “copy_from_user” fonksiyonu kullanılmalıdır. Fonksiyon iki parametre almaktadır. İlk parametre elde edilecek veriyi tutmak/saklamak için,ikinci parametre ise kullanıcı alanında ilgili adresin pointeri dir.
Fonksiyon:

get_user( x, ptr );

-put_user Fonksiyonu:
Bu fonksiyon “get_user” fonksiyonun tersini yapar. Yani Kernel alanından kullanıcı alanına basit bir değişken yazar. Başarılı durumda “0”,aksi halde “-EFAULT” dönderir.
Fonksiyon:

put_user( x, ptr );

-clear_user Fonksiyonu:
Bu fonksiyon kullanıcı alanındaki bir bellek bloğunu sırılamak için kullanılır.Fonksiyonun ilk parametresi bellek bloğunu gösteren pointer,ikinci parametresinde ise sıfırlanacak boyutu alır.
Fonksiyon:

clear_user( ptr, n );

Fonksiyonun kernel içindeki yapısı:

clear_user(void __user *to, unsigned long n)
{
might_sleep();
if (!access_ok(VERIFY_WRITE, to, n))
return n;
return __clear_user(to, n);
}

Fonksiyon kaynağına baktığımızda “access_ok” ile ilgili belleğin yazılabilir olup olmadığı kontrol edildiğini görüyoruz. Bu,yukarıda anlattığım bellek güvenliğine bir örnektir.

-copy_to_user Fonksiyonu:
Bu fonksiyon Kernel alanından kullanıcı alanına bir veri bloğu kopyalar. Fonksiyonun ilk parametresi kullanıcı alanı arabelleğini gösteren bir gösterici,ikinci parametresi ile kernel arabelleğini gösteren gösterici ve son parametresi byte olarak bir uzunluk kabul eder.Eğer fonksiyon başarılı ise “0”(sıfır),aksi halde transfer edemediği,kopyalayamadığı byte sayısını döndürür.
Fonksiyon:

copy_to_user( to, from, n );

Fonksiyonun kernel içindeki yapısı:

static inline long copy_to_user(void __user *to,
const void *from, unsigned long n)
{
might_sleep();
if (access_ok(VERIFY_WRITE, to, n))
return __copy_to_user(to, from, n);
else
return n;
}

-copy_from_user Fonksiyonu:
Bu fonksiyon kullanıcı alanından kernel alanına bir veri bloğu kopyalar.Fonksiyonun ilk parametresi kernel alanı arabelleğini,ikinci parametre kullanıcı alanı arabelleğini ve son parametre ise byte olarak bir uzunluk kabul eder.Fonkisyon başarılı ise 0(zero),aksi halde transfer edemediği,kopyalayamadığını byte sayısını döndürür.Dolayısıyla bu değer 0(sıfır) dışında bir değer olacaktır.
Fonksiyon:

copy_from_user( to, from, n );

Fonksiyonun kernel içindeki yapısı:

static inline long copy_from_user(void *to,
const void __user * from, unsigned long n)
{
might_sleep();
if (access_ok(VERIFY_READ, from, n))
return __copy_from_user(to, from, n);
else
return n;
}

-strnlen_user Fonksiyonu:
Bu fonksiyon “strlen” gibi kullanılır. Ancak arabelleğin kullanıcı alanında mevcut olduğunu varsayar.
Fonksiyon iki parametre alır. İlk parametre ile kullanıcı alanındaki arabellek adresi,ikinci parametre ise kontrol edilecek uzunluğu alır.
Fonksiyon:

strnlen_user( src, n );

Fonksiyonun kernel içindeki yapısı:

static inline long strnlen_user(const char __user *src, long n)
{
if (!access_ok(VERIFY_READ, src, 1))
return 0;
return __strnlen_user(src, n);
}

-strncpy_from_user Fonksiyonu:
Bu fonksiyon kullanıcı alanından kernel arabelleğine belirtilen uzunlukta bir string kopyalar.
Fonksiyonun ilk parametresi kernel arabelleğini,ikinci parametre kaynak ve son parametre ise uzunluk değeridir.
Fonksiyon:

strncpy_from_user( dest, src, n );

Fonksiyonun kernel içindeki yapısı:

static inline long
strncpy_from_user(char *dst, const char __user *src, long count)
{
if (!access_ok(VERIFY_READ, src, 1))
return -EFAULT;
return __strncpy_from_user(dst, src, count);
}

KAYNAKLAR:
https://www.kernel.org/
https://developer.ibm.com/
https://linux.die.net/

-end of

--

--

Kerim Fırat
Kerim Fırat

Written by Kerim Fırat

Senior Android Platform(AOSP,AAOS) Architect,Open Source Contributor | Turkey Java User Group Vice Chairman | Author

No responses yet