Windows Debugging 209

Bellek yönetimi, her isletim sisteminin en temel ihtiyacidir. Sistemin çalisan diger bütün parçalari bunun üzerine kuruludur. Bellek kavramlarini anlamak çok temeldir; en nihayetinde CPU da registerlar da bellek dir ve ‘bilgisaymak’ abaküs deki gibi degerleri gruplayip belli semalara göre kaydirmaktan meydana gelir. Windows Internals in 9cu bölümünü okurken bazi noktalari anlamakta zorlanabilirsiniz veya bazi temel bilgileri de hatirlamiyor olabilirsiniz. Burada bu noktalara dokunup 9cu bölümü tam anlayabilmeniz için bir ön özet geçecegiz.

Genel olarak bellek denildiginde mantiksal ve fiziki (logical-physical) adreslere ayrilabilir. Yani Windows isletim sistemi sanal (virtual) adresler ile çalisip bunlari gerektigi sekilde donanimin fiziki adresleri ile iliskilendirir.

Genis kavramda bellek erisimini, bellegi page/sayfa dedigimiz esit parçalara bölüp bunlara adres verip yapariz. Adres genisligimiz 32bit (8nibble, 4byte, 2word veya 1dword) oldugunda binary(0,1) sisteminde toplam 232 birlesimiz olur. Bu kadar permütasyonu da her satirda bir 32bit birlesim olacak sekilde listelersek, toplam 4,294,967,296 (Bytes/1024 = 4,194,304KB / 1024 = 4096MB / 1024 = 4GB) satirimiz olur. Her satir bir olabilir adresi gösterir. Yani 32bit adres genisliginde toplam 4GB bellek adresleyebiliriz.

Daha kolay çalismak için hexadecimal, onaltilik veya octal, sekizli sistemleri kullanabiliriz. Örnegin 4,294,967,296d, 100,000,000h ve 40,000,000,000o ya esittir.
Bu 4GB sanal adres bölgesi ikiye bölünüp düsük yarisini (00000000 - 7FFFFFFF) Kernel e ve yüksek yarisini (800000000 - FFFFFFFF) kullanici processlere verilmistir. Asagida bu noktaya geri gelecegiz, çünkü burada küçük bir matematiksel hata var.
Iki mode da da farkli CPU ring lerinde erisebiliriz. Kernel mode ringi, user mode ringinden daha düsük olandir. Böylece hep CPU, hemde bellek de kernel mode u user mode dan ayirip en temel kernel güvenligini saglamis oluyoruz. Windows Server 2008 den itibaren ama en düsük cpu ring i kernelin degil, Hyper-V in dir.

Ayni sekilde temelinde 32bit isletim sistemi de sadece 4GB fiziki bellek adresleyebilir. Her processin ram kullanimina Working set deriz. Yani fiziki bellekte tutulan verilerin total boyutudur. Processin Page file Bytes degeri page file kullanimini gösterir. Ayrica process in Virtual Bytes kullanimini ve total bellek harcamasini Private bytes olarak performance monitörden takip edebiliriz. Yalniz private aslinda bir processin paylasimsiz, yani sadece kendisinin erisebildigi belleklerin toplamidir. Genelde rakamlar benzerdir, ama bu ufak detayi unutmamak gerekir.

Windows Server 2003 de 32bit de boot.ini deki /3gb, Windows Server 2008 den itibaren BCD de increaseuserva ile yapilabilir. Yani 4GB sanal adres toplaminda, kernel mode dan bir GB adres alani alarak, bunu user mode alanina ekleyebiliriz. Böylece bir user mode process teoride 3GB a kadar sanal bellek adresleyebilir. Ancak bunu yapabilmek için de yazilim aware olmali, yani derlenirken /LARGEADDRESSAWARE ile linked olmali. Iyi bir örnek burada Microsoft Exchange olabilir. Kisaca user mode process adres bölgesinin büyümüs oldugunu bilmeli. User mode artik 00000000 dan baslayip 7FFFFFFF de bitmez, BFFFFFFF de biter ve Kernel da böylece 800000000 baslamaz, C0000000 baslayip FFFFFFFF de biter.
Ne kadar kolay degil mi? Decimal yapalim, bu noktaya geri gelecegimizi yukarida belirtmistik. 232 =4,294,967,296 ; burasi dogru. Ancak biz elbette 1 ile saymaya baslamiyoruz, 0 ile basliyoruz, adresler de ondan 00000000 ile basliyor (yani binary 32 tane 0). O zaman rakam 4,294,967,296 degil de 0 dan dolayi -1 ve böylece 4,294,967,295 . Ve bunun hex karsiligi da FFFFFFFF , 37777777777o , yani binary de 32 tane 1. Yarisi 7FFFFFFFh 2,147,483,647d 17777777777o ve ¾ , yani 3GB deC0000000h 3,221,225,472d 30000000000o.
Yani /LARGEADDRESSAWARE ile bir processin teoride maksimum kullanabilecegi alan 2,147,483,647d den 3,221,225,472d ye çikmis oluyor.

