Linux SMP啟動過程

來源:互聯網
上載者:User

1.        SMP 硬體體繫結構:

對於 SMP 最簡單可以理解為系統存在多個完全相同的 CPU ,所有 CPU 共用匯流排,擁有自己的寄存器。對於記憶體和外部裝置訪問,由於共用匯流排,所以是共用的。 Linux 作業系統多個 CPU 共用在系統空間上映射相同,是完全對等的。

 

由於系統中存在多個 CPU ,這是就引入一個問題,當外部裝置產生中斷的時候,具體有哪一個 CPU 進行處理?

為此, intel 公司提出了 IO APCI 和 LOCAL APCI 的體繫結構。

IO APIC 串連各個外部裝置,並可以設定分發類型,根據設定的分發類型,中斷訊號發送的對應 CPU 的 LOCAL APIC上。

LOCAL APIC 負責本地 CPU 的中斷處理, LOCAL APIC 不僅可以接受 IO APIC 的中斷,也需要處理本地 CPU 產生的異常。同時 LOCAL APIC 還提供了一個定時器。

 

如何確定那個 CPU 是引導 CPU ?

根據 intel 公司中的資料,系統上電後,會根據 MP Initialization Protocol 隨機播放一個 CPU 作為 BSP ,只有 BSP 會運行BIOS 程式,其他 AP 都進入等待狀態, BSP 發送 IPI 中斷觸發後才可以運行。具體的 MP Initialization Protocol 細節,可以參考 Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming
Guide, Part 1 第 8 章。

 

引導 CPU 如何控制其他 CPU 開始運行?

BSP 可以通過 IPI 訊息控制 AP 從指定的起始地址運行。 CPU 中整合的 LOCAL APIC 提供了這個功能。可以通過寫LOCAL APIC 中提供的相關寄存器,發送 IPI 訊息到指定的 CPU 上。

 

如何擷取系統硬體 CPU 資訊的?

在系統初始化後,硬體會在記憶體的規定位置提供關於 CPU ,匯流排 , IO APIC 等的資訊,即 SMP MP table 。在 linux 初始化的過程,會讀取該位置,擷取系統相關的硬體資訊。

 

2.        linux SMP 啟動過程流程簡介

setup_arch()

       setup_memory();

              reserve_bootmem(PAGE_SIZE, PAGE_SIZE);

              find_smp_config();  // 尋找 smp mp table 的位置

       smp_alloc_memory();

              trampoline_base = (void *) alloc_bootmem_low_pages(PAGE_SIZE); // 分配 trampoline ,用於啟動 AP 的引導代碼。

       get_smp_config();  // 根據 smp mp table ,擷取具體的硬體資訊

trap_init()

init_apic_mappings();

mem_init()

       zap_low_mappings(); 如果沒有定義 SMP 的話,清楚使用者空間的地址映射。

rest_init();

kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);

init();

set_cpus_allowed(current, CPU_MASK_ALL);

smp_prepare_cpus(max_cpus);

              smp_boot_cpus(max_cpus);

                     connect_bsp_APIC();

setup_local_APIC();  // 初始化 BSP 的 LOCAL APCI 。

map_cpu_to_logical_apicid();

       針對每個 CPU 調用 do_boot_cpu(apicid, cpu)

       smp_init();     // 每個 CPU 開始進行調度

 

trampoline.S AP 引導代碼,為 16 進位代碼,啟用保護模式

head.s 為 AP 建立分頁管理

initialize_secondary 根據之前 fork 建立設定的資訊,跳轉到 start_secondary 處

start_secondary 判斷 BSP 是否啟動,如果啟動 AP 進行任務調度。

 

3.        代碼學習總結

find_smp_config(); ,尋找 MP table 在記憶體中的位置。具體協議可以參考 MP 協議的第 4 章。

這個表的作用在於描述系統 CPU ,匯流排, IO APIC 等的硬體資訊。

相關的兩個全域變數: smp_found_config 是否找到 SMP MP table , mpf_found SMP MP table 的線性地址。

 

smp_alloc_memory() 為啟動 AP 的啟動程式分配記憶體空間。相關全域變數 trampoline_base ,分配的啟動地址的線性地址。

 

get_smp_config() 根據 MP table 中提供的內容,擷取硬體的資訊。

 

init_apic_mappings(); 擷取 IO APIC 和 LOCAL APIC 的映射地址 。

 

zap_low_mappings(); 如果沒有定義 SMP 的話,清楚使用者空間的地址映射。將 swapper_pg_dir 中表項清零。

 

setup_local_APIC();  初始化 BSP 的 LOCAL APCI 。

 

do_boot_cpu(apicid, cpu)

       idle = alloc_idle_task(cpu);

              task = copy_process(CLONE_VM, 0, idle_regs(&regs), 0, NULL, NULL, 0);

              init_idle(task, cpu);

