ОС1/Модификације октобар 2025

Извор: SI Wiki
Пређи на навигацију Пређи на претрагу

Modifikacija: joinAll (30p)

Doodati sistemske pozive thread_add_child i thread_join_all. thread_add_child registruje dete-nit za tekuću (pozivajuću) nit. thread_join_all blokira tekuću nit dok se sva njena deca ne izvrše. U okviru C++ API dodati nestatičke metode addChild(Thread* child) i joinAll() u klasu Thread. Napisati test program: nit A pravi 3×B i 1×C, svaki B pravi 3×C. Roditelj čeka svu svoju decu pre nego što nastavi.

Rešenje

TCB.hpp

Dodato na vrhu fajla (forward declaration):

class Semaphore;

Nova polja u private sekciji:

TCB* parent;            // pokazivač na roditelja (nullptr ako nema)
int childCount;         // broj nezavršene dece
Semaphore* childrenSem; // semafor na koji roditelj čeka u joinAll

Nove metode u public sekciji:

static void addChild(TCB* parent, TCB* child);
static void joinAllChildren();

TCB.cpp

Dodat include na vrhu:

#include "../h/Semaphore.hpp"

U konstruktoru, inicijalizacija novih polja (posle this->finished = false;):

this->parent = nullptr;
this->childCount = 0;
this->childrenSem = nullptr;

Izmenjen dispatch() — else grana proširena da obavesti roditelja kad dete završi:

void TCB::dispatch() {
    TCB *old = running;
    if (!old->isFinished()) {
        Scheduler::put(old);
    } else {
        if (old->parent != nullptr) {
            old->parent->childCount--;
            if (old->parent->childCount == 0 && old->parent->childrenSem != nullptr) {
                old->parent->childrenSem->sem_signal();
            }
        }
    }
    running = Scheduler::get();
    contextSwitch(&old->context, &running->context);
}

Nove funkcije:

void TCB::addChild(TCB* parent, TCB* child) {
    child->parent = parent;
    parent->childCount++;
    if (parent->childrenSem == nullptr) {
        Semaphore::createSemaphore(&parent->childrenSem, 0);
    }
}

void TCB::joinAllChildren() {
    if (running->childCount <= 0) return;
    running->childrenSem->sem_wait();
}

syscall_c.hpp

Dodate deklaracije:

void thread_add_child(thread_t child);
void thread_join_all();

syscall_c.cpp

void thread_add_child(thread_t child) {
    if (child == nullptr) return;
    do_syscall(0x16, (uint64)child);
}

void thread_join_all() {
    do_syscall(0x17);
}

RISCV.cpp (handleSupervisorTrap)

Dodate nove case grane u switch (posle case 0x13 dispatch):

case 0x16: {
    TCB *child = (TCB*)arg1;
    TCB::addChild(TCB::running, child);
    break;
}
case 0x17: {
    TCB::joinAllChildren();
    break;
}

syscall_cpp.hpp

Dodate metode u klasu Thread (public sekcija):

void addChild(Thread* child);
void joinAll();

syscall_cpp.cpp

void Thread::addChild(Thread* child) {
    thread_add_child(child->getMyHandle());
}

void Thread::joinAll() {
    thread_join_all();
}

Test primer

#include "../h/syscall_cpp.hpp"
#include "../h/syscall_c.hpp"
#include "../h/printing.hpp"

class ThreadC : public Thread {
public:
    ThreadC(int id) : Thread(), id(id) {}
private:
    int id;
    void run() override {
        printString("    C");
        printInt(id);
        printString(" started\n");

        volatile int sum = 0;
        for (int i = 0; i < 3000; i++)
            for (int j = 0; j < 3000; j++)
                sum += j;

        printString("    C");
        printInt(id);
        printString(" finished\n");
    }
};

class ThreadB : public Thread {
public:
    ThreadB(int id) : Thread(), id(id) {}
private:
    int id;
    void run() override {
        printString("  B");
        printInt(id);
        printString(" started, creating 3 C children\n");

        ThreadC* c[3];
        for (int i = 0; i < 3; i++) {
            c[i] = new ThreadC(id * 10 + i);
            c[i]->start();
            this->addChild(c[i]);
        }

        printString("  B");
        printInt(id);
        printString(" waiting for children...\n");
        this->joinAll();

        printString("  B");
        printInt(id);
        printString(" all children done!\n");

        for (int i = 0; i < 3; i++) delete c[i];
    }
};

void joinAllTest() {
    printString("A started, creating 3 B and 1 C\n");

    ThreadB* b[3];
    for (int i = 0; i < 3; i++) {
        b[i] = new ThreadB(i);
        b[i]->start();
        thread_add_child(b[i]->getMyHandle());
    }

    ThreadC* c = new ThreadC(99);
    c->start();
    thread_add_child(c->getMyHandle());

    printString("A waiting for all children...\n");
    thread_join_all();

    printString("\n=== A: ALL children done! ===\n");

    for (int i = 0; i < 3; i++) delete b[i];
    delete c;
}

Napomena: start() mora biti pozvan PRE addChild() jer start() interno poziva thread_create() koji postavlja myHandle. Bez toga, getMyHandle() vraća nullptr.

Očekivan output

A started, creating 3 B and 1 C
A waiting for all children...
  B0 started, creating 3 C children
  B0 waiting for children...
  B1 started, creating 3 C children
  B1 waiting for children...
  B2 started, creating 3 C children
  B2 waiting for children...
    C99 started
    C99 finished
    C0 started
    C0 finished
    C1 started
    C1 finished
    ...svi C završe...
  B0 all children done!
  B1 all children done!
  B2 all children done!

=== A: ALL children done! ===

Kako radi

Svaki TCB ima polje parent (ko je roditelj) i childCount (koliko dece još radi). Roditelj se blokira na semaforu (childrenSem, inicijalno 0). Kad nit završi, u dispatch() se dekrementira parent->childCount. Tek kad poslednje dete spusti brojač na 0, poziva se sem_signal() koji deblokirara roditelja.