I need more about creating a task/process

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

I need more about creating a task/process

Post by calpis »

Hi, I read alot about how to start multitasking but, I can't find anything about implementing a simple tasks. All I am aiming to do is to create 2 tasks and see how the whole mulititasking work.

I can only understand what the task control block and task table are for. But what i don't understand is what's inside a task(such as stack, new page directory and tables, size of stack).

I am trying to implement the multitasking for about a month now, still I am stuck at the begining stage. Here is what I think I know:

I need a tss descriptor in gdt for initialize a hardware switch.
I need a task table to hold the task control blocks.

Can anyone help me please.

I am looking at a diagram from intel. a system architecture overview.
In a task state segement(TSS), a task has code, data, and stack.
What I want to know is

what does the stack stores???
what does the code stores? is it just a cs register from the task below?
What does the data stores? is it just a ds register from the task below?
what does a new page directory and table do when create a task?


Here is my task state segment or task content, whatever the name is...

Code: Select all

struct task_state_segment
{
   short backlink, __blh;
   int esp0;
   short ss0, __ss0h;
   int esp1;
   short ss1, __ss1h;
   int esp2;
   short ss2, __ss2h;
   int cr3;
   int eip;
   int eflags;
   int eax, ecx, edx, ebx;
   int esp, ebp, esi, edi;
   short es, __esh;
   short cs, __csh;
   short ss, __ssh;
   short ds, __dsh;
   short fs, __fsh;
   short gs, __gsh;
   short ldt, __ldth;
   short trace, bitmap;
}__attribute__((packed));
Sorry if my post is a bit of a mass, coz thats the state of brain I am at at the moment~! Thanks for any help and advice~
User avatar
Pype.Clicker
Member
Member
Posts: 5964
Joined: Wed Oct 18, 2006 2:31 am
Location: In a galaxy, far, far away
Contact:

Re:I need more about creating a task/process

Post by Pype.Clicker »

okay, let's try to make it simple ...

there are two things that come into play: threads and address spaces. For a first try, i suggest you forget about address spaces (that basically means that any of your "tasks" will have the same content for CR3 and LDT, and usually the same segment registers aswell).

The stack is of course used to store local state for the current computation (e.g. function call chains, arguments, local variables and the like). The stack state (SS:ESP) is the minimal thing you have to save in order to do a switch -- in other word, while in 'all threads in the same address space', the only thing your task control block need to store is that SS:ESP.
calpis

Re:I need more about creating a task/process

Post by calpis »

So do you mean the stack stores ebp, esp, esi, eax,ebx....(unsigned) func...etc on the stack in a kind of specified order which you refer as local state? And all I need is a (SS:ESP) to do a switch

And then go and use a scheduler to switch around the (SS:ESP) between two task?

Thanks for your help.
JoeKayzA

Re:I need more about creating a task/process

Post by JoeKayzA »

Basically, it seems you got it right.

You should, however, still keep a seperate structure for each thread, that holds the corresponding ss:esp, as well as a thread 'status' (running/blocked/waiting...) and the page directory. For storing the cpu status, the stack comes in handy because you can simply push the registers on it and restore them during an interrupt.

EDIT: Storing the page directory is of course only needed when you use multiple address space, which is, as Pype mentioned, not neccessary for the beginning.

cheers Joe
User avatar
Pype.Clicker
Member
Member
Posts: 5964
Joined: Wed Oct 18, 2006 2:31 am
Location: In a galaxy, far, far away
Contact:

Re:I need more about creating a task/process

Post by Pype.Clicker »

