Log tiến trình start linux

Giới thiệu

Quá trình khởi động Linux là tiến trình khởi động nhiều giai đoạn được thực hiện trong suốt quá trình khởi động việc cài đặt Linux.

Quá trình khởi động cài đặt Linux liên quan đến nhiều giai đoạn và nhiều thành phần phần mềm bao gồm:

  • Khởi tạo (initialization) firmware
  • Thực thi boot loader
  • Load và khởi động linux kernel image
  • Thực thi nhiều startup script và daemon khác nhau

Có nhiều cách tiếp cận khác nhau trong mỗi giai đoạn và các thành phần này. VD GRUB, LILO, SYSLINUX hoặc Loadlin có thể được sử dụng như là Boot Loader trong khi các startup script có thể là loại init-style truyền thống hoặc cấu hình hệ thống có thể được thực hiện thông qua các thay thế hiện đại hơn như systemd hoặc upstart.

Tổng quan

Giai đoạn đầu tiên của tiến trình khởi động Linux phụ thuộc rất nhiều vào kiến trúc phần cứng máy tính. Trên kiến trúc PC, BIOS đóng 1 vai trò rất quan trọng. Trong các phần cứng tương thích PC, quá trình khởi động như sau:

  1. BIOS thực hiện các task khởi động cụ thể tùy thuộc vào platform phần cứng thực tế. Một khi phần cứng đã được liệt kê (enumerated) và phần cứng cần thiết cho việc boot được khởi tạo chính xác thì BIOS sẽ load và thực thi boot code từ thiết bị boot đã được cấu hình.
  2. Boot loader thường hiển thị cho user 1 menu gồm các boot option và có 1 default option sẽ được lựa chọn sau 1 khoảng thời gian trôi qua. Một khi sự lựa chọn được thực hiện, boot loader sẽ load kernel vào memory, cung cấp cho nó một vài tham số và trao quyền điều khiển cho nó.
  3. Kernel nếu bị nén sẽ được giải nén, sau đó nó sẽ thiết lập các chức năng hệ thống như phần cứng thiết yếu và memory paging và gọi start_kernel() để thực hiện các việc quan trọng của cài đặt hệ thống (interrupt, quản lý bộ nhớ còn lại, khởi tạo device và driver …). Sau đó nó sẽ khởi động riêng biệt idle process, scheduler, và init process được thực thi trong user space.
  4. Init (process) có chứa các script được thực thi bởi shell (sysv, bsd, runit) hoặc có chứa các file cấu hình được thực thi bởi các thành phần binary (systemd, upstart). Init có các level cụ thể (sysv, bsd) hoặc các target (systemd), mỗi cái bao gồm 1 tập các service (daemon) cụ thể. Chúng cung cấp các service, cấu trúc non-operating system khác nhau và hình thành user environment. Đối với môi trường server thì nó sẽ khởi động web server, database services và networking.
  5. Đối với môi trường Desktop thì nó sẽ khởi động 1 daemon được gọi là Display Manager để khởi động 1 môi trường đồ họa có chứa graphical server để cung cấp nền tảng graphical stack cơ bản và 1 Login Manager để cung cấp khả năng nhập credential và lựa chọn session. Sau khi user đã nhập credential chính xác thì Session Manager sẽ khởi động session. Session là 1 tập các chương trình như các thành phần UI (panels, desktops, applets …) để hình thành 1 môi trường Desktop đầy đủ.

Khi shutdown, init sẽ được gọi để đóng tất cả các chức năng của user space theo cách được kiểm soát. Khi tất cả các process khác đã bị terminated, init sẽ thực hiện 1 system call đến kernel để thực hiện shutdown system.

Boot Loader phase

Boot loader phase là khác nhau với các kiến trúc phần cứng khác nhau. Vì phase trước đó (phase BIOS) không phải là 1 phần của HĐH, quá trình boot dựa trên BIOS cho kiến trúc x86 và x86-64 được xem như là khởi động khi MBR (master boot record) code được thực thi trong real mode và giai đoạn đầu (1st stage) boot loader được load. Trong hệ thống UEFI, linux kernel có thể được thực thi trực tiếp nên không cần boot loader.