Ayrica Windows Server 2003 de degisken deger tutabilen /userva=3030 boot.ini switchimiz vardi. Bunun tek maksatti /3gb ortaminda kernel den alinmis olan adres bölgesinin birazini yine geri kernel e geri verebilmekti. Ancak bu switch de boot da okunur ve geri alinan alan, sadece PTE (page table entries) ler için geri aliniyordu. Yani geri alinan adres bölgesinden kernelin baska hiçbir yapisi faydalanamiyordu. 3030 ile eklenirse 42MB lik alan user mode dan aliniyordu. Rakam in degeri MB cinsindendi ve ne kadar düsürülürse o kadar alan user mode dan aliniyordu; örnegin 3000 de toplam 42MB+30MB aliniyordu. Her PTE kaydin 4KB oldugu düsünülürse, burada ne kadar düsürmemiz gerektigini hesaplayabiliyorduk. Örnegin \Memory\Free system page table entries in 10,000 in altina düsmesi önerilmiyordu, performans için de minimum 15,000 öneriliyordu. Mesela 8,000 civarindaysak ve 15,000 e artirmak istiyorsak, 7,000 bos PTE kaydi açmak için (7000x4KB)/1024= 27.34MB dan sadece 3030 eklemenin yeterli oldugunu görebiliyorduk. 3030 varsa da bunu örnegin 3002 ye düsürmek yeterli oluyordu. User mode dan gerektiginden de fazlasini almamaya çalisiyorduk. Userva 3gb ile increaseuserva ya aktarildi. Zaten örnegin çogu Exchange sistemi de artik 64bit ve bu tarz limitasyonlar artik mevcut degil.

Windows Server 2003 de boot.ini de /PAE switchini de Windows Server 2008 den itibaren BCD de pae bcd opsiyonunu ForceEnable a alarak yapabiliriz. 32bit de PAE ile 4GB dan fazla RAM ekleyebiliriz ve Address Windowing Extension (AWE) aware olan yazilimlar bu ekstra fiziki bellegi kullanabilirler. Burada AWE aware olarak verilecek örnek SQL server dir. Bunun için yazilim kendi sanal adres alaninda 256MB lik bir AWE penceresi açar ve iliskin API leri kullanarak 64 GB a kadar RAM kullanabilir. Yani yazilim/process kendi fiziki bellek islemlerinden sorumludur. Ancak kullanabildigi kadar RAM de onundur. PAE in asil farki kernel dedir. PAE için boot da farkli bir kernel yüklenir: ntkrnlpa.exe .

