Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
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:
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
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
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)
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).
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
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.
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
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.
/*
* 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?
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
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?
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).
/*
* 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;
}
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
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'.
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
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)
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
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.
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)
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]