Tryb chroniony(1)

background image

1. Wstęp

Przy dostępie do wspólnych obszarów danych przez procesor i koprocesor musiały zostać podjęte specjalne
środki, które miały na celu zapobieganie kolizjom dostępu do pamięci. Podobny problem występuje, gdy na
jednym procesorze wykonywanych jest kilka zadań. Zadanie, które modyfikuje pewien obszar pamięci, musi
być pewne, że wszystkie pozostałe maja zablokowany dostęp do niego. Gdy zadania wiedza o swojej
obecności, to problem ten mogą rozwiązać protokoły dostępu do obszarów danych. Natomiast gdy błędnie
napisane zadanie zacznie losowo zmieniać zawartość pamięci, wówczas może to wywołać zawieszenie się
systemu. By zabezpieczyć się przed taka sytuacja w procesorach 80286 i wyższych został wprowadzony
specjalny tryb, zwany trybem chronionym. Wszystkie zadania są w nim od siebie odseparowane dzięki temu,
że dostęp do różnych zasobów kontroluje procesor. Zadanie może jedynie wykorzystać te zasoby, do których
w danej chwili ma prawa dostępu. Natomiast zadania, które próbują odwołać się do nieprzydzielonej pamięci,
czy urządzeń wejścia/wyjścia, zostają usuwane. Procesor nadzoruje czynności wykonywane przez zadania,
przydzielając poszczególnym fragmentom kodu odpowiedni priorytet. Programy na danym poziomie
uprzywilejowania mogą korzystać z kodu, który ma inny priorytet tylko w pewnych okolicznościach. Pewne
instrukcje procesora mogą być natomiast wykonywane tylko przez programy o najwyższym priorytecie.
Rozkazy wejścia/wyjścia mogą być używane tylko na określonym przez pewne uprzywilejowane zadanie
poziomie. [1]

2. Przełączanie procesora w tryb chroniony

Po włączeniu zasilania procesory 80286 i 80386 pracują w trybie rzeczywistym. Można je wtedy uznać za
szybkie procesory 8086 z rozszerzonym zestawem instrukcji. By skorzystać z możliwości separacji zadań i
wielozadaniowości, procesory te należy przełączyć w tryb chroniony.
W trybie chronionym niektóre programy nie będą działać. Ich przykładem jest MS-DOS i BIOS. Oznacza to,
że nie można ich funkcji zastosować w operacjach wejścia/wyjścia. czy do obsługi przerwań.
By więc uruchomić program w systemie MS-DOS w trybie chronionym, należałoby zablokować przerwania i
zrezygnować z operacji I/O. Tego typu programów nie ma jednak wiele. Innym sposobem jest napisanie,
działających w trybie chronionym, procedur obsługi przerwań sprzętowych, a przełączanie do trybu
rzeczywistego przed przerwaniami programowymi, które wywołują np. funkcje MS-DOS-a, czy BIOS-u.

3. Pamięć w trybie chronionym

Jednym z zasobów, z którego korzystają wszystkie zadania jest pamięć. W pewnych jej obszarach zawarty
jest ich kod, w innych dane. W trybie chronionym pamięć jest klasyfikowana ze względu na jej
wykorzystanie. Zadanie, które chce się odwołać do pamięci, musi mieć do niej prawo dostępu, a także musi
wykonywać na niej prawidłową operację. Na przykład, zadanie nie może modyfikować pamięci oznaczonej
jako tylko do odczytu lub użytej jako segment kodu. [1]
Ponadto w trybie chronionym procesor może zaadresować znacznie więcej pamięci. Procesor 80286 ma
dostęp do 16M pamięci, a procesor 80386 potrafi zaadresować nawet 4 gigabajty, podczas gdy 8086 czy 8088
tylko 1 MB pamięci. Ponadto pamięć ta nie musi być fizycznie zainstalowana. Nie używany segment może
być zapisany na dysk i zastąpiony takim, który jest właśnie potrzebny.

3.1. Tablice deskryptorów

W trybie chronionym programy nigdy nie operują na adresach fizycznych. Zamiast tego używają one
selektorów, które wskazują położenie i długość każdego segmentu pamięci. Tablice deskryptorów są
przechowywane w pamięci operacyjnej, dlatego tez musza i dla nich istnieć odpowiednie deskryptory. W
procesorze mogą istnieć trzy tablice. Są to:

- globalna tablica deskryptorów (GDT),
- lokalna tablica deskryptorów (LDT),
- tablica deskryptorów przerwań (IDT).

Tablica GDT zawiera deskryptory, które mogą być wykorzystane przez dowolne z zadań w systemie.
Znajdujące się w niej deskryptory opisują segmenty, w których znajdują się tablice deskryptorów, pamięć
video oraz powszechnie dostępne segmenty kodu i danych. Format deskryptora przedstawiony został poniżej:

background image


#1 ......bity adresu podstawowego,
#2 ......bit ziarnistości 1=4096 bajtów, 0=1 bajt,
#3......rozmiar domyślny 1=32 bity, 0=16 bitów,
#4 ......zarezerwowane,
#5......bit użytkownika,
#6......bity limitu 19-16,
#7......bit obecności,
#8......poziom uprzywilejowania deskryptora,
#9......bit systemu,
#10....typ segmentu:
000 - dane, tylko odczyt,
001 - dane, odczyt/zapis,
010 - stos, tylko odczyt,
011 - stos, odczyt/zapis,
100 - kod, tylko wykonywanie,
101 - kod, odczyt/wykonanie,
110 - zgodny segment kodu, tylko wykonywanie,
111 - zgodny segment kodu, odczyt/wykonanie,
#11 ....bit dostępu
#12....bity adresu podstawowego
#13....bity limitu 15-0

Programy na najwyższym poziomie uprzywilejowania, korzystając z instrukcji SGDT, mogą pobrać dane o
tej tablicy (długość i położenie). Jej argumentem jest wskaźnik na sześciobajtowy obszar pamięci, w którym
zostaną one umieszczone. Pierwszym słowem jest liczba deskryptorów w tablicy. Następne dwa słowa
stanowią adres bazowy. Jest on fizycznym adresem tablicy GDT w pamięci.
O zmianie czy to długości, czy położenia tablicy należy procesor poinformować. Służy temu instrukcja
LGDT. Ma ona taki sam argument jak rozkaz SGDT, lecz powoduje pobranie danych spod wskazanego
miejsca pamięci i umieszczenie ich w rejestrze GDTR procesora. Tablica IDT zawiera deskryptory
wszystkich segmentów, które zawierają procedury obsługi przerwań. Zastępuje ona wektor przerwań
używany w trybie rzeczywistym. Dostęp do tej tablicy jest podobny jak dostęp do tablicy GDT i jest
realizowany przez instrukcje SIDT i LIDT.
Każde zadanie może mieć swoja oddzielna tablicę LDT. Znajdujące się w niej deskryptory opisują pamięć,
która zawiera dane specyficzne dla tego zadania. Mimo że wykorzystywana może być tylko jedna, to w
pamięci może istnieć wiele takich tablic. W systemach wielozadaniowych przełączenie zadania powoduje
również zmianę tablicy LDT. Położenie bieżącej tablicy LDT pozwalają ustalić i zapisać instrukcje SLDT i
LLDT. Argumentem obu z nich jest selektor deskryptora z tablicy GDT.

3.2. Selektory

Zadania, które działają w trybie chronionym, używają tych samych rejestrów co zwykłe programy. Różna jest
tylko zawartość tych rejestrów. W programach konwencjonalnych znajduje się tam adres paragrafu w
pamięci. Po połączeniu numeru segmentu z przemieszczeniem powstaje adres fizyczny. W trybie chronionym
natomiast rejestr segmentowy zawiera selektor. Selektor składa się z indeksu w tablicy deskryptorów,
wyróżnika tablicy i żądanego poziomu uprzywilejowania. [1]
Położenie tych pól w selektorze zobrazowane zostało poniżej:

background image

Przed użyciem selektora musi on zostać załadowany do rejestru segmentowego. Przy każdym ładowaniu
sprawdzane jest, czy podana wartość jest poprawnym selektorem i czy zadanie jest na wystarczająco
wysokim poziomie uprzywilejowania. Jeśli rejestrem jest DS, ES, FS lub GS, to segment musi mieć
zezwolenie dla odczytu. Dla rejestru SS segment musi mieć ustawione prawa pisania i czytania. Rejestr CS
wymaga natomiast segmentu "wykonywalnego". Jeśli podany został błędny selektor, wówczas w procesorze
80386 powstanie błąd GP (General Protection fault). Skutkiem tego będzie wygenerowanie przerwania (w
trybie chronionym zwanego wyjątkiem). Procedura obsługi tego przerwania może zawiesić wykonywanie
zadania, które spowodowało ten błąd.

