歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> Linux中0號進程的創建剖析

Linux中0號進程的創建剖析

日期:2017/2/27 15:58:46   编辑:Linux教程
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設定值
}
INIT_TASK宏定義,其實就是0號任務,看起來比較混亂,其實就是初始化task_struct結構體
#define INIT_TASK \
//0表示可運行的,15表示運行時間片,15表示運行優化級
/* state etc */    { 0,15,15, \
//0表示沒有信號,{{}}信號處理句柄設為0,0表示不屏蔽信號
/* signals */    0,{{},},0, \//初始化信號設置
//將exit_code以及start_code,end_code,end_data,brk,start_stack均設為0
/* ec,brk... */    0,0,0,0,0,0, \
//0表示進程號,-1表示父進程,後面三個0表示,pgrp,session,leader
/* pid etc.. */    0,-1,0,0,0, \
//設置進程的這6個成員unsigned short uid,euid,suid; unsigned short gid,egid,sgid;
/* uid etc */    0,0,0,0,0,0, \
//設置進程的報警定時器以及5個時間函數
/* alarm */    0,0,0,0,0,0, \
//表明該進程未使用協處理器
/* math */    0, \
/* fs info */    -1,0022,NULL,NULL,NULL,0, \
/* filp */    {NULL,}, \
//這裡就是很關鍵的一部份,表始初始化一個局部LDT表,即第一個任務的
    { \
        {0,0}, \
/* ldt */    {0x9f,0xc0fa00}, \
        {0x9f,0xc0f200}, \
    }, \
//第一個任務的任務狀態表,跟蹤struct tss_struct可以知道其詳細意義
/*tss*/    {0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\
,0,0,0,0,0,0,0, \
,0,0x17,0x17,0x17,0x17,0x17,0x17, \
     _LDT(0),0x80000000, \
        {} \
    }, \
}
到這了,也差不多了,額外的部份再看看第一個任務的LDT表與TSS表,由上面可知0號任務的LDT的代碼段與數據段分別為{0x9f,0xc0fa00}與{0x9f,0xc0f200},根據保護模式下的定義,可以代碼段的段基址為0,段限長為640KB,段屬性為存在於內存中、特權級為3,代碼段,同理分析得數據段的段基址為0,段限長為640KB,段屬性為存在於內存中,特權級為3,數據段

第一個任務的狀態表,提一下吧
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;
};
根據這個表可以看到的是任務0的內核態堆棧指針esp0=PAGE_SIZE+(long)&init_task,即第一個PCB塊(task_struct)的頂部空間,PAGE_SIZE=4k,ss0=0x10,0x10查一下head.s你就會發現是gdt的第一個描述符,即內核代碼段,後面還有幾個是對數據段寄存器的定義以及ldt的定義
Copyright © Linux教程網 All Rights Reserved