paint-brush
Hành trình khám phá bí mật của Firmware: Từ BIOS/UEFI đến OStừ tác giả@tristejoursoir
476 lượt đọc
476 lượt đọc

Hành trình khám phá bí mật của Firmware: Từ BIOS/UEFI đến OS

từ tác giả Aleksandr Goncharov20m2024/08/22
Read on Terminal Reader

dài quá đọc không nổi

Khám phá sự phát triển từ BIOS truyền thống đến chương trình cơ sở UEFI hiện đại, hiểu cách quản lý trình tự khởi động và khám phá vai trò của dịch vụ khởi động và dịch vụ thời gian chạy. Khám phá sự phức tạp của bộ tải khởi động hệ điều hành và xem chương trình cơ sở hiện hỗ trợ các tính năng và ứng dụng nâng cao như thế nào.
featured image - Hành trình khám phá bí mật của Firmware: Từ BIOS/UEFI đến OS
Aleksandr Goncharov HackerNoon profile picture
0-item
1-item


Bạn đã bao giờ tự hỏi điều gì xảy ra khi bạn nhấn nút nguồn trên máy tính của mình chưa? Đằng sau khoảng dừng ngắn ngủi đó, trước khi màn hình của bạn sáng lên, một loạt các quy trình phức tạp đang diễn ra. Bài viết này sẽ đi sâu vào thế giới hấp dẫn của chương trình cơ sở , khám phá cách các thành phần khác nhau tương tác trong quá trình khởi động .


Bằng cách hiểu những kết nối này, bạn sẽ có được bức tranh rõ ràng hơn về các yếu tố nền tảng giúp hệ thống của bạn trở nên sống động. Trọng tâm chính của chúng tôi sẽ là kiến trúc Intel x86 , nhưng nhiều nguyên tắc cũng áp dụng cho các kiến trúc khác.


Nếu bạn đã bỏ lỡ phần đầu tiên của loạt bài này, hãy nhấp vào đây để xem lại. Bây giờ, chúng ta hãy cùng khám phá những bí ẩn đằng sau phần mềm.

Mục lục:

  • Định nghĩa
  • Kiến trúc phần mềm tổng thể
  • Bộ nạp khởi động giai đoạn đầu (FSBL)
    • BIOS (giai đoạn POST)
    • Khởi tạo nền tảng UEFI (PI)
    • khởi động lõi
    • Các giải pháp khác
  • Bộ nạp khởi động giai đoạn thứ hai (SSBL)
    • BIOS
    • UEFI
  • Bộ nạp khởi động hệ điều hành


Định nghĩa

  • Phần mềm cơ sở : một loại phần mềm chuyên dụng được nhúng trong phần cứng, cung cấp khả năng kiểm soát cấp thấp và cho phép phần cứng hoạt động chính xác và tương tác với các thành phần khác của hệ thống.


  • Hệ thống đầu vào/đầu ra cơ bản (BIOS) : một chương trình cơ sở cũ (ban đầu được tạo cho IBM PC ) chịu trách nhiệm khởi tạo phần cứng sau khi bật nguồn nền tảng. Ngày nay, nó thường được gọi một cách mơ hồ là bộ chương trình cơ sở hoàn chỉnh.


  • Bootloader : tên chung cho chương trình cơ sở chịu trách nhiệm khởi động máy tính. Nó được sử dụng như một khái niệm hiện đại thay cho BIOS , thường cung cấp một khuôn khổ với mã bootstrap để khởi tạo bộ xử lý và chipset, cũng như giao diện cho bên thứ ba (ví dụ: nhà phát triển bo mạch chủ) để thực hiện khởi tạo nền tảng cụ thể.


  • Payload : Phần mềm được thực thi khi bộ nạp khởi động thoát. Có thể là bộ nạp khởi động giai đoạn hai, Hệ điều hành, ứng dụng BIOS/UEFI, v.v. Nó thường xử lý luồng khởi động theo thiết kế chương trình cơ sở.


  • Việc sử dụng các thuật ngữ BIOSbootloader có thể gây nhầm lẫn, vì ý nghĩa của chúng phụ thuộc vào ngữ cảnh. Tuy nhiên, khi ai đó đề cập đến firmware , BIOS hoặc bootloader , họ thường đề cập đến toàn bộ bộ firmware chạy giữa hệ điều hành và phần cứng .


  • EFI so với UEFI : Giao diện phần mềm mở rộng (EFI) là thông số kỹ thuật ban đầu do Intel phát triển. Giao diện phần mềm mở rộng hợp nhất (UEFI) là phiên bản kế thừa của EFI , do Diễn đàn UEFI tạo ra để chuẩn hóa và mở rộng thông số kỹ thuật ban đầu. Trong hầu hết các trường hợp, EFIUEFI được sử dụng thay thế cho nhau.

Kiến trúc phần mềm tổng thể

Để hiểu cách các thành phần chương trình cơ sở tương tác, chúng ta sẽ khám phá toàn bộ kiến trúc với tất cả các phần được kết nối của nó. Luồng thực thi, được hiển thị trong sơ đồ bên dưới, bắt đầu từ vectơ đặt lại , là một phần của Bộ nạp khởi động giai đoạn đầu tiên . Từ đó, nó tiến triển qua các giai đoạn chương trình cơ sở khác nhau:



Phần mềm cơ sở hoặc BIOS thường có thể được chia thành hai phần chính, với giao diện tối thiểu giữa chúng:


  1. Khởi tạo phần cứng : Chịu trách nhiệm khởi tạo các thành phần phần cứng của hệ thống.
  2. Giao diện với hệ điều hành và người dùng : Cung cấp các giao diện cần thiết cho hệ điều hành và người dùng.


Thiết kế của phần mềm nền tảng có thể là nguyên khối , kết hợp khởi tạo phần cứng và chức năng khởi động, hoặc có thể theo luồng khởi động theo mô-đun và theo giai đoạn. Lựa chọn thiết kế phụ thuộc vào yêu cầu hệ thống và có thể được ưu tiên cho một số thiết bị nhất định.


Sơ đồ sau đây minh họa cách các thành phần chương trình cơ sở khác nhau tương tác và có thể được sử dụng cùng nhau để hỗ trợ quá trình khởi động (các mũi tên chỉ ra trình tự thực hiện):



Nếu các sơ đồ này có vẻ phức tạp, đừng lo lắng. Hãy xem lại chúng sau khi đọc bài viết này và chúng sẽ rõ ràng hơn.

Bộ nạp khởi động giai đoạn đầu (FSBL)

Phần mềm này được thiết kế để khởi tạo máy tính và hệ thống nhúng với trọng tâm là khởi tạo phần cứng tối thiểu : chỉ làm những gì thực sự cần thiết, sau đó chuyển quyền điều khiển cho Second-Stage Bootloader để khởi động hệ điều hành. FSBL không tải hệ điều hành từ phương tiện lưu trữ khác ngoài chip flash . Vì nó chỉ khởi tạo phần cứng cơ bản và không xử lý phương tiện khởi động như ổ cứng, SSD hoặc ổ đĩa flash USB, nên cần có một phần mềm khác để thực sự khởi động hệ điều hành .


Trách nhiệm chính của FSBL :


  1. CPU : Chuyển từ Chế độ thực 16 bit sang Chế độ bảo vệ 32 bit ( lưu ý : hoặc ở chế độ Virtual 8086 trong trường hợp BIOS).
  2. Sử dụng bộ nhớ đệm : Gọi FSP-T để cấu hình Cache-As-RAM cho môi trường C.
  3. Cổng gỡ lỗi : Khởi tạo cổng gỡ lỗi được cấu hình bằng cách gọi các phương thức khởi tạo cụ thể cho từng bo mạch.
  4. Khởi tạo bộ nhớ : Gọi FSP-M để khởi tạo bộ nhớ chính của hệ thống và lưu thông tin bộ nhớ hệ thống quan trọng.
  5. GPIO : Cấu hình các chân Đầu vào/Đầu ra đa năng (GPIO) để giao tiếp với các thiết bị bên ngoài.
  6. Silicon : Thực hiện khởi tạo nền tảng sớm và sử dụng FSP-S để hoàn tất khởi tạo chipset, CPU và bộ điều khiển IO.
  7. Liệt kê PCI : Liệt kê các thiết bị PCI và phân bổ tài nguyên như địa chỉ bộ nhớ và IRQ.
  8. Chuẩn bị tải trọng : Thiết lập các bảng SMBIOSACPI , bao gồm thông tin chuẩn bị (bảng coreboot, HOB) cần thiết để truyền tới tải trọng.
  9. Tải và chuyển giao : Tải và chuyển giao quyền điều khiển cho tải trọng.

BIOS (Giai đoạn POST)

Vào những ngày đầu của máy tính, phần mềm nguồn mở không được phổ biến rộng rãi và hầu hết các triển khai BIOS đều là độc quyền. Chỉ có một số ít giải pháp mở có sẵn cung cấp mã nguồn BIOS POST, chẳng hạn như Super PC/Turbo XT BIOSGLaBIOS . Các dự án này được thiết kế để hoạt động trên các hệ thống IBM 5150/5155/5160 và hầu hết các bản sao XT.


Tuy nhiên, các triển khai BIOS nguồn mở nổi tiếng hơn, như OpenBIOSSeaBIOS , không thực hiện khởi tạo phần cứng vì chúng không nhằm mục đích chạy trên phần cứng trần. Nhưng chúng được sử dụng rộng rãi như Second-Stage Bootloader và chạy gốc trong các môi trường ảo như QEMU và Bochs.


Trong mọi trường hợp, khả năng bạn cần làm việc trực tiếp với các BIOS ban đầu này hoặc tìm hiểu sâu hơn về các thông số kỹ thuật của chúng là rất thấp. Nhưng nếu bạn muốn khám phá, các kho lưu trữ đã đề cập là điểm khởi đầu tốt.


Xét về xu hướng phát triển hiện tại, có vẻ như không có sự phát triển liên tục nào về các giải pháp BIOS độc quyền và các dự án như vậy đã trở nên lỗi thời khi đối mặt với các giải pháp thay thế hiện đại.