將 init 進程使用 copy_process 複製,並且調用 init_idle 函數,設定可以啟動並執行 CPU 。

       idle->thread.eip = (unsigned long) start_secondary;

修改 task_struct 中的 thread.eip ,使得 AP 初始化完成後,就運行 start_secondary 函數。

 

start_eip = setup_trampoline();

調用 setup_trampoline() 函數,複製 trampoline_data 到 trampoline_end 之間的代碼到 trampoline_base 處, trampoline_base 就是之前在 setup_arch 處申請的記憶體。 start_eip 傳回值是 trampoline_base 對應的物理地址。

 

smpboot_setup_warm_reset_vector(start_eip); 設定記憶體 40:67h 處為 start_eip 為啟動地址。

 

wakeup_secondary_cpu(apicid, start_eip); 在這個函數中通過操作 APIC_ICR 寄存器, BSP 向目標 AP 發送 IPI 訊息,觸發目標 AP 從 start_eip 地址處,從實模式開始運行。

 

trampoline.S

       ENTRY(trampoline_data)

r_base = .

       wbinvd                  # Needed for NUMA-Q should be harmless for others

       mov %cs, %ax       # Code and data in the same place

       mov %ax, %ds

 

       cli                  # We should be safe anyway

 

       movl       $0xA5A5A5A5, trampoline_data - r_base

這個是設定標識,以便 BSP 知道 AP 運行到這裡了。

 

       lidtl boot_idt - r_base    # load idt with 0, 0

       lgdtl boot_gdt - r_base   # load gdt with whatever is appropriate

載入 ldt 和 gdt

 

       xor  %ax, %ax

       inc   %ax        # protected mode (PE) bit

       lmsw       %ax        # into protected mode

       # flush prefetch and jump to startup_32_smp in arch/i386/kernel/head.S

       ljmpl       $__BOOT_CS, $(startup_32_smp-__PAGE_OFFSET)

啟動保護模式,跳轉到 startup_32_smp 處

 

       # These need to be in the same 64K segment as the above;

       # hence we don't use the boot_gdt_descr defined in head.S

boot_gdt:

       .word      __BOOT_DS + 7                 # gdt limit

       .long       boot_gdt_table-__PAGE_OFFSET       # gdt base

 

boot_idt:

       .word      0                          # idt limit = 0

       .long       0                          # idt base = 0L

 

.globl trampoline_end

trampoline_end:

在這段代碼中,設定標識,以便 BSP 知道該 AP 已經運行到這段代碼,載入 GDT 和 LDT 表基址。

然後啟動保護模式,跳轉到 startup_32_smp 處。

 

Head.s 部分代碼:

ENTRY(startup_32_smp)

       cld

       movl $(__BOOT_DS),%eax

       movl %eax,%ds

       movl %eax,%es

       movl %eax,%fs

       movl %eax,%gs

 

       xorl %ebx,%ebx

       incl %ebx

如果是 AP 的話,將 bx 設定為 1

 

       movl $swapper_pg_dir-__PAGE_OFFSET,%eax

       movl %eax,%cr3           /* set the page table pointer.. */

       movl %cr0,%eax

       orl $0x80000000,%eax

       movl %eax,%cr0           /* ..and set paging (PG) bit */

       ljmp $__BOOT_CS,$1f /* Clear prefetch and normalize %eip */

啟用分頁,

 

       lss stack_start,%esp

使 esp 執行 fork 建立的進程核心堆棧部分,以便後續跳轉到 start_secondary

 

#ifdef CONFIG_SMP

       movb ready, %cl

       movb $1, ready

       cmpb $0,%cl

       je 1f               # the first CPU calls start_kernel

                            # all other CPUs call initialize_secondary

       call initialize_secondary

       jmp L6

1:

#endif /* CONFIG_SMP */

       call start_kernel

 

如果是 AP 啟動的話,就調用 initialize_secondary 函數。

void __devinit initialize_secondary(void)

{

       /*

         * We don't actually need to load the full TSS,

         * basically just the stack pointer and the eip.

         */

 

       asm volatile(

              "movl %0,%%esp/n/t"

              "jmp *%1"

              :

              :"r" (current->thread.esp),"r" (current->thread.eip));

}

設定堆棧為 fork 建立時的堆棧, ip 為 fork 時的 ip ,這樣就跳轉的了 start_secondary 。

 

start_secondary 函數中處理如下:

       while (!cpu_isset(smp_processor_id(), smp_commenced_mask))

              rep_nop();

進行 smp_commenced_mask 判斷,是否啟動 AP 運行。 smp_commenced_mask 在 smp_init() 中設定。

       cpu_idle();

如果啟動了,調用 cpu_idle 進行任務調度。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.