29 Aralık 2015 Salı

IPC (Inter Process Communication)

Not: Pipe ile ilgili olarak Non-Blocking I/O başlıklı yazıya göz atabilirsiniz

ipcs Komutu
ipcs komutu ile IPC için kullanılan yapılar görülebilir.

Unnamed Pipe
Unnamed Pipe uygulama içi haberleşmek veya fork() ile beraber kullanılır. Unnamed pipe ile ilgili olarak gördüğüm en güzel yazı Beej's Guide to Unix IPC başlıklı yazı.

pipe açmak
Pipe ile açılan iki tane file descriptor vardır. fd [0] okuma için, fd [1] ise yazma için kullanılır.
int pipefd[2];
pipe(pipefd);
yazma ucu
fd[0] okuma ucu, fd[1] yazma ucudur. fork() ile kullanıyorsak geleneksel olarak okuyan taraf yazma ucunu kapatır.
// Fork
switch(fork())
{
  case 0:
    // Close unused write side
    close(pipefd[1]);
    while(1)
    {
      char c;
      // Read only one byte
      int ret = read(pipefd[0], &c, 1);
      printf("Woke up\n", ret);
      fflush(stdout);
    }
    default:
      // Close unused read side
      close(pipefd[0]);
      size_t len = 0;;
      char *str = NULL;
      while(1)
      {
        int nbread;
        char buf[65535];
        while (getline(&str, &len, stdin)) {
          if (sscanf(str, "%i", &nbread)) break;
        };
        // Write number byte asked
        int ret = write(pipefd[1], buf, nbread);
        printf("Written %d bytes\n", ret);
        fflush(stdout);
       }
    }

pipe büyüklüğünü ayarlamak
Linux'ta Pipe için çekirdekte ayrılmış olan alanın boyunu ayarlamak için fcntl() ve F_SETPIPE_SZ parametreleri beraber kullanılabilir.
int pipe_sz = fcntl(pipe_des[1], F_SETPIPE_SZ, sizeof(size_t));
En az system page size, en çok /proc/sys/fs/pipe-max-size değer arasında bir sayı verilebilir.

Yazma Ucunu NonBlocking Yapmak
fd [1] flag'i değiştirilerek yapılır.
int flags = fcntl(pipefd[1], F_GETFL, 0); // set write operation non-blocking
assert(flags != -1);
fcntl(pipe_des[1], F_SETFL, flags | O_NONBLOCK);

read işlemi
POSIX standardı pipe'ların read() işlemi için aşağıdaki bilgileri tanımlamış.
  • Eğer child pipe'ı kapatırsa, parent read() işlemine teşebbüs edince işlem 0 değerini döner.
  • Eğer child pipe'ı kapatmamışsa ve pipe için O_NONBLOCK bayrağı atanmışsa parent read() işlemine teşebbüs edince okunacak veri yoksa işlem -1 değerini döner ve errno'ya EAGAIN değeri atanır.
  • Eğer child pipe'ı kapatmamışsa ve pipe için O_NONBLOCK bayrağı atanmamışsa parent read() işlemine teşebbüs edince okunacak veri yoksa işlem okunacak veri gelinceye kadar bloke olur.
dup
dup en küçük müsait file descriptor değerini kullanarak, kopyalama yapar.
pipe'ın okuma ucunu stdin'e yönlendirmek için aşağıdaki kod kullanılır.
// Redirect pipes[0] to stdin
close(pipes[1]);
close(0);
dup(pipes[0]);
close(pipes[0]);

dup2
dup2 yazısına taşıdım.

Named Pipe
İki uygulama arasında iletişim sağlamak için kullanılabilecek yöntemlerden birisi olarak named pipe gösteriliyor.

mkfifo komutuyla ismi olan bir pipe yaratılır
int myPipe = mkfifo("pipe", S_IRUSR | S_IWUSR);
Daha sonra yaratılan pipe open() ile açılır.
char * myfifo = "/tmp/myfifo";
mkfifo(myfifo, 0666); //Make the fifo
int fd = open(myfifo, O_WRONLY);
Named pipe bash tarafından subshell çalıştırmak için de sıkça kullanılır. Örnek'te dikkat edilirse "|" işareti kullanılmıyor.
cat <(ls -l)
ls komutunun çıktısı bash tarafından bir named pipe'a dolduruluyor. Böylece cat'in okuyabileceği bir dosya oluşmuş oluyor.

Yazmak için kullanılan open komutu normalde okuyan taraf olmadıkça bloke olur.

