Process management

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.
Whatever5k

Process management

Post by Whatever5k »

Alright, I implemented my memory manager and I'm wondering what to do next - I think it's time to implement tasks/processes/threads...
First of all, what is task? And where can I find more informations about implementing processes?

Next, I need to clarify my understanding of process management. First of all, I will only implement kernel processes. User processes will make system calls and other stuff that isn't implemented yet.
Now when creating a kernel process, I need to do some initializing like giving the process a pid, filling up the cs, ds, es and eflags. And how do I "enable" the process? Do I just put in a runnable_processes_queue? And it's the scheduler's job to run it? Or what do I do?

best regards,
A. Blessing
Therx

Re:Process management

Post by Therx »

Firstly you must understand that multi tasking is not real multi tasking just very fast switching between (on single CPU computers) So at a switch you must do the following:-
1. Put all the registers into variables specific to the task you are switching OUT of.
2. Store the address of the currently executing code.
3. Restore all the registers from variables specific to the task you are switching to.
4. Jump to the location of the code which was running.

When you boot you must set all the tasks addresses to the location of the start of that tasks code. The process of switching tasks is controlled by the scheduler. For ease this is normally run on IRQ 0. Steps 1+2 can be done by setjmp from LibC and Steps 3+4 by longjmp. Implementations of these two functions can be found at Chris Giese's Site (http://my.execpc.com/~geezer/osd/code/lib/setjmp/) and an implementation in the OSD. This method can be used for co-operative and pre-emptive. There is some CPU structure TSS which others use for multi tasking but I have no info on this.
On top of that simple system you can add priorities etc.

Hope this helps
Whatever5k

Re:Process management

Post by Whatever5k »

Wondering if fork() differs from process_create(). process_create() just allocates memory for the process structure from the heap...what does fork() do?
And suppose the scheduler decides to call another process...how does he do it? Does he just jump to EIP?
DarylD

Re:Process management

Post by DarylD »

Essentially with multi-tasking what you are after doing is simply changing the EIP and other registers to the other tasks values. There are various ways of doing this, one of the easiest is simply to create a stack with all registers then change ESP/SS to point to your new stack and then pop all register values.

The normal way is through an interrupt. I.e. when an interrupt occurs, in your ISR push all registers and call the scheduler, the scheduler then gets the ESP of the new stack and does a lss esp, [address]. Although this sounds complex, in all honesty its dead simple once you have it clear in your head.

That is probably no clearer though!

Don't worry too much about the difference between fork etc yet, just try and get multiple threads of execution working concurrently first, after all threads are a subset of processes.

Daryl.
Therx

Re:Process management

Post by Therx »

Sorry made a mistake. If you restore ALL the registers and then do a "ret" the CPU will return to the status specified by the newly loaded registers. You do this by loading your stored registers on to the current stack and putting EIP at the bottom. Then doing a "popa" and a "pop esp" before the "ret" will make sure that when you return it jumps to the correct code.
Schol-R-LEA

Re:Process management

Post by Schol-R-LEA »

Under conventional Unices, fork() creates a new process (allocates memory, creates a process ID and process record, etc.) and copies into it the state of calling process (data stack and registers - depending on the implementation, code may be copied, or shared between the two processes). The when this is done, fork() returns to both processes - giving the p_id of the new ('child') process to the original ('parent') process (or -1 on failure), and zero (0) to the child process. Different implementations may have substantially different behaviors regarding open file descriptors, etc.

Sources:
Linux man page for fork(2)

Understanding a Unix Process, Part 1

Sources found via the following Google searches
[tt]man fork[/tt]
[tt]fork(2)[/tt]
[tt]"using fork" process[/tt]

HTH.
Therx

Re:Process management

Post by Therx »

Just remembered some stuff abuot TSS. If you want proper Kernel and user threads you need to use TSS. Then the CPU automatically switchs in some cases. Can someone please confirm and add to this?
Whatever5k

Re:Process management

Post by Whatever5k »

Alright, I will need more informations about this TSS stuff...
I will do it this way: my clock interrupt will call the scheduler - it's its job to switch the process...how do I do this with TSS, once again?
So I don't really need any low-level code except "ltss"?
richie

Re:Process management

Post by richie »

hello!
I saw in your profile that you speak German. So I suggest you to read the following German Protected Mode Tutorial which also has an interesting part about TSS:
http://www.fh-zwickau.de/doc/prmo/start.htm