Iki tane kernel mode heapimiz, sistem bellegi havuzlarimiz vardir: non-paged ve paged pool. Poolarin ikiside sistem adres bölgesindedirler ve kullanimlara göre processlerin sanal adres bölgelerine maplenirler.
Non paged pool daki sanal adresler asla page out edilmezler. Örnegin bir diskin sürücüsü o diskin page file ina page out edilmesi çok mantiksiz olur.
Ayrica page fault lar DPC veya üstü IRQL seviyelerde islenemez. Yani yüksek IRQL seviyesinde islemler hizli olmalidirlar yoksa sistemi bekletiriz. Page fault dan sonra sayfanin page file da oldugunu görüp o veriyi diskten RAM e almak uzun bir süredir. Ondan da non paged pool a ihtiyacimiz olur. Yani asla page fault gerektirmeyecek pageleri tutabilecegimiz ve böylece yüksek IRQL interrupt lara hizmet verebilecek bir heap e. IRQL i hatirlamak için sayfa 94 e bakabilirsiniz.Page fault handling ile ilgili bilgileri sayfa 774 de bulabilirsiniz.
Paged pool da DPC IRQL seviyesinin altinda bellek islemleri yapabilen aygit sürücüleri veya en azindan bu sürücülerin kismi ihtiyaçlari içindir. Buradaki bilgiler page file a aktarilabilir. Iyi yazilmis bir sürücü ve bu antivirüs veya farkli çözümlerin sürücü modülleri olabilir, gerekmedigi sürece sadece bir pool ile çalismaz. Sadece kritik bölümlerini non paged pool da çalistirir. En nihayetinde örnegin Windows Server 2003 de non paged pool yaklasik 256MB i ve paged pool yaklasik 512MB i geçemiyorlardi. /3gb ile kernel adres bölgesi yarilandigi için, bu degerler de %50 ye düsüyordu. Pool baslangiç ve boyut limitlerini sayfa 722 de bulabilirsiniz.
NUMA sistemlerde her node için bir paged pool olusturulur. Yoksa her pool islemi için poolun kilitlenmesi NUMA mantigini ezerdi, yani yine bir noktada bütün nodelar bir performans darbogazina ugrarlardi.

Abaküsle peki ne alaka? Örnegin 30/2=15. Binary 11110/10 = 1111. Binary de bu çok basit, çünkü yaptigimiz sadece radixi , virgülü bir sola kaydirmak ve sagda kalan haneyi silmek: 11110. oluyor 1111.0 .Veya 50 ikiyle çarpalim: 110010. oluyor 110010.0., ikiyle çarpinca radixi saga kaydiriyoruz ve yeni haneye 0 veriyoruz. Kisaca bilgisayarda yaptigimiz bütün islemler aslinda sadece bellek tasinmalari. Ondan da bellek konusu çok önemlidir.

Page, yani sayfa, kullanimi aslinda donanimdan kaynaklaniyor. Çogu donanim en küçük birim olarak 4KB lik pageler kullaniyor (itanium ia64 sistemlerde 8KB). Bu da kolaylik açisindan isletim sistemine yansitiliyor. Örnegin bir sayfa bu sekilde donanim seviyesinde de mesela readonly olarak korunabiliyor. Ancak 4KBlik sayfa boyutun overhead islemi de daha yogundur, yani fazla granüler olabilir. Ondan 4KB lik sayfalara small page size deyip, ayrica large page size in kullanilmasina da izin verilir (x86 4MB, x64 2MB, ia64 16MB).
Simdi, large kullandigimizda birçok arka arkaya gelen donanimin ‘küçük’ 4KB page lerini kullaniriz. Ancak OS de large kullandigimizda, fiziksel seviyede her ‘küçük donanim’ sayfasina ne yazilacagini bilemeyiz, takip etmeyiz. Her donanimsal sayfayi readonly veya read/write olarak koruyabiliriz. Ancak large page size kullandigimizda, çok sayida fiziki küçük page kullandigimizdan dolayi, güvenlik ayarini hep dogru set edemiyor olabiliriz. Gerçegi söylemek gerekirse de etmeyiz. Hiçbir isletim sistemi etmez. Yani, verimiz readonly ve read/write olarak karisik haklardan olusuyorsa, bu large page e read/write vermek zorunda kaliriz. En nihayetinde her page bir nesnedir ve sadece bir güvenlik ayari tutabilir. Bu çok sayida küçük donanimsal page lerden olusunca da, bir ayara sahip olur ve bu ayar da bütün donanimsal page lere esit verilir. Böylece large page kullanmanin dezavantaji güvenlik olabilir. Bu da troubleshooting de bir dezavantaj yaratabilir: örnegin birisi sistemin bir readonly small page ine yazarsa sistem hemen Access violationdan dan dolayi mavi ekrana düser ve mesela Windbg de !analyze -v ile hemen kimin yazmaya çalistigini görebiliriz. Baskasinin hatali yazdigi sistem sayfasi büyük sayfa ise, bu ‘donanimsal’ küçük sayfa read/write olarak tanimlanmak zorunda kalinmis olabilir ve ondan exception i almayiz. Taha ki ileride bu corrupt olmus olan sayfadan sahibi okuyana kadar sorunu fark etmeyiz. Ancak simdi de mavi ekrana düssek de çok geç kalmis oluruz. Böyle durumlar için OS in verifier.exe sini kullanabiliriz. Burada special pool u açarsak, her sayfanin sonunda bir guard bölgesi olusturulur (guard page) ve birisi buraya örnegin overflow/underflowda yazdiginda, dogru anda mavi ekrana düseriz. User mode için de benzeri, Windbg in de içinde geldigi Debugging Tools for Windows daki Global Flags dir. Burada da bir process için page heap i açariz. Gflags ayarlari hakkinda sayfa 734 den itibaren daha fazla bilgi bulabilirsiniz.
Varsayabildiginiz gibi büyük page in avantaji performansdir. Windows eskiden 255MB RAM e kadar ntoskrnl.exe yi ve hal.dll i küçük 4KB sayfalara yüklüyordu ve 256MB RAM den itibaren büyük sayfa kullaniyordu. Yani eskiden hatali çalisan, pool corruption a neden olan modülü zaten mavi ekranin kendisinde görebiliyorduk.