Khởi tạo nền tảng UEFI (PI)

Quá trình khởi động theo một luồng được phân giai đoạn, bắt đầu từ bên trái và di chuyển sang bên phải trong hình tiếp theo. Dòng thời gian của quá trình khởi động nền tảng được chia thành các cụm từ sau được chỉ ra bằng các hộp màu vàng:



  • Bảo mật (SEC) : Giai đoạn đầu tiên sau vectơ thiết lập lại , chức năng chính của nó là thiết lập RAM tạm thời (CPU Cache-As-RAM hoặc SRAM).
  • Khởi tạo trước EFI (PEI) : Giai đoạn này phân phối các trình điều khiển chuyên biệt được gọi là Mô-đun khởi tạo trước EFI (PEIM) . Các mô-đun này xử lý khởi tạo phần cứng thiết yếu, chẳng hạn như cấu hình CPU và chipset và thiết lập bộ nhớ chính (DRAM) .
  • Môi trường thực thi trình điều khiển (DXE) : Trong giai đoạn này, phần còn lại của quá trình khởi tạo hệ thống được thực hiện. Giai đoạn DXE cung cấp các dịch vụ UEFI và hỗ trợ nhiều giao thức và trình điều khiển cần thiết cho hoạt động của hệ thống.
  • Chọn thiết bị khởi động (BDS) : Giai đoạn này triển khai chính sách khởi động nền tảng, xác định trình tự khởi động và chọn thiết bị/bộ tải khởi động phù hợp.
  • Tải hệ thống tạm thời (TSL) : Trong giai đoạn này, hệ thống chạy các ứng dụng bằng dịch vụ UEFI để chuẩn bị cho hệ điều hành. Nó bao gồm quá trình chuyển đổi từ môi trường UEFI sang hệ điều hành, kết thúc bằng lệnh gọi ExitBootServices() .
  • Thời gian chạy (RT) : Ở giai đoạn này, hệ điều hành hoạt động hoàn toàn, quản lý toàn bộ hệ thống dưới sự kiểm soát của mình.
  • After Life (AL) : Giai đoạn này xử lý các tình huống mà phần cứng hoặc hệ điều hành bị sập/tắt/khởi động lại. Phần mềm có thể thử các hành động khôi phục, tuy nhiên, UEFI PI Specification không xác định các yêu cầu hoặc hành vi cụ thể cho giai đoạn này.


Quá trình này và các giai đoạn thực hiện của nó được đề cập trong Đặc tả khởi tạo nền tảng UEFI (PI) . Tuy nhiên, cũng có Giao diện UEFI (được chỉ ra bằng đường màu xanh đậm trong hình), không phải là một phần của tài liệu trước đó và được mô tả trong Đặc tả UEFI . Mặc dù tên và việc sử dụng thường xuyên UEFI có thể gây nhầm lẫn, nhưng hai tài liệu này có trọng tâm khác nhau:


  • Đặc tả UEFI PI : Tập trung vào giao diện giữa các thành phần chương trình cơ sở cấp thấp và trình bày chi tiết cách các mô-đun này tương tác để khởi tạo nền tảng.


  • UEFI Spec : Xác định giao diện tương tác giữa Hệ điều hành (OS) và chương trình cơ sở. Điều này sẽ được thảo luận thêm trong bối cảnh của Second-Stage Bootloader . Lưu ý rằng UEFI Specification dựa trên PI Specification.


Về cơ bản, cả hai thông số kỹ thuật đều liên quan đến giao diện, nhưng ở các cấp độ khác nhau. Để biết thông tin chi tiết, bạn có thể truy cập cả hai thông số kỹ thuật trên trang web Diễn đàn UEFI .


UEFI PI ban đầu được thiết kế như một giải pháp phần mềm thống nhất, không xem xét sự khác biệt giữa bộ nạp khởi động giai đoạn một và giai đoạn hai. Tuy nhiên, khi chúng ta gọi UEFIBộ nạp khởi động giai đoạn một , nó bao gồm các giai đoạn SEC , PEIDXE đầu . Lý do chúng ta chia DXE thành giai đoạn đầu và giai đoạn cuối là do vai trò khác nhau của chúng trong quá trình khởi tạo.


Trong giai đoạn DXE đầu , trình điều khiển thường thực hiện khởi tạo CPU/PCH/bo mạch cần thiết và cũng tạo ra Giao thức kiến trúc DXE (AP) , giúp cô lập giai đoạn DXE khỏi phần cứng dành riêng cho nền tảng. AP đóng gói các chi tiết dành riêng cho nền tảng, cho phép giai đoạn DXE sau hoạt động độc lập với các thông số kỹ thuật của phần cứng.



Coreboot

Các bài viết chi tiết về cách Coreboot hoạt động sẽ sớm ra mắt. Hãy theo dõi phương tiện truyền thông xã hội của tôi – chúng sẽ sớm được xuất bản!

