Linux内核分析-8/进程的调度

时间:2022-02-04 14:35:28

进程的调度

  • 之前我们在mykernel中体验了一次进程的调度,调度分为两个过程
  • 1/保存当前进程
  • 2/装载之前进程
和之前的fork exec 有什么区别呢?
fork 和exec 只是创造和修改了一个task_struct 结构体,exec后的进程是要调度模块调度才能运行的.
而进程的调度就是保存一个结构体,并装载一个结构体.

linux-0.11中的调度

//init/main.c
init
//kernel/sched.c
//这是调度的初始化模块
void sched_init(void)
{
int i;
struct desc_struct * p;

if (sizeof(struct sigaction) != 16)
panic("Struct sigaction MUST be 16 bytes");
set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
p = gdt+2+FIRST_TSS_ENTRY;
for(i=1;i<NR_TASKS;i++) {
task[i] = NULL;
p->a=p->b=0;
p++;
p->a=p->b=0;
p++;
}
/* Clear NT, so that we won't have troubles with that later on */
__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
ltr(0);
lldt(0);
outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 *///设置时钟1
outb_p(LATCH & 0xff , 0x40); /* LSB *///设置时钟2
outb(LATCH >> 8 , 0x40); /* MSB *///设置时钟3
set_intr_gate(0x20,&timer_interrupt);//设置时钟中断处理函数
outb(inb_p(0x21)&~0x01,0x21);
set_system_gate(0x80,&system_call);
}

//1/调度函数(kernel/sched.c)
//在mykernel中是内核上层的函数在想切换的时候,调用了 my_schedule 函数进行调度
//所以我猜这里也是在想要切换进程的时候调用了内核中的函数切换,这里切换函数是 schedule
//2/调度时机
//但是在什么时候切换呢?在mykernel中,是时钟中断达到了10^7次,才调用了调度函数
//在linux-0.11中只有8个地方调用了 schedule 函数
//schedule

1/修改任务数组中每个任务的 signal alarm state
2/找出next
2.1/在任务数组中从后往前找,按照counter 的大小, 越大,优先级越高
2.2/找不到,按照 priority 更新counter,回到 2.1
3/switch_to(next)
/*
* switch_to(n)将切换当前任务到任务nr,即n。首先检测任务n 是不是当前任务,
* 如果是则什么也不做退出。如果我们切换到的任务最近(上次运行)使用过数学
* 协处理器的话,则还需复位控制寄存器cr0 中的TS 标志。
*/

// 输入:%0 - 新TSS 的偏移地址(*&__tmp.a); %1 - 存放新TSS 的选择符值(*&__tmp.b);
// dx - 新任务n 的选择符;ecx - 新任务指针task[n]。
// 其中临时数据结构__tmp 中,a 的值是32 位偏移值,b 为新TSS 的选择符。在任务切换时,a 值
// 没有用(忽略)。在判断新任务上次执行是否使用过协处理器时,是通过将新任务状态段的地址与
// 保存在last_task_used_math 变量中的使用过协处理器的任务状态段的地址进行比较而作出的。
/*
* switch_to(n) should switch tasks to task nr n, first
* checking that n isn't the current task, in which case it does nothing.
* This also clears the TS-flag if the task we switched to has used
* tha math co-processor latest.
*/

#define switch_to(n) {\
struct {long a,b;} __tmp; \
__asm__("cmpl %%ecx,_current\n\t" \
"je 1f\n\t" \
"movw %%dx,%1\n\t" \
"xchgl %%ecx,_current\n\t" \
"ljmp %0\n\t" \
"cmpl %%ecx,_last_task_used_math\n\t" \
"jne 1f\n\t" \
"clts\n" \
"1:" \
::"m" (*&__tmp.a),"m" (*&__tmp.b), \
"d" (_TSS(n)),"c" ((long) task[n])); \
}
//这里说调度时机
1/中断处理过程 do_timer(timer_interrupt->do_timer)
2/主动调用 release do_exit sys_waitpid sys_pause sleep_on interruptible_sleep_on tty_write
例子:
tty_write(sys_write->rw_char->crw_table->rw_tty->rw_ttyx->tty_write- 写阻塞->schedule)
//所以说调度时机分为两种
//1/中断处理进程
//2/主动调用
//其实上面两种都是用 set_intr_gate set_trap_gate set_system_gate 来将中断号与中断处理函数绑定
//主动调用是从上面的分支 set_system_gate 来的
//中断是从上面的分支 set_intr_gate 来的

从中断来说,包括时钟中断、I/O中断、系统调用和异常(linux0.11中只有时钟中断调用了 schedule )
从系统调用来说,有open close mount sync pause 等等,这些系统调用通过 int 80 进入内核,然后产生内核线程调用了 schedule

//中断
set_intr_gate
set_intr_gate (0x2E, &hd_interrupt);//在kernel/system_call.s中
set_intr_gate (0x20, &timer_interrupt);//在kernel/system_call.s中
set_intr_gate (0x24, rs1_interrupt);//在kernel/chr_drv/rs_io.s 中
set_intr_gate (0x23, rs2_interrupt);//在kernel/chr_drv/rs_io.s中
set_trap_gate
set_trap_gate (0x21, &keyboard_interrupt);
set_trap_gate (0x26, &floppy_interrupt);
void trap_init(void)
{
int i;

set_trap_gate(0,&divide_error);// 设置除操作出错的中断向量值。以下雷同。
set_trap_gate(1,&debug);
set_trap_gate(2,&nmi);
set_system_gate(3,&int3); /* int3-5 can be called from all *///
set_system_gate(4,&overflow);//系统调用
set_system_gate(5,&bounds);//
set_trap_gate(6,&invalid_op);
set_trap_gate(7,&device_not_available);
set_trap_gate(8,&double_fault);
set_trap_gate(9,&coprocessor_segment_overrun);
set_trap_gate(10,&invalid_TSS);
set_trap_gate(11,&segment_not_present);
set_trap_gate(12,&stack_segment);
set_trap_gate(13,&general_protection);
set_trap_gate(14,&page_fault);
set_trap_gate(15,&reserved);
set_trap_gate(16,&coprocessor_error);
// 下面将int17-48 的陷阱门先均设置为reserved,以后每个硬件初始化时会重新设置自己的陷阱门。
for (i=17;i<48;i++)
set_trap_gate(i,&reserved);
set_trap_gate(45,&irq13);// 设置协处理器的陷阱门。
outb_p(inb_p(0x21)&0xfb,0x21);// 允许主8259A 芯片的IRQ2 中断请求。
outb(inb_p(0xA1)&0xdf,0xA1);// 允许从8259A 芯片的IRQ13 中断请求。
set_trap_gate(39,&parallel_interrupt);// 设置并行口的陷阱门。
}
set_system_gate
set_system_gate (0x80, &system_call);
set_system_gate(3,&int3); /* int3-5 can be called from all */
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);

下面的说的是glibc-2.25和linux-3.10中的调用路径


asmlinkage void __sched schedule(void)
{
struct task_struct *tsk = current;

sched_submit_work(tsk);
__schedule();
}
//先不看是怎么调度的
//下面看是谁调用了这个函数
//
1/驱动里面
2/系统调用
3/中断处理函数