Bên dưới là tóm tắt một vài boot loader phổ biến.

  • LILO không hiểu hoặc diễn giải bố cục file system. Thay vào đó, 1 file cấu hình (/etc/lilo.conf) được tạo ra trong live system sẽ map thông tin raw offset (mapper tool) về vị trí của kernel và ram disk (initrd hoặc initramfs). File cấu hình bao gồm dữ liệu như boot partition và kernel pathname cho mỗi (boot partition) cũng như các option đã được tùy biến nếu cần sẽ được write cùng với code của bootloader vào trong MBR bootsector. Khi bootsector này được đọc và trao quyền điều khiển bởi BIOS, LILO sẽ load menu code và vẽ nó, sau đó sử dụng các giá trị đã được lưu trữ cùng với các input của user để tính toán và load Linux kernel hoặc chain-load bất kỳ bootloader nào khác.
  • Loadlin là bootloader có thể thay thế DOS hoặc Windows 9x kernel đang chạy bằng Linux kernel tại thời điểm chạy (runtime). Điều này có thể hữu ích trong trường hợp phần cứng cần được bật thông qua phần mềm và các chương trình cấu hình đó là độc quyền và chỉ khả dụng cho DOS. Phương pháp khởi động này ngày nay ít cần thiết hơn, vì Linux có trình điều khiển (driver) cho rất nhiều thiết bị phần cứng, tuy nhiên, ta có thể thấy nó được sử dụng trong các thiết bị di động. Một trường hợp sử dụng khác là khi Linux nằm trên một thiết bị lưu trữ không khởi động được từ BIOS: DOS hoặc Windows có thể load các driver thích hợp để bù đắp cho hạn chế của BIOS và khởi động Linux từ đó.
  • GRUB 1: bao gồm logic để đọc/hiểu các file system phổ biến lúc runtime để truy cập vào file cấu hình của nó (GRUB). Điều này giúp GRUB 1 có khả năng đọc file cấu hình của nó từ file system thay vì nhúng nó vào trong MBR nên cho phép nó thay đổi cấu hình lúc runtime và chỉ định disk và partition trong định dạng có thể đọc được thay vì dựa vào các offset. Nó cũng chứa CLI để dễ dàng fix hoặc modify GRUB nếu bị lỗi.
  • GRUB 2: khác với GRUB 1 vì nó có 2 (có thể 3) stage và có khả năng tự động phát hiện các HĐH khác nhau và tự động cấu hình. 1st stage boot loader được load và thực thi bởi BIOS trên MBR hoặc bởi 1 boot loader khác từ boot sector của partition (PBR). Nhiệm vụ của nó là khám phá và truy cập đến các file system khác nhau để đọc file cấu hình. Tùy chọn stage 1.5 có thể được load và thực thi bởi 1st stage loader. 2nd stage được load cuối cùng và hiển thị GRUB menu cho phép user lựa chọn HĐH hoặc chỉnh sửa các tham số khởi động. Sau khi chọn HĐH và các tham số, GRUB sẽ load kernel vào trong memory và chuyển quyền điều khiển cho nó. GRUB 2 cũng có khả năng thực hiện chain load đến 1 boot loader khác (như NTLD của Windows)
  • systemd-boot (trước đây là Gummiboot), là một bootloader đi kèm với systemd với yêu cầu cấu hình tối thiểu (chỉ dành cho hệ thống UEFI).
  • SYSLINUX/ISOLINUX là một bootloader chuyên khởi động các bản cài đặt Linux đầy đủ từ hệ thống tập tin FAT. Nó thường được sử dụng để khởi động hoặc rescue disk, live USB và các hệ thống khởi động gọn nhẹ khác. ISOLINUX thường được sử dụng bởi Linux Live CD và đĩa CD cài đặt có khả năng khởi động.

Kernel phase

Linux kernel quản lý tất cả các process của HĐH như các process quản lý bộ nhớ, task scheduling, I/O, IPC và quản lý tổng thể hệ thống. Những process trên được load trong 2 giai đoạn.

  • Trong 1st stage, kernel (là file image đã nén) sẽ đươc load vào memory và giải nén, sau đó một vài chức năng cơ bản như quản lý bộ nhớ cơ bản được thiết lập. Quyền điều khiển sau đó sẽ được chuyển cho kernel start process (2nd stage)
  • 2nd stage: Khi kernel hoạt động đầy đủ thì kernel sẽ tìm kiếm init process để chạy để thiết lập user space tách biệt và các processes cần thiết cho user environment và login. Bản thân kernel sau đó được cho phép đi vào trạng thái idle, chờ để được gọi bởi các process.

Với một vài platform như ARM 64 bit, việc giải nén kernel phải được thực hiện bởi boot loader.

Kernel thường được load dưới dạng file image, được nén thành định dạng zImage hoặc bzImage bằng zlib. Một chương trình (routine) ở phần đầu (head) của nó sẽ thực hiện việc thiết lập phần cứng tối thiểu, giải nén image hoàn toàn vào bộ nhớ cao (high memory) và lưu ý đến bất kỳ RAM disk nào nếu được cấu hình. Sau đó, nó thực hiện khởi động kernel thông qua ./arch/i386/boot/headstartup_32() (dành cho bộ xử lý dựa trên x86).

