Do spisu tresci tematu 9

Operacje o charakterze plikowym na gniazdach

Spis tresci


Wstepik

Podobnie jak na zwykych ( albo specjalnych :) ) plikach, na gniazdach mozna operowac za pomoca operacji read, write, ioctl i tym podobne. Jest to najczestszy sposob poslugiwania sie gniazdami ( czytaj - takie wrazenie odniosl autor niniejszego skryptu po przesylabizowaniu paru ksiazek p. Stevensa ).
Chyba nie musze wyjasniac, jaka jest semantyka funkcji takich jak read czy write :). W tym dokumencie zajmujemy sie implementacja.

Jak to dziala

Przyjrzyjmy sie kodowi z socket.c. Mamy tam:

static struct file_operations socket_file_ops = {
	sock_lseek,
	sock_read,
	sock_write,
	NULL,                   /* nie mamy readdir()  */
	sock_select,
	sock_ioctl,
	NULL,			/* nie mozna uzywac mmap */
	NULL,			/* nie mamy specjalnej obslugi open... */
	sock_close,
	NULL,			/* nie mamy operacji fsync */
	sock_fasync
};
Struktura struct file_operations jest zdefiniowana w fs.h. Jej instancja, socket_file_ops, zawiera wskazniki do funkcji, zdefiniowanych takze w socket.c. Ta struktura jest uzywana w funkcji get_fd: adres ( &socket_file_ops )jest przypisywany na pole f_op nowoutworzonej struktury file. Wywolanie np. funkcji systemowej read dla gniazd jest w istocie realizowane przez funkcje sock_read.

Inaczej jest realizowana funkcja fcntl:patrz dalej.
Funkcje zdefiniowane w socket.c robia niewiele. Ich zadanie to sprawdzenie, czy bufor podany jako argument jest w istocie dostepny dla czytania/pisania dla uzytkownika, ewentualnie weryfikacja poprawnosci argumentow (wyjatki : sock_close,sock_fasync ). Potem wywolywana jest odpowiednia funkcja rodziny gniazd, zdefiniowana w af_inet.c lub w af_unix.c. Funkcje z af_unix.c zwykle realizuja zadane operacje. Funkcje z af_inet.c przeciwnie, tylko wywoluja funkcje protokolu ( TCP,UDP,ICMP ...), ktore wreszcie wykonuja zadanie. Adresy funkcji protokolu znajduja sie w strukturze ops (bedacej polem stuktury socket). O funkcjach protokolu wiecej powinno byc w Temacie 08.


Opisy funkcji

Funkcja sock_lseek


Zwraca zawsze blad ESPIPE - gniazda sa dostepne tylko sekwencyjnie.

W funkcjach read i write jest uzywana struktura iovec - jest to po prostu tablica buforow. Wiecej informacji - w opisie funkcji *msg.

Funkcja sock_read


static int sock_read(struct inode *inode, struct file *file, char *ubuf, int size)
{
     struct socket *sock;
     int err;
     struct iovec iov;
     struct msghdr msg;

     sock = socki_lookup(inode);
     if (sock->flags & SO_ACCEPTCON)  /* Jesli nie ma ustalonego polaczenia,*/
             return(-EINVAL);         /* zwroc EINVAL */

     if(size<0)
             return -EINVAL;
     if(size==0)             /* Dla zgodnosci z SYS5 */
             return 0;
     if ((err=verify_area(VERIFY_WRITE,ubuf,size))<0)
             return err;     /* czy uzytkownik moze pisac do ubuf */
     msg.msg_name=NULL;
     msg.msg_iov=&iov;
     msg.msg_iovlen=1;
     msg.msg_control=NULL;
     iov.iov_base=ubuf;
     iov.iov_len=size;

     return(sock->ops->recvmsg(sock, &msg, size,(file->f_flags & O_NONBLOCK)
, 0,&msg.msg_namelen));        /* funkcja rodziny */
}
Tu jest opis funkcja recvmsg.

