好的,OK,上次我们说到块设备请求项初始化以及如何在屏幕上显示出我们在键盘上输入的东西。下面继续看main函数中的初始化函数
time_init()
#define CMOS_READ(addr) ({ \
outb_p(0x80|addr,0x70); \
inb_p(0x71); \
})
#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
static void time_init(void) {
struct tm time;
do {
time.tm_sec = CMOS_READ(0);
time.tm_min = CMOS_READ(2);
time.tm_hour = CMOS_READ(4);
time.tm_mday = CMOS_READ(7);
time.tm_mon = CMOS_READ(8);
time.tm_year = CMOS_READ(9);
} while (time.tm_sec != CMOS_READ(0));
BCD_TO_BIN(time.tm_sec);
BCD_TO_BIN(time.tm_min);
BCD_TO_BIN(time.tm_hour);
BCD_TO_BIN(time.tm_mday);
BCD_TO_BIN(time.tm_mon);
BCD_TO_BIN(time.tm_year);
time.tm_mon--;
startup_time = kernel_mktime(&time);
}
CMOS_READ对一个端口先out写一下,然后in读一下。
这是CPU与外设交互的基本玩法,从端口读入反馈,从端口写出命令。
CMOS是一个外设,是主板上的一个可读写的RAM芯片。通过读写CMOS上的指定端口,一次获取年月日时分秒等信息。至于CMOS是怎么知道时间的,有机会查找资料。
BCD_TO_BIN就是从CMOS中得到的数据转换成二进制。
kernel_mktime就是根据获得的数据计算从1970年1月1日0时开机经过的秒数,存储在startup_time里面。
shed_init()
对于理解操作系统,流程和数据结构最为重要。
void sched_init(void) {
set_tss_desc(gdt+4, &(init_task.task.tss));
set_ldt_desc(gdt+5, &(init_task.task.ldt));
...
}
这两行代码初始化了TSS和LDT。看代码可以发现一个熟悉的东西gdt,全局描述符表,之前初始化的时候就把最后的252项留给TSS和LDT。
TSS
任务状态段,用来保存和恢复进程上下文;
上下文:各个寄存器的信息
struct tss_struct{
long back_link;
long esp0;
long ss0;
long esp1;
long ss1;
long esp2;
long ss2;
long cr3;
long eip;
long eflags;
long eax, ecx, edx, ebx;
long esp;
long ebp;
long esi;
long edi;
long es;
long cs;
long ss;
long ds;
long fs;
long gs;
long ldt;
long trace_bitmap;
struct i387_struct i387;
};
LDT
局部描述符表,与GDT全局描述符表相对应,内核态的代码用GDT里的数据段和代码段,而用户进程的代码用每个用户进程自己的LDT里的数据段和代码段。
之前GDT初始化的时候,数据段和代码段找基址,那里的基址都是0,也就是找到的偏移地址就是物理地址。
在之前的GDT的初始化中,后252项里面每创建一个进程就要多出一组TSS和LDT,存放进程上下文和对应的代码段和数据段。
struct desc_struct {
unsigned long a,b;
}
struct task_struct * task[64] = {&(init_task.task), };
void sched_init(void) {
...
int i;
struct desc_struct * p;
p = gdt+6;
for(i=1;i<64;i++) {
task[i] = NULL;
p->a=p->b=0;
p++;
p->a=p->b=0;
p++;
}
...
}
这段代码中的for循环就是将第0组初始化为init_task.task,除了第0组的其他63组进程都赋值为NULL,对应的TSS和LDT都赋值为0。
task_struct结构代表每一个进程的信息,非常重要。
struct task_struct {
/* these are hardcoded - don't touch */
long state; /* -1 unrunnable, 0 runnable, >0 stopped */
long counter;
long priority;
long signal;
struct sigaction sigaction[32];
long blocked; /* bitmap of masked signals */
/* various fields */
int exit_code;
unsigned long start_code,end_code,end_data,brk,start_stack;
long pid,father,pgrp,session,leader;
unsigned short uid,euid,suid;
unsigned short gid,egid,sgid;
long alarm;
long utime,stime,cutime,cstime,start_time;
unsigned short used_math;
/* file system info */
int tty; /* -1 if no tty, so it must be signed */
unsigned short umask;
struct m_inode * pwd;
struct m_inode * root;
struct m_inode * executable;
unsigned long close_on_exec;
struct file * filp[NR_OPEN];
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
struct desc_struct ldt[3];
/* tss for this task */
struct tss_struct tss;
};
虽然现在还没有建立起进程调度的基址,但是我们正在运行的代码会作为未来的一个进程的指令流,未来的进程调度机制一旦建立起来,正在执行的代码会化身成进程0的代码
#define ltr(n) __asm__("ltr %%ax"::"a" (_TSS(n)))
#define lldt(n) __asm__("lldt %%ax"::"a" (_LDT(n)))
void sched_init(void) {
...
ltr(0);
lldt(0);
...
}
我们之前见过lidt和lgdt,这里的作用类比一下就是ltr给tr寄存器复制,告诉CPU任务状态段TSS在内存中的位置。lldt是给ldt寄存器赋值,告诉CPU,LDT在内存中的位置。
继续往下看
void sched_init(void) {
...
outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */
outb_p(LATCH & 0xff , 0x40); /* LSB */
outb(LATCH >> 8 , 0x40); /* MSB */
set_intr_gate(0x20,&timer_interrupt);
outb(inb_p(0x21)&~0x01,0x21);
set_system_gate(0x80,&system_call);
...
}
这次CPU还是与外设进行交互,这次交互的对象时可编程定时器芯片,四行代码开启这个定时器,之后这个定时器会持续的,以一定频率向CPU发出中断信号,这段代码中设置的两个中断,一个0x20,时钟中断,中断处理程序是timer_interrupt.
时钟中断是操作系统主导进程调度的关键,定时器通过外部信号不断触发中断,使得操作系统成为进程管理的主人。
这段代码设置的第二个中断是system_call,系统调用,中断号是0x80,想所有用户态进程提供调用内核提供的方法的手段,这个中断非常重要的啦。
OK,好的,今天就学到这里啦!有问题欢迎评论区留言讨论!!!