BTW: There is no ltss-Instruction like lgdt in x86-ASM. The Instraction is called ltr (Load Task Register).
Whatever5k

Re:Process management

Post by Whatever5k »

Is it right, that I need a TSS segment for *each* task/process? That is quite annoying, because then I would have to build a task segment when creating a process. By the way, how many entries can the GDT hold?
Ok, so what are the steps I have to do?
1. create_process() called
2. assign the process a PID and save the process's registers (with the ltr instruction ???)
3. put the process in the runnable process queue
4. wait until scheduler picks our process

Is this the right way to do it? But how do I save the process's registers (EIP, CS, EAX, etc.)? With the ltr instruction or do I just put them in memory. But if so, I don't need TSS, do I?
richie

Re:Process management

Post by richie »

Hello again!
assign the process a PID
No, the PID is a logical thing in Unix. It has nothing to do with OSes in general.
Is it right, that I need a TSS segment for *each* task/process? That is quite annoying, because then I would have to build a task segment when creating a process. By the way, how many entries can the GDT hold?
Yes, every Task needs a TSS (the TSS on a 386 and higher is at least 104 Byte (all registers) but you can add variables for your own use). So you have a table with all your TSS. To every TSS there is also a GDT entry for that TSS. For example a Task has the following things:
- TSS (at least 104Byte)
- GDT-Entry for CS
- GDT-Entry for DS
- GDT-Entry for TSS (TSS-Selector)

(The GDT can hold 8192 Entries, since segment-registers are only 16Bit and every 8 Byte starts a new entry)

To perform a task-switch you only have to do a
[tt]jmp TSS-Selector:0x00000000[/tt]
The offset will be ignored because it is specified by the GDT-Entry.

To do multitasking it is useful to make although your scheduler/TaskSwitcher a Task with its own TSS. You can set up the IDT in a way that a TaskSwitch is done by the cpu when an Interrupt 0 occur. The cpu saves all registers in the TSS of the old task and loads the register values of the new Task (the scheduler-Task) out of the TaskSwitch-TSS. Then the Scheduler can decide with Task to run next and jump to the TSS of the new Task.

If you jump or call a TSS the cpu changes the type of the GDT-Entry in the GDT from TSS to active TSS. (You cannot jump to a Task if it is already active). This is why I move 0x89 to the Type-Field of the GDT-Entry before jumping to a Task.

The following code shows my TaskSwitcher:

Code: Select all

TaskSwitch:

TaskSwitch_Loop:

cli

call _schedule ;changes _cur_TSS_Desc

;reset PIC
mov al,20h
out 20h, al

mov [_gdt + TASKSWITCH_SEL + 5],BYTE 89h
xor ebx, ebx
mov bx,[_cur_TSS_Desc]
mov [_gdt + ebx + 5],BYTE 89h ; Reset Task


; The following opcodes will be manipulated by _schedule


;  jmp _cur_TSS_Desc:0x00000000
; 
db 0xEA??????; jmp
dd 0x00000000???; Offset, ignored
_cur_TSS_Desc:???dw 0x0000???; Selektor
???

jmp TaskSwitch_Loop

This works fine but perhaps there are better ways for doing TaskSwitches. I don't know.

The ltr is only used when you are init your kernel. Before going multitasking there is only one process running. When you the jmp to TaskSwitch-Selector the CPU tries to save the current state in the TSS for the old Task. But it has no TS. SO you have to set up a TSS for the init task and load this TSS-Selektor to the task Register (with ltr)
Whatever5k

Re:Process management

Post by Whatever5k »

Thanks, this clears up things a bit...
Still, some questions keep me busy:

How can I save the tasks' register in its TSS? Is there any instruction for that or is this done automatically by the CPU when a task-switch occurs?
Secondly, why should the scheduler be a task, too? That's how I would do it:

1. A clock interrupt occurs, call the scheduler to pick another process
2. the scheduler holds a list of runnable processes - it picks a process out of the list, loads its TSS-selector and jumps to it

Anything wrong with it?
This is why I move 0x89 to the Type-Field of the GDT-Entry before jumping to a Task.
What does this mean, moving 0x89 to the type field of the GDT? What does it do?

And a last question concerning the ltr:
So I have to have *one* TSS for the kernel, right? So when the *first* task-switch occurs, the CPU can store the kernel's registers in its TSS, right?
But if I enable multi-tasking, that is task-switching on every clock interrupt, I cannot longer execute code in my kernel, can I? Because the scheduler always picks tasks which aren't part of the kernel - am I wrong?