Sanal bellekdeki Page ler free, reserved veya committed olabilirler. Örnegin yazilimimizda VirtualAlloc ile bellek alani alirken 1 islem yapsak da, aslinda hep ilk reserve edip sonra commit ederiz. Free alani sahipsiz bos alan olarak görebiliriz (kisaca reserved veya committed olmayan page ler free dir). Reserved i bir threadin islem öncesi kendisine ayrilmasi istedigi bölge olarak degerlendirebiliriz ve committed da reserve edilmis ve sonra kullanilan alanlardir. Baska bir thread bu reserved veya comitted alanlara islem yaparsa Access Violation alabiliriz. Ancak reserve e Access Violation almanin nedeni farklidir, çünkü reserve eden thread de buradan örnegin okumak isterse Access Violation alir. Neden? Çünkü commit ettigimizde fiziki bellek ile de çalisiyor oluruz ve OS sanal bellege yaptigimiz islemleri fiziki bellege de yansitir. En nihayetinde islem yapariz ve bunun için donanima ihtiyacimiz olur. Ancak reserve edilmis olan sanal bellek bölgesinin fiziki karsiligi yoktur. Ondan da violation olusur. Reserve etmek yaygin bir optimizasyon ve kolayliktir. Örnegin isletim sisteminde de bir thread yaratildiginda da OS ona default olarak 1MB lik bir stack alani reserve eder. Ondan da platform açisindan hep reserve ve committed a farkli bakmaiz gerekir. Örnegin bir yazilimin asiri çok virtual Bytes i olmasi, onun çok sanal bellek reserve etmis oldugunu gösteriyor olabilir. Zaten isletim sisteminin darbogazlari önlemek için buradan geri alma mekanizmalari da mevcut.

Aslinda her reserve de default da sistem istenilenin biraz fazlasini verebilir. Örnegin 32bit de küçük page size da hep 4KB in katsayisi verilir. Yani yarim page verilmez J Ayrica çok genel anlamda ntfs deki cluster size ina benzetilebilen bir allocation granularity boyutumuz vardir. Bu çogu sistemde 64KB dir. Yani bir process 10KB reserve edip 12KB alirsa ve ikincisi de örnegin 8KB reserve ederse bunlarin sayfalari default da bütünlesik olmaz, arka arkaya gelmez. Ilk processe bir 64KB lik blogun basinda baslayarak 12KB verilir ve ikinci processe bir sonraki 64KB blogun yine basindan baslayarak 8KB verilir. Burada ama bir istisna olusturabilirsiniz ve bu da çok yaygin yapilmaktadir: VirtualAlloc u kullaniyorsaniz daha az bellek kullanabilirsiniz. En nihayetinde her ufak ihtiyaç için 64KB kullanmak uygun olmayabilir.

