recvmsg()/sendmsg()
sendmsg() i recvmsg() rodziny protokolow:
Rodzina protokolow Unixa,
Rodzina protokolow Internetu,
Inne rodziny protokolow
read()/write() i
readv()/writev()) moga operowac takze na
deskryptorach plikow innych rodzajow, inne sa specyficzne tylko dla gniazd.
Roznice pomiedzy poszczegolnymi funkcjami dotycza jedynie mozliwosci
podawania dodatkowych argumentow - wieksza elastycznosc uzycia wiaze sie
z koniecznoscia wklepania wiekszej ilosci kodu, co widocznie jest na tyle
duzym utrudnieniem, iz musialo powstac 5 roznych interfejsow
do jednej funkcji. Rzeczywiste algorytmy przesylania danych
zawarte sa w kodach poszczegolnych protokolow, co czyni je czasami bardzo
zlozonymi, jak w przypadku protokolu TCP. Mamy tu jednak do czynienia
z algorytmami pochodzacymi z konkretnego protokolu przesylania danych,
a nie specyficznymi dla jadra Linuxa. Dlatego tez na tej stronie zostanie
omowione szczegolowo jedynie przesylanie danych w rodzinie protokolow
Unixa; pobieznie przedstawiona bedzie rowniez rodzina protokolow Internetu
(TCP, UDP, RAW, PACKET), pomieniete zostana pozostale protokoly, jako
rzadziej uzywane na maszynach Linuxowych.
recvmsg()/sendmsg()u, zawierajaca informacje specyficzne dla systemu plikow, lub tez
strukture socket, jesli deskryptor pliku zwiazany jest z gniazdem. W owej
strukturze pole ops wskazuje na strukture typu
proto_ops, definiujaca operacje
na gniezdzie specyficzne dla rodziny gniazda. W szczegolnosci, znajduja sie
tam 2 interesujace nas wskazniki do funkcji przesylajacych dane:
int (*sendmsg) (struct socket *sock, struct msghdr *m, int total_len, int nonblock, int flags); int (*recvmsg) (struct socket *sock, struct msghdr *m, int total_len, int nonblock, int flags, int *addr_len);Dzialanie wszystkich funkcji gniazd przesylajacych dane sprowadza sie do wywolania jednej z wyzej wymienionych funkcji po przyjeciu domyslnych wartosci dla tych argumentow, ktorych nie dostarczyl dany interfejs. Ponizsza tabelka przedstawia interesujace nas funkcje systemowe i roznice w mozliwosciach przekazywania opcji do nich:
| wiele buforow | dodatkowe znaczniki | adres partnera | informacje kontrolne | |
| read/write | ||||
| readv/writev | + | |||
| send/recv | + | |||
| sendto/recvfrom | + | + | ||
| sendmsg/recvmsg | + | + | + | + |
W Linuxie przyjeto konwencje, na mocy ktorej kod implementujacy funkcje
systemowa f() jest zawarty w funkcji sys_f().
Ponizej przedstawione sa definicje i opis implementacji poszczegolnych
funkcji, z tym, ze pominiety jest opis znaczenia parametrow i wykaz
zwracanych bledow, poniewaz bylby to jedynie skrot informacji z
podrecznikow systemowych (ang. man pages), ktore zostaly
stworzone specjalnie po to, aby
szczegolowo opisac interfejs programisty do funkcji systemowych
(tzw. API).
DEFINICJA: ssize_t read(int fd, void *buf, size_t count)
ssize_t write(int fd, const void *buf, size_t count)
WYNIK: liczba przeslanych bajtow lub -1 (blad)
Funkcje systemowe sys_read()
i sys_write(),
zdefiniowane w pliku fs/fs.h,
dokonuja podstawowego sprawdzenia poprawnosci przekazanych argumentow,
czyli np. istnienia otwartego pliku o podanym deskryptorze, praw odczytu
lub zapisu do pliku ustalonych przy jego otwarciu, dostepnosci dla danego
uzytkownika zadanego obszaru pamieci, po czym wywoluja funkcje wlasciwe
dla danego pliku. Sa one zdefiniowane w strukturze typu
file_operations,
dostepnej ze struktury file. Struktura socket_file_ops wskazuje m. in. na
funkcje sock_read()
i sock_write()
(plik net/socket.c). Te funkcje
sprawdzaja nie wiadomo po co po raz kolejny dostepnosc obszaru pamieci oraz
flage SO_ACCEPTCON, ktorej zadne inne funkcje przesylania nie sprawdzaja
(albo jest to niepotrzebne, albo w innych funkcjach jest blad, choc byc moze
nie jest to blad, ktory moglby spowodowac awarie systemu); wypelniaja
struktury typu msghdr i iovec (brak nazwy partnera, brak wiadomosci
kontrolnych, jeden bufor I/O), a na koniec wolaja funkcje
sendmsg()/recvmsg() z rodziny (z wyzerowanymi flagami).
DEFINICJA: int readv(int fd, const struct iovec *vector, size_t count);
int writev(int fd, const struct iovec *vector, size_t count);
WYNIK: ilosc przeslanych bajtow lub -1 (blad)
Funkcje sys_readv()
i sys_writev() (plik fs/read_write.c)
sprawdzaja poprawnosc
deskryptora pliku oraz tryb otwarcia i wywoluja
do_readv_writev(), ktora
z kolei weryfikuje dostepnosc dla uzytkownika obszarow pamieci (wektor,
bufory danych), kopiuje wektor I/O do pamieci jadra, oblicza calkowita
dlugosc przesylanych danych i wywoluje (w przypadku
gniazd) sock_readv_writev().
Ta funkcja wypelnia strukture typu
msghdr,
ustawiajac brak adresu partnera, brak wiadomosci kontrolnych oraz wstawiajac
adres i dlugosc wektora I/O, po czym wywoluje funkcje
sendmsg()/recvmsg()
z rodziny (z wyzerowanymi flagami).
DEFINICJA: int recv(int s, void *buf, int len, unsigned int flags);
int recvfrom(int s, void *buf, int len, unsigned int flags,
struct sockaddr *from, int *fromlen);
int recvmsg(int s, struct msghdr *msg, unsigned int flags);
int send(int s, const void *msg, int len, unsigned int flags);
int sendto(int s, const void *msg, int len, unsigned int flags,
struct sockaddr *to, int *tolen);
int recvmsg(int s, const struct msghdr *msg, unsigned int flags);
WYNIK: ilosc przeslanych bajtow lub -1 (blad)
Funkcje sys_send(),
sys_recv(),
sys_sendto(),
sys_recvfrom(),
sys_sendmsg()
i sys_recvmsg()
z pliku net/socket.c sprawdzaja poprawnosc argumentow i
wolaja funkcje sendmsg()/recvmsg() z rodziny, przy
czym:
sys_sendto()
kopiuje najpierw adres zdalnego gniazda do pamieci jadra,
sys_recvfrom()
i sys_recvmsg()
kopiuja na koniec adres zdalnego gniazda do pamieci uzytkownika,
sys_sendmsg()
i sys_recvmsg()
kopiuja najpierw naglowek wiadomosci oraz wektor I/O do pamieci jadra,
sendmsg() i
recvmsg() rodziny protokolowunix_sendmsg()
i unix_recvmsg()
sa zaimplementowane w pliku
net/unix/af_unix.c i niestety przekraczaja dopuszczalna dlugosc, podana
w dokumencie CodingStyle w
rozdziale o funkcjach.
Najprosciej bedzie je
przedstawic w pseudo-kodzie (zwykly opis bylby dosc trudny do
zrozumienia).
algorytm: unix_recvmsg
wejscie i wyjscie: takie jak w deklaracji funkcji rodziny recvmsg()
{
if (zadanie danych pilnych (OOB))
zwroc blad EOPNOTSUPP;
if (jest niezwrocony blad z poprzedniej operacji)
zwroc go;
if (zadanie danych kontrolnych (deskryptory plikow))
skopiuj ich bufor przestrzeni jadra; /* patrz tez: Uwaga */
zablokuj gniazdo przy pomocy semafora;
while (jest jeszcze niezapelniony bufor pamieci) {
przejdz do kolejnego bufora;
while (biezacy bufor nie jest jeszcze w calosci zapelniony) {
if (juz cos przeslano, a flaga MSG_PEEK ustawiona)
goto wyjscie;
if (spelniono juz cale zadanie odczytu)
goto wyjscie;
/* powyzszy warunek jest wg mnie absurdalny */
zdejmij wiadomosc z kolejki otrzymanych;
if (nie bylo czekajacej wiadomosci) {
odblokuj gniazdo;
if (gniazdo zamkniete dla czytania)
zwroc to co dotad przeslano;
if (juz cos przeslano)
zwroc to co dotad przeslano;
if (operacja nieblokujaca)
zwroc blad EAGAIN;
czekaj na dane;
/* oznacza to spanie na kolejce sleep (jest ona
* w strukturze typu sock); zwolnienia dokonuje
* funkcja def_callback2(), wskazywana przez
* wskaznik data_ready, wywolywana przy zapisie
*/
if (obudzil nas sygnal)
zwroc blad ERESTARTSYS;
zablokuj gniazdo;
continue;
}
if (uzytkownik potrzebuje nazwy zdalnego gniazda)
skopiuj ja do naglowka wiadomosci;
skopiuj do bufora uzytkownika wiadomosc (lub te jej czesc,
ktora potrzebowal) i zwieksz odpowiednie wskazniki;
if (do wiadomosci sa dolaczone deskryptory plikow)
skopiuj deskryptory do bufora lub zwolnij je, o ile
uzytkownik ich nie zadal;
if (nie bylo flagi MSG_PEEK)
obetnij przeczytany fragment wiadomosci;
if (wiadomosc nie jest cala przeczytana) {
zwroc ja do kolejki;
continue;
}
zwolnij pamiec jadra zajeta przez wiadomosc;
if (gniazdo datagramowe lub uzytkownik zadal danych kontr.)
goto wyjscie;
}
}
wyjscie:
odblokuj gniazdo;
jesli byly zadane dane kontrolne, skopiuj je do przestrzeni uzytkownika;
zwroc ilosc skopiowanych danych;
}
Uwaga: na deskryptory plikow przekazywane przy pomocy gniazd alokowana
jest pamiec jadra, ktora w pewnych przypadkach (np. odczyt nieblokujacy)
nie jest zwalniana. Mozliwe konsekwencje tego faktu pokazuje
program przykladowy.
algorytm: unix_sendmsg
wejscie i wyjscie: takie jak w deklaracji funkcji rodziny sendmsg()
{
if (jest niezwrocony blad z poprzedniej operacji)
zwroc go;
if (zadanie danych pilnych (OOB))
zwroc blad EOPNOTSUPP;
if (jakies flagi sa ustawione)
zwroc blad EINVAL; /* w przyszlych wersjach to sie zmieni */
if (gniazdo zamkniete dla pisania) {
wyslij sygnal SIGPIPE;
zwroc blad EPIPE;
}
if (podano zdalny adres, a to jest gniazdo strumieniowe)
zwroc blad EISCONN lub EOPNOTSUPP;
if (nie podano zdalnego adresu, a gniazdo nie jest polaczone)
zwroc blad ENOTCONN;
if (dolaczona wiadomosc kontrolna) {
skopiuj ja do pamieci jadra;
if (blednie zapodana)
zwroc blad EINVAL;
skopiuj desktyptory do nowej tablicy, zamieniajac je na wskazniki
do plikow;
}
while (nie wyslano jeszcze wszystkiego) {
if (wiadomosc do wyslania przekracza polowe rozmiaru bufora sndbuf) {
if (gniazdo datagramowe)
zwroc blad EMSGSIZE;
ustaw rozmiar do wyslania jako polowe sndbuf;
}
zaalokuj pamiec na bufor - moze to byc wywolanie blokujace, jesli brakuje
w systemie pamieci (moze zwracac tez bledy: EPIPE, EAGAIN, ERESTARTSYS);
ustaw rozmiar wiadomosci do wyslania na rozmiar otrzymanego bufora (w
przypadku gniazd strumieniowych moglismy otrzymac mniejszy bufor, niz
zadalismy)
dolacz do wiadomosci przekazywane deskryptory plikow (jesli sa);
skopiuj z buforow uzytkownika tresc wiadomosci;
if (nie podano nazwy zdalnego gniazda) {
if (gniazdo, z ktorym jestesmy polaczeni, jest w stanie usuwania) {
if (gniazdo datagramowe) {
rozlacz sie;
zwroc blad ECONNRESET lub ilosc juz przeslanych
danych (jesli takie byly);
}
if (gniazdo strumieniowe)
zwroc blad EPIPE i wyslij sygnal SIGPIPE lub zwroc
ilosc juz przeslanych danych;
}
} else
if (gniazda zdalnego nie ma)
zwroc blad ECONNREFUSED lub blad zwracany przez
open_namei() lub zwroc ilosc juz przeslanych danych;
wstaw wiadomosc na koniec kolejki odbiorczej gniazda zdalnego;
obudz procesy czekajace na tym gniezdzie, wywolujac jego funkcje
data_ready();
uaktualnij ilosc juz przeslanych danych;
}
zwroc ilosc przeslanych danych;
}
inet_sendmsg()
i inet_recvmsg()
(plik net/ipv4/af_inet.c) korzystaja
ze struktury typu sock (wskazywanej przez strukture typu
socket w polu
data), aby dostac sie do struktury typu
proto okreslajacej m. in. funkcje
sendmsg()/recvmsg() dla danego protokolu (TCP, UDP, RAW, PACKET). Na
poziomie rodziny jest wykonywane jedynie:
SIGPIPE, o ile probuje on pisac do gniazda
zamknietego do zapisu,
INADDR_ANY i port wybrany przez system z
puli wolnych portow),
proto jest dla kazdego protokolu zdefiniowana funkcja
rcv() (np.
tcp_rcv(),
udp_rcv()),
zajmujaca sie odbieraniem przychodzacych
(oczywiscie asynchronicznie) pakietow - funkcje recvmsg() protokolow
musza pozniej odczytac otrzymane dane z buforow, a nie z urzadzen
wejscia/wyjscia.
Funkcje przesylania danych na poziomie protokolow sa dosc zlozone, zaleznie
od konkretnego protokolu. Zawieraja w sobie zarowno logike protokolu
(np. TCP - prawie 150 KB kodu), jak i kod wejscia/wyjscia na nizszym
poziomie. Kod przesylania danych przez gniazda Unixa powinien wystarczyc do
zrozumienia zagadnienia takze dla innych protokolow (oczywiscie bez tych
aspektow, ktore sa specyficzne dla konkretnych protokolow),
dlatego w tym punkcie jedynie wymienie protokoly Internetu wraz z krotkim
opisem. Troche informacji na temat kodu protokolow IP, TCP i UDP mozna
znalezc w temacie nr 8.
dev_queue_xmit()
wywolywana z
packet_sendmsg(),
a odbieranie - poprzez
skb_recv_datagram()
wywolywana z
packet_recvmsg().
ip_build_xmit()
(wywolywana z
raw_sendto(),
dosc zlozona, na najnizszym poziomie wywolujaca
dev_queue_xmit()),
a odbiera - wywolujac tak jak poprzednio
skb_recv_datagram() .
udp_recvmsg(),
udp_send()),
ale opakowane w wieksza ilosc kodu.
tcp.c, tcp_input.c, tcp_output.c.
proto_ops. Zaimplementowane
sa juz np. protokoly appletalk, ax25, decnet, ipx (Novell); w przyszlosci
bedzie dodany kod dla wersji 6 protokolu IP.
net/)