thanks and best regards,
A. Blessing

PS: Is it better to use TSS or software task-switching?
Perica
Member
Member
Posts: 454
Joined: Sat Nov 25, 2006 12:50 am

Re:Process management

Post by Perica »

..
Last edited by Perica on Sun Dec 03, 2006 8:56 pm, edited 1 time in total.
Whatever5k

Re:Process management

Post by Whatever5k »

Some more questions:
If every process needs a TSS, I will have to reserve enough space in the GDT. So I can do:

Code: Select all

gdt:
  ; NULL descriptor
  ...
  ; CS
  ...
  ; DS
  ...
  resb (8192 * 8 - ($ - gdt))
$ - gdt is the length of the NULL, CS and DS descriptor (should be 24 bytes).

Now I need to determine if a GDT entry is free or not - what about this code:

Code: Select all

extern addr_t *gdt;
int i;

for (i = 8; i < (GDT_ENTRIES * DESC_SIZE); i += DESC_SIZE)
   if (gdt[i] == 0)
      break;
i is initialized as 8 to overjump the NULL descriptor...
Using this method, I can easily set up TSS descriptors at run-time...
But where are the TSS *segments* placed - can s.b. help me out with this, please?

thanks,
A. Blessing
richie

Re:Process management

Post by richie »

Hello!
First a response to Perica Senjak:
Why should it be only 255 Entries in the GDT? Every selector is 16Bit . 2Bits are used for Privilege level and 1 Bit is Table Indicator (Local or Global Descriptor Table). This makes 16 - 2 - 1 = 13 Bits. In 13 Bits you can adress 2^13 = 8192 Entries in the GDT. This is the GDt on a 386.
I heard something about an extension for adressing more than 4GB memory. Then the GDT-Entries are larger than 8 Byte but I'm not sure about that. I think for simple OS Development 4GB is enough for these days.

To abless:
What does this mean, moving 0x89 to the type field of the GDT? What does it do?
The processor changes the type of a TSS-Descriptor to "active TSS" if you jump there. When this task is interrupted the type remains "active TSS".
Until now there is no problem but if you try to jump again to that task (that is now marked as "active TSS") the Processor causes an exeception. So you have to change the type back to "non-active TSS". This is done by moving 0x89 in the type-field.
So I have to have *one* TSS for the kernel, right? So when the *first* task-switch occurs, the CPU can store the kernel's registers in its TSS, right?
Yes, that's right. This is why you have to use the ltr instructions. If you have no valid GDT-Selector to a TSS-Descriptor in the Task Register the CPU causes an exception (or triple faults).
But if I enable multi-tasking, that is task-switching on every clock interrupt, I cannot longer execute code in my kernel, can I? Because the scheduler always picks tasks which aren't part of the kernel - am I wrong?
If you want to run kernel-code after going multitasking you have to schedule your kernel-task.
How can I save the tasks' register in its TSS? Is there any instruction for that or is this done automatically by the CPU when a task-switch occurs?
Secondly, why should the scheduler be a task, too? That's how I would do it:

1. A clock interrupt occurs, call the scheduler to pick another process
2. the scheduler holds a list of runnable processes - it picks a process out of the list, loads its TSS-selector and jumps to it

Anything wrong with it?
The CPU-registers are saved automatically if a Task-Switch is done. This is why I suggest to make the TaskSwitcher a seperate Task. If the IRO0 occur the CPU recognize that it has to load the TaskSwitcher-Task and saves all registers in the old TSS and load the TaskSwitcher-Registers out of the new TSS. If IRQ0-handler is a normal interrupt-Handler your INT-handler will be called within the user process. The Int-Handler uses the user-stack and you have to take care that the registers are not changed. This is not very comfortable since you have to save the registers for your own.
But where are the TSS *segments* placed - can s.b. help me out with this, please?
The TSSs are somewhere in memory. It isn't neccassary to have them in an array but it's easier to handle if they are organized in an array. Because every TSS needs its own Descriptor the adress of the TSS is given by the Descriptor.
You can also define your TSS array in C. For first thest I suggest to define it in ASM.
(First I defined it in ASM and everything works fine. Later I defined it in C and nothing works. I had to spend a lot of time to find the bug: DJGPP optimizes the code. I never used the array within C and so DJGPP thought that the arry is never used and left it out.)
Post Reply