SMP Önyükleme
Önceki Linux i386 Önyükleme Kodu NASIL Sonraki
SMP Önyükleme
SMP ile ilgili CONFIG_SMP, CONFIG_X86_LOCAL_APIC, CONFIG_X86_IO_APIC, CONFIG_MULTIQUAD ve CONFIG_VISWS gibi bir kaç makro bulunmaktadır. Birçok insanın dikkate almadığı (eğer IBM üst-uç çok işlemcili sunucu veya SGI Görsel İşistasyonu kullanmıyorsa) CONFIG_MULTIQUAD veya CONFIG_VISWS gerektiren kodları yoksayacağım.
BSP APleri tetiklemek için start_kernel() -> smp_init() -> smp_boot_cpus() -> do_boot_cpu() -> wakeup_secondary_via_INIT()'i çalıştırır. Teknik ayrıntılar için bakınız: MultiProcessor Specification ve IA-32 Manual Vol.3 (Ch.7. Multile-Processor Management, and Ch.8. Advanced Programmable Interrupt Controller).
smp_init() Öncesi
smp_init()'i çağırmadan önce, start_kernel() SMP ortamını kurmak için birşeyler yapar.
start_kernel()
|-- setup_arch()
|   |-- parse_cmdline_early();  // SMP "noht" ve "acpismp=force" arar
|   |   `-- /* "noht" çoklu işlem (HyperThreading) özelliğini iptal eder
|   |        * (Xeon başına 2 mantıksal işlemci) */
|   |       if (!memcmp(from, "noht", 4)) {
|   |           disable_x86_ht = 1;
|   |           set_bit(X86_FEATURE_HT, disabled_x86_caps);
|   |       }
|   |       /* "acpismp=force" ACPI SMP tablosunu kullanıma ve
|   |        * çözümlemeye zorlar */
|   |       else if (!memcmp(from, "acpismp=force", 13))
|   |           enable_acpi_smp_table = 1;
|   |-- setup_memory();         // MP yapılandırma tablosu için bellek ayırır
|   |   |-- reserve_bootmem(PAGE_SIZE, PAGE_SIZE);
|   |   `-- find_smp_config();
|   |       `-- find_intel_smp();
|   |           `-- smp_scan_config();
|   |               |-- set flag smp_found_config
|   |               |-- set MP floating pointer mpf_found
|   |               `-- reserve_bootmem(mpf_found, PAGE_SIZE);
|   |-- if (disable_x86_ht) {   // çoklu işlem iptal edilmişse
|   |       clear_bit(X86_FEATURE_HT, &boot_cpu_data.x86_capability[0]);
|   |       set_bit(X86_FEATURE_HT, disabled_x86_caps);
|   |       enable_acpi_smp_table = 0;
|   |   }
|   |-- if (test_bit(X86_FEATURE_HT, &boot_cpu_data.x86_capability[0]))
|   |       enable_acpi_smp_table = 1;
|   |-- smp_alloc_memory();
|   |   `-- /* AP işlemcilerinin gerçek kip kod uzayını düşük bellekte ayırır */
|   |       trampoline_base = (void *) alloc_bootmem_low_pages(PAGE_SIZE);
|   `-- get_smp_config();     /* önyükleme anı MP yapılandırmasını al */
|       |-- config_acpi_tables();
|       |   |-- memset(&acpi_boot_ops, 0, sizeof(acpi_boot_ops));
|       |   |-- acpi_boot_ops[ACPI_APIC] = acpi_parse_madt;
|       |   `-- /* have_acpi_tables değerini ACPI tablosunda
|       |        * MADT kullanıldığını belirtecek şekilde değiştirir;
|       |        * başarısız olursa MPS kullan. */
|       |       if (enable_acpi_smp_table && !acpi_tables_init())
|       |           have_acpi_tables = 1;
|       |-- set pic_mode
|       |   /* =1, IMCR varsa ve PIC Kipi gerçekleştirilmişse;
|       |    * =0, aksi taktirde Sanal Tel Tipi (Virtual Wire Mode)
|       |    *     gerçekleştirilmiştir. */
|       |-- mp_lapic_addr'deki yerel APIC adresini sakla
|       `-- MP_PROCESSOR, MP_BUS, MP_IOAPIC, MP_INTSRC ve MP_LINTSRC.MP gibi
|             yapılandırma tablo girdilerini tara.
|
|-- trap_init();
|   `-- init_apic_mappings();   // PTE'yi APIC için ayarla
|       |-- /* Eğer hiç yerel APIC bulunmazsa tüm sıfır sayfalarını
|       |    * (zeroes page) yerel APIC'i ve bir de IO-APIC'i
|       |    * taklit edecek şekilde ayarla. */
|       |   if (!smp_found_config && detect_init_APIC()) {
|       |       apic_phys = (unsigned long) alloc_bootmem_pages(PAGE_SIZE);
|       |       apic_phys = __pa(apic_phys);
|       |   } else
|       |       apic_phys = mp_lapic_addr;
|       |-- /* yerel APIC adresini,
|       |    *  çoğu durumda mp_lapic_addr'dir (0xfee00000),
|       |    *  bunu doğrusal FIXADDR_TOP (0xffffe000) adresine eşle */
|       |   set_fixmap_nocache(FIX_APIC_BASE, apic_phys);
|       |-- /* öntanımlı yapılandırmamız (veya MP tablosunun bozuk olması)
|       |    * durumu için BSP'nin APIC ID değerini al. */
|       |   if (boot_cpu_physical_apicid == -1U)
|       |       boot_cpu_physical_apicid = GET_APIC_ID(apic_read(APIC_ID));
|       `-- // IOAPIC adresini arabelleksiz doğrusal adrese eşle
|           set_fixmap_nocache(idx, ioapic_phys);
|       // Artık APIC uzayına erişmek için doğrusal adres kullanabiliriz.
|-- init_IRQ();
|   |-- init_ISA_irqs();
|   |   |-- /* sanal tel kipinin ilk kurulumu. */
|   |   |   init_bsp_APIC();
|   |   `-- init_8259A(auto_eoi=0);
|   `-- setup SMP/APIC interrupt handlers, esp. IPI.
`-- mem_init();
    `-- /* SMP için düşük adresleme geçişini (zapping)
         * geciktir: zap_low_mappings() */
IPI (İşlemcilerarası Kesme - InterProcessor Interrupt), yani işlemciden işlemciye yerel APIC üzerinden kesme, BSP'nin APleri tetiklemek için kullandığı mekanizmadır.
Bilmeniz gerekir ki MP-uyumlu sistemlerde "CPU başına bir yerel APIC gereklidir". İşlemciler APIC yerel birimlerinin adres uzayını (0xFEE00000 - 0xFEEFFFFF fiziksel adresini) paylaşmazlar, fakat APIC giriş-çıkış birimlerini (0xFEC00000 - 0xFECFFFFF) paylaşırlar. Her iki adres uzayı da arabelleklenebilir değildir.
smp_init()
BSP her bir işlemcinin veri yapılarını kurmak ve kalan APleri etkinleştirmek için start_kernel() -> smp_init() -> smp_boot_cpus() çağrısı yapar.
///////////////////////////////////////////////////////////////////////////////
static void __init smp_init(void)
{
  /* Get other processors into their bootup holding patterns. */
  smp_boot_cpus();
  wait_init_idle = cpu_online_map;
  clear_bit(current->processor, &wait_init_idle); /* Don't wait on me! */

  smp_threads_ready=1;
  smp_commence() {
    /* Lets the callins below out of their loop. */
    Dprintk("Setting commenced=1, go go go\n");
    wmb();
    atomic_set(&smp_commenced,1);
  }

  /* Wait for the other cpus to set up their idle processes */
  printk("Waiting on wait_init_idle (map = 0x%lx)\n", wait_init_idle);
  while (wait_init_idle) {
    cpu_relax();    // i.e. "rep;nop"
    barrier();
  }
  printk("All processors have done init_idle\n");
}

///////////////////////////////////////////////////////////////////////////////
void __init smp_boot_cpus(void)
{
  // ... çok ilginç olmayan birşey :-)

  /* Initialize the logical to physical CPU number mapping
    * and the per-CPU profiling router/multiplier */
  prof_counter[0..NR_CPUS-1] = 0;
  prof_old_multiplier[0..NR_CPUS-1] = 0;
  prof_multiplier[0..NR_CPUS-1] = 0;

  init_cpu_to_apicid() {
    physical_apicid_2_cpu[0..MAX_APICID-1] = -1;
    logical_apicid_2_cpu[0..MAX_APICID-1] = -1;
    cpu_2_physical_apicid[0..NR_CPUS-1] = 0;
    cpu_2_logical_apicid[0..NR_CPUS-1] = 0;
  }

  /* Setup boot CPU information */
  smp_store_cpu_info(0); /* Final full version of the data */
  printk("CPU%d: ", 0);
  print_cpu_info(&cpu_data[0]);

  /* We have the boot CPU online for sure. */
  set_bit(0, &cpu_online_map);
  boot_cpu_logical_apicid = logical_smp_processor_id() {
    GET_APIC_LOGICAL_ID(*(unsigned long *)(APIC_BASE+APIC_LDR));
  }
  map_cpu_to_boot_apicid(0, boot_cpu_apicid) {
    physical_apicid_2_cpu[boot_cpu_apicid] = 0;
    cpu_2_physical_apicid[0] = boot_cpu_apicid;
  }

  global_irq_holder = 0;
  current->processor = 0;
  init_idle();    // will clear corresponding bit in wait_init_idle
  smp_tune_scheduling();

  // ... some conditions checked

  connect_bsp_APIC();     // enable APIC mode if used to be PIC mode
  setup_local_APIC();

  if (GET_APIC_ID(apic_read(APIC_ID)) != boot_cpu_physical_apicid)
    BUG();

  /* Scan the CPU present map and fire up the other CPUs
    *   via do_boot_cpu() */
  Dprintk("CPU present map: %lx\n", phys_cpu_present_map);
  for (bit = 0; bit < NR_CPUS; bit++) {
    apicid = cpu_present_to_apicid(bit);
    /* Don't even attempt to start the boot CPU! */
    if (apicid == boot_cpu_apicid)
      continue;
    if (!(phys_cpu_present_map & (1 << bit)))
      continue;
    if ((max_cpus >= 0) && (max_cpus <= cpucount+1))
      continue;
    do_boot_cpu(apicid);
    /* Make sure we unmap all failed CPUs */
    if ((boot_apicid_to_cpu(apicid) == -1) &&
        (phys_cpu_present_map & (1 << bit)))
      printk("CPU #%d not responding - cannot use it.\n", apicid);
  }

  // ... SMP BogoMIPS
  // ... B stepping processor warning
  // ... HyperThreading handling

  /* Set up all local APIC timers in the system */
  setup_APIC_clocks();

  /* Synchronize the TSC with the AP */
  if (cpu_has_tsc && cpucount)
    synchronize_tsc_bp();

smp_done:
  zap_low_mappings();
}

///////////////////////////////////////////////////////////////////////////////
static void __init do_boot_cpu (int apicid)
{
  cpu = ++cpucount;

  // 1. prepare "idle process" task struct for next AP

  /* We can't use kernel_thread since we must avoid to
    * reschedule the child. */
  if (fork_by_hand() < 0)
    panic("failed fork for CPU %d", cpu);
  /* We remove it from the pidhash and the runqueue
    * once we got the process: */
  idle = init_task.prev_task;
  if (!idle)
    panic("No idle process for CPU %d", cpu);

  /* we schedule the first task manually */
  idle->processor = cpu;
  idle->cpus_runnable = 1 << cpu; // only on this AP!

  map_cpu_to_boot_apicid(cpu, apicid) {
    physical_apicid_2_cpu[apicid] = cpu;
    cpu_2_physical_apicid[cpu] = apicid;
  }

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

  del_from_runqueue(idle);
  unhash_process(idle);
  init_tasks[cpu] = idle;

  // 2. prepare stack and code (CS:IP) for next AP

  /* start_eip had better be page-aligned! */
  start_eip = setup_trampoline() {
    memcpy(trampoline_base, trampoline_data,
            trampoline_end - trampoline_data);
    /* trampoline_base was reserved in
      * start_kernel() -> setup_arch() -> smp_alloc_memory(),
      * and will be shared by all APs (one by one) */
    return virt_to_phys(trampoline_base);
  }

  /* So we see what's up */
  printk("Booting processor %d/%d eip %lx\n", cpu, apicid, start_eip);
  stack_start.esp = (void *) (1024 + PAGE_SIZE + (char *)idle);
  /* this value is used by next AP when it executes
    *   "lss stack_start,%esp" in
    *   linux/arch/i386/kernel/head.S:startup_32(). */

  /* This grunge runs the startup process for
    * the targeted processor. */
  atomic_set(&init_deasserted, 0);
  Dprintk("Setting warm reset code and vector.\n");

  CMOS_WRITE(0xa, 0xf);
  local_flush_tlb();
  Dprintk("1.\n");
  *((volatile unsigned short *) TRAMPOLINE_HIGH) = start_eip >> 4;
  Dprintk("2.\n");
  *((volatile unsigned short *) TRAMPOLINE_LOW) = start_eip & 0xf;
  Dprintk("3.\n");
  // we have setup 0:467 to start_eip (trampoline_base)

  // 3. kick AP to run (AP gets CS:IP from 0:467)

  // Starting actual IPI sequence...
  boot_error = wakeup_secondary_via_INIT(apicid, start_eip);
  if (!boot_error) {      // looks OK
    /* allow APs to start initializing. */
    set_bit(cpu, &cpu_callout_map);

    /* ... Wait 5s total for a response */

    // bit cpu in cpu_callin_map is set by AP in smp_callin()
    if (test_bit(cpu, &cpu_callin_map)) {
      print_cpu_info(&cpu_data[cpu]);
    } else {
      boot_error= 1;
      // marker 0xA5 set by AP in trampoline_data()
      if (*((volatile unsigned char *)phys_to_virt(8192)) == 0xA5)
        /* trampoline started but... */
        printk("Stuck ??\n");
      else
        /* trampoline code not run */
        printk("Not responding.\n");
    }
  }
  if (boot_error) {
    /* Try to put things back the way they were before ... */
    unmap_cpu_to_boot_apicid(cpu, apicid);
    clear_bit(cpu, &cpu_callout_map); /* set in do_boot_cpu() */
    clear_bit(cpu, &cpu_initialized); /* set in cpu_init() */
    clear_bit(cpu, &cpu_online_map);  /* set in smp_callin() */
    cpucount--;
  }

  /* mark "stuck" area as not stuck */
  *((volatile unsigned long *)phys_to_virt(8192)) = 0;
}
start_secondary() ile trampoline_data()'yı karıştırmayın. İlki AP "idle" süreci görev yapısı EIP değeridir, ikincisi ise AP'nin BSP onu uyardıktan sonra (wakeup_secondary_via_INIT() kullanarak) çalıştırdığı gerçek kipli koddur.
linux/arch/i386/kernel/trampoline.S
Bu dosya 16 bitlik gerçek kipli AP başlatma kodunu içerir. BSP start_kernel() -> setup_arch() -> smp_alloc_memory() içinde trampoline_base bellek uzayını ayırdı. BSP APyi tetiklemeden önce trampoline kodunu trampoline_data ve trampoline_end arasından trampoline_base'e (do_boot_cpu() -> setup_trampoline() içindeki) kopyalar. BSP 0:467 noktasını, AP bu noktadan itibaren çalışabilmesi için, trampoline_base olarak atar.
///////////////////////////////////////////////////////////////////////////////
trampoline_data()
{
r_base:
  wbinvd;         // diğeri için zararsız olması gereken NUMA-Q için gerekli
  DS = CS;
  BX = 1;         // Bir SMP trampoline seçeneği
  cli;

  // master'in çalıştığımızı bilmesi için imleyiciyi yaz
  trampoline_base = 0xA5A5A5A5;

  lidt idt_48;
  lgdt gdt_48;

  AX = 1;
  lmsw AX;        // korumalı kip!
  goto flush_instr;
flush_instr:
  goto CS:100000; // see linux/arch/i386/kernel/head.S:startup_32()
}

idt_48:
  .word   0                       # idt limit = 0
  .word   0, 0                    # idt base = 0L

gdt_48:
  .word   0x0800                  # gdt limit = 2048, 256 GDT girdisi
  .long   gdt_table-__PAGE_OFFSET # gdt base = gdt (ilk SMP CPU)

.globl SYMBOL_NAME(trampoline_end)
SYMBOL_NAME_LABEL(trampoline_end)
Dikkat ederseniz AP linux/arch/i386/kernel/head.S:startup_32()'ye geçtiğinde, BSPninkinden (BX=0) farklı olarak, BX=1 oluyor. Bakınız: linux/arch/i386/kernel/head.S.
initialize_secondary()
BSP'den farklı olarak, Çekirdeği Başlat bölümündeki
linux/arch/i386/kernel/head.S:startup_32()'nin sonunda AP start_kernel() yerine initialize_secondary()'yi çağırır.
/* İkincil işlemciler için herşey kurulmuş durumda;
 * bunlar sadece görev yapısındaki herşeyi yeniden
 * yüklemek için gerekli.
 * Bu işlev değer döndürmemeli. */
void __init initialize_secondary(void)
{
  /* Aslında tüm TSS'yi yüklememiz gerekmiyor,
    * basitçe sadece yığıt göstericisi ve eip. */
  asm volatile(
    "movl %0,%%esp\n\t"
    "jmp *%1"
    :
    :"r" (current->thread.esp),"r" (current->thread.eip));
}
BSPnin thread.eip'yi start_secondary()'ye atamak için do_boot_cpu()'yu çağırmasıyla denetim bu işleve geçer. AP, BSP tarafından do_boot_cpu() -> fork_by_hand() -> do_fork() içinde kurulan, yeni bir yığıt çerçevesi kullanır.
start_secondary()
Tüm APler smp_init() içindeki smp_init() -> smp_commence() ile tetiklenen BSP'den smp_commenced sinyalini beklerler. Bu sinyali aldıktan sonra "idle" süreçlerini çalıştırırlar.
///////////////////////////////////////////////////////////////////////////////
int __init start_secondary(void *unused)
{
  /* smp_callin() öncesine birşey koyma, en yapılması gereken şeyleri
   * yapmak için yapılacak şeyleri sınırlamak istediğimiz bu yerde
   * SMP önyüklemesi çok kırılgandır.*/
  cpu_init();
  smp_callin();
  while (!atomic_read(&smp_commenced))
          rep_nop();
  /* düşük bellek adreslemesi temizlendi,
    * onları yerel TLB'lerden de çıkar (flush). */
  local_flush_tlb();
  return cpu_idle();      // asla dönmez, bakınız: cpu_idle()
}
cpu_idle() -> init_idle() wait_init_idle içindeki ilgili biti temizler ve son olarak BSPnin smp_init()'i bitirmesini sağlar ve start_kernel() içindeki takip eden işlevle devam eder (örn. rest_init()).
Kaynakça
Önceki Üst Ana Başlık Sonraki
linux/init/main.c Başlangıç A. Çekirdek Derleme ve Kurulum Örneği
Bir Linux Kitaplığı Sayfası