Các giải pháp khác

  • Intel Slim Bootloader (SBL) : bộ nạp khởi động giai đoạn đầu tiên thuần túy chỉ cung cấp khởi tạo các thành phần phần cứng cốt lõi, sau đó tải trọng. Tuy nhiên, nó chỉ hoạt động trên nền tảng Intel x86 và không hỗ trợ AMD x86 hoặc các kiến trúc khác.
  • Das U-Boot : vừa là bộ nạp khởi động giai đoạn một vừa là bộ nạp khởi động giai đoạn hai. Tuy nhiên, hỗ trợ khởi động trực tiếp từ vectơ đặt lại x86 của nền tảng (được gọi là chế độ trần) bị hạn chế so với các chương trình cơ sở khác. Nó phổ biến hơn trên các hệ thống nhúng và các thiết bị dựa trên ARM. Là bộ nạp khởi động giai đoạn hai, U-Boot triển khai một tập hợp con của UEFI nhưng tập trung vào các hệ thống nhúng.

Bộ nạp khởi động giai đoạn thứ hai (SSBL)

Sau khi thiết lập phần cứng ban đầu hoàn tất, giai đoạn thứ hai sẽ bắt đầu. Vai trò chính của nó là thiết lập giao diện phần mềm giữa hệ điều hành và phần mềm nền tảng , đảm bảo rằng hệ điều hành có thể quản lý tài nguyên hệ thống và tương tác với các thành phần phần cứng.


SSBL nhằm mục đích ẩn các biến thể phần cứng càng nhiều càng tốt, đơn giản hóa việc phát triển hệ điều hành và ứng dụng bằng cách xử lý hầu hết các giao diện cấp phần cứng. Sự trừu tượng này cho phép các nhà phát triển tập trung vào các chức năng cấp cao hơn mà không phải lo lắng về sự khác biệt phần cứng cơ bản.


Trách nhiệm chính của SSBL :


  1. Truy xuất thông tin nền tảng : Thu thập thông tin cụ thể về nền tảng từ Bộ nạp khởi động giai đoạn đầu , bao gồm ánh xạ bộ nhớ, SMBIOS, bảng ACPI, flash SPI, v.v.


  2. Chạy trình điều khiển độc lập với nền tảng : Bao gồm trình điều khiển cho SMM, SPI, PCI, SCSI/ATA/IDE/DISK, USB, ACPI, giao diện mạng, v.v.


  3. Triển khai dịch vụ (hay còn gọi là Giao diện) : Cung cấp một bộ dịch vụ giúp tạo điều kiện thuận lợi cho việc giao tiếp giữa hệ điều hành và các thành phần phần cứng.


  4. Menu thiết lập : Cung cấp menu thiết lập cấu hình hệ thống, cho phép người dùng điều chỉnh các cài đặt liên quan đến thứ tự khởi động, tùy chọn phần cứng và các thông số hệ thống khác.


  5. Boot Logic : Cơ chế định vị và tải dữ liệu (có thể là hệ điều hành) từ phương tiện khởi động có sẵn.

BIOS

Giao diện trong BIOS được gọi là BIOS services/functions/interrupt calls . Các chức năng này cung cấp một tập hợp các thói quen để truy cập phần cứng, nhưng các chi tiết cụ thể về cách chúng được thực hiện trên phần cứng cụ thể của hệ thống bị ẩn khỏi người dùng.


Chế độ thực 16 bit, chúng có thể dễ dàng truy cập bằng cách gọi ngắt phần mềm thông qua lệnh ngôn ngữ lắp ráp INT x86. Ở chế độ bảo vệ 32 bit, hầu như tất cả các dịch vụ BIOS đều không khả dụng do cách xử lý giá trị phân đoạn khác nhau.




Hãy lấy ví dụ về Disk Services ( INT 13h ), cung cấp dịch vụ đọc và ghi đĩa cứng và đĩa mềm dựa trên sector sử dụng địa chỉ Cylinder-Head-Sector (CHS) , như một ví dụ về cách sử dụng giao diện này. Giả sử chúng ta muốn đọc 2 sector (1024 byte) và tải chúng tại địa chỉ bộ nhớ 0x9020 , thì có thể thực thi đoạn mã sau:


 mov $0x02, %ah # Set BIOS read sector routine mov $0x00, %ch # Select cylinder 0 mov $0x00, %dh # Select head 0 [has a base of 0] mov $0x02, %cl # Select sector 2 (next after the # boot sector) [has a base of 1] mov $0x02, %al # Read 2 sectors mov $0x00, %bx # Set BX general register to 0 mov %bx, %es # Set ES segment register to 0 mov $0x9020, %bx # Load sectors to ES:BX (0:0x9020) int $0x13 # Start reading from drive jmp $0x9020 # Jump to loaded code


Nếu bạn quan tâm đến cách dịch vụ này được viết trong SeaBios, hãy xem src/disk.c .