Chức năng khởi động cho kernel (còn được gọi là swapper hoặc process 0) sẽ thiết lập việc quản lý bộ nhớ (bảng phân trang và phân trang bộ nhớ), phát hiện loại CPU và bất kỳ chức năng bổ sung nào như khả năng dấu chấm động, sau đó chuyển sang chức năng Linux kernel cụ thể không phụ thuộc kiến ​​trúc thông qua lời gọi đến start_kernel().

start_kernel thực thi một loạt các hàm khởi tạo. Nó thiết lập xử lý ngắt (IRQs), cấu hình thêm bộ nhớ, bắt đầu quá trình Init (process đầu tiên trong user space) và sau đó khởi động idle task thông qua cpu_idle(). Đáng chú ý, quá trình khởi động kernel cũng gắn RAM disk ban đầu ("initrd đã được load trước đó làm root file system tạm thời trong giai đoạn khởi động (boot phase). Initrd cho phép các driver module được load trực tiếp từ bộ nhớ mà không phụ thuộc vào các thiết bị khác (ví dụ: đĩa cứng) và các driver cần thiết để truy cập chúng (ví dụ: SATA driver). Sự phân chia này của một số driver được biên dịch tĩnh vào kernel và các driver khác được load từ initrd cho phép tạo ra một kernel nhỏ hơn. Root filesystem sau đó được chuyển qua lời gọi đến pivot_root() để unmount root filesystem tạm thời và thay thế nó bằng việc sử dụng root filesystem, sau khi có thể truy cập được. Sau đó, bộ nhớ được sử dụng bởi root filesystem tạm thời sẽ được lấy lại.

Như vậy, kernel khởi tạo các thiết bị, mount root filesystem được boot loader chỉ định ở dạng read only và chạy Init (/sbin/init) được chỉ định như là process đầu tiên được chạy bởi hệ thống (PID = 1). Một thông báo được in bởi kernel khi mount filesystem và bởi Init khi khởi động Init process. Nó cũng có thể tùy chọn chạy Initrd để cho phép cài đặt và xử lý các vấn đề liên quan đến thiết bị (RAM disk hoặc tương tự) trước khi root filesystem được mount.

Theo Red Hat, chi tiết về kernel process ở giai đoạn này được tóm tắt như sau:

“Khi kernel được load, nó ngay lập tức khởi tạo và cấu hình bộ nhớ của máy tính cũng như cấu hình các phần cứng khác nhau được gắn vào hệ thống, bao gồm tất cả các bộ xử lý, hệ thống I/O và thiết bị lưu trữ. Sau đó, nó sẽ tìm kiếm initrd image được nén ở một vị trí xác định trước trong bộ nhớ, giải nén nó, mount nó và load tất cả các driver cần thiết. Tiếp theo, nó khởi tạo các thiết bị ảo (virtual device) liên quan đến filesystem, chẳng hạn như LVM hoặc phần mềm RAID trước khi un-mount initrd disk image và giải phóng tất cả bộ nhớ mà disk image đã chiếm dụng. Sau đó, kernel tạo ra một root device, mount root partition ở chế độ read only và giải phóng mọi bộ nhớ không sử dụng. Tại thời điểm này, kernel được load vào bộ nhớ và hoạt động. Tuy nhiên, vì không có ứng dụng người dùng nào cho phép nhập liệu vào hệ thống nên không thể làm được gì nhiều với nó”. Quá trình khởi động theo kiểu initramfs cũng tương tự, nhưng không hoàn toàn giống với quá trình khởi động initrd được mô tả ở đây.

Tại thời điểm này, với các ngắt (interrupt) được bật, bộ lập lịch (scheduler) có thể kiểm soát việc quản lý tổng thể của hệ thống, để cung cấp tính năng đa nhiệm có ưu tiên (pre-emptive multi-tasking) và init process sẽ tiếp tục khởi động môi trường người dùng trong user space.

User space ban đầu

Bài đầy đủ: initramfs

initramfs, còn được gọi là user space ban đầu, đã có từ phiên bản 2.5.46 của Linux kernel, với mục đích thay thế nhiều chức năng nhất có thể mà trước đó kernel đã thực hiện trong quá trình khởi động. Việc sử dụng điển hình của user space ban đầu là để phát hiện trình điều khiển thiết bị (device driver) nào cần thiết để load filesystem chính trong user space và load chúng từ filesystem tạm thời. Nhiều bản phân phối sử dụng dracut để tạo và duy trì initramfs image.

Init process

Khi kernel đã khởi động, nó sẽ khởi động init process. Trong lịch sử, đây là “SysV init”, thường được gọi là “init”. Các bản phân phối Linux gần đây có khả năng sử dụng một trong những lựa chọn thay thế hiện đại hơn như systemd.

SysV init

init là tổ tiên (parent) của tất cả các process trong hệ thống, nó được kernel thực thi và chịu trách nhiệm cho việc khởi động tất cả các process khác. Nó là parent của tất cả các process có parent bị chết và nó chịu trách nhiệm thu nhận (reap) các process khi chúng bị bỏ rơi. Các process được quản lý bởi init được gọi là các job và được định nghĩa bởi các file trong thư mục /etc/init

Nhiệm vụ (job) của init là làm cho mọi thứ chạy theo cách nó nên chạy một khi kernel đã khởi động thành công. Cơ bản, nó thiết lập và vận hành toàn bộ user space, bao gồm kiểm tra và mount file system, khởi động các user service cần thiết và cuối cùng chuyển sang user environment khi hệ thống khởi động thành công. Nó được kế thừa từ init process của Unix nhưng đã được tùy biến. Trong 1 hệ thống Linux chuẩn, init được thực thi với 1 tham số gọi là runlevel có giá trị từ 0 đến 6 và quyết định subsystem nào có thể hoạt động. Mỗi runlevel có script riêng của nó để lập trình các process khác nhau liên quan đến việc thiết lập hoặc rời khỏi 1 runlevel và các script này được tham chiếu khi cần trong quá trình boot. Init script thường được lưu trong thư mục có tên /etc/rc. File cấu hình ở cấp cao nhất (top level) cho init nằm tại /etc/inittab

Trong quá trình khởi động hệ thống, nó kiểm tra xem có 1 runlevel mặc định nào được chỉ định trong /etc/inittab chưa và yêu cầu bước vào runlevel đó thông qua system console hay không. Nó sau đó tiếp tục chạy tất cả các boot script cho runlevel đó bao gồm load các module, kiểm tra tính toàn vẹn của root file system (đã được mount là read only) và sau đó remount lại nó với đầy đủ quyền read-write access và thiết lập network.

Sau khi nó sinh ra (spawn) tất cả các process đã được chỉ định, init sẽ vào trạng thái ngủ (dormant) và đợi 1 trong 3 sự kiện xảy ra:

  • Các process được khởi động và kết thúc hoặc bị chết
  • Một tín hiệu lỗi nguồn (power fail)
  • Một request thông qua /sbin/telinit để thay đổi runlevel

Systemd

Mục đích của systemd là thay thế init. Cũng giống như init, systemd là 1 daemon (chạy ngầm) để quản lý các daemon khác. Tất cả các daemon bao gồm cả systemd đều là các process trong background. Systemd là daemon đầu tiên khởi động (trong suốt quá trình boot) và là daemon cuối cùng bị terminate (trong suốt quá trình shutdown)

Systemd được thiết kế để cải thiện software framework để diễn đạt các dependencies để cho phép xử lý song song trong suốt quá trình boot hệ thống và giảm overhead tính toán của shell.

Các cpu instruction để khởi tạo của systemd cho mỗi daemon được record trong 1 file cấu hình tường minh thay vì shell script. Với IPC (inter process communication), systemd sử dụng Unix domain socket và D-Bus cho các daemon đang chạy. Systemd cũng có khả năng song song chủ động (aggressive parallelization)

Upstart

Init process truyền thống ban đầu chỉ chịu trách nhiệm mang máy tính vào trạng thái chạy bình thường sau khi power on hoặc shutdown các service từ từ trước khi shutdown. Kết quả của thiết kế này là đồng bộ tuyệt đối (strictly synchronous), ngăn chặn các task khác cho đến khi task hiện tại hoàn thành. Các task của nó cũng phải được định nghĩa trước và bị giới hạn với chức năng prep hoặc cleanup này. Điều này làm cho nó không thể quản lý các non-startup task khác nhau trên máy tính hiện đại.

Upstart hoạt động bất đồng bộ, nó quản lý việc khởi động các task và service trong suốt quá trình boot và stop chúng trong suốt quá trình shutdown và cũng giám sát các task và service khi hệ thống đang chạy.

Dễ dàng chuyển đổi (transition) và tương thích ngược hoàn hảo với sysvinit là mục tiêu thiết kế rõ ràng. Dựa vào đó, upstart có thể chạy unmodified sysvinit script. Theo cách này, nó khác với hầu hết các bản thay thế init khác (bên cạnh systemd và OpenRC) trong đó nó (systemd và OpenRC) thường giả định và yêu cầu dịch chuyển hoàn toàn để chạy chính xác và không hỗ trợ môi trường hỗn hợp (mixed) của các phương thức khởi động truyền thống và mới.

Upstart cho phép các mở rộng (extension) cho mô hình event của nó thông qua việc sử dụng initctl để nhập các event tùy biến hoặc kết nối (bridge) event để tích hợp nhiều hoặc các event phức tạp hơn. Mặc định upstart bao gồm bridge cho socket, dbus, udev, file và dconf event.

Link gốc