代码路径:init/main.c
...
#define DRIVE_INFO (*(struct drive_info *)0x90080)
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)
...
struct drive_info
{
char dummy[32];
} drive_info;
void main(void)
{
ROOT_DEV = ORIG_ROOT_DEV;
drive_info = DRIVE_INFO;
...
}
代码路径:fs/super.c
/* this is initialized in init/main.c */
int ROOT_DEV = 0;
参考此图,可知ROOT_DEV存的2个字节(unsigned short)的根设备号,定义在fs/super.c,29行,这个值为0。drive_info填充了32个字节的硬盘参数。
预备知识
<<20 或 >>20 相当于乘或除以 1 MB,
<<12 或 >>12 相当于乘或除以 4 KB(联想到页),
<<10 或 >>10 相当于乘或除以 1 KB
代码路径:init/main.c
...
#define EXT_MEM_K (*(unsigned short *)0x90002) //从1MB开始的扩展内存(KB)数
...
void main(void)
{
...
memory_end = (1<<20) + (EXT_MEM_K<<10); //1MB+扩展内存(MB),即内存总数
memory_end &= 0xfffff000; 按页的倍数取整,忽略内存末端不足一页的部分
if (memory_end > 16*1024*1024)
memory_end = 16*1024*1024; //执行到此,memory_end为16MB
if (memory_end > 12*1024*1024)
buffer_memory_end = 4*1024*1024;//执行到此,buffer_memory_end为4MB
else if (memory_end > 6*1024*1024)
buffer_memory_end = 2*1024*1024;
else
buffer_memory_end = 1*1024*1024;
main_memory_start = buffer_memory_end;//执行到此,main_memory_start为4MB
...
}
如下图所示,主内存结束(memory_end)为0xFFFFFF,主内存开始(main_memory_start)此时为0x3FFFFF,高速缓冲区末端(buffer_memory_end)为0x3FFFFF。此时还没有虚拟盘。
代码路径:init/main.c
void main(void)
{
...
#ifdef RAMDISK
main_memory_start += rd_init(main_memory_start, RAMDISK*1024);//主内存从0x5FFFFF开始
#endif
...
}
代码路径:kernel/blk_drv/ll_rw_blk.c
...
struct blk_dev_struct blk_dev[NR_BLK_DEV]= {
{ NULL, NULL }, /* no_dev */
{ NULL, NULL }, /* dev mem */
{ NULL, NULL }, /* dev fd */
{ NULL, NULL }, /* dev hd */
{ NULL, NULL }, /* dev ttyx */
{ NULL, NULL }, /* dev tty */
{ NULL, NULL } /* dev lp */
};
...
代码路径:kernel/blk_drv/blk.h
...
#define NR_BLK_DEV 7
...
struct blk_dev_struct {
void (*request_fn)(void);
struct request * current_request;
};
...
#if (MAJOR_NR== 1)
...
#define DEVICE_REQUEST do_rd_request
...
代码路径:kernel/blk_drv/ramdisk.c
...
#define MAJOR_NR 1
...
char *rd_start;
int rd_length = 0;
...
long rd_init(long mem_start, int length)
{
int i;
char *cp;
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;//内核能够通过调用 do_rd_request 函数处理与虚拟盘相关的请求项操作
rd_start = (char *) mem_start;
rd_length = length;
cp = rd_start; //从0x3FFFFF开始
for (i=0; i < length; i++)//共2MB
*cp++ = '\0'; //0x3FFFFF~0x5FFFFF都是虚拟盘
return(length);
}
如上图所示,
主内存结束(memory_end)为0xFFFFFF,主内存开始(main_memory_start)此时为0x5FFFFF,高速缓冲区末端(buffer_memory_end)为0x3FFFFF,虚拟盘从0x3FFFFF~0x5FFFFF。
代码路径:init/main.c
void main(void)
{
...
mem_init(main_memory_start,memory_end);
...
}
代码路径:mm/memory.c
...
#define LOW_MEM 0x100000 //1 MB
#define PAGING_MEMORY (15*1024*1024)
#define PAGING_PAGES (PAGING_MEMORY>>12) //15 MB 的页数
#define MAP_NR(addr) (((addr)-LOW_MEM)>>12)
#define USED 100
...
static long HIGH_MEMORY= 0;
...
static unsigned char mem_map [PAGING_PAGES]= {0,};
...
void mem_init(long start_mem, long end_mem)
{
int i;
HIGH_MEMORY= end_mem;
for (i=0;i<PAGING_PAGES;i++)
mem_map[i]= USED; //所有的页都设置为USED
i= MAP_NR(start_mem); //虚拟盘开始的页标
end_mem -= start_mem;
end_mem >>= 12; //虚拟盘后总共的页数
while (end_mem-->0)
mem_map[i++]=0; //虚拟盘后所有的页设置为空闲
}
之所以这样设置,是因为从内核结束到4MB之前,是缓冲区申请的页面,都标志位已经使用。从4MB到6MB是虚拟盘所占用,不用于申请页面。
形成的结果,如下图所示:
代码路径:init/main.c
void main(void)
{
...
trap_init();
...
}
代码路径:kernel/traps.c
void trap_init(void)
{
int i;
set_trap_gate(0,÷_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); // 无效指令
第 2 章 设备环境初始化及激活进程 0 53
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); // 无效 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); // 协处理器错误
for (i=17;i<48;i++) // 都先挂接好,中断服务程序函数名初
// 始化为保留
set_trap_gate(i,&reserved);
set_trap_gate(45,&irq13); // 协处理器
outb_p(inb_p(0x21)&0xfb,0x21); // 允许 IRQ2 中断请求
outb(inb_p(0xA1)&0xdf,0xA1); // 允许 IRQ13 中断请求
set_trap_gate(39,¶llel_interrupt); // 并口
}
代码路径:include\asm\system.h
...
#define _set_gate(gate_addr,type,dpl,addr) \
__asm__("movw %%dx,%%ax\n\t" \ // 将 edx 的低字赋值给 eax 的低字
"movw %0,%%dx\n\t" \ //%0 对应第二个冒号后的第 1 行的 "i"
"movl %%eax,%1\n\t" \ //%1 对应第二个冒号后的第 2 行的 "o"
"movl %%edx,%2" \ //%2 对应第二个冒号后的第 3 行的 "o"
: \ // 这个冒号后面是输出,下面冒号后面
// 是输入
: "i" ((short) (0x8000 + (dpl<<13) + (type<<8))), \ // 立即数
"o" (*((char *) (gate_addr))), \ // 中断描述符前 4 个字节的地址
"o" (*(4 + (char *) (gate_addr))), \ // 中断描述符后 4 个字节的地址
"d" ((char *) (addr)),"a" (0x00080000)) //"d" 对应 edx,"a" 对应 eax
...
#define set_intr_gate(n,addr) \
_set_gate(&idt[n],14,0,addr)
#define set_trap_gate(n,addr) \
_set_gate(&idt[n],15,0,addr)
#define set_system_gate(n,addr) \
_set_gate(&idt[n],15,3,addr)
首先看下图
Selector为0x0008,Offset为中断函数的偏移。set_trap_gate,P为1,DPL为00,TYPE为F。set_intr_gate,P为1,DPL为00,TYPE为E。set_system_gate,P为1,DPL为11,TYPE为F。
代码路径:init/main.c
void main(void)
{
...
blk_dev_init();
...
}
代码路径:kernel/blk_dev/blk.h
...
#define NR_REQUEST 32
struct request {
int dev; /* -1 if no request */
int cmd; /* READ or WRITE */
int errors;
unsigned long sector;
unsigned long nr_sectors;
char * buffer;
struct task_struct * waiting;
struct buffer_head * bh;
struct request * next; // 说明 request 可以构成链表
};
...
代码路径:kernel/blk_dev/ll_rw_block.c
...
struct request request[NR_REQUEST]; // 数组链表
...
void blk_dev_init(void)
{
int i;
for (i=0;i<NR_REQUEST;i++) {
request[i].dev= -1; // 设置为空闲
request[i].next= NULL; // 互不挂接
}
}
代码路径:init/main.c
void main(void)
{
...
tty_init();
...
}
代码路径:kernel/chr_dev/tty_io.c
void tty_init(void)
{
rs_init();
con_init()
}
代码路径:kernel/chr_dev/serial.c
void rs_init(void)
{
set_intr_gate(0x24,rs1_interrupt); // 设置串行口 1 中断,参看上图
set_intr_gate(0x23,rs2_interrupt); // 设置串行口 2 中断
init(tty_table[1].read_q.data); // 初始化串行口 1
init(tty_table[2].read_q.data); // 初始化串行口 2
outb(inb_p(0x21)&0xE7,0x21); // 允许 IRQ3,IRQ4
}
代码路径:kernel/chr_dev/console.c
...
void con_init(void)
{
...
set_trap_gate(0x21,&keyboard_interrupt);// 设置键盘中断,参看 2.5 节
outb_p(inb_p(0x21)&0xfd,0x21);// 允许 IRQ1
a=inb_p(0x61);
outb_p(a|0x80,0x61); // 禁止键盘工作
outb(a,0x61); // 再允许键盘工作
}
省略了很多初始化显示设备的工作,这部分主要是通过读0x90000~0x9000C的内容来初始化显示器,初始化完成后,原来的0x90000~0x901FE就没有用了,下面会被用作高速缓冲区。
代码路径:init/main.c
void main(void)
{
...
time_init();
...
}
time_init()的代码省略,从机器中读取开机时间。
代码路径:init/main.c
void main(void)
{
...
sched_init();
...
}
代码路径:init/linux/sched.h
...
struct tss_struct {
long back_link; /* 16 high bits zero */
long esp0;
long ss0; /* 16 high bits zero */
long esp1;
long ss1; /* 16 high bits zero */
long esp2;
long ss2; /* 16 high bits zero */
long cr3;
long eip;
long eflags;
long eax,ecx,edx,ebx;
long esp;
long ebp;
long esi;
long edi;
long es; /* 16 high bits zero */
long cs; /* 16 high bits zero */
long ss; /* 16 high bits zero */
long ds; /* 16 high bits zero */
long fs; /* 16 high bits zero */
long gs; /* 16 high bits zero */
long ldt; /* 16 high bits zero */
long trace_bitmap; /* bits: trace 0, bitmap 16-31 */
struct i387_struct i387;
};
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;
};
/*
* INIT_TASK is used to set up the first task table, touch at
* your own risk!. Base=0, limit=0x9ffff (=640kB)
*/
#define INIT_TASK \
/* state etc */ { 0,15,15, \ //就绪态,15个时间片
/* signals */ 0,{{},},0, \
/* ec,brk... */ 0,0,0,0,0,0, \
/* pid etc.. */ 0,-1,0,0,0, \ //进程0
/* uid etc */ 0,0,0,0,0,0, \
/* alarm */ 0,0,0,0,0,0, \
/* math */ 0, \
/* fs info */ -1,0022,NULL,NULL,NULL,0, \
/* filp */ {NULL,}, \
{ \
{0,0}, \
/* ldt */ {0x9f,0xc0fa00}, \
{0x9f,0xc0f200}, \
}, \
/*tss*/ {0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\
0,0,0,0,0,0,0,0, \
0,0,0x17,0x17,0x17,0x17,0x17,0x17, \
_LDT(0),0x80000000, \
{} \
}, \
}
...
#define FIRST_TSS_ENTRY 4
#define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
#define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))
#define ltr(n) __asm__("ltr %%ax"::"a" (_TSS(n)))
#define lldt(n) __asm__("lldt %%ax"::"a" (_LDT(n)))
...
代码路径:include/asm/system.h
...
#define _set_tssldt_desc(n,addr,type) \ // 嵌入汇编参看 trap_init 的注释
__asm__ ("movw $104,%1\n\t" \ // 将 104,即 1101000 存入描述符的第 1、2 字节
"movw %%ax,%2\n\t" \ // 将 tss 或 ldt 基地址的低 16 位存入描述符的第
//3、4 字节
"rorl $16,%%eax\n\t" \ // 循环右移 16 位,即高、低字互换
"movb %%al,%3\n\t" \ // 将互换完的第 1 字节,即地址的第 3 字节存入第 5 字节
"movb $" type ",%4\n\t" \ // 将 0x89 或 0x82 存入第 6 字节
"movb $0x00,%5\n\t" \ // 将 0x00 存入第 7 字节
"movb %%ah,%6\n\t" \ // 将互换完的第 2 字节,即地址的第 4 字节存入第 8 字节
"rorl $16,%%eax" \ // 复原 eax
::"a" (addr), "m" (*(n)), "m" (*(n + 2)), "m" (*(n + 4)), \
"m" (*(n + 5)), "m" (*(n + 6)), "m" (*(n + 7)) \
//"m" (*(n)) 是 gdt 第 n 项描述符的地址开始的内存单元
//"m" (*(n + 2)) 是 gdt 第 n 项描述符的地址向上第 3 字节开始的内存单元
// 其余依此类推
)
//n :gdt 的项值,addr : tss 或 ldt 的地址,0x89 对应 tss,0x82 对应 ldt
#define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x89")
#define set_ldt_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x82")
...
代码路径:/linux/include/linux/head.h
...
typedef struct desc_struct {
unsigned long a,b;
} desc_table[256];
...
代码路径:kernel/sched.c
...
#define LATCH (1193180/HZ) // 每个时间片的振荡次数
...
union task_union { // task_struct 与内核栈的共用体
struct task_struct task;
char stack[PAGE_SIZE]; // PAGE_SIZE 是 4 KB
};
static union task_union init_task= {INIT_TASK,};// 进程 0 的 task_struct
...
// 初始化进程槽 task[NR_TASKS] 的第一项为进程 0,即 task[0] 为进程 0 占用
struct task_struct * task[NR_TASKS]= {&(init_task.task), };
...
void sched_init(void)
{
int i;
struct desc_struct * p;
...
set_tss_desc(gdt + FIRST_TSS_ENTRY,&(init_task.task.tss));// 设置 TSS0
set_ldt_desc(gdt + FIRST_LDT_ENTRY,&(init_task.task.ldt));// 设置 LDT0
p= gdt + 2+FIRST_TSS_ENTRY; // 从 GDT 的 6 项,即 TSS1 开始向上全部清零,并且将进程槽从
for(i=1;i<NR_TASKS;i++) { //1 往后的项清空。0 项为进程 0 所用 NR_TASKS=64
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 */
...
ltr(0); // 重要!将 TSS 挂接到 TR 寄存器
lldt(0); // 重要!将 LDT 挂接到 LDTR 寄存器
outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */// 设置定时器
outb_p(LATCH & 0xff , 0x40); /* LSB */ // 每 10 毫秒一次时钟中断
outb(LATCH >> 8 , 0x40); /* MSB */
set_intr_gate(0x20,&timer_interrupt); // 重要!设置时钟中断,进程调度的基础
outb(inb_p(0x21)& ~ 0x01,0x21); // 允许时钟中断
set_system_gate(0x80,&system_call); // 重要!设置系统调用总入口
}
...
LDT如下图所示:
{0x9f,0xc0fa00}被分解为上面的格式,那就是
00 c0 fa 00 00 00 00 9f
第一个LDT数据全为0
第二个LDT数据如下:
Segment Base为0x0
Segment Limit为159*4K=636K
G=1 表示界限粒度为4K 字节
D=1 表示是32位
P=1 表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中
DPL为11,表示用户态
DT=1,存储段描述符
TYPE为a,存储段,执行/读
第三个LDT数据只是TYPE为2,数据段,读/写
set_tss_desc(gdt + FIRST_TSS_ENTRY,&(init_task.task.tss));// 设置 TSS0
Segment Base:tss0的基地址
Segment Limit为104
G=0 D=0 AVL = 0 P=1 DPL=00
DT=0 为系统段描述符
TYPE为9,可用386TSS
set_ldt_desc(gdt + FIRST_LDT_ENTRY,&(init_task.task.ldt));// 设置 LDT0
和上面的主要区别是
TYPE为2,LDT
ltr(0)
LDTR选择器为4<<3=0x20,TI=0 RPL=00
TR选择器为5<<3=0x28,TI=0 RPL=00
高速缓存寄存器中存的就是上面刚刚说的系统段描述符
其中,
p->a=p->b=0;//清TSS
p++;
p->a=p->b=0;//清LDT
p++;
此段代码的意思是把TSS1,LDT1,TSS2,LDT2.........TSS63,LDT63全部清零
代码路径:init/main.c
void main(void)
{
...
buffer_init(buffer_memory_end);//0x3FFFFF
...
}
代码路径:linux/include/linux/fs.h
...
struct buffer_head {
char * b_data; /* pointer to data block (1024 bytes) */
unsigned long b_blocknr; /* block number */
unsigned short b_dev; /* device (0 = free) */
unsigned char b_uptodate;
unsigned char b_dirt; /* 0-clean,1-dirty */
unsigned char b_count; /* users using this block */
unsigned char b_lock; /* 0 - ok, 1 -locked */
struct task_struct * b_wait;
struct buffer_head * b_prev;
struct buffer_head * b_next;
struct buffer_head * b_prev_free;
struct buffer_head * b_next_free;
};
...
代码路径:fs/buffer.c
...
struct buffer_head * start_buffer= (struct buffer_head *) &end;
struct buffer_head * hash_table[NR_HASH];
static struct buffer_head * free_list;
...
void buffer_init(long buffer_end)
{
struct buffer_head * h= start_buffer;
void * b;
int i;
if (buffer_end== 1<<20)
b= (void *) (640*1024);
else
b= (void *) buffer_end;
//h、b 分别从缓冲区的低地址端和高地址端开始,每次对进 buffer_head、缓冲块各一个
// 忽略剩余不足一对 buffer_head、缓冲块的空间
while ( (b -= BLOCK_SIZE) >= ((void *) (h + 1)) ) {
h->b_dev= 0;
h->b_dirt= 0;
h->b_count= 0;
h->b_lock= 0;
h->b_uptodate= 0;
h->b_wait= NULL;
h->b_next= NULL; // 这两项初始化为空,后续的使用将与 hash_table 挂接
h->b_prev= NULL;
h->b_data= (char *) b; // 每个 buffer_head 关联一个缓冲块
h->b_prev_free= h-1; // 这两项使 buffer_head 分别与前、
h->b_next_free= h + 1; // 后 buffer_head 挂接,形成双向链表,因为地址本身就放在那,虽然还没有后一项,也能提前链接上
h++;
NR_BUFFERS++;
if (b== (void *) 0x100000) // 避开 ROMBIOS&VGA
b= (void *) 0xA0000;
}
h--;//多加了一个h,要减去
free_list= start_buffer; // free_list 指向第一个 buffer_head
free_list->b_prev_free= h; // 使 buffer_head 双向链表,第一个h的前一个原来没有地址
h->b_next_free= free_list; // 形成双向环链表,最后一个h的后一个没有地址
for (i=0;i<NR_HASH;i++) // 清空 hash_table[307]
hash_table[i]=NULL;
}
先看下图:
形成双向链表
start_buffer是内核system模块结束的位置,buffer_end为0x3FFFFF,从内核的末端及buffer_end同时开始,方向相对增长、配对 地做出 buffer_head、缓冲块,直到不足一对 buffer_head、缓冲块
代码路径:init/main.c
...
#define MAJOR_NR 3
...
void main(void)
{
...
hd_init();
...
}
代码路径:
kernel/blk_drv/hd.c
void hd_init(void)
{
blk_dev[MAJOR_NR].request_fn= DEVICE_REQUEST;// 挂接 do_hd_request()
set_intr_gate(0x2E,&hd_interrupt); // 设置硬盘中断
outb_p(inb_p(0x21)&0xfb,0x21); // 允许 8259A 发出中断请求
outb(inb_p(0xA1)&0xbf,0xA1); // 允许IRQ14
}
代码路径:init/main.c
...
#define MAJOR_NR 2
...
void main(void)
{
...
floppy_init();
...
}
代码路径:
kernel/floppy.c
...
void floppy_init(void)
{
blk_dev[MAJOR_NR].request_fn= DEVICE_REQUEST; // 挂接 do_fd_request()
set_trap_gate(0x26,&floppy_interrupt); // 设置软盘中断
outb(inb_p(0x21)& ~ 0x40,0x21); // 允许IRQ6
}
代码路径:init/main.c 开中断
void main(void)
{
...
sti();
...
}
代码路径:init/main.c
void main(void)
{
...
move_to_user_mode();
...
}
代码路径:include/system.h
#define move_to_user_mode() \ // 模仿中断硬件压栈,顺序是 ss、esp、eflags、cs、eip
__asm__("movl %%esp,%%eax\n\t" \
"pushl $0x17\n\t" \ //SS 进栈,0x17 即二进制的 10111(3 特权级、LDT、数据段)
"pushl %%eax\n\t" \ //ESP 进栈
"pushfl\n\t" \ //EFLAGS 进栈
"pushl $0x0f\n\t" \ //CS 进栈,0x0f 即 1111(3 特权级、LDT、代码段)
"pushl $1f\n\t" \ //EIP 进栈
"iret\n" \ // 出栈恢复现场、翻转特权级从 0 到 3
"1:\tmovl $0x17,%%eax\n\t" \ // 下面的代码使 ds、es、fs、gs 与 ss 一致
"movw %%ax,%%ds\n\t" \
"movw %%ax,%%es\n\t" \
"movw %%ax,%%fs\n\t" \
"movw %%ax,%%gs" \
:::»ax»)
看下图:
和0特权级本质的变化的就是这些选择器和高速缓存寄存器不同了,现在是3特权级,而且从LDT取得描述符。