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/
)