Błąd GP jest również generowany przy próbie zapisu do segmentu, który ma nadane prawa tylko do odczytu.
Do sprawdzania praw dostępu danego segmentu służą trzy instrukcje. Pierwsza z nich jest LAR. Najpierw
sprawdza ona poprawność podanego selektora i jeśli jest on prawidłowy, zwraca prawa dostępu do
wskazywanego przezeń segmentu. Pierwszym argumentem rozkazu jest 16- lub 32-bitowy rejestr, w którym
zostaną umieszczone prawa dostępu selektora podanego w drugim. Jeśli podany selektor jest poprawny, to
ustawiony zostanie znacznik zera; w przeciwnym razie znacznik ten zostanie wyzerowany, a prawa nie
zostaną skopiowane. Jest to więc sposób na rozstrzygnięcie wątpliwości co do poprawności selektora przed
zapisaniem go do rejestru segmentowego. Nieprawidłowy selektor nie wygeneruje bowiem błędu GP.
Zwrócona dana będzie prawdopodobnie zawierać więcej informacji, niż jest to potrzebne. Jeśli argument
docelowy jest rejestrem 32-bitowym, wówczas cała ta informacja będzie do niego przesłana, w przeciwnym
razie zostanie wykorzystane tylko młodsze 16 bitów.
Znaczenie poszczególnych bitów zwróconej przez instrukcję LAR 32- bitowej informacji
przedstawione zostało w poniższej tabeli:

Bity: Opis:
31-24 Nie wykorzystane. Równe 0

23

Ziarnistość; gdy ustawiony granica podana jest w bajtach, gdy wyzerowany w 4096
bajtowych blokach

22

Domniemany rozmiar; gdy ustawiony, argumenty są 32-bitowe, gdy wyzerowany jest 16-
bitowe

21

Zarezerwowany

20

Nie zarezerwowany

19-16 Nie wykorzystane
15

Obecność; gdy ustawiony, segment jest fizycznie załadowany do pamięci

14-13 Poziom uprzywilejowania; 0 oznacza najbardziej, 3 najmniej uprzywilejowany
12

System; gdy ustawiony, segment nie jest segmentem systemowym

11-9

Typ; używane są następujące typy:
000 - dane, tylko odczyt,
001 - dane, odczyt/zapis, ,
010 - stos, tylko odczyt,
011 - stos, odczyt/zapis,
100 - kod, tylko wykonywanie,
101 - kod, odczyt/wykonanie,
110 - zgodny segment kodu, tylko wykonywanie,
111 - zgodny segment kodu, odczyt/wykonanie,

8

Dostęp; bit ustawiony, gdy dana była z segmentem odczytana lub zapisana

7-0

Nie wykorzystane; ustawione na 0


Instrukcja LAR nie jest zbyt wygodna, gdy zachodzi potrzeba sprawdzenia, czy pewien selektor jest
poprawny oraz czy posiada prawo zapisu. W tym wypadku lepiej użyć rozkazu VERW. Ma on jeden
argument, którym jest testowany selektor. Jeśli jest on poprawny i można do niego zapisywać, to znacznik
zera jest ustawiany, inaczej zaś jest zerowany. Bardzo podobna instrukcja VERR pozwala na sprawdzenie,
czy selektor ma prawo odczytu. W trybie chronionym segmenty mogą być różnej długości. Dla procesora
80286 ich rozmiar może wahać się od 1 do 65536 bajtów, a na procesorze 80386 jego długość może

background image

maksymalnie wynosić 4GB, Próba odczytu lub zapisu danej, która znajduje się poza segmentem, powoduje
powstanie wyjątku GP. Do sprawdzenia długości segmentu służy instrukcja LSL o formacie:

lsl regl6/32, regmem16/32

W pierwszym argumencie zostanie zwrócona długość segmentu, wskazywanego przez selektor umieszczony
w drugim argumencie. Ostatnie cztery instrukcje (LAR, VERW, VERR, LSL) dostarczają wszelkiej
niezbędnej o segmencie informacji, z wyjątkiem jego położenia w pamięci. Jednakże w trybie chronionym
programy nie muszą znać rozmieszczenia segmentów. [1 ]

