Linux中1号进程是由0号进程来创建的,因此必须要知道的是如何创建0号进程,由于在创建进程时,程序一直运行在内核态,而进程运行在用户态,因此创建0号进程涉及到特权级的变化,即从特权级0变到特权级3,Linux是通过模拟中断返回来实现特权级的变化以及创建0号进程,通过将0号进程的代码段选择子以及程序计数器EIP直接压入内核态堆栈,然后利用iret汇编指令中断返回跳转到0号进程运行。
代码如下:
move_to_user_mode();//创建0号进程,开始进入0号进程,切换到特权级3运行
if (!fork()) {init();}//创建1号进程
跟踪代码:
#define move_to_user_mode() \
__asm__ ("movl %%esp,%%eax\n\t" \//将esp寄存器的内容存入eax中
"pushl $0x17\n\t" \//压入0号任务的数据段选择符
"pushl %%eax\n\t" \//压入堆栈指针
"pushfl\n\t" \//压入标志寄存器
"pushl $0x0f\n\t" \//压入0号任务的代码段选择符
"pushl $1f\n\t" \//压入EIP,即切换到0号任务后CPU运行的位置
"iret\n" \//中断返回指令
"1:\tmovl $0x17,%%eax\n\t" \//由于发生了切换,需要更改各段寄存器
"movw %%ax,%%ds\n\t" \//更改段寄存器ds
"movw %%ax,%%es\n\t" \//更改段寄存器es
"movw %%ax,%%fs\n\t" \//更改段寄存器fs
"movw %%ax,%%gs" \//更改段寄存器gs
:::"ax")
分析如下,注释已经很清楚:
代码为嵌入汇编语句的C程序,::”ax”表示的是输出为空,输入为空,在这个宏定义的执行过程中可以发生改变的是ax寄存器,这属于GNU的gas语法,不作解释
0x17与0x0f的真实意义,跟踪查看前先写成二进制形式
0x17=0000 0000 0001 0111
0x0f=0000 0000 0000 1111
0x17与0x0f的后三们均是111,段选择子的后三位分别表示RPL以及TI,因此后三位即表示请示特权级为3,描述符在LDT中,故0x17与0x0f分别表示LDT中的第二项与第一项,即然是LDT表,在使用之前肯定要进行初始化,帮初始化代码肯定在move_to_user_mode之前,跟踪分析可以发现在sched_init中,源码如下:
void sched_init(void)
{
int i;
//desc_struct表示是描述符表类型typedef struct desc_struct{a,b}desc_table[256];
struct desc_struct * p;
if (sizeof(struct sigaction) != 16)
panic("Struct sigaction MUST be 16 bytes");
//这里开始是关键部分,gdt是全局描述符表的基地址
//FIRST_TSS_ENTRY与FIRST_LDT_ENTRY分别是4,5即全局描述符表中的第4项
//与第五项代表的是第一个任务,对其进行设置
//查看static union task_union init_task = {INIT_TASK,};可以看到INIT_TASK可以看到//INIT_TASK是个宏定义,即下面的注释
set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
//p指向GDT表中0号任务的下一个位置,即GDT表中第6项
p = gdt+2+FIRST_TSS_ENTRY;
//NR_TASKS是Linux 0.11中最多支持的进程数64个
for(i=1;i<NR_TASKS;i++) {
task[i] = NULL;
//重复两次是因为每个进程对应一个LDT与一个TSS
p->a=p->b=0;
p++;
p->a=p->b=0;
p++;
}
//将标志寄存器的NT位禁止
__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
//#define ltr(n) __asm__("ltr %%ax"::"a" (_TSS(n)))这是宏定义,很显然吧
//加载当前的任务寄存器与ldtr寄存器
ltr(0);
//#define lldt(n) __asm__("lldt %%ax"::"a" (_LDT(n)))
lldt(0);
//定时器8253的初始化
outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */
//#define LATCH (1193180/HZ),用此设置后时钟中断为每10ms一次
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);
//备注:
//定时器有三个锁存器,他们各有其则,锁存器0用于维护系统时钟,地址为0x40
//锁存器1用于周期性的向DMA发送数据信号,供存储器刷新用,地址为0x41
//锁存器2用于扬声器发出声音,地址为0x42,因此这里向0x40设定值
}