"The kernel maintains exactly one pipe object for each FIFO special file that is opened by at least one process. The FIFO must be opened on both ends (reading and writing) before data can be passed. Normally, opening the FIFO blocks until the other end is opened also.

A process can open a FIFO in nonblocking mode. In this case, opening for read only will succeed even if no-one has opened on the write side yet, opening for write only will fail with ENXIO (no such device or address) unless the other end has already been opened."


Socket Pair
socketpair ile dikkat edilmesi gereken en önemli nokta aşağıdaki cümle

Pipes and socketpairs are limited to communication between processes with a common ancestor.
Yani bu yöntem de fork() yöntemiyle ortak bir atadan gelen ik uygulama için kullanılabilir. socketpair() metodu Windows üzerinde mevcut değildir. socketpair ile stream veya datagram socketler açılabilir.
Stream socket açma örneği aşağıda bulunabilir.


socketpair ile farklı uygulamalar arasında ancillary (yardımcı) mesaj geçebilmek te mümkün. Konuyla ilgili olan bu yazıya göz atabilirsiniz.

Unix domain socketi ile ilgili olarak QLocalSocket'e göz atmak ta faydalı olabilir.Aşağıda QLocalSocket sınıfının hiyerarşisini görmek mümkün.


Shared Memory
Shared Memory kullanmak için shm_open() ve mmap() sistem çağrılarını kullanmak lazım. shm_open() ile ismi olan bir shared memory parçası yaratılıyor ve bu parça bir file descriptor olarak döndürülüyor. Yaratılan shared memory alanını /dev/shm dizini altında görmek mümkün. shm_open() örnekleri için Shared Memory başlıklı yazıya bakabilirsiniz.

Burada , umask ayarlarının, yaratılan shared memory dosyasının izinlerini etkileyebileceği de belirtilmiş.

Yaratılan shared memory parçasını kapatmak için shm_unlink kullanılıyor.

Not : shm_open() yerine shm_get() metodu da kullanılabilir ancak shm_get ile isim yerine bir anahtar değer kullanmak gerekiyor.shm_open() ve shm_get metodlarının karşılaştırmasını shmget vs. shm_open başlıklı yazıda görebilirsiniz.

Not :  Linux ve Windows arasında shared memory'nin ne zaman yok edileceğine dair farklılıklar var. Konuyla ilgili olarak QSharedMemory sınıfına bakabilirsiniz. En temel fark, Windows'ta shared memory yaratan program çökerse işletim sistemi tarafından silinir. ancak Unix'te shared memory shm_unlink çağırılmadığı müddetçe var olmaya devam ediyor.



Burada dikkat edilmesi gereken nokta mmap() metodu aynen malloc() gibi sanal bir adres döndürür.
boost kullanan örnekleri boost interprocess kütüphanesi başlığı altında bulabilirsiniz.

shmget yöntemi
shmget ile Shared Memory yazısına taşıdım.

Büyük Dosyaları Okumak İçin
Memory Mapped File yöntemiyle büyük dosyalar daha kolay yüklenir.Bu yöntem ile ilgili olarak Memory Mapped File başlıklı yazıya göz atabilirsiniz.
Memory Mapped I/O (Hafızaya eşlenmiş giriş/çıkış)
Yöntem itibariyle Memory Mapped File ile aynı şey. Bu kez dosya yerine bir cihaz açılıyor ve cihazın file descriptor numarası kullanılıyor.Konuyla ilgili olarak Memory Mapped File başlıklı yazıya göz atabilirsiniz.


Hangi amaçla kullanılmış olursa olsun, yaratılmış olan Shared Memory ya da bir file descriptor, mmap() sistem çağrısı kullanılarak uygulamanın adres alanına (address space) dahil edilir. Aşağıdaki şekil nasıl çalıştığını özetliyor.

mmap Shared Memory



Posix Message Queue
Message queue kullanmak için iki farklı API seti var, ve anladığım kadarıyla her ikisi de standardın bir parçası. İlk API seti msgXXX ile başlıyor, diğeri ise mq_XXX ile başlıyor.

Posix Message Queue işletim sistemi tekrar başlatılırsa yok olur.
  
Posix ile message queue kullanabilmek mümkün. mq_overview başlıklı yazıda güzel bir özet var. Burada dikkat edilmesi gereken nokta, mq_open() sistem çağrısı ile yaratılan kuyruk int yerine mqd_t verisi dönmektedir.Dolayısıyla bu yapı normal file descriptorlardan farklı olup select/poll gibi non-blocking çalışmayı sağlayan sistem çağrıları ile beraber kullanılamazlar. Her ne kadar Linux üzerinden bu çağrılar çalışsa da kod portable olmaz ! Burada libev ile kullanıma bir örnek var.

