Add ability for other processes to bind to eachother's stdin and stdout. Add PoC for this functionality

This commit is contained in:
Xnoe 2022-01-17 19:43:13 +00:00
parent 55a5bea35a
commit 73c0fe429f
Signed by: xnoe
GPG Key ID: 45AC398F44F0DAFE
14 changed files with 498 additions and 140 deletions

View File

@ -2,7 +2,8 @@ CFLAGS = -g -std=gnu11 -m32 -mgeneral-regs-only -nostdlib -fno-builtin -fno-exce
CXXFLAGS = -g -m32 -fno-use-cxa-atexit -mgeneral-regs-only -nostdlib -fno-builtin -fno-rtti -fno-exceptions -fno-leading-underscore -fpermissive -fno-pie -fno-stack-protector -I.
LDFLAGS =
DISK_IMG_FILES = build/kernel/kernel.bin build/world/world.bin hello.txt alpha.txt
DISK_IMG_FILES = build/kernel/kernel.bin build/world/world.bin hello.txt alpha.txt \
build/hello/hello.bin
KERNEL_CPP_SRCS = $(wildcard src/kernel/*.cpp) $(wildcard src/kernel/*/*.cpp)
KERNEL_ASM_SRCS = $(wildcard src/kernel/*.asm)
@ -44,6 +45,7 @@ disk.img: clean prepare build/boot/boot.bin build/boot_stage2/boot.bin $(DISK_IM
dd obs=512 seek=1 if=build/boot_stage2/boot.bin of=disk.img conv=notrunc
mount disk.img img.d
cp $(DISK_IMG_FILES) img.d/
sleep 0.1
umount img.d
chmod 777 disk.img

View File

@ -28,6 +28,18 @@ int write(uint32_t count, void* filehanlder, uint8_t* buffer) {
asm volatile ("mov $11, %%eax; mov %0, %%ebx; mov %1, %%esi; mov %2, %%edi; int $0x7f" : : "m" (count), "m" (filehanlder), "m" (buffer): "ebx", "esi", "edi");
}
uint32_t fork(char* filename) {
asm volatile("mov $7, %%eax; mov %0, %%esi; int $0x7f" : : "m" (filename) : "esi");
}
uint32_t bindStdout(uint32_t PID) {
asm volatile("mov $13, %%eax; mov %0, %%esi; int $0x7f" : : "m" (PID) : "esi");
}
uint32_t bindStdin(uint32_t PID) {
asm volatile("mov $14, %%eax; mov %0, %%esi; int $0x7f" : : "m" (PID) : "esi");
}
void bindToKeyboard() {
asm volatile ("mov $12, %%eax; int $0x7f" : : :);
}

View File

@ -9,6 +9,10 @@ void* localalloc(uint32_t size);
void localdelete(void* ptr);
uint32_t filesize(char* filename);
uint32_t fork(char* filename);
uint32_t bindStdout(uint32_t PID);
uint32_t bindStdin(uint32_t PID);
uint32_t getPID();
int read(uint32_t count, void* filehandler, uint8_t* buffer);

View File

@ -1,16 +1,40 @@
#include "../common/common.h"
void readline(int count, char* buffer) {
int index = 0;
char c;
while (index < count) {
if (read(1, 1, &c)) {
if (c == '\n')
break;
if (c == '\b') {
if (index == 0)
continue;
else {
index--;
buffer[index] = 0;
write(1, 0, &c);
continue;
}
}
buffer[index++] = c;
write(1, 0, &c);
}
}
print("\n");
}
int main() {
uint32_t counter = 0;
uint32_t PID = getPID();
char intbuffer[32];
uint32_t index = int_to_decimal(PID, intbuffer);
print("Hello, World!\n");
char buffer[32];
while (1) {
counter++;
if (counter == 312500) {
print(intbuffer+index);
print(" ");
counter = 0;
}
for (int i=0; i<32; i++)
buffer[i] = 0;
print(">>> ");
readline(32, buffer);
print("You said: ");
print(buffer);
print("\n\n");
}
}

View File

@ -5,7 +5,7 @@ namespace Global {
Kernel* kernel = 0;
Process* currentProc = 0;
tss_struct* tss = 0;
bool currentProcValid = true;
bool currentProcValid = false;
}
void* operator new (uint32_t size) {

View File

@ -21,18 +21,36 @@ void set_entry(uint8_t interrupt_number, uint16_t code_segment, void(*handler)()
};
}
void page_fault(frame_struct* frame) {
// Clear interrupts, we don't want to perform a context switch during a page fault.
void handle_fault(frame_struct* frame) {
// Clear interrupts, we don't want to perform a context switch whilst handling a fault.
asm ("cli");
uint32_t problem_address;
asm ("mov %%cr2, %0" : "=a" (problem_address):);
Global::kernel->terminal->printf("(CS %x EIP %x): Page Fault at %x Error Code: %x Gate: %d\n", frame->cs, frame->eip, problem_address, frame->errcode, frame->gate);
Global::kernel->terminal->printf("(CS %x EIP %x): ", frame->cs, frame->eip);
switch (frame->gate) {
case 0: // Divide by zero
Global::kernel->terminal->printf("Divide by Zero");
break;
case 6: // Invalid Opcode
Global::kernel->terminal->printf("Invalid Opcode");
break;
case 13: // GPF
Global::kernel->terminal->printf("General Protection Fault!");
break;
case 14: // Page Fault
Global::kernel->terminal->printf("Page Fault at %x", problem_address);
break;
default:
Global::kernel->terminal->printf("Unkown Fault!");
break;
}
Global::kernel->terminal->printf(" Error Code: %x\n", frame->errcode);
if (!(frame->cs & 3)) {
Global::kernel->terminal->printf("[FATAL] Kernel Page Fault!!!\n");
Global::kernel->terminal->printf("[FATAL] Kernel Fault!!!\n");
while (1) asm("hlt");
} else {
// Print an error message.
Global::kernel->terminal->printf("PID %d Terminated due to page fault!\n", Global::currentProc->PID);
Global::kernel->terminal->printf("PID %d Terminated due to fault!\n", Global::currentProc->PID);
asm volatile ("mov %0, %%esp" ::"m"(Global::kernel->globalISRStack));
Global::kernel->PD->select();
@ -49,11 +67,6 @@ void page_fault(frame_struct* frame) {
void ignore_interrupt(frame_struct* frame) {}
void gpf(frame_struct* frame) {
printf("(EIP %x) General Protection Fault %x\n", frame->eip, frame->errcode);
while (1) asm("hlt");
}
void context_switch(frame_struct* frame) {
// When any interrupt occurs (including context_switch), SS:ESP is set to
// the values of SS0:ESP0 in Global::tss
@ -70,26 +83,21 @@ void context_switch(frame_struct* frame) {
asm ("cli"); // Disable interrupts whilst handling the context switch.
// Restore eax
asm ("mov %0, %%eax"::"r"(frame->eax));
xnoe::linkedlist<Process*>* processes = &Global::kernel->processes;
Process* currentProc = 0;
Process* nextProc = 0;
if (Global::currentProcValid) {
currentProc = Global::currentProc;
// Write current esp to currentProc->kernelStackPtr
asm ("mov %%esp, %0" : "=a" (currentProc->kernelStackPtr):);
if (!processes->start) {
Global::kernel->terminal->printf("[FATAL] No more processes! Halting!\n");
while (1) asm ("hlt");
}
if (currentProc || !Global::currentProcValid) {
xnoe::linkedlist<Process*>* processes = &Global::kernel->processes;
if (Global::currentProcValid)
asm ("mov %%esp, %0" : "=a" (Global::currentProc->kernelStackPtr):);
// This cursed bit of code first determines if the processes list is longer than 1 and if it is
// - Determines if it has 2 or more elements
// - If it has two, swap the first and last, update prev and next of each to be null or the other item
// - If it has more than two, add the start to the end then set start to the second element
if (processes->start) {
if (Global::currentProc) {
if (processes->start->next != 0) {
if (processes->end->prev == processes->start) {
xnoe::linkedlistelem<Process*>* tmp = processes->start;
@ -112,26 +120,17 @@ void context_switch(frame_struct* frame) {
}
}
// Get the next process.
if (processes->start)
nextProc = processes->start->elem;
if (nextProc == 0) {
Global::kernel->terminal->printf("[FATAL] No more processes! Halting!\n");
while (1) asm ("hlt");
}
Global::currentProc = nextProc;
Global::currentProc = processes->start->elem;
// Select the next processes page directory
asm volatile ("mov %0, %%cr3" : : "r" (nextProc->PD->phys_addr));
asm volatile ("mov %0, %%cr3" : : "r" (Global::currentProc->PD->phys_addr));
// Restore kernelStackPtr of the new process.
asm volatile ("mov %0, %%esp" : : "m" (Global::kernel->processes.start->elem->kernelStackPtr));
asm volatile ("mov %0, %%esp" : : "m" (Global::currentProc->kernelStackPtr));
// At this point interrupts are disabled till iret so we can safely set
// Global::tss->esp0 to the new Process's kernelStackPtrDefault
Global::tss->esp0 = Global::kernel->processes.start->elem->kernelStackPtrDefault;
Global::tss->esp0 = Global::currentProc->kernelStackPtrDefault;
// Set the current proc to valid
Global::currentProcValid = true;
@ -140,7 +139,9 @@ void context_switch(frame_struct* frame) {
Global::currentProc->firstRun = false;
asm("add $4, %esp");
asm("ret");
}
} else {
asm("add $28, %esp");
asm("ret");
}
}
@ -153,13 +154,16 @@ void syscall(frame_struct* frame) {
// 4: localalloc: LocalAlloc: Allocate under current process (in esi: size; out eax void* ptr)
// 5: localdelete: LocalDelete: Deallocate under current process (in esi: pointer)
// 6: X
// 7: X
// 7: fork :: char* filename esi -> int PID // Spawns a process and returns its PID.
// 8: getPID: returns the current process's PID (out eax: uint32_t)
// 9: getFileHandler :: char* path esi -> void* eax // Returns a file handlers for a specific file
// 10: read :: uint32_t count ebx -> void* filehandler esi -> uint8_t* outputbuffer edi -> int read // Reads from a file handler in to a buffer, returns successful read
// 11: write :: uint32_t count ebx -> void* filehandler esi -> uint8_t* inputbuffer edi -> int written // Reads from a buffer in to a file, returns successful written
// 12: bindToKeyboard :: void -> void // Binds the current process's stdout to the keyboard.
// 13: bindStdout :: int PID esi -> int filehandler // Returns a filehandler for a CircularRWBuffer binding stdout of another process.
// 14: bindStdin :: int PID esi -> int filehandler // Returns a filehandler for a CircularRWBuffer binding stdin of another process.
// File handlers:
// 0: Stdout
// 1: Stdin
@ -190,8 +194,16 @@ void syscall(frame_struct* frame) {
break;
case 6:
break;
case 7:
case 7: {
asm("cli");
char filename[12];
for (int i=0; i<12; i++)
filename[i] = ((char*)(frame->esi))[i];
Process* p = Global::kernel->createProcess(filename);
rval = p->PID;
asm("sti");
break;
}
case 8:
rval = currentProc->PID;
break;
@ -208,8 +220,10 @@ void syscall(frame_struct* frame) {
rval = stdin->read(frame->ebx, edi);
} else {
xnoe::Maybe<ReadWriter*> fh = Global::kernel->FH->get(esi);
if (!fh.is_ok())
if (!fh.is_ok()) {
rval = 0;
break;
}
ReadWriter* rw = fh.get();
rval = rw->read(frame->ebx, edi);
@ -226,8 +240,10 @@ void syscall(frame_struct* frame) {
rval = stdout->write(frame->ebx, edi);
} else {
xnoe::Maybe<ReadWriter*> fh = Global::kernel->FH->get(esi);
if (!fh.is_ok())
if (!fh.is_ok()) {
rval = 0;
break;
}
ReadWriter* rw = fh.get();
rval = rw->write(frame->ebx, edi);
@ -241,6 +257,33 @@ void syscall(frame_struct* frame) {
currentProc->stdin = new CircularRWBuffer(currentProc->PID, 0);
Global::kernel->KBListeners.append(currentProc);
break;
case 13: {
xnoe::Maybe<Process*> pm = Global::kernel->pid_map->get(esi);
if (!pm.is_ok())
break;
Process* p = pm.get();
if (!p->stdout) {
ReadWriter* buffer = new CircularRWBuffer(currentProc->PID, esi);
p->stdout = buffer;
rval = Global::kernel->mapFH(buffer);
}
break;
}
case 14: {
xnoe::Maybe<Process*> pm = Global::kernel->pid_map->get(esi);
if (!pm.is_ok())
break;
Process* p = pm.get();
if (!p->stdin) {
ReadWriter* buffer = new CircularRWBuffer(esi, currentProc->PID);
p->stdin = buffer;
rval = Global::kernel->mapFH(buffer);
}
break;
}
default:
break;
@ -259,12 +302,28 @@ void init_idt() {
for (int i=0; i<256; i++)
gates[i] = &ignore_interrupt;
gates[0x20] = &context_switch;
gates[0xd] = &gpf;
gates[0xe] = &page_fault;
gates[0x7f] = &syscall;
gates[32] = &context_switch;
gates[0] = &handle_fault;
gates[5] = &handle_fault;
gates[6] = &handle_fault;
gates[7] = &handle_fault;
gates[9] = &handle_fault;
gates[10] = &handle_fault;
gates[11] = &handle_fault;
gates[12] = &handle_fault;
gates[13] = &handle_fault;
gates[14] = &handle_fault;
gates[16] = &handle_fault;
gates[17] = &handle_fault;
gates[19] = &handle_fault;
gates[20] = &handle_fault;
gates[21] = &handle_fault;
gates[29] = &handle_fault;
gates[30] = &handle_fault;
gates[31] = &handle_fault;
gates[127] = &syscall;
idt[0x7f].privilege = 3;
idt[127].privilege = 3;
outb(0x20, 0x11);
outb(0xA0, 0x11);

View File

@ -11,11 +11,14 @@ Kernel::Kernel(PageDirectory* page_directory, PageMap* phys, PageMap* virt, uint
this->stack = stack;
this->lastFH = 8;
//this->processes.append(this);
}
void Kernel::init_kernel() {
this->pid_map = new xnoe::hashtable<uint32_t, Process*>();
this->FH = new xnoe::hashtable<void*, ReadWriter*>();
this->globalISRStack = (new uint8_t[0x8000]) + 0x8000;
}
@ -41,6 +44,11 @@ void Kernel::destroyProcess(Process* p) {
delete p;
}
int Kernel::mapFH(ReadWriter* fh) {
this->FH->set(this->lastFH++, fh);
return this->lastFH - 1;
}
//void Kernel::loadPrimaryStack() {
// asm volatile("mov %0, %%esp"::"m"(this->stack - 64));
//}

View File

@ -7,6 +7,8 @@
#include "terminal.h"
class Kernel : public Process {
private:
int lastFH;
public:
uint32_t currentPID;
uint32_t stack;
@ -26,6 +28,8 @@ public:
Process* createProcess(char* filename);
Process* createProcess(char* filename, ReadWriter* stdout);
void destroyProcess(Process* p);
int mapFH(ReadWriter* fh);
//void loadPrimaryStack();
};

View File

@ -36,12 +36,15 @@ bool shift_on = false;
void keyboard_interrupt(frame_struct* frame) {
uint8_t decoded = 0;
uint8_t current_scancode = inb(0x60);
outb(0x20, 0x21);
if ((current_scancode&0x7f) == 0x2a)
if ((current_scancode&0x7f) == 0x2a) {
shift_on = !(current_scancode&0x80);
return;
}
if (current_scancode == 0x3a)
if (current_scancode == 0x3a) {
caps_on ^= 1;
return;
}
if (shift_on)
decoded = key_to_char_shift[current_scancode&0x7f];

View File

@ -44,10 +44,7 @@ int main() {
term->printf("KERNEL OK!\n");
Global::currentProc = &kernel;
Process* p1 = kernel.createProcess("WORLD BIN", term);
//kernel.createProcess("HELLO BIN");
init_keyboard();

View File

@ -244,6 +244,9 @@ void* Allocator::allocate(uint32_t size) {
this->PD->map(phys_addr, virt_addr + 4096 * i, this->privilege);
}
for (int i=0; i<4096; i++)
((uint8_t*)virt_addr)[i] = 0;
return virt_addr;
}

View File

@ -59,44 +59,26 @@ Process::Process(uint32_t PID, PageDirectory* inherit, uint32_t inheritBase, cha
// We also need to initialise ESP and the stack
uint32_t* stack32 = ((uint32_t)this->kernelStackPtr);
stack32--;
*stack32 = 0x23; // SS
stack32--;
*stack32 = ((uint32_t)this->stack + 0x8000); // ESP
stack32--;
*stack32 = 0x200; // EFLAGS
stack32--;
*stack32 = 27; // CS 0x08
stack32--;
*stack32 = (uint32_t)program_data; // EIP
stack32--;
*stack32 = ((uint32_t)this->stack + 0x8000); // EBP
*(--stack32) = 0x23; // SS
*(--stack32) = ((uint32_t)this->stack + 0x8000); // ESP
*(--stack32) = 0x200; // EFLAGS
*(--stack32) = 27; // CS
*(--stack32) = (uint32_t)program_data; // EIP
*(--stack32) = ((uint32_t)this->stack + 0x8000); // EBP
uint32_t rEBP = stack32;
//stack32--;
*(--stack32) = 0; // EAX
*(--stack32) = 0; // ECX
*(--stack32) = 0; // EDX
*(--stack32) = 0; // EBX
*(--stack32) = 0; // ESP
*(--stack32) = rEBP; // EBP
*(--stack32) = 0; // ESI
*(--stack32) = 0; // EDI
stack32--;
*stack32 = 0; // EAX
stack32--;
*stack32 = 0; // ECX
stack32--;
*stack32 = 0; // EDX
stack32--;
*stack32 = 0; // EBX
stack32--;
*stack32 = 0; // ESP
stack32--;
*stack32 = rEBP; // EBP
stack32--;
*stack32 = 0; // ESI
stack32--;
*stack32 = 0; // EDI
stack32--;
stack32--;
*stack32 = &catchall_return; // cachall_return
*(--stack32) = &catchall_return; // cachall_return
stack32--;
this->kernelStackPtr = stack32;

View File

@ -46,6 +46,23 @@ Terminal::Terminal(uint32_t width, uint32_t height, uint32_t pages)
this->active = false;
}
int strToInt(char* str) {
int r=0;
while (*str >= 0x30 && *str <= 0x39) {
r *= 10;
r += *(str++) - 0x30;
}
return r;
}
int clamp(int a, int b, int c) {
if (a < b)
return b;
if (a > c)
return c;
return a;
}
void Terminal::printf(const char* string, ...) {
va_list ptr;
va_start(ptr, string);
@ -59,10 +76,50 @@ void Terminal::printf(const char* string, ...) {
this->cur_y++;
}
if (current == 0x1b && string[index] == '[') {
index++;
char* parameterStart = (string+index);
while (string[index] >= 0x30 && string[index] <= 0x3F)
index++;
char* parameterEnd = (string+index);
char* intermediateStart = (string+index);
while (string[index] >= 0x20 && string[index] <= 0x2F)
index++;
char final = *(string+(index++));
switch (final) {
case 'A':
this->cur_y -= clamp(strToInt(parameterStart), 0, this->cur_y);
break;
case 'B':
this->cur_y += clamp(strToInt(parameterStart), 0, this->height - this->cur_y);
break;
case 'C':
this->cur_x += clamp(strToInt(parameterStart), 0, this->width - this->cur_x);
break;
case 'D':
this->cur_x -= clamp(strToInt(parameterStart), 0, this->cur_x);
break;
case 'H': {
char* s=parameterStart;
while (*s != ';' && s < parameterEnd)
s++;
s++;
this->cur_y = clamp(strToInt(parameterStart), 1, this->height) - 1;
this->cur_x = clamp(strToInt(s), 1, this->width) - 1;
break;
}
}
continue;
}
if (current == '\b') {
if (this->cur_x > 0) {
this->cur_x--;
} else {
} else if (this->cur_y > 0) {
this->cur_y--;
this->cur_x = this->width-1;
}
@ -125,14 +182,94 @@ void Terminal::printf(const char* string, ...) {
va_end(ptr);
}
int Terminal::write(uint32_t count, uint8_t* buffer) {
char* buf = new char[count+1];
int Terminal::write(uint32_t count, uint8_t* string) {
/*char* buf = new char[count+1];
for (int i=0;i<count;i++) {
buf[i] = buffer[i];
}
buf[count] = 0;
buf[count] = 0x00;
printf(buf);
delete buf;
delete buf;*/
int index = 0;
char current;
while (index < count) {
current=string[index++];
if (current == '\n') {
this->cur_x = 0;
this->cur_y++;
}
if (current == 0x1b && string[index] == '[') {
index++;
char* parameterStart = (string+index);
while (string[index] >= 0x30 && string[index] <= 0x3F)
index++;
char* parameterEnd = (string+index);
char* intermediateStart = (string+index);
while (string[index] >= 0x20 && string[index] <= 0x2F)
index++;
char final = *(string+(index++));
switch (final) {
case 'A':
this->cur_y -= clamp(strToInt(parameterStart), 0, this->cur_y);
break;
case 'B':
this->cur_y += clamp(strToInt(parameterStart), 0, this->height - this->cur_y);
break;
case 'C':
this->cur_x += clamp(strToInt(parameterStart), 0, this->width - this->cur_x);
break;
case 'D':
this->cur_x -= clamp(strToInt(parameterStart), 0, this->cur_x);
break;
case 'H': {
char* s=parameterStart;
while (*s != ';' && s < parameterEnd)
s++;
s++;
this->cur_y = clamp(strToInt(parameterStart), 1, this->height) - 1;
this->cur_x = clamp(strToInt(s), 1, this->width) - 1;
break;
}
}
continue;
}
if (current == '\b') {
if (this->cur_x > 0) {
this->cur_x--;
} else if (this->cur_y > 0) {
this->cur_y--;
this->cur_x = this->width-1;
}
int mem_pos = this->cur_y * this->width + this->cur_x;
this->putchar(mem_pos, ' ');
continue;
}
if (this->cur_x == this->width) {
this->cur_x = 0;
this->cur_y++;
}
if (this->cur_y == this->height)
this->scroll_up();
if (current != '\n') {
int mem_pos = this->cur_y * this->width + this->cur_x++;
this->putchar(mem_pos, current);
}
}
this->set_curpos(this->cur_x, this->cur_y);
}
int Terminal::read(uint32_t count, uint8_t* buffer) {}

