Synchronizacja dwóch procesów( w tym jednego potomnego) za pomocą semaforów nienazwanych:

int main(int argc, char **argv){
    sem_t shared_semaphore;
    int x = 3; // zasob dzielony
    sem_init(&shared_semaphore, 0, 1);
    int pid;
    pid = fork();
    if (pid == 0) {
        //jestesmy w dziecku
        sem_wait(&shared_semaphore);
        x ++;
        sem_post(&shared_semaphore);
    }
    else {
        // jestesmy w rodzicu.
        sem_wait(&shared_semaphore);
        x++;
        sem_post(&shared_semaphore);
        
    }
    
}

Co robi, po co jest mlock oraz mlockall?

Metody te blokują część lub całość pamięci wirtualnej komputera przed wymiecieniem strony na dysk.
Odwrotne do nich operacje to munlock() oraz munlockall()
Sygnatura:

lock(const void *addr, size_t len); 

Blokuje strony w zasięgu adresu zaczynającym się w addr i trwającym przez len bajtów.Wszystkie strony, które zawierają choć część wyspecyfikowanego zasięgu adresów mają zagwarantowane, że nie zostaną wymiecione na dysk.
Używamy je często do aplikacji czasu rzeczywistego, ponieważ chcemy aby niektóre części pamięci były bardzo szybko dla nas dostępne - czas dostępu jest wtedy deterministyczny, nie jest konieczne wolne pobieranie danych z dysku twardego.
Mamy jeszcze inne zastosowania. Przyjmijmy, że proces zainstalował funkcję obsługi przerwania.Gdy nie mamy statusu rezydentnego, a procedura obsługi przerwania jest wymieciona na dysk w razie wystąpienia przerwania mamy błąd systemu-> zakończenie.
Podobna sytuacja następuje gdy używamy kanałów DMA do transmisji danych. Gdy uruchamiamy dysk i tworzymy bufor do którego zapisujemy dane z dysku, a ciało zostaje wymiecione tracimy wtedy dane -> mamy błąd systemu
Memory lock nie jest dziedziczony w procesie FORK.

mlockall() - nadaje status rezydentności całemu obszarowi pamięci który jest we władaniu procesu.
Ważny znacznik - FUTURE | CURRENT.
Co one znaczą?
Przypuśćmy że jest sobie proces, który ma jakieś zasoby. Wywołuje on funkcję mlockall. A za chwilę wywołuje funkcję mallock.
Gdy mamy włączony FUTURE - nowa pamięć również będzie miała status rezydentności. CURRENT - utrzymują go tylko te zasoby które były zaalokowane do momentu wywołania funkcji mlockall.

Co to jest zdarzenie(event ) w systemie POSIX i jakie możliwości obsługi przewiduje standard.

Zdarzenie to rozszerzenie sygnałów. Służy ono do powiadamiania procesów o jakiejś wyjątkowej sytuacji.
Jest ono definiowane za pomocą stworzenia struktury sigevent, która opisuje zdarzenie:

struct sigevent { 
int sigev_notify
int sigev_signo //  numer sygnału który należy zgłosić w razie zdarzenia(tylko gdy SIGEV_SIGNAL)
union sigval sigev_value //wartosc sygnału(jak wyżej, tylko gdy SIGEV_SIGNAL)
void(*)(union sigval) sigev_notify_function // funkcja uruchamiana gdy jest SIGEV_THREAD
(pthread_attr_t *) sigev_notify_attributes // atrybuty dla nowego wątku
}

Napisać szkielet programu, który tworzy trzy procesy potomne (ich programy zapisane są w plikach o nazwach pp1, pp2, pp3) i oczekuje na zakończenie jednego z nich.


int main(int argc, char **argv) {
    int child_pids[3];
    char names[3] = {"pp1", "pp2", "pp3"};
    for(int i = 0; i < 3; i++ ){
        if ((child_pid = fork()) == 0) {
            exec(names[i], 0, 0)
            child_pids[i] = child_pid;
        }
    }
    waitpid(child_pids[1], 0, 0);
    //Teraz mamy odpalone procesy, czekamy na zakonczenie 2.
    
}
/* UWAGA: Gdy wywołamy waitpid z -1 jako 1 argumentem - zaczeka na którykolwiek z procesów */

Wyjaśnić działanie protokołu z przekazywaniem znacznika */