Bellek alokasyonu yapabilmemizi mümkün kilan heap manager dir. Kodlarini ntdll.dll ve ntoskrnl.exe de bulabiliriz. Heap API lerini kullaniyorsak bunlar ntdll de manifeste edilmislerdir. Malloc gibi C run time fonksiyonlari da kendisini kullanirlar. Executive bilesenler ve aygit sürücüleri de kerneli kullanirlar. Bunlarla bütün heap islemlerini yapabiliriz. Peki Heap ne? Her processin en az bir Process Heap i vardir. Bunu aslinda processin bütün threadlerinin veya baska processlerin de threadlerinin erisebildigi bir alan olarak görebiliriz. Heap manager in kendisi threadlerin bu alana olan erisimlerindeki senkronizasyonu saglar. Tek threadli processlerde bu örnegin yazilim tarafindan kapatilabilir veya heaplere erisim de engellenebilir. Heap in asil farki birkaç page den olusmasi ve alanin istenilen granülarite de kullanilabilmesidir. Granüler derken bu 32bit de 8Byte ve 64Bit de 16Byte kadar düsük olabilir. Yani Heap manager aslinda büyük adres alanlarinda küçük boyutlu bellek islemlerinin yapilabilmesinden sorumludur. Sistemin en efektif çalismasini saglayan parçasi low fragmentation heap dir. Fragmentasyonu ve beraberinde gelebilen yer azalmasi veya performans sorunlarini engelleyebilmek için lfh gelen alan isteklerini hep o boyut için en uygun ‘bucket size’ a göre yapar. Yani kendisinin birkaç sabit degisken formatlari vardir ve istekleri hep en uygun ‘kova’ boyutuna koyar. Yani önceden belirlenmis bucketlara göre istekleri karsilar.

Section Objects/file mapping objects/memory mapped files farkli isimler altinda bilinir. Temeli, herhangi bir yerde olan bir adresin (sanal bellek, ram, page file ya da hatta ha bir dosyanin) sanki fiziki bellekte duruyormus gibi bir processin sanal bellegine maplemek. Örnegin bu sekilde memory mapped file olusturulabilir, yani bir dosya sanal bellege maplenebilir. Ya da shared memory olusturulabilir: örnegin bir DLL bir sefer fiziki bellege yüklenir ve ilgili bütün processlerin sanal bellek alanlarina maplenebilir. Bu yapilarin güvenligi dosyalarda ve bir çok objede kullanilan ACL ile saglanir. Burada ayrica shared senaryolarin en önemlisini karsilayabilmek için copy on write mantigi implemente edilir. Yani shared olan bir page, copy on write özelligini tasiyabilir. O zaman shared erisen processlerden biri buraya yazdiginda, olusan memory management fault, Access Violation vermez ve farkli isleme tabii tutulur: orijinal page kopyalanir ve process in yazmak istedigi bu kopyaya yazilir. Sonra bu page de process in private sanal bölgesine maplenir. Açikçasi shared alana yazan, shared alana artik erisemez. Kendi yapmis oldugu degisikliklerden sonra shared olmayan page bu sefer kendi kopyalarina eklenir. Bu tarz faultlari sistem bazinda perfmon da memory\write copies/sec ile takip edebiliriz.

Umarim burada artik bildiginiz gibi bütün bilgiler hep fiziki bellekte tutulmaz ve gerektigi sekilde, özellikle uzun zamandir kullanilmayan bilgiler, diskdeki page file a aktarilirlar. Yazimlar için de bu mekanizmadan kaçinma imkâni vardir. Örnegin VirtualLock fonksiyonu ile bir process belli page lerini workingsetinde kalmasini saglayabilir. Bu arada fark etmissinizdir, Virtual ile baslayan bütün fonksiyonlar sanal belege yöneliktir. Mm ile memory manager in kernel fonksiyonlari baslar ve bunlari kullanarak örnegin aygit sürücünüzü RAM e ‘kilitleyebilirsiniz’. User mode yazilimlar için bu lock yaklasiminda farkli limitasyonlar olsada, kernel mode sürücüler için limitler daha azdir.