Giai đoạn KHỞI ĐỘNG

  • Bắt đầu tìm kiếm thiết bị có thể khởi động (có thể là ổ cứng, CD-ROM, đĩa mềm, v.v.) bằng cách đọc sector 512 byte đầu tiên ( sector số 0) từ các thiết bị.


  • Trình tự khởi động trong BIOS sẽ tải Bản ghi khởi động chính (MBR) hợp lệ đầu tiên mà nó tìm thấy vào bộ nhớ vật lý của máy tính tại địa chỉ vật lý 0x7C00 (gợi ý: 0x0000:0x7c000x7c0:0x0000 tham chiếu đến cùng một địa chỉ vật lý).


  • BIOS chuyển quyền điều khiển đến 512 byte đầu tiên của payload. Sector được tải này, quá nhỏ để chứa toàn bộ mã payload, có mục đích tải phần còn lại của payload từ thiết bị có thể khởi động . Tại thời điểm này, payload có thể sử dụng giao diện được BIOS hiển thị.


Điều đáng chú ý là các thông số kỹ thuật BIOS không tồn tại trong những ngày đầu. BIOS là một tiêu chuẩn thực tế - nó hoạt động theo cách mà nó hoạt động trên các máy tính IBM thực tế, vào những năm 1980. Các nhà sản xuất còn lại chỉ thiết kế ngược và tạo ra các BIOS tương thích với IBM. Do đó, không có quy định nào ngăn cản các nhà sản xuất BIOS phát minh ra các chức năng BIOS mới hoặc có các chức năng chồng chéo.

Giao diện phần mềm mở rộng hợp nhất (UEFI)

Như đã đề cập trước đó, bản thân UEFI chỉ là một thông số kỹ thuật và có nhiều triển khai. Triển khai được sử dụng rộng rãi nhất là TianoCore EDK II , một triển khai tham chiếu nguồn mở của thông số kỹ thuật UEFI và PI. Mặc dù EDKII không đủ để tạo ra một chương trình cơ sở khởi động đầy đủ chức năng, nhưng nó cung cấp nền tảng vững chắc cho hầu hết các giải pháp thương mại.


Để hỗ trợ các First-Stage Bootloader khác nhau và cung cấp giao diện UEFI, dự án UEFI Payload được sử dụng. Dự án này dựa trên thiết lập ban đầu được thực hiện và thông tin nền tảng do chương trình cơ sở khởi động cung cấp để chuẩn bị hệ thống cho môi trường UEFI.


UEFI Payload sử dụng các giai đoạn DXEBDS , được thiết kế để độc lập với nền tảng. Nó cung cấp một payload chung có thể thích ứng với các nền tảng khác nhau. Trong hầu hết các trường hợp, nó không yêu cầu bất kỳ tùy chỉnh hoặc điều chỉnh cụ thể nào cho nền tảng và có thể được sử dụng như hiện trạng bằng cách sử dụng thông tin nền tảng từ First-Stage Bootloader .


Các biến thể của UEFI Payload :


  1. Tải trọng UEFI cũ : Yêu cầu thư viện phân tích cú pháp để trích xuất thông tin nền tảng cụ thể cần thiết cho việc triển khai. Nếu bộ nạp khởi động cập nhật API của nó, tải trọng cũng phải được cập nhật.



  2. Tải trọng UEFI phổ quát : Tuân theo Đặc tả phần mềm có thể mở rộng phổ quát (USF) , sử dụng Định dạng có thể thực thi và liên kết (ELF) hoặc Cây hình ảnh phẳng (FIT) làm định dạng hình ảnh chung. Thay vì tự phân tích cú pháp, nó mong đợi nhận được các Khối chuyển giao (HOB) tại mục nhập tải trọng.


Trong khi Payload UEFI Legacy hoạt động tốt, cộng đồng EDK2 đang cố gắng chuyển hướng ngành công nghiệp sang Payload UEFI Universal . Sự lựa chọn giữa các payload phụ thuộc vào các thành phần chương trình cơ sở của bạn. Ví dụ, không thể chạy Payload Legacy với hỗ trợ SMM trên Slim Bootloader mà không có bản vá của tôi . Mặt khác, sử dụng Payload Universal với coreboot yêu cầu một lớp shim để dịch các bảng coreboot thành HOB , một tính năng chỉ có trong nhánh EDK2 của StarLabs .

Giao diện

Mỗi hệ thống tuân thủ UEFI đều cung cấp một Bảng hệ thống được truyền đến mọi mã đang chạy trong môi trường UEFI (trình điều khiển, ứng dụng, trình tải hệ điều hành). Cấu trúc dữ liệu này cho phép tệp thực thi UEFI truy cập vào các bảng cấu hình hệ thống như ACPI , SMBIOS và một bộ sưu tập các dịch vụ UEFI .