Protokół z przekazywaniem znacznika to deterministyczna metoda dostępu do łącza danych.
Zasada działania polega na tym, że w danej chwili tylko jedna maszyna, która posiada znacznik ma prawo zajęcia łącza i wysłania przez nie danych. Pozostałe maszyny w tym czasie nie przeszkadzają jej w niczym. Następnie jest on przekazywany do kolejnej maszyny z wcześniej ustaloną kolejnością.
Problemem z protokołem może być fakt, że w chwili gdy maszyna posiadająca obecnie znacznik ulegnie awarii następuje cisza w sieci. Jednakże dzięki charakterystyce protokołu(m.in tego, że ciągle obecne są jakieś transmisje) możemy uznać te momenty ciszy jako coś nienormalnego - być może potencjalną awarię.
Charakterystyczny dla metody jest właśnie jej determinizm(każdy węzeł doczega się transmisji w czasie znanym z góry), brak jakichkolwiek kolizji.
Minusy to jak już wspomniane:
- zniknięcie znacznika
- możliwość jego zduplikowania
- narzut czasu potrzebny do przekazania znacznika
Odzyskujemy znacznik:
Wykrywamy ciszę i wszystkie węzły zaczynają nadawać, z długością proporcjonalną do adresu. Ten, który ostatni skończy otrzymuje odzyskany znacznik.

Wyjaśnić cel, mechanizm działania i ograniczenia protokołu pułapu priorytetu.

Protokół pułapu priorytetu ma za zadanie przeciwdziałać(minimalizować) zjawisku inwersji priorytetów.
Podstawą jego działania jest wcześniejsze przypisanie określenie zasobów, które wymagane są przez zadania(wraz z ich priorytetami).
Idea wygląda następująco:
Każdy zasób ma przypisany do siebie pułap priorytetu.
Wówczas, gdy proces zajmie zasób, jego priorytet zwiększany jest do wartości pułapu priorytetu przypisanego dla danego zasobu.
Po zwolnieniu dostaje ponownie priorytet początkowy.
Dzięki temu zadania, które mają dostęp do tego samego zasobu działają są tak samo ważne.
Jedyne niebezpieczeństwo to zwiększony czas odpowiedzi. Wtedy zadanie, którego sekcja krytyczna jest obecnie wykonywana jest na tyle długa, że opóźni wykonywanie innych działań

Zaprojektować drajwer wielokanałowego przetwornika a/c w postaci biblioteki funkcji działającej w systemie zgodnym ze standardem POSIX. Drajwer nie powinien dopuszczać do inwersji priorytetów.

a) Zaproponować listę funkcji
b) Narysować szkielety programów

Pomysł jest następujący - mamy jedną funkcję dostępną dla korzystających z urządzenia:

int read_from_ac(int channo);
/* Oraz funkcje, które są dostępne wewnątrz naszej implementacji */
ac_open(phtread_mutex_t* mutex); // Funkcja inicjalizuje mutex
int ac_read(phtread_mutex_t* mutex, char channo); // Funkcja odczytuje synchronicznie dane z a/c
void ac_close(phtread_mutex_t* mutex); // Zamyka mutex

