Kernel Schedules a Process for the First Time

Một phần của tài liệu The art of linux kernel design (Trang 124 - 128)

Now, the code of process 0 is executed. From this time, process 0 prepares to switch to process 1 to execute.

In the process-scheduling mechanism of Linux 0.11, there are two situations when process switch happens.

First, the time allows running processes to end.

When created, a process is assigned with limited time slice to ensure that any process runs in limited time every time. When time slice reduces to 0, it means that this process used up the time and switches to another process to execute. This, in turn, is the imple- mentation of multiprocesses.

Second, a process stops running.

In those situations where a process waits for data applied by peripherals or the results of other programs or a process is finished, even if there is time slice left, a process does not have the logical conditions to continue to run. If it remains waiting for the time interrup- tion to switch to another process, time is wasted and switching to another process should be done immediately.

Either of the above situations happening leads to process switch.

The role of process 0 is special. Now, switching from process 0 to process 1 meets the second situation and also means idling process. We will talk about the idling process in Section 3.3.1.

Process 0 executes the line for(;;) pause() and finally switches to process 1 by execut- ing the function schedule. The process is shown in Figure 3.12.

The code of the function pause() is as follows:

The way of calling pause() is similar to that of fork(). When it comes to syscall0 in unistd.h, according to interruption int 0x80, map call _sys_call_table(,%eax,4) in system_call.s to the system calling function in sys_pause() to execute. The procedure in detail refers to calling the function fork() in Section 3.1.1. There is little difference that the function fork() is written in assembly language and the function sys_pause() is in C language.

In sys_pause(), it sets process 0 as in an interruptible state, just as the first step in Figure 3.12. Then, call the function schedule() to switch process. The code is as follows:

//Code path:init/main.c:

……

static inline _syscall0(int,fork) static inline _syscall0(int,pause)

……

void main(void) {

……

move_to_user_mode();

if (!fork()) { /* we count on this going ok */

init();

}

for(;;) pause();

}

//Code path:kernel/sched.c:

int sys_pause(void) {

//set Process 0 as interruptible state. When interruption happens or //other process transfers special

//signals to this process, this process is possibly set as ready //state.

current->state = TASK_INTERRUPTIBLE;

schedule();

return 0;

}

ROM BIOS and VGA

Kernel

Enable interrupt

0x00000 0x9FFFF 0xFFFFF 0x3FFFFF 0x5FFFFF 0xFFFFFF

Kernel code area Kernel data area

Process status

Process 0 Process 1

Ready Interruptible

Current process

Schedule sys_pause

task_struct of process 0

init_task

Step 1: set to interruptible Step 2 : process switching

After executing sys_pause Before executing sys_pause

Ready state

Figure 3.12 Process 0 hangs and executes scheduling programs.

In the function schedule, the system first checks the necessity of switching the pro- cess. If necessary, it will switch.

First, on the basis of the structure task[64], traverse all the processes for the first time. As long as the address pointer is not null, deal with alarm timer value “alarm” and signal bitmap

“signal.” Currently, these handles will not take effect, especially since process 0 does not receive any signal and, thus, is in an interruptible state, and impossible to revert to a ready state.

We need to traverse all the processes for the second time and compare the state and time slice of processes to find the process in ready state and with the maximum counter.

Now, there are only process 0 and process 1. Process 0 is in an interruptible state, and not in a ready state. Only process 1 is in a ready state. Hence, execute switch_to(next), and switch to process 1 to run. It is shown in the first step of Figure 3.13.

The code is as follows:

//Code path:kernel/sched.c:

void schedule(void) {

int i,next,c;

struct task_struct ** p;

/* check alarm, wake up any interruptible tasks that have got a signal */

for(p = &LAST_TASK ; p > &FIRST_TASK ;— — p) ROM BIOS

and VGA

Kernel

Enable interrupt

0x00000 0x9FFFF 0xFFFFF 0x3FFFFF 0x5FFFFF 0xFFFFFF

Kernel code area Kernel data area

Schedule task[64]

switch_to

Step 1: after two-round traverse, and pick process 1

Step 2: ready to switch process 1 to execute

Process status

Process 0 Process 1

Ready Interruptible

Current process

Figure 3.13 Execute scheduling process 1.

The code is as follows:

In “ljmp%0\n\t,” through the task-gate mechanism of the CPU, ljmp saves the values of each register of the CPU into the TSS of process 0 and also reverts the TSS data of process 1 and the descriptor data of the code/data segment of LDT to each register of the

if (*p) {

if ((*p)->alarm && (*p)->alarm < jiffies) { //set timing or timer //has expired

(*p)->signal | = (1<<(SIGALRM-1)); //set SIGALRM (*p)->alarm = 0; //clear alarm }

if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&

(*p)->state = =TASK_INTERRUPTIBLE) (*p)->state = TASK_RUNNING;

}

/* this is the scheduler proper: */

while (1) { c = -1;

next = 0;

i = NR_TASKS;

p = &task[NR_TASKS];

while (— — i) { if (!*— — p)

continue;

if ((*p)->state = = TASK_RUNNING && (*p)->counter > c) //find process in ready state with //maximum counter

c = (*p)->counter, next = i;

}

if (c) break;

for(p = &LAST_TASK ; p > &FIRST_TASK ;— — p) if (*p)

(*p)->counter = ((*p)->counter >> 1) +

(*p)->priority; //counter = counter/2 + priority }

switch_to(next);

}

//Code path:include/sched.h:

……

//FIRST_TSS_ENTRY<<3 is 100000, ((unsigned long) n)<<4, it is 1000 for Process 1 //_TSS(1) is 110000.Last 2 bit is privilege level.Third bit on the left is gdt. 110 is //subscript of tts0

#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))

……

#define switch_to(n) {\ //refer to 2.9.1

struct {long a,b;} __tmp; \ //prepare data structure for cs and eip of //ljmp

__asm__(“cmpl%%ecx,_current\n\t” \

“je 1f\n\t” \ //if Process n is current process, exit // without switching

“movw%%dx,%1\n\t” \ //low word of edx is assigned to *&__tmp.b

“xchgl%%ecx,_current\n\t” \ //task[n] switch with task[current]

ljmp%0\n\t” \ //ignore the offset

“cmpl%%ecx,_last_task_used_math\n\t” \ //whether using coprocessor or not last time

“jne 1f\n\t” \

“clts\n” \ //clear the switch task flag in CR0

“1:” \

::“m” (*&__tmp.a),“m” (*&__tmp.b), \ //.a corresponds to eip..b corresponds to cs

“d” (_TSS(n)),“c” ((long) task[n]));\ //edx is index number of tss n. Ecx is task[n]

}

CPU. Hence, it is a switch from a kernel code with privilege level 0 to a process 1 code with privilege level 3. It is shown in the second step of Figure 3.13.

Next, it is process 1’s turn. It will further build the environment, so that the process in the form of a file can interact with the peripherals.

Please note that the procedures of calling pause() involves switching from code of process 0 with privilege level 3 to the kernel code with privilege level 0 by int 0x80 interrupt, and then calling switch() in schdule() in sys_pause(), where the code switch to process 1 by ljmp instruction is executed. However, now, the code after ljmp and call _sys_call_table(,%eax,4) is not executed and int 0x80 does not return as well.

Một phần của tài liệu The art of linux kernel design (Trang 124 - 128)

Tải bản đầy đủ (PDF)

(524 trang)