Cấu trúc bảng được mô tả trong MdePkg/Include/Uefi/UefiSpec.h :


 typedef struct { EFI_TABLE_HEADER Hdr; CHAR16 *FirmwareVendor; UINT32 FirmwareRevision; EFI_HANDLE ConsoleInHandle; EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn; EFI_HANDLE ConsoleOutHandle; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut; EFI_HANDLE StandardErrorHandle; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr; // // A pointer to the EFI Runtime Services Table. // EFI_RUNTIME_SERVICES *RuntimeServices; // // A pointer to the EFI Boot Services Table. // EFI_BOOT_SERVICES *BootServices; UINTN NumberOfTableEntries; EFI_CONFIGURATION_TABLE *ConfigurationTable; } EFI_SYSTEM_TABLE;


Dịch vụ bao gồm các loại sau: Dịch vụ khởi động , Dịch vụ thời gian chạyDịch vụ do giao thức cung cấp .


UEFI trừu tượng hóa quyền truy cập vào thiết bị bằng cách thiết lập Giao thức UEFI . Các giao thức này là các cấu trúc dữ liệu chứa các con trỏ hàm và được xác định bằng Mã định danh duy nhất toàn cầu (GUID) cho phép các mô-đun khác định vị và sử dụng chúng. Chúng có thể được phát hiện thông qua Dịch vụ khởi động.


Trình điều khiển UEFI tạo ra các giao thức này và các chức năng thực tế (không phải con trỏ!) được chứa trong chính trình điều khiển. Cơ chế này cho phép các thành phần khác nhau trong môi trường UEFI giao tiếp với nhau và đảm bảo rằng hệ điều hành có thể tương tác với các thiết bị trước khi tải trình điều khiển của riêng nó.



Trong khi một số giao thức được xác định trước và mô tả trong thông số kỹ thuật UEFI, các nhà cung cấp phần mềm cũng có thể tạo ra các giao thức tùy chỉnh của riêng họ để mở rộng chức năng của nền tảng.


Dịch vụ khởi động

Cung cấp các chức năng chỉ có thể sử dụng trong thời gian khởi động. Các dịch vụ này vẫn khả dụng cho đến khi hàm EFI_BOOT_SERVICES.ExitBootServices() được gọi ( MdeModulePkg/Core/Dxe/DxeMain/DxeMain.c ).


Các con trỏ tới tất cả các dịch vụ khởi động được lưu trữ trong Bảng dịch vụ khởi động ( MdePkg/Include/Uefi/UefiSpec.h ):


 typedef struct { EFI_TABLE_HEADER Hdr; ... EFI_GET_MEMORY_MAP GetMemoryMap; EFI_ALLOCATE_POOL AllocatePool; EFI_FREE_POOL FreePool; ... EFI_HANDLE_PROTOCOL HandleProtocol; ... EFI_EXIT_BOOT_SERVICES ExitBootServices; ... } EFI_BOOT_SERVICES;


Dịch vụ thời gian chạy

Một tập hợp tối thiểu các dịch vụ vẫn có thể truy cập được trong khi Hệ điều hành đang chạy. Không giống như Dịch vụ khởi động, các dịch vụ này vẫn hợp lệ sau khi bất kỳ tải trọng nào (ví dụ: bộ nạp khởi động hệ điều hành) đã kiểm soát nền tảng thông qua lệnh gọi đến EFI_BOOT_SERVICES.ExitBootServices() .


Các con trỏ tới tất cả các dịch vụ thời gian chạy được lưu trữ trong Bảng dịch vụ thời gian chạy ( MdePkg/Include/Uefi/UefiSpec.h ):


 typedef struct { EFI_TABLE_HEADER Hdr; ... EFI_GET_TIME GetTime; EFI_SET_TIME SetTime; ... EFI_GET_VARIABLE GetVariable; EFI_GET_NEXT_VARIABLE_NAME GetNextVariableName; EFI_SET_VARIABLE SetVariable; ... EFI_GET_NEXT_HIGH_MONO_COUNT GetNextHighMonotonicCount; EFI_RESET_SYSTEM ResetSystem; ... } EFI_RUNTIME_SERVICES;


Hình ảnh bên dưới hiển thị dòng thời gian cho các dịch vụ khởi động và thời gian chạy, do đó bạn có thể biết chính xác thời điểm mỗi dịch vụ hoạt động.



Giai đoạn chọn thiết bị khởi động (BDS)

Đặc tả UEFI định nghĩa một công cụ chính sách khởi động được gọi là trình quản lý khởi động UEFI . Công cụ này sẽ cố gắng tải các ứng dụng UEFI theo thứ tự cụ thể. Thứ tự này và các thiết lập khác có thể được cấu hình bằng cách sửa đổi các Biến NVRAM (bộ nhớ truy cập ngẫu nhiên không dễ bay hơi) toàn cục. Chúng ta hãy thảo luận về những biến quan trọng nhất trong số chúng:


  • Boot#### ( #### được thay thế bằng một giá trị hex duy nhất) — tùy chọn khởi động/tải.
  • BootCurrent — tùy chọn khởi động được sử dụng để khởi động hệ thống đang chạy.
  • BootNext — tùy chọn khởi động chỉ dành cho lần khởi động tiếp theo. Tùy chọn này thay thế BootOrder chỉ cho một lần khởi động và bị trình quản lý khởi động xóa sau lần sử dụng đầu tiên. Tùy chọn này cho phép bạn thay đổi hành vi khởi động tiếp theo mà không cần thay đổi BootOrder .
  • BootOrder — danh sách tải tùy chọn khởi động được sắp xếp. Trình quản lý khởi động cố gắng khởi động tùy chọn hoạt động đầu tiên trong danh sách này. Nếu không thành công, nó sẽ thử tùy chọn tiếp theo, v.v.
  • BootOptionSupport — các loại tùy chọn khởi động được trình quản lý khởi động hỗ trợ.
  • Timeout — thời gian chờ của trình quản lý khởi động chương trình cơ sở, tính bằng giây, trước khi tự động chọn giá trị khởi động từ BootNext hoặc BootOrder .


Những biến này có thể dễ dàng lấy được từ Linux bằng cách sử dụng efibootmgr(8) :


 [root@localhost ~]# efibootmgr BootCurrent: 0000 Timeout: 5 seconds BootOrder: 0000,0001,2001,2002,2003 Boot0000* ARCHLINUX HD(5,GPT,d03ca3cf-1511-d94e-8400-c7a125866442,0x40164000,0x100000)/File(\EFI\ARCHLINUX\grubx64.efi) Boot0001* Windows Boot Manager HD(1,GPT,6f185443-09fc-4f15-afdf-01c523565e52,0x800,0x32000)/File(\EFI\Microsoft\Boot\bootmgfw.efi)57a94e544f5753000100000088900100780000004200430044039f0a42004a004500430054003d007b00390064006500610038003600320063002d1139006300640064002d0034006500370030102d0061006300630031002d006600330032006200330034003400640034003700390035007d00000033000300000710000000040000007fff0400 Boot0002* ARCHLINUX HD(5,GPT,d03ca3cf-1511-d94e-8400-c7a125866442,0x40164000,0x100000) Boot2001* EFI USB Device RC Boot2002* EFI DVD/CDROM RC Boot2003* EFI Network RC


Hãy cùng xem xét quá trình khởi động bằng cách dựa vào đoạn mã ở trên. UEFI sẽ bắt đầu lặp lại danh sách BootOrder . Đối với mỗi mục trong danh sách, nó sẽ tìm kiếm một biến Boot#### tương ứng — Boot0000 cho 0000, Boot2003 cho 2003, v.v. Nếu biến không tồn tại, nó sẽ tiếp tục đến mục tiếp theo. Nếu biến tồn tại, nó sẽ đọc nội dung của biến. Mỗi biến tùy chọn khởi động chứa một mô tả EFI_LOAD_OPTION là một bộ đệm đóng gói byte của các trường có độ dài thay đổi (nó chỉ là cấu trúc dữ liệu).


Cấu trúc dữ liệu được mô tả trong [MdePkg/Include/Uefi/UefiSpec.h][ https://github.com/tianocore/edk2/blob/edk2-stable202405/MdePkg/Include/Uefi/UefiSpec.h#L2122 )


 typedef struct _EFI_LOAD_OPTION { /// The attributes for this load option entry. UINT32 Attributes; /// Length in bytes of the FilePathList. UINT16 FilePathListLength; /// The user readable description for the load option. /// Example: 'ARCHLINUX' / 'Windows Boot Manager' / `EFI USB Device` // CHAR16 Description[]; /// A packed array of UEFI device paths. /// Example: 'HD(5,GPT,d03ca3cf-1511-d94e-8400-c7a125866442,0x40164000,0x100000)/File(\EFI\ARCHLINUX\grubx64.efi)' // EFI_DEVICE_PATH_PROTOCOL FilePathList[]; /// The remaining bytes in the load option descriptor are a binary data buffer that is passed to the loaded image. /// Example: '57a9...0400' in Boot0001 variable // UINT8 OptionalData[]; } EFI_LOAD_OPTION;


Tại thời điểm này, chương trình cơ sở sẽ kiểm tra Đường dẫn thiết bị ( EFI_DEVICE_PATH_PROTOCOL ). Trong hầu hết các trường hợp, máy tính của chúng ta được khởi động từ thiết bị lưu trữ (Ổ cứng/SSD/NVMe/v.v.). Vì vậy, Đường dẫn thiết bị sẽ chứa HD(Partition Number, Type, Signature, Start sector, Size in sectors) .


  • Loại — biểu thị định dạng được sử dụng cho lược đồ phân vùng với các từ khóa MBR (1) hoặc GPT (2).
  • Chữ ký — chữ ký MBR 4 byte nếu Loại là MBR hoặc UUID 16 byte nếu Loại là GPT .


Lưu ý : Nếu bạn quan tâm đến cách dịch các đường dẫn khác, hãy đọc Đặc tả UEFI v2.10, Tham chiếu nút thiết bị văn bản 10.6.1.6 .


UEFI sẽ xem xét đĩa và xem nó có phân vùng khớp với nút hay không. Nếu có, nó sẽ được gắn nhãn với Mã định danh duy nhất toàn cầu (GUID) cụ thể đánh dấu nó là Phân vùng hệ thống EFI (ESP) . Phân vùng này được định dạng bằng hệ thống tệp có thông số kỹ thuật dựa trên phiên bản cụ thể của hệ thống tệp FAT và được đặt tên là Hệ thống tệp EFI ; thực tế, nó chỉ là FAT12/16/32 thông thường.


  • Khởi động gốc : nếu Đường dẫn thiết bị chứa đường dẫn rõ ràng đến File(\Path\To\The\File.efi) thì UEFI sẽ tìm kiếm tệp cụ thể đó. Ví dụ: tùy chọn Boot0000 chứa File(\EFI\ARCHLINUX\grubx64.efi) .
  • Khởi động dự phòng : nếu Đường dẫn thiết bị chỉ trỏ đến một đĩa, thì trong những tình huống như vậy, chương trình cơ sở sẽ sử dụng đường dẫn khởi động dự phòng dựa trên kiến trúc — \EFI\BOOT\BOOT{arch}.EFI ( BOOTx64.EFI cho amd64 hoặc BOOTia32.EFI cho i386 / IA32 ). Cơ chế này cho phép phương tiện di động có thể khởi động (ví dụ: ổ USB) hoạt động trong UEFI; chúng chỉ sử dụng đường dẫn khởi động dự phòng . Ví dụ: tùy chọn Boot0002 sẽ sử dụng cơ chế này.


Lưu ý: Tất cả các tùy chọn Boot#### được đề cập ở trên đều đề cập đến các tùy chọn khởi động được hiển thị trong ví dụ đầu ra của efibootmgr .


Trong cả hai trường hợp, UEFI Boot Manager sẽ tải Ứng dụng UEFI (có thể là bộ nạp khởi động hệ điều hành , UEFI Shell, phần mềm tiện ích, Thiết lập hệ thống và bất kỳ thứ gì) vào bộ nhớ. Tại thời điểm này, quyền điều khiển sau đó được chuyển đến điểm nhập của ứng dụng UEFI . Không giống như BIOS , ứng dụng UEFI có thể trả lại quyền điều khiển cho chương trình cơ sở (ngoại trừ trường hợp ứng dụng chiếm quyền điều khiển hệ thống). Nếu điều đó xảy ra hoặc có bất kỳ sự cố nào xảy ra, Boot Manager sẽ chuyển sang mục nhập Boot#### tiếp theo và thực hiện chính xác quy trình tương tự.


Đặc tả đề cập rằng trình quản lý khởi động có thể tự động duy trì các biến cơ sở dữ liệu. Điều này bao gồm việc xóa các biến tùy chọn tải không được tham chiếu hoặc không thể phân tích cú pháp. Ngoài ra, nó có thể viết lại bất kỳ danh sách có thứ tự nào để xóa bất kỳ tùy chọn tải nào không có các biến tùy chọn tải tương ứng.


Văn bản trên mô tả quá trình khởi động UEFI . Ngoài ra, chương trình cơ sở UEFI có thể chạy ở chế độ Mô-đun hỗ trợ tương thích (CSM) mô phỏng BIOS.

Bộ nạp khởi động hệ điều hành

Một phần mềm được khởi động bởi chương trình cơ sở (thường là Second-Stage Bootloader ) và sử dụng giao diện của nó để tải hạt nhân hệ điều hành . Nó có thể phức tạp như một hệ điều hành, cung cấp các tính năng như:


  • Đọc từ nhiều hệ thống tập tin khác nhau (HFS+, ext4, XFS, v.v.)
  • Tương tác qua mạng (ví dụ: TFTP, HTTP)
  • Khởi động các hạt nhân tương thích với Multiboot
  • Tải chuỗi
  • Đang tải ramdisk ban đầu ( initrd )
  • Và nhiều hơn thế nữa!


Thiết kế chung của các chương trình này nằm ngoài phạm vi của bài viết này. Để so sánh chi tiết các trình khởi động hệ điều hành phổ biến, bạn có thể tham khảo wiki ArchLinuxbài viết Wikipedia .


Hệ thống Windows sử dụng trình nạp khởi động hệ điều hành độc quyền được gọi là Trình quản lý khởi động Windows (BOOTMGR) .


Firmware không còn là một đoạn mã nhỏ, phức tạp nữa. Nó đã trở thành một lượng lớn mã phức tạp và xu hướng hiện tại chỉ góp phần vào điều này. Chúng ta có thể chạy Doom , Twitter và nhiều ứng dụng thú vị khác trên đó.


Hiểu được kiến trúc tổng thể giúp sắp xếp các thành phần này trong tâm trí bạn. Bằng cách kiểm tra thiết kế của chương trình cơ sở hiện có, bạn sẽ hiểu sâu hơn về quá trình hấp dẫn diễn ra mỗi khi máy tính được bật nguồn. Góc nhìn từ trên xuống này không chỉ làm rõ vai trò của từng bộ phận mà còn làm nổi bật bản chất tinh vi và đang phát triển của các hệ thống chương trình cơ sở hiện đại.

Tài nguyên


L O A D I N G
. . . comments & more!

About Author

Aleksandr Goncharov HackerNoon profile picture
Aleksandr Goncharov@tristejoursoir
I respect & support ideas of open-source. Interested in low-level solutions (firmware/kernel) and application software.

chuyên mục

BÀI VIẾT NÀY CŨNG CÓ MẶT TẠI...