Hello I'm trying to make a simple os, and I'm currently trying to do double buffering.
I have two arrays in the size of the screen and a function that copy the array that currently is being used to the memory of the screen and then swap the being used array to the second one.
And there is a function that clear the screen and set it to a specific color.
screen.c
static u8 *BUFFER = (u8*) 0xA0000;
u8 buffers[2][SCREEN_SIZE];
u8 current_buffer = 0;
#define CURRENT (buffers[current_buffer])
#define SWAP() (current_buffer = 1 - current_buffer)
void screen_swap(){
memcpy(BUFFER, CURRENT, SCREEN_SIZE);
SWAP();
}
void clear_screen(u8 color){ // set all the memory of the screen to one color
memset(&CURRENT, color, SCREEN_SIZE);
}
memory.c
void memset(void* src, u8 val, u32 len){
u8* ptr = (u8*)src;
while(len--){
*ptr++ = val;
}
}
void* memcpy(void* dst, void* src, u32 len){
u8 *d = (u8*)dst;
const u8 *s = (const u8*)src;
while (len-- > 0){
*d++ = *s++;
}
return dst;
}
When I try to run these functions the system keeps rebooting. For example:
clear_screen(COLOR(0,0,255));
screen_swap();
A link to my github repo for more context
The TL;DR is that you are blasting your program stack.
I added some debug
printf
calls using a macro:dbgprt
main
)&buffers[0][0]
is 0x5f60SCREEN_SIZE
is 0xfa00buffers
is 0x5f60-0x1f960memset
onbuffers
, we write well beyond the address of the stack.The stack address in question (i.e. 0x7be8) is the stack address used during the boot process. In
main.asm
, this is set with:At
jmpToKernel
, we need to set theesp
register to a value that doesn't conflict with anything (e.g.):Here is the [redacted] output of
make run
:Here is a
git diff
of what I changed:Edit: Removed this for space. See update below.
UPDATE:
Of course. I just grabbed that from my other "build your own OS" answer: How can I fix my VBE implementation for my OS?
That was a bit of a hack [and what the original author was doing].
At first, I was going to use the
make
process to define the address forboot.asm
. Then, I realized a better way is to have the_start
inkernel_entry.asm
set it.kernel_entry.asm
is the simplest way to go.While in the process of doing that, I was still having some trouble, so I changed some unrelated things to improve debugging:
printf
-like function to allow (e.g.%d
,%ld
, and%lld
) to supportint/long/long long
numbers (so I could print the timer value).printf
to usestdarg.h
main
so that it only changed the background color on initial key press (e.g. rising edge).Here is the "no crash" version of the code:
UPDATE #2:
Yes, you are quite correct. You don't need two
buffers
. But, it's your code, so why did you do this? ;-)Just one is needed because filling
buffers
and then "blitting" to the frame buffer (i.e. thememcpy
) is [already] "double buffering"It would actually be faster with just one
buffers
. After sending the first frame to the frame buffer, generating the second frame could be just a small incremental change tobuffers
This is similar to using a graphics library like
SDL2
.buffers
), draw into that surface. Or, just draw into the offscreen buffer with (e.g.)SDL_RenderDrawRect
SDL_RenderPresent
(like yourmemcpy
).I've seen this in some other similar SO questions for this like the linked answer. The github repo linked to on that page is [AFAICT] no longer available. But, it had a buffer that was just text chars, it would change those, then use those to index into a font to produce the raster lines in the frame buffer.
Really, I don't know why, so the following is just speculation ...
Although not really true on modern systems, IIRC [and I could easily be wrong about this] access to the frame buffer could be slower than regular RAM on older systems. So, with two offscreen
buffers
, we could domemcpy
only for the pixels that change frame-to-frame.Or, with multiple buffers, it could help with animation (loosely):
.gif
file intobuffers[0]
buffers[1]
memcpy
ofbuffers[0]
to H/W frame bufferSo, reading data from a file for frame
N
could be overlapped with thememcpy
for frameN-1
. This could be more efficient.