Reputation: 407
Im trying to implement custom threads in C by creating a simple context switch function plus a FCFS scheduler.
The first step that i want to perform is to copy the entire function stack frame to heap to a saved frame and replace it by the first in queue.
The problem i have is that after finishing task one the stack of the second gets corrupted. I dont have any idea about why.
The code i have is the following:
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define ITERATIONS 10
#define SSIZE 15
int * last;
void kwrite(const char*);
void kyield(int *);
void f1() {
int i = ITERATIONS;
while (i--) kwrite("A\n");
}
void f2() {
int i = ITERATIONS*2;
while (i--) {
printf("[%d]", i);
kwrite("B\n");
getchar();
}
}
void kwrite(const char* str) {
int a[10] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5};
write(1, str, strlen(str));
int *frame = malloc(sizeof(int)*SSIZE);
memcpy(frame, a, SSIZE*sizeof(int));
kyield(frame);
printf("ERROR\n");
}
void kyield(int * from) {
if (from == NULL) {
f1();
from = malloc(sizeof(int)*SSIZE);
memcpy(from, last, SSIZE*sizeof(int));
}
if (last == NULL) {
last = malloc(sizeof(int)*SSIZE);
memcpy(last, from, SSIZE*sizeof(int));
free(from);
f2();
exit(0);
}
int a[10] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3};
memcpy(a, last, SSIZE*sizeof(int));
memcpy(last, from, SSIZE*sizeof(int));
free(from);
}
int main(int argc, char** argv) {
kyield(NULL);
free(last);
}
It should call 10 times f1 and 20 f2 then exit. But when the var i of f2 is 8 it gets corrupted on the next iteration. Therefore entering an infinite loop.
Any help or suggestions would be appreciated! Have a nice day.
[edit] I suppose the code can be a bit tricky to understand so here it is a little clarification:
main calls kyield with null parameters.
kyield detects it and calls f1
f1 executes until kwrite is called
kwrite calls kyield passing its current stack frame
kyield detects the last stack frame is null so it copies the stack frame (sf from now on) given by kwrite, then calls f2 f2 does the same as f1
when kyield is executed next, both from and last wont be NULL so it will overwrite its current s f with the one in last, swap it with the one in from and lastly it will return, as the stack has been altered it will jump to the return address of the last kwrite, not the actual one, thus. Jumping from the f1 thread to f2.
Your memcpy(frame, a, SSIZE*sizeof(int)) looks wrong. Your SSIZE is defined to 15, but a has only a size of 10.
this is deliberate, as by copying 15 elements of 4 bytes we are copying the rax last value, the last ebp and the return address of the function.
https://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/
Upvotes: 0
Views: 242
Reputation: 13073
I can see a couple of issues with the design. The normal way of scheduling different threads is for them to get complete stacks, not to share the same stack.
What that means in this case, is that the stack depends on the addresses being used.
+---------+--------+---------+--------+---------+
| main | kyield | f1 | kwrite | kyield |
| | | |a[10] | |
+---------+--------+---------+--------+---------+
|-------------| << copied by the slice of the stack.
The slice of the stack you are taking, is independent of the amount used in the function preceding it, so would be broken if the functions calling kwrite have different stack requirements (different amounts of state would be required).
It is also broken because the amount of information which is captured on the stack is not complete. The state of execution is based on the stack and the current values in non-volatile registers. These values can pollute the alternate threads.
Finally the stack also contains addresses. This scheme could only work, if all the threads executed had identical stack requirements, as if the yield function pastes a value back in which requires addresses, then they always have to align.
Upvotes: 1
Reputation: 94
Your memcpy(frame, a, SSIZE*sizeof(int))
looks wrong. Your SSIZE
is defined to 15, but a
has only a size of 10.
Upvotes: 0