View File

@ -1,14 +1,137 @@
#include "../common/common.h"
#include <stdbool.h>
void scrollBuffer(char* buf) {
for (int y=0; y<21; y++)
for (int x=0; x<38; x++)
if (y != 20)
buf[y*38+x] = buf[(y+1)*38+x];
else
buf[y*38+x] = ' ';
}
void writeToBuf(char c, char* buf, int* cx, int* cy) {
switch (c) {
case '\n':
*cx = 0;
(*cy)++;
break;
case '\b':
if (*cx > 0)
(*cx)--;
else if (*cy > 0) {
*cx = 37;
(*cy)--;
}
buf[(*cy)*38+(*cx)] = ' ';
break;
default:
buf[(*cy)*38+(*cx)++] = c;
}
if (*cx == 38) {
*cx = 0;
(*cy)++;
}
if (*cy == 21) {
(*cy)--;
scrollBuffer(buf);
}
}
void writeStrToBuf(char* c, char* buf, int* cx, int* cy) {
char* s = c;
while(*s)
writeToBuf(*(s++), buf, cx, cy);
}
void displayBuf(char* buf, int dx, int dy) {
char pset[9] = "\x1b[00;00H";
for (int i=0; i<dy;i++) {
pset[3]++;
if (pset[3] == 0x3a) {
pset[3] = 0x30;
pset[2]++;
}
}
for (int i=0; i<dx;i++) {
pset[6]++;
if (pset[6] == 0x3a) {
pset[6] = 0x30;
pset[5]++;
}
}
for (int i=0; i<21; i++) {
print(pset);
write(38, 0, buf+(38*i));
pset[3]++;
if (pset[3] == 0x3a) {
pset[3] = 0x30;
pset[2]++;
}
}
}
int main() {
bindToKeyboard();
print("Hello from Ring 3!\n");
char space = ' ';
char plus = '+';
print("\x1b[1;1H");
for (int i=0; i < 1000; i++)
write(1, 0, &space);
print("\x1b[1;1H");
char* mid = "+ ++ +";
char* bottom = "+ +";
for (int i=0; i<80;i++)
write(1, 0, &plus);
for (int i=0; i<21;i++)
write(80, 0, mid);
for (int i=0; i<80;i++)
write(1, 0, &plus);
write(80, 0, bottom);
for (int i=0; i<80;i++)
write(1, 0, &plus);
char* hello_bin = "HELLO BIN";
uint32_t p1 = fork(hello_bin);
uint32_t p2 = fork(hello_bin);
uint32_t p1out = bindStdout(p1);
uint32_t p1in = bindStdin(p1);
uint32_t p2out = bindStdout(p2);
uint32_t p2in = bindStdin(p2);
char* buf1 = localalloc(21 * 38);
char* buf2 = localalloc(21 * 38);
int b1cx = 0;
int b1cy = 0;
int b2cx = 0;
int b2cy = 0;
char* selectedBuf = buf1;
uint32_t selectedOut = p1out;
uint32_t selectedIn = p1in;
while (1) {
char c;
if (read(1, 1, &c))
write(1, 0, &c);
if (read(1, selectedOut, &c))
writeToBuf(c, selectedBuf, &b1cx, &b1cy);
if (read(1, p2out, &c))
writeToBuf(c, buf2, &b2cx, &b2cy);
if (read(1, 1, &c)) {
write(1, selectedIn, &c);
write(1, p2in, &c);
}
displayBuf(selectedBuf, 2, 2);
displayBuf(buf2, 42, 2);
}
}