fcntl()
fcntl()
Funkcja fcntl()
umozliwia programiscie wykonywanie bardzo wielu
manipulacji na deskryptorze otwartego pliku. Czesc z operacji dostepna
jest rowniez w postaci osobnej funkcji systemowej, ale nie wszyskie daja
takie mozliwosci jak odwolanie do fcntl()
. Za pomoca tej funkcji
mamy rowniez dostep do flag pliku, mechanizmu blokad typu POSIX oraz do
kontroli nad gniazdami.
Deklaracja funkcji w pliku fs/fcntl.c
ma postac:
int fcntl(
unsigned int fd /* deskryptor pliku */,
unsigned int cmd /* komenda */
unsigned long arg /* argument zalezny od wykonywanej komendy */ );
Komendy funkcji fcntl()
zdefiniowane sa w pliku include/asm/fcntl.h
nastepujaco:
#define F_DUPFD 0 /* duplikacja deskryptora pliku */
#define F_GETFD 1 /* pobierz flage close_on_exec */
#define F_SETFD 2 /* ustaw flage close_on_exec */
#define F_GETFL 3 /* pobranie flag pliku (get f_flags) */
#define F_SETFL 4 /* ustawienie flag pliku (set f_flags) */
#define F_GETLK 5 /* pobierz blokady zalozone na plik */
#define F_SETLK 6 /* ustaw blokade na plik */
#define F_SETLKW 7 /* ustaw blokade na plik jesli mozliwe */
#define F_SETOWN 8 /* ustaw wlasciciela gniazda (ang. socket) */
#define F_GETOWN 9 /* pobierz wlasciciela gniazda (ang. socket) */
Dodatkowo zdefiniowana jest jedna wartosc argumentu w tym samym pliku:
#define FD_CLOEXEC 1 /* wlasciwie wszystko nieparzyste jest dobre */
Akcja przeprowadzana w przypadku roznych komend jest nastepujaca:
F_DUPFD
F_GETFD
F_SETFD
FD_CLOEXEC
, flaga zostaje ustawiona. Gdy arg
rowne zero, flaga jest kasowana. Funkcja nie zwraca nic istotnego. F_GETFL
open()
. F_SETFL
F_SETLK, F_GETLK, F_SETLKW
F_GETOWN
f_owner
zawierajace identyfikator wlasciciela
gniazda. Wartosc ujemna oznacza wlasciciela grupowego. Brak kontroli,
czy operacja jest przeprowadzana na gniezdzie. F_SETOWN
f_owner
), jesli gniazdo do
nikogo juz nie nalezy.
Jesli wywolujemy funkcje fcntl()
z parametrem F_GETLK
,
F_SETLK
lub F_SETLKW
, argument arg jest traktowany
jako wskaznik na strukture typu struct flock
, ktorej definicja
znajduje sie w pliku include/asm/fcntl.h
, a wyglada nastepujaco:
struct flock {
short l_type; /* typ blokady */
short l_whence; /* tryb obliczania przesuniecia w rekordzie (jak w lseek)*/
off_t l_start; /* poczatek obszaru */
off_t l_len; /* przesuniecie w bajtach */
pid_t l_pid; /* wlasciciel blokady zwracany przez F_GETLK */
};
Stale dla typu blokady zdefiniowane sa w tym samym pliku :
#define F_RDLCK 0 /* blokada odczytu */
#define F_WRLCK 1 /* blokada zapisu */
#define F_UNLCK 2 /* usuwanie blokady */
Pole l_whence
determinuje sposob wyznaczania poczatku rekordu
do zablokowania. Jesli pole zawiera wartosc 0, pozycja obliczona bedzie
od poczatku pliku; jesli 1, od aktualnej pozycji pliku oraz od konca pliku,
jesli podamy wartosc 2.
Pobranie blokady (F_GETLK
) polega na przeszukaniu listy blokad
podwieszonej pod i-wezlem danego pliku (pole i_flock) i sprawdzeniu,
czy kolejne blokady nie koliduja z blokada, jaka zostala okreslona przez
uzytkownika w strukturze flock. Jesli napotkana zostanie blokada
kolidujaca, jej parametry zostaja przypisane odpowiednim polom struktury,
lacznie z wlascicielem. Jesli zadna z blokad nie koliduje, na pole l_type
zostaje przypisana wartosc F_UNLCK.
Zdejmowanie blokady jest rownie proste jak sprawdzanie konfliktow. Polega
na znalezieniu na liscie blokad tej, ktora chcemy zlikwidowac. Znacznie
bardziej skomplikowanie przedstawia sie zakladanie blokady. Oczywiscie
w pierwszej kolejnosci sprawdzane jest, czy planowana blokada nie koliduje
z zadna, zalozona wczesniej. W przypadku kolizji postepowanie zalezy od
sposobu wywolania. Jesli byla wywolana w trybie nieblokujacym, zostaje
zwrocony blad i dodatkowo zwracana jest informacja o blokadzie kolidujacej
(podobnie jak przy F_GETLK
).
A oto wlasciwy opis alorytmu zakladania
i zdejmowania blokad typu POSIX:
algorytm fcntl_setlk z pliku fs/locks.c
wejscie: fd deskryptor pliku
cmd komenda (F_SETLK,F_SETLKW,F_UNLCK)
l wskaznik na stukture typu flock
wyjscie: 0 jesli wykonano, kod bledu jesli nie mozna wykonac
if (zly deskryptor)
return (-EBADF);
if (plik nie ma i-wezla)
return (-EINVAL);
skopiuj dane ze struktury l do struktury file_lock;
if ((plik tylko do odczytu)&&(blokada typu F_RDLCK))
return (-EBADF);
if ((plik tylko do zapisu)&&(blokada typu F_WRLCK))
return (-EBADF);
etykieta repeat:
if (na plik zalozona blokada typu FLOCK)
return (-EBUSY);
if (zalozenie blokady)
{
wez pierwsza zalozona blokade;
while (sa jeszcze blokady zalozone)
if (zalozenie blokady niemozliwe (konflikt))
{
if (wywolanie nie blokujace)
return (-EAGAIN);
if (uspienie wywola blokade)
return (-EDEADLOCK);
uspij proces na konfliktowej blokadzie;
goto repeat;
}
}
znajdz blokade nalezaca do wolajacego ;
while (sa jeszcze blokady nalezace do wolajacego)
{
if (poczatek zakladanej blokady wiekszy od konca znalezionej)
{
wstaw nowa blokade w tym miejscu;
}
if (znaleziona blokada ma niepuste przeciecie z zakladana)
{
if (zakladana blokada tego samego typu)
{
rozszerz blokade aby obejmowala obydwie;
sprawdzaj konflikt blokad z poszerzona;
}
else
{
if (zakladana zawiera istniejaca)
{
if (juz raz dokonalismy rozszerzenia)
usun aktualna blokade z listy;
obudz wszystkich czekajacych na starej blokadzie;
zastap istniejaca blokade nowozakladana;
}
}
}
}
wez pierwsza zniesiona blokade;
if ("wystaje" z obu stron zalozonej)
{
stworz dwie blokady;
przypisz na nie fragmenty wystajace;
wstaw je do kolejki blokad;
obudz procesy czekajace na nich;
}
if ("wystaje" z prawej)
{
skroc stara blokade;
wstaw do kolejki;
obudz procesy czejakace na niej;
}
wez ostatnia zniesiona blokade;
if (rozna od pierwszej usunietej)&&("wystaje" z lewej strony)
{
skroc blokade do czesci "wystajacej";
wstaw do kolejki blokad;
}
return (0);
Komentarza wymaga sposob sprawdzania zaistnienia zastoju.
Sprawdzanie takiego stanu wywolywane jest przed uspieniem procesu, aby
nie doprowadzic do uspienia kilku procesow bez szans na ich ponowne obudzenie.
Algorytm postepowania jest bardzo prosty :
algorytm posix_locks_deadlock z pliku fs/locks.c
wejscie: my_task wskaznik do procesu, ktory chcemy uspic
blocked_task wskaznik do procesu blokujacego
wyjscie: 1 gdy zaistnial zastoj; 0 jesli nie
etykieta next_task:
if (my_task==blocked_task)
return (1);
for (wszystkie blokady)
{
if (blocked_task uspiony na kolejce tej blokady)
{
if (wlasciciel blokady==my_task)
return (0);
blocked_task=wlasciciel blokady;
goto next_task;
}
}
return (0);
include/asm/fcntl.h
(naglowki funkcji, definicje stalych i struktur) include/linux/fs.h
(definicje struktur) fs/fcntl.c
(implementacja
funkcji glownej) fs/locks.c
(implementacja
blokad) net/socket.c
(implementacja
funkcji na gniazdach) Documentation/locks.txt
lockf()
sluzaca do obslugi blokad typu POSIX. Czy w Linuxie istnieje analogon
tej funkcji? lockf()
, ktora byla
tylko opakowaniem (ang. wrapper) funkcji fcntl()
. Od wersji 1.2.4
zrezygnowano z tej funkcji, chyba ze wzgledu na wielkosc jadra, i teraz
zakladaniem blokad typu POSIX bedzie sie zajmowala tylko funkcja
fcntl()
.