mirror of
https://github.com/wichtounet/thor-os.git
synced 2025-09-17 16:51:33 -04:00
Merge branch 'develop' of github.com:wichtounet/thor-os into develop
Conflicts: cpp.mk
This commit is contained in:
commit
3fb29dab1c
10
Makefile
10
Makefile
@ -20,6 +20,12 @@ programs/hello/a.out: force_look
|
|||||||
programs/long/a.out: force_look
|
programs/long/a.out: force_look
|
||||||
cd programs/long; ${MAKE} a.out
|
cd programs/long; ${MAKE} a.out
|
||||||
|
|
||||||
|
programs/longone/a.out: force_look
|
||||||
|
cd programs/longone; ${MAKE} a.out
|
||||||
|
|
||||||
|
programs/longtwo/a.out: force_look
|
||||||
|
cd programs/longtwo; ${MAKE} a.out
|
||||||
|
|
||||||
programs/loop/a.out: force_look
|
programs/loop/a.out: force_look
|
||||||
cd programs/loop; ${MAKE} a.out
|
cd programs/loop; ${MAKE} a.out
|
||||||
|
|
||||||
@ -27,7 +33,7 @@ hdd.img:
|
|||||||
dd if=/dev/zero of=hdd.img bs=516096c count=1000
|
dd if=/dev/zero of=hdd.img bs=516096c count=1000
|
||||||
(echo n; echo p; echo 1; echo ""; echo ""; echo t; echo c; echo a; echo 1; echo w;) | sudo fdisk -u -C1000 -S63 -H16 hdd.img
|
(echo n; echo p; echo 1; echo ""; echo ""; echo t; echo c; echo a; echo 1; echo w;) | sudo fdisk -u -C1000 -S63 -H16 hdd.img
|
||||||
|
|
||||||
thor.flp: hdd.img bootloader/stage1.bin bootloader/stage2.bin kernel/kernel.bin programs/one/a.out programs/hello/a.out programs/long/a.out programs/loop/a.out
|
thor.flp: hdd.img bootloader/stage1.bin bootloader/stage2.bin kernel/kernel.bin programs/one/a.out programs/hello/a.out programs/long/a.out programs/loop/a.out programs/longone/a.out programs/longtwo/a.out
|
||||||
mkdir -p mnt/fake/
|
mkdir -p mnt/fake/
|
||||||
dd if=bootloader/stage1.bin of=hdd.img conv=notrunc
|
dd if=bootloader/stage1.bin of=hdd.img conv=notrunc
|
||||||
dd if=bootloader/stage2.bin of=hdd.img seek=1 conv=notrunc
|
dd if=bootloader/stage2.bin of=hdd.img seek=1 conv=notrunc
|
||||||
@ -38,6 +44,8 @@ thor.flp: hdd.img bootloader/stage1.bin bootloader/stage2.bin kernel/kernel.bin
|
|||||||
sudo /bin/cp programs/one/a.out mnt/fake/one
|
sudo /bin/cp programs/one/a.out mnt/fake/one
|
||||||
sudo /bin/cp programs/hello/a.out mnt/fake/hello
|
sudo /bin/cp programs/hello/a.out mnt/fake/hello
|
||||||
sudo /bin/cp programs/long/a.out mnt/fake/long
|
sudo /bin/cp programs/long/a.out mnt/fake/long
|
||||||
|
sudo /bin/cp programs/longone/a.out mnt/fake/longone
|
||||||
|
sudo /bin/cp programs/longtwo/a.out mnt/fake/longtwo
|
||||||
sudo /bin/cp programs/loop/a.out mnt/fake/loop
|
sudo /bin/cp programs/loop/a.out mnt/fake/loop
|
||||||
sleep 0.1
|
sleep 0.1
|
||||||
sudo /bin/umount mnt/fake/
|
sudo /bin/umount mnt/fake/
|
||||||
|
2
cpp.mk
2
cpp.mk
@ -12,6 +12,6 @@ CPP_FLAGS_32=$(COMMON_CPP_FLAGS) $(CPP_FLAGS_LOW) -mpreferred-stack-boundary=4
|
|||||||
CPP_FLAGS_64=$(COMMON_CPP_FLAGS) -mno-sse3 -mno-sse4 -mno-sse4.1 -mno-sse4.2
|
CPP_FLAGS_64=$(COMMON_CPP_FLAGS) -mno-sse3 -mno-sse4 -mno-sse4.1 -mno-sse4.2
|
||||||
|
|
||||||
COMMON_LINK_FLAGS=-lgcc
|
COMMON_LINK_FLAGS=-lgcc
|
||||||
PROGRAM_LINK_FLAGS=-z max-page-size=0x1000 $(COMMON_LINK_FLAGS) -T ../linker.ld
|
PROGRAM_LINK_FLAGS=$(COMMON_LINK_FLAGS) -z max-page-size=0x1000 -T ../linker.ld
|
||||||
|
|
||||||
PROGRAM_FLAGS=-I../../userlib/include/
|
PROGRAM_FLAGS=-I../../userlib/include/
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "stl/vector.hpp"
|
#include "stl/vector.hpp"
|
||||||
|
|
||||||
#include "paging.hpp"
|
#include "paging.hpp"
|
||||||
|
#include "interrupts.hpp"
|
||||||
|
|
||||||
namespace scheduler {
|
namespace scheduler {
|
||||||
|
|
||||||
@ -25,19 +26,16 @@ struct process_t {
|
|||||||
|
|
||||||
bool system;
|
bool system;
|
||||||
|
|
||||||
size_t code_selector;
|
|
||||||
size_t data_selector;
|
|
||||||
|
|
||||||
size_t physical_cr3;
|
size_t physical_cr3;
|
||||||
size_t paging_size;
|
size_t paging_size;
|
||||||
|
|
||||||
size_t physical_user_stack;
|
size_t physical_user_stack;
|
||||||
size_t physical_kernel_stack;
|
size_t physical_kernel_stack;
|
||||||
|
|
||||||
size_t rip;
|
|
||||||
size_t user_rsp;
|
|
||||||
size_t kernel_rsp;
|
size_t kernel_rsp;
|
||||||
|
|
||||||
|
interrupt::syscall_regs regs;
|
||||||
|
|
||||||
std::vector<segment_t> segments;
|
std::vector<segment_t> segments;
|
||||||
std::vector<size_t> physical_paging;
|
std::vector<size_t> physical_paging;
|
||||||
};
|
};
|
||||||
|
@ -16,7 +16,7 @@ namespace scheduler {
|
|||||||
void init();
|
void init();
|
||||||
void start();
|
void start();
|
||||||
|
|
||||||
void kill_current_process();
|
void kill_current_process(const interrupt::syscall_regs& regs);
|
||||||
|
|
||||||
void reschedule(const interrupt::syscall_regs& regs);
|
void reschedule(const interrupt::syscall_regs& regs);
|
||||||
|
|
||||||
|
@ -200,15 +200,25 @@ void init_head(){
|
|||||||
malloc_head->prev_ref() = malloc_head;
|
malloc_head->prev_ref() = malloc_head;
|
||||||
}
|
}
|
||||||
|
|
||||||
void expand_heap(malloc_header_chunk* current){
|
void expand_heap(malloc_header_chunk* current, size_t bytes = 0){
|
||||||
|
auto blocks = MIN_BLOCKS;
|
||||||
|
|
||||||
|
if(bytes){
|
||||||
|
auto necessary_blocks = ((bytes + META_SIZE) / BLOCK_SIZE) + 1;
|
||||||
|
|
||||||
|
if(necessary_blocks > blocks){
|
||||||
|
blocks = necessary_blocks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Allocate a new block of memory
|
//Allocate a new block of memory
|
||||||
uint64_t* block = allocate_block(MIN_BLOCKS);
|
uint64_t* block = allocate_block(blocks);
|
||||||
|
|
||||||
//Transform it into a malloc chunk
|
//Transform it into a malloc chunk
|
||||||
auto header = reinterpret_cast<malloc_header_chunk*>(block);
|
auto header = reinterpret_cast<malloc_header_chunk*>(block);
|
||||||
|
|
||||||
//Update the sizes
|
//Update the sizes
|
||||||
header->size() = MIN_BLOCKS * BLOCK_SIZE - META_SIZE;
|
header->size() = blocks * BLOCK_SIZE - META_SIZE;
|
||||||
header->footer()->size() = header->size();
|
header->footer()->size() = header->size();
|
||||||
|
|
||||||
//Insert the new block into the free list
|
//Insert the new block into the free list
|
||||||
@ -301,7 +311,7 @@ void* malloc::k_malloc(uint64_t bytes){
|
|||||||
if(current == malloc_head){
|
if(current == malloc_head){
|
||||||
//There are no blocks big enough to hold this request
|
//There are no blocks big enough to hold this request
|
||||||
//So expand the heap
|
//So expand the heap
|
||||||
expand_heap(current);
|
expand_heap(current, bytes);
|
||||||
} else if(current->size() >= bytes){
|
} else if(current->size() >= bytes){
|
||||||
//This block is big enough
|
//This block is big enough
|
||||||
|
|
||||||
|
@ -45,19 +45,19 @@ void create_idle_task(){
|
|||||||
idle_process.physical_user_stack = 0;
|
idle_process.physical_user_stack = 0;
|
||||||
idle_process.physical_kernel_stack = 0;
|
idle_process.physical_kernel_stack = 0;
|
||||||
|
|
||||||
idle_process.rip = reinterpret_cast<size_t>(&idle_task);
|
idle_process.regs.rflags = 0x200;
|
||||||
idle_process.user_rsp = reinterpret_cast<size_t>(&idle_stack[63]);
|
idle_process.regs.rip = reinterpret_cast<size_t>(&idle_task);
|
||||||
|
idle_process.regs.rsp = reinterpret_cast<size_t>(&idle_stack[63]);
|
||||||
idle_process.kernel_rsp = reinterpret_cast<size_t>(&idle_kernel_stack[4095]);
|
idle_process.kernel_rsp = reinterpret_cast<size_t>(&idle_kernel_stack[4095]);
|
||||||
|
|
||||||
idle_process.code_selector = gdt::LONG_SELECTOR;
|
idle_process.regs.cs = gdt::LONG_SELECTOR;
|
||||||
idle_process.data_selector = gdt::DATA_SELECTOR;
|
idle_process.regs.ds = gdt::DATA_SELECTOR;
|
||||||
|
|
||||||
processes.push_back(std::move(idle_process));
|
processes.push_back(std::move(idle_process));
|
||||||
rounds.push_back(0);
|
rounds.push_back(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void switch_to_process(size_t index) __attribute__((noreturn));
|
void switch_to_process(const interrupt::syscall_regs& regs, size_t index){
|
||||||
void switch_to_process(size_t index){
|
|
||||||
current_index = index;
|
current_index = index;
|
||||||
|
|
||||||
k_printf("Switched to %u\n", index);
|
k_printf("Switched to %u\n", index);
|
||||||
@ -67,7 +67,29 @@ void switch_to_process(size_t index){
|
|||||||
gdt::tss.rsp0_low = process.kernel_rsp & 0xFFFFFFFF;
|
gdt::tss.rsp0_low = process.kernel_rsp & 0xFFFFFFFF;
|
||||||
gdt::tss.rsp0_high = process.kernel_rsp >> 32;
|
gdt::tss.rsp0_high = process.kernel_rsp >> 32;
|
||||||
|
|
||||||
asm volatile("mov rax, %0; mov ds, ax; mov es, ax; mov fs, ax; mov gs, ax;"
|
auto stack_pointer = reinterpret_cast<uint64_t*>(regs.placeholder);
|
||||||
|
|
||||||
|
*(stack_pointer + 4) = process.regs.ds;
|
||||||
|
*(stack_pointer + 3) = process.regs.rsp;
|
||||||
|
*(stack_pointer + 2) = process.regs.rflags;
|
||||||
|
*(stack_pointer + 1) = process.regs.cs;
|
||||||
|
*(stack_pointer + 0) = process.regs.rip;
|
||||||
|
*(stack_pointer - 3) = process.regs.r12;
|
||||||
|
*(stack_pointer - 4) = process.regs.r11;
|
||||||
|
*(stack_pointer - 5) = process.regs.r10;
|
||||||
|
*(stack_pointer - 6) = process.regs.r9;
|
||||||
|
*(stack_pointer - 7) = process.regs.r8;
|
||||||
|
*(stack_pointer - 8) = process.regs.rdi;
|
||||||
|
*(stack_pointer - 9) = process.regs.rsi;
|
||||||
|
*(stack_pointer - 10) = process.regs.rdx;
|
||||||
|
*(stack_pointer - 11) = process.regs.rcx;
|
||||||
|
*(stack_pointer - 12) = process.regs.rbx;
|
||||||
|
*(stack_pointer - 13) = process.regs.rax;
|
||||||
|
*(stack_pointer - 14) = process.regs.ds;
|
||||||
|
|
||||||
|
asm volatile("mov cr3, %0" : : "r" (process.physical_cr3) : "memory");
|
||||||
|
|
||||||
|
/*asm volatile("mov rax, %0; mov ds, ax; mov es, ax; mov fs, ax; mov gs, ax;"
|
||||||
: //No outputs
|
: //No outputs
|
||||||
: "r" (process.data_selector)
|
: "r" (process.data_selector)
|
||||||
: "rax");
|
: "rax");
|
||||||
@ -77,9 +99,7 @@ void switch_to_process(size_t index){
|
|||||||
asm volatile("push %0; push %1; pushfq; pop rax; or rax, 0x200; push rax; push %2; push %3; iretq"
|
asm volatile("push %0; push %1; pushfq; pop rax; or rax, 0x200; push rax; push %2; push %3; iretq"
|
||||||
: //No outputs
|
: //No outputs
|
||||||
: "r" (process.data_selector), "r" (process.user_rsp), "r" (process.code_selector), "r" (process.rip)
|
: "r" (process.data_selector), "r" (process.user_rsp), "r" (process.code_selector), "r" (process.rip)
|
||||||
: "rax", "memory");
|
: "rax", "memory");*/
|
||||||
|
|
||||||
__builtin_unreachable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t select_next_process(){
|
size_t select_next_process(){
|
||||||
@ -89,8 +109,7 @@ size_t select_next_process(){
|
|||||||
void save_context(const interrupt::syscall_regs& regs){
|
void save_context(const interrupt::syscall_regs& regs){
|
||||||
auto& process = processes[current_index];
|
auto& process = processes[current_index];
|
||||||
|
|
||||||
process.user_rsp = regs.rsp;
|
process.regs = regs;
|
||||||
process.rip = regs.rip;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} //end of anonymous namespace
|
} //end of anonymous namespace
|
||||||
@ -105,10 +124,16 @@ void scheduler::start(){
|
|||||||
|
|
||||||
started = true;
|
started = true;
|
||||||
|
|
||||||
switch_to_process(processes.size() - 1);
|
current_index = 0;
|
||||||
|
rounds[current_index] = TURNOVER;
|
||||||
|
|
||||||
|
//Wait for the next interrupt
|
||||||
|
while(true){
|
||||||
|
asm volatile ("nop; nop; nop; nop");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void scheduler::kill_current_process(){
|
void scheduler::kill_current_process(const interrupt::syscall_regs& regs){
|
||||||
k_printf("Kill %u\n", current_index);
|
k_printf("Kill %u\n", current_index);
|
||||||
|
|
||||||
processes.erase(current_index);
|
processes.erase(current_index);
|
||||||
@ -121,7 +146,7 @@ void scheduler::kill_current_process(){
|
|||||||
|
|
||||||
//Select the next process and switch to it
|
//Select the next process and switch to it
|
||||||
auto index = select_next_process();
|
auto index = select_next_process();
|
||||||
switch_to_process(index);
|
switch_to_process(regs, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void scheduler::reschedule(const interrupt::syscall_regs& regs){
|
void scheduler::reschedule(const interrupt::syscall_regs& regs){
|
||||||
@ -141,7 +166,7 @@ void scheduler::reschedule(const interrupt::syscall_regs& regs){
|
|||||||
|
|
||||||
save_context(regs);
|
save_context(regs);
|
||||||
|
|
||||||
switch_to_process(index);
|
switch_to_process(regs, index);
|
||||||
} else {
|
} else {
|
||||||
++rounds[current_index];
|
++rounds[current_index];
|
||||||
}
|
}
|
||||||
|
@ -809,7 +809,7 @@ bool create_paging(char* buffer, scheduler::process_t& process){
|
|||||||
|
|
||||||
//2.1 Allocate user stack
|
//2.1 Allocate user stack
|
||||||
allocate_user_memory(process, scheduler::user_stack_start, scheduler::user_stack_size, process.physical_user_stack);
|
allocate_user_memory(process, scheduler::user_stack_start, scheduler::user_stack_size, process.physical_user_stack);
|
||||||
process.user_rsp = scheduler::user_rsp;
|
process.regs.rsp = scheduler::user_rsp;
|
||||||
|
|
||||||
//2.2 Allocate all user segments
|
//2.2 Allocate all user segments
|
||||||
|
|
||||||
@ -865,22 +865,8 @@ bool create_paging(char* buffer, scheduler::process_t& process){
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void exec_command(const std::vector<std::string>& params){
|
void queue_process(const std::string& file){
|
||||||
if(params.size() < 2){
|
auto content = read_elf_file(file, "exec");
|
||||||
k_print_line("exec: Need the name of the executable to read");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!disks::mounted_partition() || !disks::mounted_disk()){
|
|
||||||
k_print_line("Nothing is mounted");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduler::init();
|
|
||||||
|
|
||||||
auto content = read_elf_file(params[1], "exec");
|
|
||||||
|
|
||||||
if(!content){
|
if(!content){
|
||||||
return;
|
return;
|
||||||
@ -896,12 +882,34 @@ void exec_command(const std::vector<std::string>& params){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
process.rip = header->e_entry;
|
process.regs.rip = header->e_entry;
|
||||||
|
|
||||||
process.code_selector = gdt::USER_CODE_SELECTOR + 3;
|
process.regs.cs = gdt::USER_CODE_SELECTOR + 3;
|
||||||
process.data_selector = gdt::USER_DATA_SELECTOR + 3;
|
process.regs.ds = gdt::USER_DATA_SELECTOR + 3;
|
||||||
|
|
||||||
|
process.regs.rflags = 0x200;
|
||||||
|
|
||||||
scheduler::queue_process(std::move(process));
|
scheduler::queue_process(std::move(process));
|
||||||
|
}
|
||||||
|
|
||||||
|
void exec_command(const std::vector<std::string>& params){
|
||||||
|
if(params.size() < 2){
|
||||||
|
k_print_line("exec: Need the name of the executable to read");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!disks::mounted_partition() || !disks::mounted_disk()){
|
||||||
|
k_print_line("Nothing is mounted");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler::init();
|
||||||
|
|
||||||
|
for(size_t i = 1; i < params.size(); ++i){
|
||||||
|
queue_process(params[i]);
|
||||||
|
}
|
||||||
|
|
||||||
scheduler::start();
|
scheduler::start();
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ void system_call_entry(const interrupt::syscall_regs& regs){
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x666:
|
case 0x666:
|
||||||
scheduler::kill_current_process();
|
scheduler::kill_current_process(regs);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
15
programs/longone/Makefile
Normal file
15
programs/longone/Makefile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
.PHONY: default clean
|
||||||
|
|
||||||
|
default: a.out
|
||||||
|
|
||||||
|
include ../../cpp.mk
|
||||||
|
|
||||||
|
%.cpp.o: src/%.cpp
|
||||||
|
$(CC) $(PROGRAM_FLAGS) $(CPP_FLAGS_64) $(WARNING_FLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
a.out: main.cpp.o
|
||||||
|
$(CC) $(PROGRAM_LINK_FLAGS) $(CPP_FLAGS_64) -o a.out main.cpp.o
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm *.cpp.o
|
||||||
|
rm -rf a.out
|
30
programs/longone/src/main.cpp
Normal file
30
programs/longone/src/main.cpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
//=======================================================================
|
||||||
|
// Copyright Baptiste Wicht 2013-2014.
|
||||||
|
// Distributed under the Boost Software License, Version 1.0.
|
||||||
|
// (See accompanying file LICENSE_1_0.txt or copy at
|
||||||
|
// http://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
//=======================================================================
|
||||||
|
|
||||||
|
#include <print.hpp>
|
||||||
|
#include <system.hpp>
|
||||||
|
|
||||||
|
volatile uint64_t current = 45;
|
||||||
|
|
||||||
|
uint64_t fibonacci_slow(uint64_t s){
|
||||||
|
if(s == 1 || s == 2){
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fibonacci_slow(s - 1) + fibonacci_slow(s - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto message = "I'm one";
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
while(true){
|
||||||
|
fibonacci_slow(current);
|
||||||
|
print_line(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
15
programs/longtwo/Makefile
Normal file
15
programs/longtwo/Makefile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
.PHONY: default clean
|
||||||
|
|
||||||
|
default: a.out
|
||||||
|
|
||||||
|
include ../../cpp.mk
|
||||||
|
|
||||||
|
%.cpp.o: src/%.cpp
|
||||||
|
$(CC) $(PROGRAM_FLAGS) $(CPP_FLAGS_64) $(WARNING_FLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
a.out: main.cpp.o
|
||||||
|
$(CC) $(PROGRAM_LINK_FLAGS) $(CPP_FLAGS_64) -o a.out main.cpp.o
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm *.cpp.o
|
||||||
|
rm -rf a.out
|
30
programs/longtwo/src/main.cpp
Normal file
30
programs/longtwo/src/main.cpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
//=======================================================================
|
||||||
|
// Copyright Baptiste Wicht 2013-2014.
|
||||||
|
// Distributed under the Boost Software License, Version 1.0.
|
||||||
|
// (See accompanying file LICENSE_1_0.txt or copy at
|
||||||
|
// http://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
//=======================================================================
|
||||||
|
|
||||||
|
#include <print.hpp>
|
||||||
|
#include <system.hpp>
|
||||||
|
|
||||||
|
volatile uint64_t current = 45;
|
||||||
|
|
||||||
|
uint64_t fibonacci_slow(uint64_t s){
|
||||||
|
if(s == 1 || s == 2){
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fibonacci_slow(s - 1) + fibonacci_slow(s - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto message = "I'm two";
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
while(true){
|
||||||
|
fibonacci_slow(current);
|
||||||
|
print_line(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user