Sanal bellek ile fiziki bellek adreslerinin iliskilendirilmelerinden ve verileri fiziki bellek ile diskteki paging file arasinda en uygun sekilde tasimadan Memory Manager sorumludur. Kendisi bellek ile ilgili olan bütün islemlerden sorumludur. Kendisinin mimari abstraksiyondaki konumunu sayfa 50 deki ünlü çizimde görebilirsiniz. Kendisi Executive in bir parçasidir. Yapisina baktigimizda elbette API ler ile erisebilecegimiz bir takim executive sistem servisleri bulabiliriz ve bunlar ile örnegin C run-time fonksiyonu ‘malloc’ kullanarak sanal bellek islemleri gerçeklestirebiliriz veya CreateFileMapping ile bir memory mapped file olusturabiliriz; yani bir dosyayi RAMe alabiliriz. Ayrica hardware tarafindan isletim sistemine yönlendirilebilecek bellek erisim exceptionlari için bir trap handlerimiz vardir.
Farkli temel islemler için farkli kernel mode sistem threadlerimiz vardir.
Kitabin 5. bölümündeki ‘balance set manager’ i hatirliyor musunuz (WD205)? Bu thread her saniye thread ready queuelari tarayarak threadlerin ‘cpu starvation’ a ugramasini engelliyordu. Ayni zamanda her saniye çalistiginda priority 16 da çalisan working set manager i da çagirir. Ayrica bellek belli bir alt limite geldiginde de balance set manager, working set manager i çagirir. Bu thread çok temeldir. Working set trimming, aging ve modified page writing gibi temel islemlerden sorumludur.
Swap islemi gerektiginde kendisi 23 priority sinde çalisan stack swapper i çagirir. Modified page writing gerektiginde, yani modified listesi belli bir uzunlugu geçtiginde, 17 priority sinde çalisan modified page writer i çagirir ve bu degismis bilgileri RAM den page file a geri yazar. Mapped Page writer ile aslinda iki tane modified page writer vardir. Ikincisinin arti özellikleri: modified listede 5 dakikayi geçen mapped file lar i ele alabilmesi ve page fault olusturabilmesi, yani bos bellek sayfasi talep edebilmesi. Böylece ramdeki modified page ler ile bir daha basit ve rutin thread ilgilenir ve ram de eskimis bilgileri diske geri atar ve bir daha karmasik thread de daha uç durumlarda olusabilecek islemleri üstlenir. Bu iki thread sayesinde RAM hep ‘temiz’ tutulur. Priority 18 de dereference segment writer çalisir. Gerektiginde cache in küçültülmesinden ve page file in boyutundan sorumludur. Son olarak da priority 0 da çalisan zero page thread free list de olan page leri sifirlar.
Senkronizasyon (WD203) açisindan Page frame number, PFN database i bir spinlock ile korunurken, section objeleri ve sistemin working set i push locklar ile korunurlar. Processlerin bellek kullanimi ile ilgili olarak working set lock (working set listeleri için) ve address space lock (adres bölgesi degisimleri için) pushlocklari kullanilir. Page file olusturulmasi bir guarded mutex ile korunur.

DEP, Kernel Pool monitoring, look aside lists, x86 and x64 detatyli address space layouts, özellikle Address Translationlarin çok detayli sekilde nasil yapildiklarini Windows bellek yönetiminin en detayli anlatilan kaynagi olan Windows Internals 5th ed. chapter 9 ‘Memory Management’ dan okuyabilirsiniz: https://technet.microsoft.com/en-us/sysinternals/bb963901

Windows Internals 6th Edition’in ilk bölümü yayinlandi:
https://technet.microsoft.com/en-us/sysinternals/bb963901
Yazarlardan Mark Russinovich hakkinda bilgi:
https://en.wikipedia.org/wiki/Mark_Russinovich

Memory Limits for Windows Releases:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa366778(v=vs.85).aspx

malloc: https://msdn.microsoft.com/en-us/library/6ewkz86d.aspx
CreateFileMapping: https://msdn.microsoft.com/en-us/library/aa914748.aspx
VirtualAlloc https://msdn.microsoft.com/en-us/library/aa908768.aspx
VirtualLock: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366895(v=vs.85).aspx

WD205: https://blogs.technet.com/b/platformtr/archive/2012/03/13/windows-debugging-205.aspx
WD203: https://blogs.technet.com/b/platformtr/archive/2012/02/07/windows-debugging-203.aspx
WD101: https://blogs.technet.com/b/platformtr/archive/2009/06/04/windows-debugging-101.aspx

Debugging Tools for Windows: https://msdn.microsoft.com/en-us/windows/hardware/gg463009.aspx

Basar Güner
Sr. Support Engineer, Microsoft
https://www.microsoft.com/surface/en/us/default.aspx
https://cdn-smooth.ms-studiosmedia.com/news/mp4_mq/06182012_Surface_750k.mp4