3.3. Poziomy uprzywilejowania

W trybie chronionym niektóre operacje mogą wykonywać tylko pewne programy. Na przykład, tablica GDT
musi być aktualizowana, gdy zmieni się zapotrzebowanie programu na pamięć. Gdyby każdy program mógł
ja modyfikować, to doprowadziłoby to do zniszczenia danych i chaosu w pamięci.
W trybie chronionym istnieją cztery różne poziomy uprzywilejowania: poziom 0,
poziom 1 ,
poziom 2,
poziom 3.

Poziom stawia ograniczenia na to, do jakich danych program ma dostęp, jakie podprogramy może wywołać
czy też nawet jakich instrukcji wolno mu użyć.
W tablicy poniżej przedstawione zostały instrukcje, które mogą wykonać tylko programy na najwyższym
poziomie uprzywilejowania (poziom 0).

Instrukcja

Opis

ARPL

Zmiana pola DPL selektora

CLTS

Wyzerowanie znacznika przełączenia zadania

HLT

Zatrzymanie procesora

LGDT, LIDT, LLDT

Ładowanie tablic deskryptów

LMSW

Ładowanie słowa stanu procesora

LTR

Ładowanie rejestru zadania

MOV CRn/reg, reg/CRn Ładowanie rejestrów sterujących
MOV DRn/reg, reg/DRn Ładowanie rejestrów uruchamiania
MOV CRn/reg, reg/CRn Ładowanie rejestrów testowania
SGDT, SIDT, SLDT

Zapisanie tablic deskryptów

Jeśli program mniej uprzywilejowany będzie próbował użyć tych rozkazów, wówczas spowoduje to
wygenerowanie wyjątku.
Instrukcje wejścia/wyjścia oraz instrukcje, które operują na znaczniku zezwolenia na przerwanie, mogą być
wykonywane tylko przez programy, których poziom uprzywilejowania jest równy lub wyższy niż przywilej
operacji I/O (IOPL). IOPL jest częścią rozszerzonego rejestru znaczników. Rejestr ten zawiera oprócz
zwykłych znaczników trzy nowe flagi oraz dwa bity na IOPL. Kontrolę nad dostępem do wejścia/wyjścia
można zapewnić, poprzez ustawienie poziomów zadań różnych od zera oraz wartości IOPL mniejszej od
wszystkich tych poziomów. Jeśli teraz zadanie będzie próbowało użyć instrukcji wejścia/wyjścia, wówczas
zostanie wygenerowane przerwanie. Jeśli zaś przerwania są zablokowane. spowoduje to wyzerowanie
procesora.
Każdy segment posiada swój poziom uprzywilejowania. Zapamiętany on jest w polu DPL (Descriptor
Privilege Level) deskryptora. Poziom uprzywilejowania segmentu, który jest właśnie wykonywany, jest
oznaczany skrótem CPL (Current Privilege Level). Zapamiętany jest on na dwóch młodszych bitach rejestru
CS:

background image

mov ax, cs ;Pobierz selektor segmentu kodu
and ax, 3 ;Istotne są tylko dwa najmłodsze bity

Wartość CPL ogranicza zestaw operacji dostępnych w danej chwili. Program może mieć dostęp do danych o
poziomach niższych lub równych jego poziomowi. Dozwolone są tylko wywołania funkcji i skoki do
segmentów o identycznych poziomach. Również stos musi mieć poziom uprzywilejowania równy CPL.
Oznacza to więc, że zadanie, które posiada trzeci poziom uprzywilejowania nie będzie mogło modyfikować
tablicy GDT, która ma najprawdopodobniej poziom zerowy. Zadanie, które ładuje inne programy do pamięci
i działa na poziomie 1 , również nie może zmieniać tablicy GDT, może natomiast w obszarze zadania o
poziomie 3 umieścić nowa aplikację.
Program działający na poziomie 1 nie tylko nie ma dostępu do danych poziomu 0, ale nie może również
wykonać skoków, czy wywołać procedur umieszczonych w tym obszarze. Co więcej, nie może on nawet
wywołać żadnego podprogramu z poziomu 3.
Rozwiązaniem problemu wywoływania procedur, które znajdują się na innych poziomach uprzywilejowania,
jest zastosowanie bramek wywołań (gate). Jest to specjalny deskryptor, zawierający selektor oraz
przemieszczenie funkcji. W ten sposób programy mogą wywoływać tylko określone procedury z innego
poziomu.
Poniższy przykład stanowi procedurę, która zmienia deskryptor w tablicy GDT na bramkę wywołania. Może
być ona użyta w programie, który przełącza procesor w tryb chroniony. Powinna ona być wywoływana w
czasie ustawiania wartości innych deskryptorów (jeszcze przed przełączeniem w tryb chroniony).