Funkcja sock_write


Jest w pelni symetryczna do sock_read - zamiast recvmsg jest uzyta sendmsg.

Funkcja sock_ioctl


int sock_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
           unsigned long arg)
{
        struct socket *sock;
        /* po prostu sock = &inode->u.socket_i  */
        sock = socki_lookup(inode);
        /* funkcja rodziny */
        return(sock->ops->ioctl(sock, cmd, arg));
}
Funkcja ioctl dla rodziny internetu

Jest to wielka instrukcja switch(cmd). Wiekszosc mozliwych cmd powoduje wykonanie operacji dotyczacej
Nas interesuja galezie
  case FIOSETOWN:    /* okresl wlasciciela */
  case SIOCSPGRP:    /* okresl grupe procesow */
       err=verify_area(VERIFY_READ,(int *)arg,sizeof(long));
       if(err)
         return err;
       pid = get_user((int *) arg);
       /* patrz komentarz do fcntl  */
      if (current->pid != pid && current->pgrp != -pid && !suser())
                                return -EPERM;
      sk->proc = pid;
      return(0);
 case FIOGETOWN:   /* pobierz wlasciciela */
 case SIOCGPGRP:  /* pobierz grupe procesow */
      err=verify_area(VERIFY_WRITE,(void *) arg, sizeof(long));
      if(err)
        return err;
      put_fs_long(sk->proc,(int *)arg);
      return(0);
*************************************************************************
Tu sa inne galezie case.
*************************************************************************
 default:
      if ((cmd >= SIOCDEVPRIVATE) && (cmd <= (SIOCDEVPRIVATE + 15)))
          return(dev_ioctl(cmd,(void *) arg));
      if (sk->prot->ioctl==NULL)
          /* jesli nie mamy ioctl zaimplementowanej w protokole */
          return(-EINVAL);   
      return(sk->prot->ioctl(sk, cmd, arg));  /* funkcja protokolu */
Funkcja ioctl dla rodziny unixa

Jest to rowniez blok switch(cmd),ale obsluguje tylko dwie wartosci cmd: TIOCOUTQ i TIOCINQ,zwracajace ilosc wolnego miejsca w buforach odpowiednio wyjscia i wejscia. Dla innych wartosci cmd zwraca blad EINVAL.

Funkcja sock_select


static int sock_select(struct inode *inode, struct file *file,
                       int sel_type, select_table * wait)
{
    struct socket *sock;
    sock = socki_lookup(inode);

    if (sock->ops->select)
           /* funkcja protokolu */
           return(sock->ops->select(sock, sel_type, wait)); 
    return(0);
}
Funkcja select dla rodziny internetu

Sprowadza sie do wywolania funkcji protokolu
return( sk->prot->select( sk, sel_type, wait ) );

Funkcja tcp_select z pliku tcp.c wykorzystuje glownie funkcje select_wait z sched.h
Funkcja select dla rodziny unixa

Sprowadza sie do
 return datagram_select(sock->data,sel_type,wait);

Funkcja sock_close


void sock_close(struct inode *inode, struct file *filp)
{
/* inode moze byc NULL jesli zamykamy deskryptor gniazda, ktore nie  */
/* zostalo do konca zainicjalizowane (?) ( oryg. unfinished socket ) */
           if (!inode)
                return;
        /* zwalniamy pamiec zajeta przez liste klientow, */
        /* ktorym mamy wysylac SIGIO */
        sock_fasync(inode, filp, 0);
        /* budzimy polaczone z nami i czekajace na polaczenie procesy */
        sock_release(socki_lookup(inode));
}

Funkcja sock_fasync