chaisu chase wrote: So do you mean the stack stores ebp, esp, esi, eax,ebx....(unsigned) func...etc on the stack in a kind of specified order which you refer as local state? And all I need is a (SS:ESP) to do a switch
No. The stacks only stores what you push on it. So if you want it to store the local state, you need to use things like (assuming fs/gs isn't used, and that all the threads use the same stack segment, only changing the stack pointer)

Code: Select all

do_switch:
   pusha   ;;; save the cpu state on the stack
   push ds
   push es ;;; just to keep it sane
   pushf    ;;; keep the status register aswell
   cli         ;; so that you don't get interrupted
   mov [outgoing_task],esp
   mov esp,[incoming_task]   
   popf     ;;; retrieve 'incoming task's state 
   pop es
   pop ds
   popa 
   ret
Really, if that doesn't sound clear, i suggest you get an assembly book and read through the 'stack' chapter: that's a crucial element of system programming and you won't get far without it.
calpis

Re:I need more about creating a task/process

Post by calpis »

hi, I have tried to implement the task. But it seems to be not working.

my kernel is based around bran's dev kernel. I use the c method to install a task interrupts. and added the asm code for the task irq handler. I also have a method to create task. but again, I am not sure if this whole thing is correct.

I don't know what is wrong, the kernel runs like normal. But its doesn't print what I want. I am really just testing and see it the switcher works.

Please tell me whats missing or wrong. Many Thanks~

My asm for task switch

Code: Select all

extern current_task
extern kernel_task
extern task_schedule_handler
extern tss
irq_task_stub:
   pusha
   push ds
   push es
   push fs
   push gs
   
   mov eax, [current_task]
   mov [eax], esp
   mov eax, [kernel_task]
   mov esp, [eax]
   
   mov eax, task_schedule_handler;;irq_handler
   call eax
   
   mov eax, [tss]
   add eax, 4
   mov [eax], esp
   mov eax, [kernel_task]
   mov [eax], esp
   mov eax, [current_task]
   mov esp, [eax]
   
   pop es
   pop ds
   pop fs
   pop gs
   popa
   add esp, 8
   iret;;ret


This is what I have in task.c which includes task handler installer.

Code: Select all

unsigned int create_task(void (*function)(), unsigned int priority)//, priority)
{
   //store all the regs into task_state_info by save reg
   struct task_control_block *this_task = (struct task_control_block *)page_allocator(sizeof(struct task_control_block));
   
   this_task[0].task_esp = getesp();
   this_task[0].task_time = 10;
   this_task[0].task_cr3 = first_page_directory;
   this_task[0].task_priority = priority;
   //this_task[0].task_id = ;
   this_task[0].tasks = (struct task_control_block *)page_allocator(sizeof(struct task_state));
   this_task[0].tasks->eax = 0;
   this_task[0].tasks->ebx = 0;
   this_task[0].tasks->ecx = 0;
   this_task[0].tasks->edx = 0;
   this_task[0].tasks->edi = 0;
   this_task[0].tasks->esi = 0;
   this_task[0].tasks->cs = 0x8;
   this_task[0].tasks->eflags = 0x202;
   
   this_task[0].tasks->eip = (unsigned)function;
   this_task[0].tasks->esp = (unsigned int)this_task[0].tasks;
   setcr3(this_task[0].task_cr3);
   
   
   
   return (unsigned int) this_task;
}

void task_schedule_handler(struct regs *r)
{
   if(r->int_no == 32)
   {
      
      kprintf("\nHello from timer\n");
      outportb(0x20,0x20);
   }else if (r->int_no ==34)
   {
      kprintf("\nHello from scheduler\n");
      outportb(0x20,0x20);

   }
}

void task_schedule_install()
{
   irq_install_handler(2, task_schedule_handler);
}

This is what I have in the kernel main.

Code: Select all

unsigned int simpletask1()
{
   int i;
   for   (i = 0; i < 1000000; i++)
   {
      kprintf(" simple task %d\n",i);
   }
}
.
.
.

     task_schedule_install(); //install irq handler for task scheduler
     kprintf(" Task Initialized \n");
     
     unsigned int *task_queue;
     unsigned int num_of_task =0;
     
     
     struct task_control_block *kernel = (struct task_control_block *)page_allocator(sizeof(struct task_control_block));
     kernel[0].task_esp = getesp();
     kernel[0].task_cr3 = first_page_directory;
     kernel[0].task_id = 0;
     kernel[0].task_time = 0;
   
     task_queue[0] = (unsigned int) kernel;

     current_task = kernel;
     kernel_task = kernel;
    
     struct task_control_block *task1 = (struct task_control_block *)create_task(simpletask1,255);
     task_queue[1] = (unsigned int) task1;
     
     struct task_control_block *task2 = (struct task_control_block *)create_task(simpletask2,255);
     task_queue[2] = (unsigned int) task2;
     

calpis

Re:I need more about creating a task/process

Post by calpis »

Hi, I have changed my code and just forget about using the interrupt. Instead, I use the timer instead. Which called the contentt_switch_stub from there. Look as tho it is switching now~

Thanks anyway~
calpis

Re:I need more about creating a task/process

Post by calpis »

hi, can anyone tell me what this mean?
I used the do_switch asm from pype's code and bochs got freezen~!
And give the following in bochs log.

Code: Select all

00015117148e[CPU  ] load_seg_reg: GDT: ES: index(1800) > limit(00001f)
00029446500p[MGUI ] >>PANIC<< POWER button turned off.
00029446500i[SYS  ] Last time is 1124065649
00029446500i[CPU  ] protected mode
00029446500i[CPU  ] CS.d_b = 32 bit
00029446500i[CPU  ] SS.d_b = 32 bit
00029446500i[CPU  ] | EAX=00000007  EBX=0000000a  ECX=00000000  EDX=000003d5
00029446500i[CPU  ] | ESP=0012cfa8  EBP=00104b50  ESI=0002cb46  EDI=001055d8
00029446500i[CPU  ] | IOPL=0 NV UP DI PL NZ NA PO NC
00029446500i[CPU  ] | SEG selector     base    limit G D
00029446500i[CPU  ] | SEG sltr(index|ti|rpl)     base    limit G D
00029446500i[CPU  ] |  DS:0010( 0002| 0|  0) 00000000 000fffff 1 1
00029446500i[CPU  ] |  ES:0010( 0002| 0|  0) 00000000 000fffff 1 1
00029446500i[CPU  ] |  FS:0010( 0002| 0|  0) 00000000 000fffff 1 1
00029446500i[CPU  ] |  GS:0010( 0002| 0|  0) 00000000 000fffff 1 1
00029446500i[CPU  ] |  SS:0010( 0002| 0|  0) 00000000 000fffff 1 1
00029446500i[CPU  ] |  CS:0008( 0001| 0|  0) 00000000 000fffff 1 1
00029446500i[CPU  ] | EIP=00100e03 (00100e03)
00029446500i[CPU  ] | CR0=0xe0000011 CR1=0x00000000 CR2=0x00000000
00029446500i[CPU  ] | CR3=0x0009c000 CR4=0x00000000
00029446500i[     ] restoring default signal behavior
00029446500i[CTRL ] quit_sim called with exit code 1
Thanks in advanced.
AR

Re:I need more about creating a task/process

Post by AR »

That says you attempted to load ES with 1800th descriptor which is outside the GDT's limit.
calpis

Re:I need more about creating a task/process

Post by calpis »

Any other how to fix it? All I know its that it happened when I change my content_switch_code to this. Thanks for your reply.

Code: Select all

extern in_task
extern out_task
extern tss
extern stub_test
global content_switch_stub
content_switch_stub:
   pusha
   

   push ds
   push es
   push fs
   push gs
   ;;;;;;;;;;;;;;;;;;;;;;;;;;;
   pushf
   ;;cli
   mov [out_task],esp
   
   mov esp, [in_task]
   mov eax,[tss]
   add eax, 4
   
   popf

   ;;;;;;;;;;;;;;;;;;;;;;;;;;;
   
   ;;mov eax, [in_task]
   ;;mov [eax], esp
   ;;mov eax, [out_task]
   ;;mov esp, [eax]
   
   
   ;;mov eax, [tss]
   ;;add eax, 4
   ;;mov [eax], esp
   ;;mov eax, [out_task]
   ;;mov [eax], esp
   ;;mov eax, [in_task]
   ;;mov esp, [eax]
   

   pop gs
   pop fs
   pop es ;;problem

   pop ds
      

   popa
   ;;call stub_test  ;;from timer.c
   ;;add esp, 8
   ret   ;;problem
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:I need more about creating a task/process

Post by Brendan »

Hi,

The very first time a newly created thread does a thread switch, it will skip the start of your thread switch code and begin running in the middle (without pushing values on it's stack first). To avoid problems you'd need to create the new thread's stack with everything "pre-pushed" onto it, so that during the first thread switch these values can be popped off.

I think you're either not doing this, or not doing it correctly.

For example, when you create a new thread you need to "emulate" the following (based on the thread switch code you posted):
  • push EIP (the near return address)
    pusha
    push ds
    push es
    push fs
    push gs
    pushf
Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
calpis

Re:I need more about creating a task/process

Post by calpis »

May be I am not doing it correctly, because I don't know what to do. :)
I create the first and the rest of tasks using the same method. Did you mean I must initializes the first tasks first with all the regs prepushed to (which stack?)

can you tell me what is "the near return address"? And what should do to get use switching task.

I don't really get the push EIP method.

I m pretty messed up with my code at the moment~ Many thanks for your help.

P.S. Could anyone tell me what it means please.

Code: Select all

00004456835i[CPU  ] can_push(): expand-up: esp < N
00004456835i[CPU  ] push_32(): push outside stack limits
00004456835i[CPU  ] can_push(): expand-up: esp < N
00004456835i[CPU  ] can_push(): expand-up: esp < N
00004456835e[CPU  ] exception(): 3rd (12) exception with no resolution, 
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:I need more about creating a task/process

Post by Brendan »

Hi,

Let's start with very simple thread switch code:

Code: Select all

;Switch threads
;
;Input
; eax = address where next thread's ESP is stored

switchThreads:

   ;Save the previous thread's state

   pusha
   mov ebx,[address_of_last_threads_ESP]
   mov [ebx],esp

   ;Change to next thread's stack

   mov [address_of_last_threads_ESP],eax
   mov esp,eax

   ;Load the next thread's state

   popa
   ret
As long as EAX is set correctly before this code is called it will work (note: it won't change segment registers or anything and it will cause problems if any of the threads use CPL=3, but it's meant to be as simple as possible).

When your OS is initializing you'd want to create scheduler data for the first thread, which is the initialization code itself. For this example the thread switch code is so simple that this can hopefully be ignored for now.

During the very first task switch where you switch from the OS's "initialization thread" to a newly created thread, the initialization thread would run the "Save the previous thread's state" part of the thread switch code, then you change stacks, and after this you load the new thread's state from the new thread's stack. Here's the problem - the new thread's state must be on it's stack so that it can be loaded by the thread switch code.

To create a new thread you'd need to create the scheduler data for the new thread, and initialize the new thread's stack so that it contains the "state" that the thread switch code is going to pop off the new thread's stack. In this simple case it could be something like:

Code: Select all

;Spawn a new thread
;
;Input
; eax = address of new thread's code to run

spawnThread:
   pusha
   pushf

   <setup some scheduler data>

   ;Switch to the new thread's stack

   mov ebp,esp
   cli
   mov esp,<the address for the top of the new thread's kernel stack>

   ;Store the state data that the thread switch code will try to get from the stack

   push eax           ;EIP = address of new thread's code to run
   pusha

   ;Restore the original stack

   mov esp,ebp

   ;Return to caller

   popf
   popa
   ret
For your old code, the thread switch routine tried to pop a heap of things from the stack that (as far as I can tell) were never put on the stack to begin with.

chaisu chase wrote:P.S. Could anyone tell me what it means please.

Code: Select all

00004456835i[CPU  ] can_push(): expand-up: esp < N
00004456835i[CPU  ] push_32(): push outside stack limits
00004456835i[CPU  ] can_push(): expand-up: esp < N
00004456835i[CPU  ] can_push(): expand-up: esp < N
00004456835e[CPU  ] exception(): 3rd (12) exception with no resolution, 
Exception 12 is a "stack fault", and is caused when you haven't got a valid stack or ESP is outside of the segment limits. In this case the CPU tries to start the "stack fault" exception handler but can't push the return information onto the stack, so it triple faults.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
calpis

Re:I need more about creating a task/process

Post by calpis »

hi, thanks for your reply,

I have tried the method you posted...but I think, I don't quite understand how the context are saved to the task stack or task_state in my tcb. May be I think I don't know how the assembly code works.
But I tried to use some emulator to help me understand it. However, its a still confuse.

I need to know what you mean from the last post.

In the spawnThread:
<setup some scheduler data>
What sort of scheduler data did you mean? Can you tell abit in details please.

Also:

Code: Select all

 
mov esp,<the address for the top of the new thread's kernel stack>

  ;Store the state data that the thread switch code will try to get from the stack

  push eax          ;EIP = address of new thread's code to run
  pusha

 ;Restore the original stack

  mov esp,ebp

  ;Return to caller

  popf
  popa
  ret
Restore original stack... that I just don't get.
The "address for the top of the new thread's kernel stack" its exactly the address of the new process stack, the &stack.

In each create process method, we always have some preset data(in blueIllusion multitasking example). we put
0x0202;
0x08;
(unsigned int)function;
0;
0;
0;
.
.
.
0x10;

So all the create process will have these preset data, when we switch, we are switching the these data with the current context of the register everytime, and restore them. I just don't see how...I guess I am really confuse or I have missed something.

I have looked at afew examples already, I now understand each process must have a stack of its own.

But it is the switch code or the assembly code that I just don't get.

I really wanted to understand and get this done.
Please feed me with more details.

Thanks in advance.
calpis

Re:I need more about creating a task/process

Post by calpis »

Hi, I have manage to understand how the switch works.

Now I have test my scheduler and the queue. It works fine. But I don't actually swap the two task yet. I do it by pressing key

Code: Select all

My context switch code ends with ret that gives me a reboot
00601919001e[CPU  ] seg = DS
00601919001e[CPU  ] seg->selector.value = 0000
00601919001e[CPU  ] write_virtual_checks: valid bit = 0
00601919001e[CPU  ] CS: 0008
00601919001e[CPU  ] IP: 1f3c
00601965429p[CPU  ] >>PANIC<< prefetch: running in bogus memory

Code: Select all

My context switch code ends with iret that gives me a reboot
00007004380p[CPU  ] >>PANIC<< iret: return CS selector null

Code: Select all

   mov [current_task_esp],esp
   call scheduler_handler
        ;;All my scheduler code did was to make the
        ;;current_task_esp = the next task[0].task_esp 
   mov esp, [current_task_esp]
do I also need to do something about the CS selector? I tried to follow the legend os tutorial as much as possible.

Thanks in advacnce
Post Reply