Linux-0.11 kernel目錄fork.c詳解

程序員小x 發佈 2024-04-30T13:40:13.746880+00:00

fork.c中主要實現內核對於創建新的進程的行為。其中copy_process是其最核心的函數。

fork.c中主要實現內核對於創建新的進程的行為。其中copy_process是其最核心的函數。

copy_process

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
                long ebx,long ecx,long edx,
                long fs,long es,long ds,
                long eip,long cs,long eflags,long esp,long ss)

該函數的作用是從old進程中複製出一個new進程。 該函數在system_call.s中的sys_fork函數中執行。

copy_process從INT 0x80中斷觸發system_call系統調用,進而調用sys_fork。此時內核棧的狀態如下所示:

這與copy_process的參數是一致的。

該函數首先在內存中分配了一個空閒頁用於存儲進程的PCB,即task_struct結構。並將該PCB放入了PCB的數組中。

最後將old進程的PCB內容先直接拷貝給new進程。

p = (struct task_struct *) get_free_page();
if (!p)
    return -EAGAIN;
task[nr] = p;   
*p = *current;  

下面這段就是將繼承來的PCB結構進行適當的修改,詳細解釋見注釋。

p->state = TASK_UNINTERRUPTIBLE;//設置進程狀態為不可被中斷
p->pid = last_pid;//last_pid為find_empty_process找到的沒有被使用的pid值, 將其設置給新的進程
p->father = current->pid;//設置該進程的父進程
p->counter = p->priority;//設置該進程的時間片, 值等於其優先級的值。
p->alarm = 0; //alarm定時的時間
p->leader = 0;       //是否是進程組的leader
p->utime = p->stime = 0; //用戶態運行時間和和核心態運行時間
p->cutime = p->cstime = 0;//子進程用戶態運行時間和核心態運行時間。
p->start_time = jiffies;//進程的開始時間設置為系統的滴答數。

下面一段是設置PCB中有關TSS寄存器的值。下面也通過注釋進行詳解。

首先設置了內核棧的棧

p->tss.back_link = 0;
p->tss.esp0 = PAGE_SIZE + (long) p;//進程的內核棧棧頂指針
p->tss.ss0 = 0x10;//內核棧的段選擇符

接下來是設置tss寄存器關於其他cpu寄存器的值。

p->tss.eip = eip;
p->tss.eflags = eflags;
p->tss.eax = 0;
p->tss.ecx = ecx;
p->tss.edx = edx;
p->tss.ebx = ebx;
p->tss.esp = esp;
p->tss.ebp = ebp;
p->tss.esi = esi;
p->tss.edi = edi;
p->tss.es = es & 0xffff;     //段寄存器取16位
p->tss.cs = cs & 0xffff;
p->tss.ss = ss & 0xffff;
p->tss.ds = ds & 0xffff;
p->tss.fs = fs & 0xffff;
p->tss.gs = gs & 0xffff;

下面這裡,設置tss中ldt的值。

p->tss.ldt = _LDT(nr);
p->tss.trace_bitmap = 0x80000000;

GDT表中每一項是8個字節,每個進程擁有一個TSS和LDT,因此每個進程占用字節是16位元組, 因此序號為n的進程的LDT在GDT表中的偏移量就是n*16 + 5*8

#define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))

對上述知識遺忘,可以通過下面這張圖進行溫故。

下面這裡進程內存的拷貝, 實際上確定進行進程新的線性地址, 並進行頁表的拷貝。詳見本文中copy_mem的講解。

if (copy_mem(nr,p)) {
    task[nr] = NULL;
    free_page((long) p);
    return -EAGAIN;

下面主要處理對進程打開的文件的引用計數增加1。

for (i=0; i<NR_OPEN;i++)
    if ((f=p->filp[i]))
        f->f_count++;
if (current->pwd)
    current->pwd->i_count++;
if (current->root)
    current->root->i_count++;
if (current->executable)
    current->executable->i_count++;

這裡設置GDT表中tss和ldt描述符的內容。

set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
p->state = TASK_RUNNING;     /* do this last, just in case */

copy_mem

int copy_mem(int nr,struct task_struct * p)

該函數的作用是複製進程的頁表。

code_limit=get_limit(0x0f);//根據代碼段選擇符獲取代碼段的長度
data_limit=get_limit(0x17);//根據數據段選擇符獲取數據段的長度
old_code_base = get_base(current->ldt[1]);//獲取代碼段的起始位置
old_data_base = get_base(current->ldt[2]);//獲取數據段的起始位置
if (old_data_base != old_code_base)  //兩個段起始位置相等
    panic("We don't support separate I&D");
if (data_limit < code_limit)
    panic("Bad data_limit");
//確立新進程的代碼段地址, Linux-0.11的線性地址是按照64M劃分的,所以進程號nr的線性地址的起始位置是nr* 0x4000000
new_data_base = new_code_base = nr * 0x4000000;
p->start_code = new_code_base;  // 設置該位置到PCB中
set_base(p->ldt[1],new_code_base); //設置代碼段的地址
set_base(p->ldt[2],new_data_base); //設置數據段的地址

下面這段代碼是將數據段所屬的頁表的進行。

if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
    printk("free_page_tables: from copy_mem\n");
    free_page_tables(new_data_base,data_limit);
    return -ENOMEM;
}

copy_page_tables在memory.c中定義。

verify_area

void verify_area(void * addr,int size)

該函數用於在進程空間進行寫操作時進行地址驗證的函數。

addr是指在進程線性地址中相對於起始位置的偏移量, size指的是大小。

由於檢測判斷是以4K頁為單位進行操作的,因此程序需要找出addr所在頁的起始地址,如下圖所示。

下面這段代碼就是去尋找addr所在的內存頁的起始地址, 即start。

unsigned long start;

start = (unsigned long) addr;
size += start & 0xfff; //size 加上頁內偏移
start &= 0xfffff000; //start為邏輯地址的以4K為劃分的起始地址
start += get_base(current->ldt[2]);//獲取當前進程在線性地址中數據段的起始地址, 加起來就是該邏輯地址轉化到了線性地址

下面進行防寫驗證, 如果頁面不可以寫,則進行頁面複製。

while (size>0) {
    size -= 4096;
    write_verify(start);
    start += 4096;
}

write_verify函數詳解可以參考memory.c文件的講解。

find_empty_process

int find_empty_process(void)

該函數的作用是在全局的task數組中找到一個空閒的項,並返回其下標。其在system_call.s中的sys_fork函數中被調用。

首先是尋找一個pid值

repeat:
    if ((++last_pid)<0) last_pid=1;
    for(i=0 ; i<NR_TASKS ; i++)
        if (task[i] && task[i]->pid == last_pid) goto repeat;

接著是去task數組中尋找可用的位置

for(i=1 ; i<NR_TASKS ; i++)
    if (!task[i])
        return i;
關鍵字: