24 Nisan 2015 Cuma

NonBuffered ve Buffered I/O

Bu yazı ile ilgili olarak Devpts başlıklı yazıyı okuyabilirsiniz.Orada da anlatıldığı gibi her process 3 tane posix stream ile başlatılıyor. Ancak bir çok programlama dili işletim sistemi tarafından atana bu posix streamlerin de üzerine başka soyutlamalar getiriyor. Bu yazıda bunlara kısaca bakacağız.

Not : I/O işlemlerinde eğer disk dolarsa ENOSPC hata kodu döndürülür.

Non-Buffered I/O

Non-Buffered I/O için işletim sistemi tarafından sağlanan open() metodu kullanılabilir Örnek :
open(filename, O_RDRW | O_CREAT, 0644);
open aslında 
int open(const char *pathname, int flags) ve
int open(const char *pathname,int flags, mode_t mode);
şeklinde iki imzaya sahip. 3 parametreli metod yani mode parametresi alan metod, flags alanında O_CREATE varsa dosyanın hangi haklarla yaratılacağını belirtmek için kullanılıyor. Yani özeti şu cümle.
mode specifies the permissions to use in case a new file is created.

Page Cache'in Kullanılmaması (Virtual Memory kullanılmaması)

Page Cache, the Affair Between Memory and Files başlıklı yazıda da anlatıldığı gibi non-buffered I/O yapsak bile işletim sistemi halen page cache yapısını kullanmaya devam etmekte. Bu sayfadaki page cache yapısını anlatan şekil aşağıda.



Eğer bu yapıyı da devre dışı bırakıp direkt diske yazmak istersek Linux üzerinde O_DIRECT seçeneği kullanılabilir. Bu durumda Page Cache kullanılmıyor ancak Disk Write Cache halen kullanılıyor.
Burada O_DIRECT açıklanmış.

 O_DIRECT'e karşılık olarak Windows'ta FILE_FLAG_NO_BUFFERING  (önbellekte tutma) seçeneği kullanılabilir.


Small file not committed to disk for over a minute sorusunda da açıklandığı gibi farklı dosya sistemleri farklı cache ayarları kullanabiliyorlar.


Bir dosyanın O_DIRECT seçeneği ile açılıp açılmadığını görmek için How to tell if a given process opened files with O_DIRECT? sorusunun cevabına göz atabilirsiniz. Bu bayrağın kullanılması diske yazma işleminde tüm verinin tamamen yazıldıktan sonra write() metodunun dönmesini garanti etmez! How are the O_SYNC and O_DIRECT flags in open(2) different/alike? sorusunda da bu durum açıklanıyor.

Verinin Diske Yazıldığını Garanti Etmek (Disk Write Cache kullanılmaması)
O_SYNC bayrağı ile verinin diske yazıldığını garanti etmek man sayfasına göre mümkün  çünkü man sayfasında şu cümle geçiyor. 
O_SYNC The file is opened for synchronous I/O. Any write(2)s on the resulting file descriptor will block the calling process until the data has been physically written to the underlying hardware.
Örnek : 
fd = open(filename, O_SYNC);
O_SYNC'e karşılık olarak Windows'ta FILE_FLAG_WRITE_THROUGH kullanılabilir. FILE_FLAG_WRITE_THROUGH seçeneğinin FILE_FLAG_NO_BUFFERING ile kullanılıp kullanılmaması aşağıda açıklanmış.

Ancak HDD, FS, O_SYNC : Throughput vs. Integrity yazısında da açıklandığı gibi HDD de kendi içinde bir önbellek kullanıyor. O_SYNC bayrağı kullanılsa bile bu verinin tamamen manyetik ortama yazıldığını garanti etmiyor.

POSIX guaranteeing write to disk sorusunda ise synch() metodunun kullanılması söylenmiş.

posix_fadvise metodu
Dosya açıldıktan sonra posix_fadvise metodu ile bir çok ince ayar daha yapmak mümkün. Örneğin 

