ARM Integrator-CP ITPTMME Phase2: Difference between revisions

m
no edit summary
[unchecked revision][unchecked revision]
m (added kernel and idle thread and updated scheduler switch)
mNo edit summary
 
(5 intermediate revisions by 4 users not shown)
Line 1:
{{FirstPerson}}{{You}}{{Sole Editor}}
== Processes & Threads, Exception Handling, And System Calls ==
In this phase we are going to improve our exception handling, rework out tasking system to support processes and threads (instead of tasks), and implement some basic system calls for demonstration.
Line 11 ⟶ 12:
 
=== New Define For Timer Support ===
<sourcesyntaxhighlight lang="c">
/* max number of ticks for a task */
#define KTASKTICKS 10000
</syntaxhighlight>
</source>
This sets the time in which threads are interrupted for a context switch. The lower the
higher the frequency or maximum time a thread can execute.
Line 21 ⟶ 22:
I like these functions because they can work with ''any'' data structure that has the fields
at the top. It can be cast to type ''LL'' and used that way.
<sourcesyntaxhighlight lang="c">
typedef struct _LL {
struct _LL *next;
Line 67 ⟶ 68:
}
}
</syntaxhighlight>
</source>
=== Separate Scheduler Function ===
I have added support for sleeping threads, and a wake up flag. This is by no means a correct design, but rather just a demonstration of how you could put threads to sleep and wake them up. It also supports a timeout which allows a thread to not only wake up with an external signal but also after a specified amount of time.
<sourcesyntaxhighlight lang="c">
void ksched() {
KSTATE *ks;
Line 250 ⟶ 251:
}
}
</syntaxhighlight>
</source>
Here we have the basic save thread state and load thread state blocks. These possibly could be implemented in a much faster way, but I choose to avoid premature optimization for the sake of being straight forward. In the middle between the save and load state blocks you have the code to choose the next thread. In it's simplest form it simply grabs the next thread in the current process, and if no thread left it grabs the next process and the first thread. It continues this until it has a thread to run. In the form above it does the exact same except it checks if the thread is sleeping, then it checks the ''timeout'' (minimum time to sleep for) and if it is expired it sets the thread as awake and runs it. It also checks if any signal has been asserted to the thread and if so (and only if) then the thread is woken if it is sleeping. You might be wondering why I remove the wake signal (bit) only if the thread is sleeping. This has to do with my future design of the IPC for the system.
 
 
=== Added Idle Thread And Kernel Thread ===
I added an ''idle'' thread that does nothing but yield, and a kernel work thread. The idle thread keeps the scheduler with something to switch too when no other threads are running (supposed to be), but in reality in the current code it just switches to it and it yields. Needs some improvement, but it is simple for now.
<sourcesyntaxhighlight lang="c">
int ksleep(uint32 timeout) {
int result;
Line 280 ⟶ 283:
}
}
The ''kidle'' immediantly yields. The scheduler could be coded differently where it only executes the ''kidle'' if there are no other threads to run, but I decided to keep it simple at this stage.
</source>
</syntaxhighlight>
 
=== Creation Of Kernel And Idle Thread ===
<sourcesyntaxhighlight lang="c">
.... in main ....
process = (KPROCESS*)kmalloc(sizeof(KPROCESS));
Line 312 ⟶ 316:
ks->idleproc = process;
....
</syntaxhighlight>
</source>
Here both the kernel thread and ''idle'' thread are created under the same process. You could have created a separate process if you like.
 
=== New Exception Handlers ===
<sourcesyntaxhighlight lang="c">
void k_exphandler(uint32 lr, uint32 type) {
uint32 *t0mmio;
Line 438 ⟶ 443:
return;
}
</syntaxhighlight>
</source>
The biggest difference here is the addition of systems call and the termination of a thread when it has an exception.
 
 
=== Added memset ===
I added a useful utility function ''memset''.
<sourcesyntaxhighlight lang="c">
void memset(void *p, uint8 v, uintptr sz) {
uint8 *_p;
Line 453 ⟶ 460:
}
}
</syntaxhighlight>
</source>
 
=== Changes In kelfload ===
<sourcesyntaxhighlight lang="c">
int kelfload(KPROCESS *proc, uintptr addr, uintptr sz) {
ELF32_EHDR *ehdr;
Line 533 ⟶ 540:
asm("mcr p15, #0, r0, c8, c7, #0");
}
</syntaxhighlight>
</source>
Here we simple allocate memory for the sections and then copy them into memory. I switch address spaces and flush the TLB to make this easier to perform. It could be faster just to map the memory into kernel space and copy to it in certain situations, but I decided that this method was the simplest.
 
