# Lecture 7a - Create (continued)

## Public Service Annoucements

1. Due date for kernel 1: Friday, 26 May, 2017

# Scheduling

There are two important issues for scheduling

1. When do we reschedule?
2. Who do we activate when we schedule

### When to schedule

Every time we are in the kernel, so the question is `When do we enter the kernel?'
The answer is: whever user code executes SWI.

### Who to Schedule

Whoever is needed to meet all the deadlines

• or to optimize something.

Because this is not an easy problem, we don't want to solve it within the kernel. What the kernel does should be fast (=constant time) and not resource constrained.

#### Scheduling algorithm

1. Find the highest priority non-empty ready queue. A ready queue can be as simple as a linked list of pointers to task descriptors.
2. The task found is removed from its queue and becomes the active task. (Until this point active has pointed to the TD of the previously active task.)
3. When a task is made ready it is put at the end of its ready queue.Thus, all tasks oat the same priority get equal chances of running.

The main data structure is usually an array of ready queues, one for each priority.

Each ready queue is a list with a head pointer (for extraction)and a tail pointer (for insertion).

### Implementation

#### Decisions

1. How many priorities
2. Which task should have which priority

#### General structure

Array of ready queues, one for each priority.

Each ready queue is a list with a head pointer (for extraction)and a tail pointer (for insertion).

#### Implementing lists without memory allocation

You are probably used to implementing lists (and similar data structures) like this

```      struct element { struct element *next;
struct whatever *content;
}
struct list { struct element *head;
struct element *tail;
}
void insert( list *l, content *c ) {
struct element *e;
e = malloc( element );
e->content = c;
e->next = null;
l->tail->next = e;
l->tail = e;
}
```
We don't like this because it requires allocating and freeing memory.

Here's the most common way to do this without allocating memory

```      typedef struct task-descr { ...
...
} TD;
```
All the allocation is done when the task descriptors are declared.

Of course, because allocating and freeing constant sized pieces of data can be done in constant time, you could allocate a pool of list elements when you initialize, and manage it using a free list.

### Static/Global Variables.

There are four ways we can use memory for storing data

1. in the text section with the instructions, constant data only,, visible only to tasks that can see the instructions,
2. in the bss section, uninitialized data only, visible by any task running the same code,
3. in the data section, data used for initialization of any type of variable, constant, initialized variables are somewhere else,
4. on the stack, separate tasks with the same code have their own stack.

C storage classes are not cleanly mapped onto these categories.

1. auto, on the stack
2. register, put it in a register if possible, not guaranteed
3. static
• outside a function, visible everywhere function names are visible
• inside a function, preserves its value across function calls, two functions running the same code see the same value,
4. extern, gives visibility to program components compiled in other files.

In creating a task you have to do two things

1. Get and initialize resources needed by the task
• TD
• memory
2. Make the task look as if it had just entered the kernel
• it's ready to execute when it's scheduled

#### Things you need to do

Get an unused TD and memory for its stack. To do this you must decide what will be the maximum number of tasks in your system.

• Most projects I have seen had fewer than 50 tasks, but there have been a few exceptions.
• Some students have had two sets of tasks, big ones and small ones. The small ones are tasks where you can easily bound their stack usage. To keep memory allocation constant time, you will need to have the sizes constant, using two free lists.
A few implementation details.
• memory can be associated with TD during initialization
• actually a form of constant time memory allocation
• unless you implement Destroy

Most of the work in creating a task fill in fields in the TD.

• task id must be unique; Destroy introduces complications.
• It must be easy to find TD from task id. A common model breaks the task id into two parts: an index into the array of TDs, and a generation number.
2. stack pointer
• This must be in the TD so that the kernel can find the data stored on the task's stack.
• See below. Before you can initialize the stack pointer you must have decided how your kernel will allocate stack space among tasks.
• Before you initialize the stack you must first decide what will be saved on the task's stack, what will be saved in the task descriptor.
• One rule of thumb says that stuff used when handling a request should be in the TD, the rest on the stack.
3. SPSR.
Forgetting to save this, or doing it incorrectly is unlikely to crash kernel 1. But it's worth getting right because it can cause hard to fix bugs later in the course.
Remember that there are two link registers: the one the kernel uses when it's time to restart the task that called Create, the other the one that is used to return from the Create function.
5. Parent tid
• The task that made the request, which for now is the active task. This assumes that the active task pointer remains valid until scheduling is performed.
• In the absence of Destroy task creation creates a tree of tasks. Figuring out what to do with this implicit structure is one of the things that makes Destroy complicated.
6. return value
• different return values for the active task and for the next to be scheduled task.
• the one for the active task -- the one receiving service -- goes in its TD or on its stack.
• the one for the next active task should already be in its TD or on its stack.

Must also initialize the stack

• Stuff you left out of the TD must be unrolled from the stack.
• Exactly as if the task had just done a kernel entry
• look carefully at what your kernel exit code will do
• At the end stack pointer must correspond to stack contents
• I initialize the stack pointer to the top of allocated memory
• my stacks grow down
then change it as I push stuff onto the stack
• imitating the context switch code

The following implementation decisions are up to you.

• What should be saved on the stack; what should be saved in the task descriptor.
• How memory is laid out.
• The argument and return value passing conventions for system calls.

## Initializing the Kernel

### Set up the Hardware

RedBoot gives you the processor with

• caching turned off,
• slow cpu and bus clock,
• memory mapped into a single block starting at 0x0, (32 Mbytes is 2^25 bytes.)
• the interrupt control unit turned off,
• interrupts disabled in the CPSR, and
• MMU turned on to get the memory map above.
Then there are a few things you need to do. While RedBoot gives you the system as described above, there may be things done by a previous student that changed what RedBoot thinks it is giving you.
1. Initialize busy-wait I/O.
2. Initialize low memory.
• How do you find the kernel entry?
• To test whether or not you have done this correctly
• Find the hex value of the first instruction executed on kernel entry.
• Read the number in 0x28 using Redboot.
• Read what's in the word it points to, which should be the same as the first instruction. (Hint. RedBoot and gcc don't agree about endedness.)
3. Turn off interrupts in the ICU etc.
• This should be unnecessary, but what if the previous kernel turned them on?
• Later you will initialize the ICU differently.
4. As a debugging aid I sometimes put distinct bit patterns in the registers in order to get some easy information about what is going where.

### Prepare the Kernel Data Structures

Where is the kernel's stack pointer, right now? What does the stack look like?

• Do you want it there? Would you rather have it somewhere else?
• This is your last chance to change it. (If you decide to change it you might want to keep what you are replacing around. Why?)

The kernel data structures. At the very least you need

1. an array of empty ready queues
2. a pointer to the TD of the active task
3. an array of TDs
4. a free list of pointers to free TDs. This might take the form of bits set in a couple of words.

### Create the First User Task

Can run with hardware interrupts turned off for now. But when hardware interrupts are turned on in kernel 3 interrupts in user tasks must be turned on, though they stay off in the kernel.

Reminder. The place where the kernel starts executing has the global name main, which cannot be re-used.

## Other Primitives

These primitives exist mostly so that we, which includes you, can ensure that task creation and scheduling are working when there is not much else implemented.

#### `Tid MyTid( )`

Self-explanatory

• Doesn't block, but does reschedule.

A question, to which there is a correct answer, or more specifically, a correct (answer, reason) pair.

• Should the Tid be stored in user space?

#### `Tid MyParentTid( )`

Self-explanatory

• Doesn't block, but does reschedule.

Where is the parent Tid, and how does the kernel find it?

#### `void Pass( )`

Doesn't block: task calling `Pass( )` makes a state transition from `ACTIVE` to `READY`.

Does reschedule.

When is `Pass( )` a `NOP`?

#### `void Exit( )`

Calling task is removed from all queues, but its resources are not reclaimed or reused.

That is, the task goes into a zombie state, and will never become active or ready, but continues to own all its resources.