POSIX_FADV_RANDOM :  Bu bayrak ile dosyaya rastgele bir şekilde erişileceğini belirtip işletim sistemi 
tarafından kullanılan read-ahead (önden okuma özelliği kapatılabilir).

POSIX_FADV_SEQUENTIAL : Bu bayrak ile dosyaya daha fazla read-ahead yapılması gerektiği belirtilir.
posix_fadvise(in_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
POSIX_FADV_WILLNEED: İşletim sistemine read-ahead (önden okuma) işlemini ne kadar yapması söylenebilir. Örnek.
posix_fadvise(fd, 0, 512*1024*1024, POSIX_FADV_WILLNEED);
POSIX_FADV_DONTNEED: İşletim sistemine read-ahead için ayrılan bellek alanına ihtiyaç kalmadığı söylenir.

Büyük Dosyaları Okumak

Non-Buffered I/O ile Kullanılan Bazı Metodlar

close metodu
close metodu dosyayı kapatır. Burada anlatıldığı gibi bu metodun döndürdüğü değere mutlaka bakılması tavsiye ediliyor.
Eğer zaten kapatılmış olan bir dosyayı, tekrar kapatmaya kalkışırsak EBADF hatası alırız. Örnek:

fgets ve gets
gets() güvenli bir metod değil, bu yüzden mümkünse fgets() kullanılmalı.

Since the user cannot specify the length of the buffer passed to gets(), use of this function is discouraged. The length of the string read is unlimited. It is possible to overflow this buffer in such a way as to cause applications to fail, or possible system security violations.
fgets'in imzası şöyle
char * fgets ( char * str, int num, FILE * stream );
Metod şöyle kullanılır.

char name[256];
fgets(name,256,stdin);
Önemli bir nokta şu , gets newline karakterini de okunan string'e dahil eder!

fgets() reads in at most one less than size characters from stream and stores them into the buffer pointed to by s. Reading stops after an EOF or a newline. If a newline is read, it is stored into the buffer. A terminating null byte ('\0') is stored after the last character in the buffer.
Bu karakterden kurtulmak için şöyle yapılabilir. Böylece \n karakteri yerine \0 koymuş oluruz.
while (fgets(buffer, sizeof buffer, stdin) != NULL) {
  size_t len = strlen(buffer);
  if (len > 0 && buffer[len - 1] == '\n')
    buffer[--len] = '\0';

  // use buffer
  printf("\"%s\"\n", buffer);
}
strlen yerine strcspn kullanılabilir. Veya strtok kullanılabilir. Her ne olursa olsun C ile dosya işlemleri ve string'ler yanyana gelmemesi gereken şeyler. İğrenç :)

ftruncate
Dosyayı verilen büyüklüğe getirir. Bu metod kullanılarak bir dosyanın başından ven sonundan bazı satırları silme örneği burada. ftruncate şu hataları dönebilir.
EROFS : File resides in a readonly file system.
EBADF : File is open for reading only
EINVAL : File descriptor refers to a file on which this operation is impossible.

Buffered I/O
C Dili
İşletim sistemi tarafından atanan 3 posix stream için C dilinde unistd.h dosyasında macrolar tanımlanmıştır. Bu macrolar aşağıdaki gibidir.

Ancak posix streamleri unbuffered I/O için tasarlanmışlardır. C dili ise buffered I/O yapmak için başka yapılar sunmaktadır. Bunlar yapılar stdio.h dosyasında tanımlıdır ve FILE yapılarıdır. Aşağıda bu durumu görebiliriz.



Yani C dilindeki stdin , stdout , stderr FILE struct'larıdır ve macrolardan farklı şeylerdir.

Burada bir konuya daha değinmek lazım. Nasıl olduğunu bilimiyorum ancak stderr stream'inin non-buffered olduğu yazıyor. Dolayısıyla stdout ve sterr aynı terminale yazdıklarında stdout çıktısı daha yazılmadan sterr çıktısını görebiliriz. Benzer sıkıntılar C# içinde geçerli.

Aşağıdaki şekli buradan aldım ve C dilinde buffered I/O yapıldığını daha iyi gösteriyor.
FILE struct'a Yazmak İçin
fprintf kullanılır. Burada dikkat edilmesi gereken nokta fprintf sadece FILE struct'a yazmaya yarar örneğin socket handle için kullanılamaz.

FILE Struct ile ilgili Diğer Metodlar
Listeyi buradan aldım.
fopen , fclose, freopen
fread, fwrite,
fflush,
fseek
fseek metodunu complexity'si burada tartışılmış.

fgetc,getc, getchar,
fputc, putc, putchar,
fputs

printf, fprintf, sprintf
scanf,fscanf,sscanf

fopen
Dosyayı sadece yazmak için açmak için "w" kullanmalıyız. Örnek:
FILE *file = fopen("/path","w")
freopen
freopen metodu yazısına taşıdım.

fputc
Tek bir karakter yazar.
fputc('1',file);
fputs
fputs sadece NULL ile biten string'i yazar. Eğer dosyaya NULL yazmak istiyorsak fwrite kullanmalıyız.

fscanf
scanf ailesinden olan bu metod ile dosyadan formatlı olarak veri okumak mümkün.


Buffer'ın Boyunu Değiştirmek
C Dili
I/O için kullanılan tampon'unun (buffer) boyunu değiştirmek için setbuf metodu kullanılır. Buffer yöntemini (unbuffered, line buffered, fully buffered) değiştirmek içinse  setvbuf metodu kullanılır.

setvbuf için örnek:
FILE* outf = //...
char obuffer[BUFSIZ];
//Full buffering: tampon tamamen dolunca yaz ve tamamen boşalınca oku
setvbuf(outf, obuffer, _IOFBF, sizeof(obuffer));
Buffer'ın boyunu seçerken BUFSIZ macrosu kullanılabilir. Bu macro her sistemde farklı bir değere sahip ve stream işlemleri için en uygun değeri taşıdığı yazılı.

NonBuffered I/O'dan Buffered I/O'ya Geçmek
C Dili
NonBuffered I/O'yu temsil eden bir stream fdopen() metodu ile buffered FILE* haline getirilebilir. Windows üzerinde ise _fdopen() kullanılır. Örnek:

int file;
FILE *stream = fdopen (file, "r");

Buffered I/O'dan NonBuffered I/O'ya Geçmek
C dili
Buffered I/O'yu temsil eden FILE* yapısı fileno (FILE* stream) metodu kullanılarak işletim sistemi tarafından kullanılan stream (file descriptor) numarasına çevirilebilir. Burada, gcc -std=gnu99 veya -D_POSIX_SOURCE seçenekleri ile derlenmesi gerektiği söylenmiş. Örnek:


Buffer'ı Boşaltmaya Zorlamak
C Dili
Bu iş için fflush() metodu kullanılıyor. Yanlız input için kullanılan bir stream üzerinde fflush kullanmak tanımsız bir davranış. Bu yüzden yapmamak lazım.

Buffer'ın Wide Oriented veya Byte Oriented Çalıştığını Kontrol Etmek
Sadece C dilinde  Byte-oriented veya Wide-oriented çalışma durumu var. Implementing perror() - issue sorusunda fwide kullanma örneği mevcut.
C++ Dili
std::cin , std::cout , std::cerr  C dilindeki yapılara karşılık geliyorlar. std::clog std::cerr ile aynı aycak std::cerr non-buffered iken std::clog buffered bir yapı.

C++ streamleri ile kullanılabilen özel karakterler var. 
\f : Ekranı temizlemek için kullanılabilir
\t : Bir tab karakteri koymak için kullanılabilir.

Java Dili
Java'da Flushable arayüzü ve bu arayüzü gerçekleştiren bir sürü sınıf var.

Bir diğer ilginç nokta ise Java ile gelen InputStream sınıfının non-buffered olmasına rağmen yine de available() diye bir metod sunması. Bu metod ile bloke olmadan okunabilecek byte sayısını almak mümkün

Hiç yorum yok:

Yorum Gönder