=== Changed To Creation Of Process From Modules ===
<sourcesyntaxhighlight lang="c">
....
#define KMODTYPE_ELFUSER 1
Line 555 ⟶ 563:
}
.....
</syntaxhighlight>
</source>
I simply changed from creating of a task to the creation of a process, and ''kelfload'' was changed to create a thread of the passed process instead of a task.
 
=== SWI Exception Entry And Exit Changed ===
These were changed to push ''SPSR'' so it can be saved and restored easily from inside
our new ''sched()'' function.
<sourcesyntaxhighlight lang="c">
#define KEXP_TOPSWI \
uint32 lr; \
Line 575 ⟶ 584:
asm("pop {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12}"); \
asm("LDM sp!, {pc}^")
</syntaxhighlight>
</source>
The ''SWI'' entry and exit assembly now saves everything the same way as the other exception handlers allowing the scheduler to be called to save the current thread and load the next meaning system calls can switch threads for ''sleep'' and ''yield''.
 
=== Added System Call Numbers ===
<sourcesyntaxhighlight lang="c">
#define KSWI_WAKEUP 100
#define KSWI_SLEEP 101
#define KSWI_YEILD 102
#define KSWI_GETTICKPERSECOND 103
</syntaxhighlight>
</source>
 
=== New KPROCESS And Updated KTHREAD ===
<sourcesyntaxhighlight lang="c">
typedef struct _KTHREAD {
struct _KTHREAD *next;
Line 603 ⟶ 613:
KTHREAD *threads;
} KPROCESS;
</syntaxhighlight>
</source>
The ''KTHREAD'' was updated with linked list fields added, ''timeout'', and ''flags''. The ''KPROCESS'' is new and was added to provide a process like structure to tasking. The ''vmm'' field was moved from the thread structure to the process structure since all threads share the same
address space.
=== Changes To KSTATE ===
<sourcesyntaxhighlight lang="c">
typedef struct _KSTATE {
- /* process/thread support */
Line 631 ⟶ 641:
KVMMTABLE vmm; /* kernel virtual memory map */
uint32 vmm_ucte; /* unused coarse table entries */
</syntaxhighlight>
</source>
The old thread/task structures removed and the new field added. A pointer to the first process entry (linked list) was added, and a pointer to the current process and thread.
 
=== Second Module Added ===
<sourcesyntaxhighlight lang="c">
int _start(unsigned int *smmio) {
int x;
Line 648 ⟶ 658:
return 0;
}
</syntaxhighlight>
</source>
 
=== First Module ===
This module demonstrates going to sleep.
<sourcesyntaxhighlight lang="c">
unsigned int getTicksPerSecond() {
unsigned int out;
Line 675 ⟶ 685:
for (;;) {
for (x = 0; x < 0xfffff; ++x);
smmio[0] = 'G';
smmio[0] = 'K';
sleep(tps);
}
return 0;
}
</syntaxhighlight>
</source>
This module demonstrates going to sleep instead of busy looping which wastes CPU that could be used for another thread.
 
=== Change In Timer Initialization ===
<sourcesyntaxhighlight lang="c">
t0mmio = (uint32*)0x13000100;
t0mmio[REG_LOAD] = KTASKTICKS;
Line 693 ⟶ 702:
t0mmio[REG_INTCLR] = ~0; /* make sure interrupt is clear (might not be mandatory) */
ks->tpers = 1000000;
</syntaxhighlight>
</source>
We now use ''KTASKTICKS'' and initialize ''ks->tpers'' which is read by threads using a software interrupts (''see exphandler''). This allows them to adjust their ticks for the sleep call to sleep for a certain amount of actual real time (''see exphandler'' SWI handler).
 
I also switched to using a 1MHZ timer just because I have not yet figured out how to detect the system clock speed. I have a few ideas but I am currently working on more important things, but it might be a good exercise for the reader to try to do this. The board may allow you to determine the system clock speed. Also, an idea is to use the 1MHZ timer to estimate the system clock synchronized timer 0. This might yield a different value on different systems depending on how the system clock timer is implemented in QEMU, but on real hardware it should always be the same.
 
[[Category:ARM]]