;Stworzenie deskryptora bramki wywołania
;SI = Przemieszenie modyfikowanego deskryptora
;AL = Poziom uprzywilejowania wywoływanej funkcji
;BX = Przemieszczenie funkcji
;CX = Selektor segmentu zawierającego ta funkcję

MakeCG PROC
mov WORD PTR [si], bx ;Przemieszczenie funkcji
mov WORD PTR [si+2], cx ;Selektor
mov BYTE PTR [si+4],0
shl al, 5
or al, 10001100b ;Bity identyfikujące bramkę wywołującą
sub ah, ah
mov WORD PTR (si+5],ax
mov WORD PTR [si+7],0
ret
MakeCG ENDP

Funkcje mniej uprzywilejowane mogą, za pośrednictwem tak stworzonej bramki. wywoływać funkcje
bardziej uprzywilejowane. Argument instrukcji CALL musi wskazywać na deskryptor bramki wywołania:

Call FAR PTR 0010: 0000

Segmentowa część adresu (0010) wskazuje bramkę wywołania. Procesor ignoruje podawane
przemieszczenie. Zamiast niego bowiem używa on przemieszczenia zawartego w deskryptorze bramki.
Niektóre procedury muszą być dostępne dla wielu programów, które działają na różnych poziomach
uprzywilejowania. Na przykład, program, ładujący inne zadania do pamięci czy program wymiany pamięci,
musi mieć dostęp do procedur, które zapisują i odczytują dane z dysku. Zamiast bramek wywołań lepiej jest
w tym wypadku użyć zgodnego segmentu kodu. Zgodne segmenty kodu mają ten poziom uprzywilejowania,
który miał podprogram wywołujący. Segment jest zgodnym segmentem kodu, jeśli są odpowiednio ustawione
wartości bitów w polu typu deskryptora.
Programy uprzywilejowane mogą zmieniać poziom uprzywilejowania segmentu, modyfikując pole DPL w
tablicy deskryptorów. Pozostałe programy mogą tylko zwiększać poziom uprzywilejowania segmentu. Służy
do tego instrukcja ARPL o formacie:

arpl selekitor, reg

background image

Selektor segmentu może być zawarty w rejestrze lub w pamięci. W rejestrze zawarty jest żądany poziom
uprzywilejowania. Jeśli selektor jest mniej uprzywilejowany, to nadawany mu jest nowy poziom, a znacznik
zera jest ustawiany. W przeciwnym razie znacznik jest zerowany. [1]

3.4. Stronicowanie pamięci

W systemie MS-DOS, na komputerach kompatybilnych z IBM PC, pamięć video oraz BIOS znajdują się w
obszarze pamięci o adresach od 0A0000h do 0FFFFFh. W systemach z procesorami 8088 i 8086 jest to
końcowy obszar adresowalnej pamięci. Przy większych przestrzeniach adresowych procesorów 80286 i
80386 obszar ten wypada w środku dostępnej pamięci. Programy muszą brać pod uwagę tą nieciągłość
pamięci. (8]
Problem ten został w procesorze 80386 pokonany dzięki zastosowaniu stronicowania. Pamięć fizyczna jest
podzielona na strony, które mogą być dowolnie rozmieszczone w wirtualnej przestrzeni adresowej. Programy
widzą pamięć jako zbiór kolejno numerowanych komórek, choć w rzeczywistości może być ona bardzo
rozproszona. Gdy program odwołuje się do pamięci, to układ zarządzania pamięcią (MMU) w procesorze
przekształca podany adres wirtualny w adres fizyczny i wykonuje żądana operację.
Jeżeli podanemu adresowi logicznemu nie odpowiada żaden adres fizyczny, wówczas MMU wywołuje
program, który zapisuje stronę pamięci fizycznej na dysk, a odczytuje stronę poszukiwana po czym dokonuje
translacji adresów. Oznacza to, że programista może używać całej czterogigabajtowej przestrzeni adresowej
procesora 80386 niezależnie od rozmiaru fizycznie zainstalowanej pamięci operacyjnej. Jedyna różnica
będzie tylko zwolnienie działania, które wynikają z konieczności wymiany stron pamięci.
Stronicowanie odbywa się poprzez specjalne tablice, które znajdują się w pamięci oraz przez rejestry
procesora. Zalety stronicowania można wykorzystać, stosując jeden z kilku programów implementujących
stronicowanie. Pod kontrola systemem MS-DOS programista ma do dyspozycji oprogramowanie, które
odpowiadają standardowi wypracowanemu przez firmy Lotus, Intel i Microsoft (LIM). [8]