static int sock_fasync(struct inode *inode, struct file *filp, int on)
{
        struct fasync_struct *fa, *fna=NULL, **prev;
        struct socket *sock;
        unsigned long flags;

        if (on)
        {
        /* alokujemy element listy fasync  */
         fna=(struct fasync_struct *)
                  kmalloc(sizeof(struct fasync_struct), GFP_KERNEL);
         if (fna==NULL)
            return -ENOMEM;
        }
        sock = socki_lookup(inode);
        prev=&(sock->fasync_list);
        save_flags(flags);
        cli();

        /* szukamy w liscie fasync_list wezla o polu fa_file==filp  */
        for(fa=*prev; fa!=NULL; prev=&fa->fa_next,fa=*prev)
                if(fa->fa_file==filp)
                        break;

        if(on)
        {
                if(fa!=NULL)  /* znalezlismy wezel */
                {
            /* nie trzeba nic robic,*/
            /* zwalniamy niepotrzebnie uprzednio zaalokowana pamiec */
            /* czy to jest elegancki styl programowania ? */
                        kfree_s(fna,sizeof(struct fasync_struct));
                        restore_flags(flags);
                        return 0;
                }
           /* tworzymy nowy wezel */
                fna->fa_file=filp;
                fna->magic=FASYNC_MAGIC;
             /* wstawiamy g do listy */
                fna->fa_next=sock->fasync_list;
                sock->fasync_list=fna;
        }
        else
        {
                if(fa!=NULL)      /* znalezlismy wezel */
                {
                       /* usuwamy go z listy */
                        *prev=fa->fa_next;
                        /* zwalniamy pamiec */
                        kfree_s(fa,sizeof(struct fasync_struct));
                }
        }
        restore_flags(flags);
        return 0;
}
To wszystko o funkcjach z socket_file_ops.

Funkcja sock_fcntl


Jest osiagana w inny sposob. Funkcja systemowa fcntl (zdefiniowana w fs/fcntl.c ) sprawdza, czy przekazany jej jako argument deskryptor pliku odnosi sie do gniazda. Jesli tak, wywoluje funkcje sock_fcntl .
int sock_fcntl(struct file *filp, unsigned int cmd, unsigned long arg)
{
        struct socket *sock;

        sock = socki_lookup (filp->f_inode);
        if (sock != NULL && sock->ops != NULL && sock->ops->fcntl != NULL)
         /* wywolujemy funkcje rodziny */
                return(sock->ops->fcntl(sock, cmd, arg));
        return(-EINVAL);
}
Funkcja fcntl dla rodziny internetu

static int inet_fcntl(struct socket *sock, 
                       unsigned int cmd, unsigned long arg)
{
  struct sock *sk;

  sk = (struct sock *) sock->data;
  switch(cmd)
    {
       
       case F_SETOWN:
         /*
         * Musimy sprawdzic, czy ustanawiany wlasciciel to biezacy proces 
         * ( albo czy biezacy proces ma przywileje nadzorcy) .
         * Jest to silne ograniczenie, ale konieczne - w przeciwnym razie
         * moznaby do dowolnego procesu w systemie wyslac sygnal SIGURG,
         * co zwykle zabija proces.
         * To ograniczenie weszlo stosunkowo niedawno...
         */
         if (!suser() && current->pgrp != -arg && current->pid != arg)
             return(-EPERM);
         sk->proc = arg;
         return(0);
       case F_GETOWN:
         return(sk->proc);
       /* nie mamy innych operacji */
       default:
         return(-EINVAL);
        }
}
Funkcja unix_fcntl
zwraca zawsze blad EINVAL.

Warto jeszcze napomknac, ze przy pomocy fcntl mozna ustawic na gniezdzie flage O_NDELAY - wtedy operacje na gniezdzie sa nieblokujace (tj. jesli wymagalyby uspienia wywolujacego procesu, to odpowiednia funkcja wraca natychmiast z bledem ).


Bibliografia


  1. Zrodla kernela Linuxa 2.0.0 + Linux Navigator
  2. W. Stevens "Programowanie zastosowan sieciowych"

Autor: Rafal Wojtczuk