/* UWAGA: Używamy mutexów, jako, że są w stanie zapewnić zabezpieczenie dostępu do sekcji krytycznej, oraz ponadto zastosowanie odpowiednich flag PRZECIWDZIAŁA inwersji priorytetów.
Tutaj krótka teoria:
Inwersja priorytetów:
Wyobraźmy sobie dwa zadania o różnych priorytetach(wysoki, niski) mające wspólny zasób.
Zadanie z wysokim priorytetem zaczyna się wykonywać i powiedzmy czeka na I/O. Wtedy procesor przyznawany jest dla zadania o niskim priorytecie. Ono zajmuje zasób i zostaje wywłaszczone(bo np. dane zostały dostarczone) przez zadanie o wysokim priorytecie. W rezultacie dochodzi do inwersji priorytetów - zadanie o wyższym priorytecie nie wykonuje się “pierwsze”. Tutaj jednak mamy dość deterministyczną sytuację, nie tak ciężką.
Gorzej jest gdy mamy trzy procesy(wysoki, średni, niski).
Jak wcześniej - wysoki, niski dzielą zasób.
Przyjmijmy - działa przez chwilę niski, zajmuje zasób. Wtedy jest wywłaszczony przez zadanie z wysokim priorytetem. Ono zatrzymuje się na semaforze, pojawia się zadanie o średnim priorytecie. I tutaj czas, który minie do ponownego odzyskania kontroli przez zadanie z niskim priorytetem(które musi zwolnić zasób) jest nieznany - nie możemy go określić.
Dopiero gdy skończy się zadanie o średnim priorytecie, procesor dostaje zadanie z niskim, zwalnia zasób i pozwala działać zadaniu które się zablokowało.
Dziedziczenie priorytetów, które ustalamy w tym miejscu przeciwdziała inwersji. Ustawia ono priorytet zadania, które zajmuje zasób(czyli tego o niskim priorytecie) na priorytet zadania, które prosi o zasób(czyli wysokiego). W tej chwili żadne zadanie o średnim priorytecie nie może go wywłaszczyć.

Używamy danych jak z wykładu:

ADRES ODCZYT ZAPIS
0 STAN(READY) START POMIARU
1 NR KANAŁU NR KANAŁU
2 WYNIK POMIARU -
3 WYNIK POMIARU -
Kod:
//Zarys działania funkcji dostępowej:
int read_from_ac(char channo) {
   int value;
   pthread_mutex_t mutex;
   ac_open(&mutex);
   ac_read(&mutex, channo);
   ac_close(&mutex);
}
//Funkcja ac_open()
void ac_open(pthread_mutex_t *mutex) {
    pthread_mutexattr_t mattr;
    pthread_mutex_attr_init(&mattr);// inicjalizujemy atrybut
    pthread_mutexattr_setprotocol(&mattr, PTHREAD_PRIO_INHERIT);
    pthread_mutex_init(mutex, &mattr);
    pthread_mutexattr_destroy(&mattr);
}
void ac_close(pthread_mutex_t *mutex) {
    pthread_mutex_destroy(mutex);
}
int ac_read(pthread_mutex_t* mutex, char channo) {
    uint8_t stan, al, ah;
    pthread_mutex_lock(mutex);
    out8(BASE + 1, channo ); // zapisujemy kanał
    out(BASE, 0); // startujemy pomiar
    do {
        stan = in8(BASE);
    } while(stan == 0) // odpytywanie, dopóki nie otrzymamy wyniku
    ah = in8(BASE + 2);
    al = in8(BASE + 3);
    pthread_mutex_unlock(mutex);
    return (ah << 8) + al;
}

Napisać szkielet programu cyklicznego, o dokładnie określonym czasie cyklu

Napiszemy sobie prosty program, który co jakiś czas odpalał będzie wątek zwiększający wartość zmiennej.
TODO.

Narysować szkielet programów monitora realizującego stos liczb całkowitych w systemie zgodnym ze standardem POSIX */

//Statyczna część danych
define SIZE 5
int stack[SIZE];
int top = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond;
//Tutaj inicjalizacja...
pthread_mutex_init(&mutex, 0);
pthread_cond_init(&cond, 0);

//Mamy dwie funkcje - push oraz pop
int pop() {
    int value;
    //Standard - sekcja krytyczna.
    pthread_mutex_lock(&mutex);
    if (top == 0) {
        pthread_cond_wait(&cond, &mutex); // ***
    }
    //Tutaj element jest juz dostepny.
    value = stack[top--];
    
    pthread_mutex_unlock(&mutex);
}
void push(int value) {
    pthread_mutex_lock(&mutex);
    stack[++top] = value;
    if (top == 1){
		cond_signal(&cond);
	}
	// sygnalizujemy tylko wtedy gdy pojawil sie element - znaczy to ze ktos na niego czekal i musimy odblokowac proces, czy wątek które jest zawieszone w linii oznaczonej *** 
    pthread_mutex_unlock(&mutex);
}

Wyjaśnić działanie protokołu z odpytywaniem

Protokół z odpytywaniem zakłada istnienie jednego węzła typu master oraz dowolnej liczby węzłów typu slave.
Węzeł master regularnie wysyła do węzłów(jednego na raz) komunikat w którym pyta o dane. Po otrzymaniu takowego zapytania, węzeł slave zwraca w odpowiedzi odpowiednią informację(dane, lub komunikat o ich braku).
Dzięki takiemu protokołowi tylko jeden węzeł na raz może mieć dostęp do sieci danych(brak kolizji).
Praktycznie (przede wszystkim na niższych poziomach sieci, które kontaktują się z elementami instalacji takimi jak czujniki, czy przetworniki) węzeł master najpierw wysyła wiadomość rozpocznij pomiar, a dopiero w następnej wiadomości do węzła slave odczytuje dane. Ma to zastosowanie optymalizacyjne(przez czas trwania pomiaru łącze nie jest blokowane).