Bir diğer bilgi ise Posix Message Queue kuyruğunun azami uzunluğunu /proc/sys/fs/mqueue/msg_max altında görebilmek mümkün. Ancak uzunluğu nasıl değiştirebiliriz bilmiyorum.

Kuyruğu Yaratmak ve Yazma/Okumaya Açmak İçin


msgget
Yeni bir kuyruk açmak için örnek:
#define PERMS (S_IRUSR | S_IWUSR) 
int msqid = msgget(IPC_PRIVATE, PERMS);
mq_getattr
Bu metod ile kuyruğun özelliklerini almak mümkün.Örneğin kuyruğun non_blocking olup olmadığını anlamak için aşağıdaki gibi yapılabilir.
mq_attr mystruct;
mqd_t mqdes;//opened fd
mq_getattr (mqdes,&mystruct);
if (mystruct.mq_flags & O_NONBLOCK) //nonblocking
Kuyruktaki elemanların ne kadar büyük olduğunu anlamak için aşağıdaki gibi yapılabilir.
mq_getattr(mq , &attr);
printf("mq maximum msgsize = %d\n" , (int) attr.mq_msgsize);

mq_open
mq_open kullanabilmek için
#include <mqueue.h> yapıp librt ile -lrt şeklinde link etmek lazım.

Default değerler ile açmak için aşağıdaki kod lazım.
mqd_t queue = mq_open("unique_name", O_RDWR|O_CREAT);
Exclusive açmak için. Bu ne için lazım bilmiyorum.
mqd_t queue = mq_open("unique_name", O_RDWR | O_CREAT | O_EXCL);

Eğer kullanıcılara bazı haklar tanıyarak açmak istiyorsak aşağıdaki kod lazım.

mqd_t qd = mq_open("/tempqueue", O_RDONLY | O_CREAT, 0666, NULL);
O_RDWR : Open the queue to both send and receive messages.

Eğer kuyruğun uzunluğunu ve mesaj  büyüklüğünü kendimiz değer atamak istiyorsak aşağıdaki kod lazım.
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = 1000;
attr.mq_msgsize = sizeof(int_32);

mqd_t queue = mq_open("unique_name", O_RDWR|O_CREAT, 0600, attr);
mq_attr yapısı aşağıdaki gibi.

Tanımlanan maxmsg ile kuyruğun uzunluğu atanabilir. Ancak bloke olmadan kaç tane eleman yazılabileceği /proc/sys/fs/mqueue/msg_max dosyasında tanımlı.

Kuyruğa Yazmak İçin
msgsnd
Örnek:
int len = 250;
mymsg_t* mbuf = malloc(len);
mbuf = //populate
msgsnd(msqid, mbuf, len, 0);

mq_send 
Örnek:
struct item  {
    int maxvalue;
    int minvalue;
    char queuename [50];
};
item.maxvalue = 1;
item.minvalue = 100;
stcpy (item.queuename,"test");
mq_send(mq , (char *) &item , sizeof(item) , 0);
Kuyruktan Okumak İçin
msgrcv
Bu çağrının imzası şöyle:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
Çağrıya geçilen 3. parametre yani msgtyp 0 ise kuyruktaki ilk eleman okunur.

Örnek:
mymsg_t mymsg; //buffer to hold message
int size = msgrcv(msqid, &mymsg, len, 0, 0);

mq_receive
Örnek:
mqd_t qd; //mq_open() ile açılır
char buf[400];
mq_receive(qd, buf, 400, NULL);//Mesajı okumak için buf yeterince büyük olmalı
Kuruğa Mesaj Gelince Signal İle Haberdar Olmak İçin
Örnek:
void handler()
{
}
signal(SIGUSR1, handler);
sigevent.sigev_signo = SIGUSR1;;
mq_notify (msgq_id, &sigevent);

Kuyruğu Kapatmak İçin
Örnek:
mqd_t qd = mq_open("/tempqueue", O_RDONLY | O_CREAT, 0666, NULL);

Kuyruğu Silmek İçin
mq_close(qd); //Kuyruğa erişimi kapat
mq_unlink("/tempqueue"); //Kuyruğu sil

boost
Send complex data structure via boost message queue sorusunda boost ile message queue kullanılması örneği var.
Message queue açmak için

#include <boost/interprocess/ipc/message_queue.hpp>
message_queue mq
            (
             open_or_create,
             "mq",
             100,
             MAX_SIZE
            );
okumak içinse

message_queue mq
            (
             open_only,
             "mq"
            );
yapmak lazım.

Hiç yorum yok:

Yorum Gönder