3.5. Wielozadaniowość

Gdy komputer przełącza się z jednego zadania na inne, to musi zapamiętać informację o przerywanym
zadaniu i pobrać informację o zadaniu wznawianym. Informacja ta zawiera wartości wszystkich rejestrów
procesora, stos, tablicę LDT i adres instrukcji, od której należy rozpocząć wykonywanie zadania. Jest to tzw.
kontekst zadania. [1 ]

3.5.1. Przełączanie zadań

Pierwszym krokiem, podejmowanym przy przełączaniu zadań, jest zapamiętanie kontekstu bieżącego
zadania. Następnie pobierany jest kontekst nowego zadania i na koniec jest ono uruchamiane. Procesor 80386
używa specjalnego deskryptora, który wskazuje segment stanu każdego zadania (TSS). TSS zawiera kontekst
związanego z nim zadania. Inny deskryptor zwany bramka zadania używany jest w procesie przełączenia.
Działanie tej bramki podobne jest do działania bramki wywołania.
Rejestr, zwany rejestrem zadania (TR), informuje procesor 80386. w którym segmencie TSS zapisać kontekst
aktualnego zadania. Po przełączeniu rejestr TR otrzymuje wartość selektora zadania, które to zadanie zostanie
uruchomione. Służy do tego instrukcja LTR (Load Task Register). Jedynym jej argumentem jest rejestr lub
komórka pamięci, która zawiera selektor bieżącego zadania. Do sprawdzenia, w którym segmencie TSS
zostanie zapisany kontekst bieżącego zadania, służy rozkaz STR. Jednakże nie można użyć zwróconego
selektora do dostępu do danych znajdujących się w segmencie TSS.

3.5.2. Testowanie wystąpienia przełączenia zadania

Dzięki zachowywaniu i odtwarzaniu kontekstu zadania, nie musi ono nawet wiedzieć o pozostałych
zadaniach, działających z nim równolegle. Jednakże czasami istnieje potrzeba sprawdzenia, czy w systemie
wykonują się jeszcze inne zadania. Na przykład program, używający wspólnych segmentów danych wie, że
dane te pozostaną tak długo nie zmienione, jak długo nie pojawi się inne zadanie. W słowie stanu procesora
MSW istnieje bit 3, który jest ustawiany w momencie przełączenia zadań. Instrukcja CLTS (Clear Task
Switched) powoduje jego wyzerowanie. Rozkaz SMSW pozwala pobrać słowo MSW, co umożliwia
testowanie tego bitu.


Wyszukiwarka

Podobne podstrony:
Tryb chroniony i rzeczywisty
!tutorials, tryb chroniony 3, Modele pamięci
!tutorials, tryb chroniony 2
!tutorials tryb chroniony 1
!tutorials, tryb chroniony 1
Tryb rzeczywisty i chroniony procesora
Tryb rzeczywisty, chroniony i wirtualny
Chronic Hepatitis
Zespół kanału łokciowego i nerw pachowy (tryb edytowalny)
2012 KU W5 tryb dzienny moodle tryb zgodnosci
(W7a Stale do kszta t na zimno cz I [tryb zgodno ci])
II rok tryb rozkazujacy
2 Sieci komputerowe 09 03 2013 [tryb zgodności]
Microsoft PowerPoint IP5 klasyfikacje tryb zgodnosci
Microsoft PowerPoint IP tryb zgodnosci
PA2 opis matematyczny [tryb zgodności]
ATMOSFERA [tryb zgodnosci]a id Nieznany
(Rachunkowosc podatkowa wyklad 4 5 [tryb zgodności])
Microsoft PowerPoint IP5 bazydanych tryb zgodnosci

więcej podobnych podstron