Page 1 of 2
Fork and Processes
Posted: Fri Feb 27, 2009 2:27 pm
by Creature
Hello,
Yes, here I am again with another question
, but this time it's not a problem, I promise! Anyway, I was just wondering: after implementing multitasking (using a UNIX fork clone), I was wondering how to create a 'CreateProcess' function from this (the way Win32 creates threads). The idea is I want to be able to do something like the following:
Code: Select all
void Foo()
{
//Thread running 'simultaneously' with the main thread.
}
...
CreateProcess(&Foo);
Mostly a process or task that starts inside a function instead of from the current instruction (like forking). I've tried a few things, including setting the instruction pointer (EIP) to the function, allocating a new stack, etc... but I guess I'm just doing it all wrong. What would be the best way to go about this?
Thanks,
Creature
Re: Fork and Processes
Posted: Fri Feb 27, 2009 2:45 pm
by nekros
Well, personally I'd make a separate function CreateThread. That's all I have to say for the moment.
Re: Fork and Processes
Posted: Fri Feb 27, 2009 3:09 pm
by bewing
Why not
Code: Select all
int CreateThread(*foo)
{
int ParentPid = GetPID();
int myPID = fork();
// use PIDs to determine which copy is parent and child -- however you implemented fork()
if (myPID != ParentPID)
{
int ret = foo (args);
exit (ret);
}
return (wait_for_child());
}
?
(of course, you don't need to wait for the return value)
Re: Fork and Processes
Posted: Fri Feb 27, 2009 3:21 pm
by Creature
That's what I tried in the first place, but without the 'wait for the child process' (as what I'm trying to do is make them run at the same time, not let the main thread be hogged until the child process is finished). But for some reason, if the main function (creating the thread running 'foo') prints a string after creating the process and the foo function (which the child process is running) also prints another string, only the child process prints the string and the main thread can't do anything for some reason (and that's not how I remember threads).
Re: Fork and Processes
Posted: Fri Feb 27, 2009 10:30 pm
by bewing
Sounds like you need to do a little work on making your string printing functions be threadsafe.
Re: Fork and Processes
Posted: Sat Feb 28, 2009 4:28 am
by Creature
bewing wrote:Sounds like you need to do a little work on making your string printing functions be threadsafe.
I tried lots of things besides printing, I tried letting them do something else that is notable and not related to eachother in any way or using volatile variables, but none seems to work, the child process is the only process being run. I guess it must be some bug in my fork code, then.
EDIT: I've noticed that the timer (PIT) just randomly stops as soon as I fork a process.
Re: Fork and Processes
Posted: Sat Feb 28, 2009 9:11 am
by bewing
Creature wrote:
I tried lots of things besides printing, I tried letting them do something else that is notable and not related to eachother in any way or using volatile variables, but none seems to work, the child process is the only process being run. I guess it must be some bug in my fork code, then.
I'd be more likely to guess that it's a bug in your scheduler -- for some reason, it's choosing to give the child process all of the timeslices, and none to the parent.
EDIT: I've noticed that the timer (PIT) just randomly stops as soon as I fork a process.
Well, that's not good.
Re: Fork and Processes
Posted: Sat Feb 28, 2009 10:30 am
by Creature
Here we go again, there SEEMS to be nothing wrong, but there's probably something small wrong again.
Code: Select all
/*
* ScheduleProcesses
* Switches processes if necessary, this is called by the process scheduler.
*/
void ScheduleProcesses()
{
/* If this variable is 0, multitasking has not been initialized yet. */
if(!CurrentProcess)
return;
/* Read some registers. */
unsigned ESP;
unsigned EBP;
unsigned EIP;
ASMV("mov %%ESP, %0" : "=r" (ESP) :);
ASMV("mov %%EBP, %0" : "=r" (EBP) :);
/* Read the instruction pointer, to see if processes have just been switched. */
EIP = ReadEIP();
/* Have processes just been switched? */
if(EIP == 0xABCDE)
return;
/* If processes weren't switched, they will be switched now. Save the current state. */
CurrentProcess->ESP = ESP;
CurrentProcess->EBP = EBP;
CurrentProcess->EIP = EIP;
/* Select the next process. */
CurrentProcess = CurrentProcess->Next;
/* Prevent going past the end of the linked list. */
if(!CurrentProcess)
CurrentProcess = ProcessQueue;
ESP = CurrentProcess->ESP;
EBP = CurrentProcess->EBP;
EIP = CurrentProcess->EIP;
/* Change paging directories (so MemoryManager knows what to do). */
CurrentDir = CurrentProcess->PageDirectory;
/* Go to the new process! */
ASMV(" \n"
"cli \n" /* Disable interrupts. */
" \n"
"mov %0, %%ecx \n" /* Save EIP in ECX. */
"mov %1, %%esp \n" /* Load ESP (stack pointer). */
"mov %2, %%ebp \n" /* Load EBP (base pointer). */
"mov %3, %%CR3 \n" /* Change page directories. */
" \n"
"mov $0xABCDE, %%eax \n" /* Allow us to see if processes were just switched when re-entering this function (see above). */
" \n"
"sti \n" /* Re-enable interrupts. */
"jmp *%%ecx " /* Jump to the point where the process left off (EIP was stored in ECX above). */
: : "r" (EIP), "r" (ESP), "r" (EBP), "r" (CurrentDir->Address));
}
Looks perfectly fine, doesn't it?
Re: Fork and Processes
Posted: Sat Feb 28, 2009 5:14 pm
by bewing
Yes, but when you fork() and are adding the child process to the linked list run queue, the question is whether the parent process is getting (partially) unlinked?
Re: Fork and Processes
Posted: Sun Mar 01, 2009 5:13 am
by Creature
Noes, I've checked the code again and again, and even went down the entire linked list to see how many nodes there were after forking once. The result was as expected: 2 processes (parent and just-created child).
Code: Select all
/*
* ForkProcess
* Forks the currently active process (clones it in a different memory space).
*/
int ForkProcess()
{
/* No more interrupts. */
ASMV("cli");
Process *Parent = (Process *) CurrentProcess; /* Cast away volatileness. */
PageDir *Dir = ClonePageDir(CurrentDir);
/* Create a new process. */
Process *RetProcess = reinterpret_cast<Process *>(MM->Alloc(sizeof(Process)));
RetProcess->PID = NextPID++;
RetProcess->ESP = 0;
RetProcess->EBP = 0;
RetProcess->EIP = 0;
RetProcess->Next = 0;
RetProcess->PageDirectory = Dir;
/* Add the process to the linked list. */
Process *Node = (Process *) ProcessQueue; /* Cast away volatileness. */
/* Find the last node. */
while(Node->Next)
Node = Node->Next;
Node->Next = RetProcess;
/* Fill in the entry point. */
unsigned EIP = ReadEIP();
/* Is this the parent process? */
if(CurrentProcess == Parent)
{
/* Set up the child process. */
unsigned ESP;
unsigned EBP;
ASMV("mov %%ESP, %0" : "=r" (ESP) :);
ASMV("mov %%EBP, %0" : "=r" (EBP) :);
RetProcess->ESP = ESP;
RetProcess->EBP = EBP;
RetProcess->EIP = EIP;
/* Re-enable interrupts. */
ASMV("sti");
return RetProcess->PID;
}
/* If this point is reached, this is the child process. */
return 0;
}
Re: Fork and Processes
Posted: Sun Mar 01, 2009 12:36 pm
by yemista
Why do you read the up? If schedule process is called doesnt that mean the process has switched?
Re: Fork and Processes
Posted: Sun Mar 01, 2009 1:02 pm
by Creature
yemista wrote:Why do you read the up? If schedule process is called doesnt that mean the process has switched?
Schedule process is called when a timer interrupt occurs and switches processes all the time. Since I'm using JamesM's tutorials, I'm going to quote what James says (he explains best):
JamesM's Tutorials wrote:
Read the instruction pointer. We do some cunning logic here:
One of two things could have happened when this function exits -
(a) We called the function and it returned the EIP as requested.
(b) We have just switched tasks, and because the saved EIP is essentially the instruction after read_eip(), it will seem as if read_eip has just returned.
In the second case we need to return immediately. To detect it we put a dummy
value in EAX further down at the end of this function. As C returns values in EAX,
it will look like the return value is this dummy value! (0x12345).
This is what happens at the ReadEIP() in 'ScheduleProcess'.
Re: Fork and Processes
Posted: Sun Mar 01, 2009 4:00 pm
by Combuster
Creature wrote:(code snipped)
Looks perfectly fine, doesn't it?
No, its a time bomb waiting to go off (or it already did). Your code is making assumptions on how the compiler treats the stack, which can vary under different optimizations. (I'm probably cracking down some tutorial here in the same time...)
Since I'm too pendantic about these things, I would grab a debugger to see what's truly happening (or being executed)
Re: Fork and Processes
Posted: Sun Mar 01, 2009 4:59 pm
by JamesM
Combuster wrote:Creature wrote:(code snipped)
Looks perfectly fine, doesn't it?
No, its a time bomb waiting to go off (or it already did). Your code is making assumptions on how the compiler treats the stack, which can vary under different optimizations. (I'm probably cracking down some tutorial here in the same time...)
Since I'm too pendantic about these things, I would grab a debugger to see what's truly happening (or being executed)
Please quote what assumptions that code makes, and what it violates. I'm interested to know.
Re: Fork and Processes
Posted: Mon Mar 02, 2009 5:17 am
by Combuster
As I said, I'm a pendant (and the actual task switching should imo ALWAYS be done in pure assembly).
It took me two seconds to spot this bit (as the straightforward example of what I mean):
Code: Select all
unsigned EIP = ReadEIP();
// new task starts from here
if(CurrentProcess == Parent)
{
unsigned ESP;
unsigned EBP;
// allocate variables on the stack, adjust ESP and/or EBP to make room for them
// (depending on compiler and options this may or may not happen)
ASMV("mov %%ESP, %0" : "=r" (ESP) :);
ASMV("mov %%EBP, %0" : "=r" (EBP) :);
// get modified EBP/ESP
RetProcess->ESP = ESP;
RetProcess->EBP = EBP;
RetProcess->EIP = EIP;
// store process with EIP with a mismatched stack
ASMV("sti");
return RetProcess->PID;
}
// once new process returns, it pops off the wrong return address, and the world says BOOM
Since the standards do not define how the stack is treated during function execution (they merely specify an calling convention; a defined state at the start and end of a function, not somewhere halfway through). If I'd add inline assembly to the compiler I wrote for college, the above snippet would break in the way I just mentioned (because that's the way my compiler deals with the scoping).
I think things would also get rather ugly when the compiler decides to map local variables to registers that aren't saved.
Anyway, I hope you got an idea of what I meant. (and why this kind of magic scares me)