I've got a project that is sort of a virtual operating system.
In this project, the Kernel
class is responsible for creating Process
classes. The process class consists of Thread
classes and the Thread
class will consist of a CPU
class. The CPU
class is actually a CPU
emulator and the Thread
is therefore an "emulated" thread. So when the CPU
class encounters an interrupt instruction, it needs to be handled by the kernel because it is usually a system call. The Kernel
class cannot actually see the CPU
class directly, it is embedded in the Thread
class, which is embedded in the Process
class.
The approach I'm using now uses a InterruptHandler
class, which handles the system calls and breakpoints. Here's what the code looks like.
class CPU final {
std::shared_ptr<MemoryBus> memoryBus;
std::shared_ptr<InterruptHandler> interruptHandler;
std::uint32_t regs[32];
public:
void Run(unsigned int steps) {
for (decltype(steps) i = 0; i < steps; i++)
RunInst();
}
protected:
// This function will call the interrupt
// handler if it encounters an interrupt instruction.
void RunInst();
};
class Thread final {
std::shared_ptr<CPU> cpu;
std::shared_ptr<InterruptHandler> interruptHandler;
public:
Thread() : cpu(new CPU) {
}
void Run(unsigned int steps) {
cpu->Run(steps);
}
};
class Process final {
std::vector<std::shared_ptr<Threads>> threads;
std::shared_ptr<MemoryMap> memoryMap;
std::shared_ptr<InterruptHandler> interruptHandler;
public:
void Run(unsigned int steps) {
for (auto &t : threads)
t->Run(steps);
}
};
class InterruptHandler {
std::shared_ptr<FS> fs;
public:
void HandleInterrupt(CPU &cpu, int interrupt_type);
void HandleSyscall(CPU &cpu, int syscall_type);
void HandleWrite(CPU &cpu, int fd, const void *buf, unsigned int len);
// More system calls follow
};
class Kernel final {
std::vector<std::shared_ptr<Process>> processes;
std::vector<InterruptHandler> interruptHandler;
public:
void Run(unsigned int steps) {
for (auto &p : processes)
p->Run(steps);
}
void AddProcess(const std::string &path) {
// opens process, assigns interrupt handler
processes.emplace_back(process);
}
};
The problem here is that the interrupt handler class has to be passed to just about every sub class of the Process
class, including the Process
class. Also, the InterruptClass
seems like it's going to end up containing a lot of the same data as the Kernel
class. I don't like this because I feel like it violates the principle of "don't repeat yourself."
I know there's a better way of designing this but I haven't really figured it out yet. Is the Proxy design pattern applicable to this scenario? What about using signals and slots, similar to Qt and also described here?
I've left out the URL of this project, because I don't want to come off as self promoting. If the code I provided is too incomplete, I'll reference files in the project for more info.