دسته بندی:

فرایند بوت شدن لینوکس: از BIOS و UEFI تا systemd

فرایند بوت شدن لینوکس: از BIOS و UEFI تا systemd

سرور را ریستارت می‌کنید و به جای خط فرمان، یک صفحه سیاه یا پیغام GRUB rescue> تحویل می‌گیرید. در چنین لحظه‌ای تنها چیزی که به کارتان می‌آید، دانستن این است که فرایند بوت شدن لینوکس دقیقا از چه مراحلی عبور می‌کند و سیستم در کدام مرحله متوقف شده است. اگر بدانید مسئولیت هر مرحله با چه اجزایی است، عیب‌یابی از حدس‌وگمان به یک بررسی مرحله‌به‌مرحله تبدیل می‌شود.

در این راهنما تمام مراحل و پروسه‌هایی که در پشت صحنه بوت شدن سیستم‌عامل رخ می‌دهد، از لحظه فشردن دکمه پاور تا رسیدن به prompt ورود، مرحله به مرحله بررسی می‌شود. مطلب برای کسانی نوشته شده که در مراحل اولیه آشنایی با دنیای لینوکس هستند، اما بخش‌های عیب‌یابی آن برای مدیر سرور باتجربه هم کاربرد دارد.

نمای کلی: پنج مرحله بوت شدن

  • میان‌افزار یا Firmware (یعنی BIOS یا UEFI) سخت‌افزار را شناسایی و تست می‌کند و دستگاه بوت را پیدا می‌کند.
  • کد بوت اولیه از MBR یا پارتیشن EFI خوانده و اجرا می‌شود.
  • بوت‌لودر (امروزه GRUB2) کرنل و initramfs را در حافظه بارگذاری می‌کند.
  • کرنل سخت‌افزار را راه‌اندازی می‌کند و فایل‌سیستم ریشه را mount می‌کند.
  • فرایند init (امروزه systemd) با PID 1 اجرا می‌شود و سرویس‌ها را بالا می‌آورد.

مرحله اول: روشن شدن و میان‌افزار

BIOS مخفف Basic Input Output System یک برنامه نرم‌افزاری است که روی چیپ‌ست مادربرد قرار دارد. با روشن شدن سیستم، BIOS ابتدا تست POST را اجرا می‌کند، سپس دیوایس‌هایی همچون Hard Disk و CD-ROM و RAM را شناسایی و مقداردهی اولیه می‌کند و در نهایت طبق ترتیب بوتی که تنظیم کرده‌اید، به جستجوی MBR (مخفف Master Boot Record) می‌گردد؛ یک سکتور کم‌حجم که روشن شدن سیستم‌عامل به آن بسته است. BIOS این سکتور را در RAM کپی می‌کند و کنترل را به آن می‌سپارد.

نکته مهم و به‌روز: امروز روی تقریبا تمام سرورها و مادربردهای جدید، به جای BIOS سنتی از UEFI استفاده می‌شود. UEFI به جای خواندن یک سکتور خام، یک پارتیشن مجزا به نام ESP (مخفف EFI System Partition) را که با فایل‌سیستم FAT32 فرمت شده و معمولا روی /boot/efi mount است می‌خواند و یک فایل اجرایی .efi را مستقیما اجرا می‌کند. فهرست گزینه‌های بوت هم در حافظه NVRAM مادربرد ذخیره می‌شود، نه روی دیسک. اگر Secure Boot فعال باشد، ابتدا فایل امضاشده shim اجرا می‌شود و سپس GRUB.

برای اینکه بدانید سرور شما در کدام حالت بوت شده است:

[ -d /sys/firmware/efi ] && echo "UEFI" || echo "Legacy BIOS"

# فهرست گزینه‌های بوت در میان‌افزار (فقط روی UEFI)
efibootmgr -v

هر دو دستور بالا فقط خواندنی هستند. توجه کنید که efibootmgr با سوییچ‌های دیگر (مثل -b و -B) می‌تواند گزینه‌های بوت را حذف کند؛ روی سرور فقط با -v کار کنید مگر آنکه دقیقا بدانید در حال انجام چه کاری هستید.

مرحله دوم: MBR یا Master Boot Record

MBR اولین سکتور فیزیکی دیسک اول است، با اندازه ثابت ۵۱۲ بایت. این سکتور جزء هیچ‌یک از پارتیشن‌ها نیست و در سیلندر صفر، هد صفر و سکتور یک قرار دارد؛ این ناحیه به‌طور کامل برای برنامه‌های بوت رزرو شده است. MBR شامل یک برنامه اجرایی کوتاه و یک جدول پارتیشن اولیه است و اطلاعات لودر سیستم‌عامل‌هایی همچون UNIX و Linux و Windows در آن قرار می‌گیرد. ساختار این ۵۱۲ بایت به این شکل است:

Boot Code (GRUB) 446 bytes
partition 1: 16 bytes
partition 2: 16 bytes
partition 3: 16 bytes
partition 4: 16 bytes
magic Number: 2 bytes

دو بایت آخر همان امضای 0x55AA است که به میان‌افزار می‌گوید این سکتور واقعا قابل بوت است. کد بوت استانداردِ MBR طبق جدول پارتیشن، پارتیشن فعال (active) را شناسایی می‌کند و بوت را از آنجا ادامه می‌دهد. (نکته: GRUB2 کد بوت خودش را در همین ۴۴۶ بایت می‌نویسد و برای یافتن مرحله بعد به فلگ active وابسته نیست، اما مکانیزم کلی همین است.)

محدودیت‌هایی که باید بدانید: چون جدول پارتیشن MBR فقط چهار خانه ۱۶ بایتی دارد، حداکثر چهار پارتیشن اصلی ممکن است (برای بیشتر باید از پارتیشن extended استفاده کرد) و با سکتورهای ۵۱۲ بایتی، حداکثر ظرفیت قابل آدرس‌دهی هر دیسک حدود ۲ ترابایت است. به همین دلیل در سیستم‌های UEFI از جدول پارتیشن GPT استفاده می‌شود که این دو محدودیت را ندارد. برای دیدن نوع جدول پارتیشن دیسک:

parted /dev/sda print | grep "Partition Table"
# خروجی msdos یعنی MBR و خروجی gpt یعنی GPT

lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT

مرحله سوم: بوت‌لودر

اولین کار در لود سیستم‌عامل، انتقال بوت‌لودر به حافظه RAM توسط کد داخل MBR است. چون ۴۴۶ بایت برای یک بوت‌لودر کامل کافی نیست، این کد فقط بخش بعدی بوت‌لودر را از دیسک می‌خواند؛ به همین دلیل به آن بوت چندمرحله‌ای می‌گویند. در مرحله دوم، بوت‌لودر اصلی یعنی GRUB بارگذاری می‌شود.

در متن‌های قدیمی نام LILO و GRUB Legacy (نسخه ۰.۹۷) هم دیده می‌شود. هر دو منسوخ شده‌اند و توزیع‌های امروزی آن‌ها را به‌صورت پیش‌فرض ارائه نمی‌کنند؛ استاندارد فعلی GRUB2 است. اگر روی سروری با LILO روبه‌رو شدید، تقریبا به‌طور قطع با سیستمی خارج از رده پشتیبانی طرف هستید.

پس از آنکه GRUB در RAM لود شود، جستجوی مسیر کرنل آغاز می‌شود. GRUB منوی بوت را نمایش می‌دهد، فایل کرنل و initramfs را که در دایرکتوری /boot قرار دارند (در بسیاری از سرورها این دایرکتوری یک پارتیشن مجزاست) در حافظه بارگذاری می‌کند، پارامترهای کرنل مثل root=UUID=... را به آن پاس می‌دهد و سپس کنترل را تحویل کرنل داده و خودش از کار خارج می‌شود.

فایل تولیدشده grub.cfg را هرگز دستی ویرایش نکنید، چون اولین آپدیت کرنل آن را بازنویسی می‌کند. تنظیمات را در /etc/default/grub (و در صورت نیاز اسکریپت‌های /etc/grub.d/) بنویسید و بعد فایل نهایی را بازتولید کنید:

# خانواده RHEL: CentOS / AlmaLinux / Rocky
grub2-mkconfig -o /boot/grub2/grub.cfg

# روی نسخه‌های 7 و 8 در حالت UEFI مسیر خروجی جداست و نام
# دایرکتوری همان نام توزیع است (redhat / centos / almalinux / rocky):
# grub2-mkconfig -o /boot/efi/EFI/almalinux/grub.cfg

# خانواده Debian / Ubuntu
update-grub

روی RHEL 9 و بالاتر این تفکیک برداشته شده است: در هر دو حالت BIOS و UEFI فقط /boot/grub2/grub.cfg تولید می‌شود و فایل /boot/efi/EFI/<توزیع>/grub.cfg صرفا یک stub است که نباید آن را با grub2-mkconfig بازنویسی کنید؛ بازنویسی آن سرور را از بوت می‌اندازد. پیش از هر بازتولید، از فایل فعلی یک کپی بگیرید.

مرحله چهارم: کرنل و initramfs

کرنل ابتدا خودش را در حافظه از حالت فشرده خارج می‌کند و سپس initramfs را که یک فایل‌سیستم ریشه موقت در RAM است بالا می‌آورد. علت وجود initramfs این است که کرنل برای mount کردن فایل‌سیستم ریشه واقعی به درایورهایی نیاز دارد (کنترلر RAID، درایور دیسک مجازی، LVM، رمزنگاری LUKS) که خودشان روی همان فایل‌سیستم ریشه هستند. initramfs این درایورها را فراهم می‌کند، ریشه واقعی را mount می‌کند و بعد کنار می‌رود.

ls -l /boot/vmlinuz-* /boot/initramfs-* 2>/dev/null   # خانواده RHEL
ls -l /boot/vmlinuz-* /boot/initrd.img-* 2>/dev/null  # خانواده Debian و Ubuntu

# بازسازی initramfs پس از تغییر درایور یا سخت‌افزار
# ابتدا از فایل فعلی نسخه پشتیبان بگیرید:
cp /boot/initramfs-$(uname -r).img /root/initramfs-$(uname -r).img.bak

dracut -f --kver $(uname -r)        # خانواده RHEL
update-initramfs -u -k all          # خانواده Debian و Ubuntu

هر دو دستور بالا فایل initramfs موجود را بازنویسی می‌کنند. اگر ساخت نیمه‌کاره بماند (مثلا پارتیشن /boot پر باشد) سرور در ریستارت بعدی بالا نمی‌آید؛ پس فقط زمانی این کار را انجام دهید که به کنسول یا KVM دسترسی دارید.

مرحله پنجم: init و بالا آمدن سرویس‌ها

پس از mount شدن ریشه، کرنل اولین فرایند فضای کاربر را با PID 1 اجرا می‌کند. اینجا همان نقطه‌ای است که بیشتر مقالات قدیمی متوقف می‌شوند و بزرگ‌ترین تغییر ده سال اخیر لینوکس هم دقیقا همین‌جاست:

  • SysV init (منسوخ): پیکربندی در /etc/inittab و اسکریپت‌ها در /etc/init.d/ و /etc/rc.d/rc3.d/. تا CentOS/RHEL 5 استفاده می‌شد.
  • Upstart (منسوخ): در CentOS/RHEL 6 و اوبونتوهای قدیمی.
  • systemd (استاندارد فعلی): از RHEL 7 و اوبونتو ۱۵٫۰۴ به بعد. مسیر /sbin/init امروز فقط یک symlink به باینری systemd است.

systemd به جای runlevel از مفهوم target استفاده می‌کند. معادل‌ها چنین است: runlevel 0 برابر poweroff.target، runlevel 1 برابر rescue.target، runlevel 3 برابر multi-user.target، runlevel 5 برابر graphical.target و runlevel 6 برابر reboot.target.

دستورهای قدیمی که هنوز در آموزش‌های اینترنتی می‌بینید و معادل امروزی آن‌ها:

service httpd restart        ->  systemctl restart httpd
/etc/init.d/httpd restart    ->  systemctl restart httpd
chkconfig httpd on           ->  systemctl enable httpd
chkconfig --list             ->  systemctl list-unit-files --type=service
init 3                       ->  systemctl isolate multi-user.target

# دیدن و تغییر target پیش‌فرض
systemctl get-default
systemctl set-default multi-user.target

دستورهای ستون چپ روی سیستم‌های امروزی هنوز کار می‌کنند، چون service و chkconfig بازنویسی شده‌اند و درخواست را به systemctl تحویل می‌دهند؛ اما فقط برای سازگاری نگه داشته شده‌اند و رفتارشان همیشه یکسان نیست. مثلا chkconfig --list تنها سرویس‌های قدیمی SysV را نشان می‌دهد و یونیت‌های native سیستم را از قلم می‌اندازد، و مسیر /etc/init.d/ برای سرویس‌هایی که فقط یونیت systemd دارند اصلا وجود ندارد. عادت کنید ستون راست را بنویسید.

چطور مطمئن شویم بوت درست انجام شده است

# سرویس‌هایی که در بوت شکست خورده‌اند
systemctl --failed

# مدت زمان بوت و سهم هر بخش
systemd-analyze
systemd-analyze blame
systemd-analyze critical-chain

# لاگ کامل بوت فعلی و خطاهای بوت قبلی
journalctl -b
journalctl -b -1 -p err

# پیام‌های کرنل
dmesg --level=err,warn

خروجی systemd-analyze زمان صرف‌شده در میان‌افزار، بوت‌لودر، کرنل و userspace را جدا نشان می‌دهد؛ همین یک خط معمولا مشخص می‌کند کندی بوت مربوط به سخت‌افزار است یا به یک سرویس. (سهم firmware و loader فقط روی سیستم‌های UEFI گزارش می‌شود.) اگر journalctl -b -1 چیزی برنگرداند یعنی نگهداری دائمی ژورنال فعال نیست؛ در آن صورت Storage=persistent را در /etc/systemd/journald.conf تنظیم کنید.

خطاهای رایج و محل بروزشان

  • ورود به GRUB rescue> — بوت‌لودر پیدا نشده یا خراب است. باید با رسانه rescue بوت کنید، به سیستم chroot کنید و GRUB را دوباره نصب کنید. روی سیستم‌های BIOS دستور grub2-install /dev/sda کار می‌کند؛ پیش از اجرا حتما با lsblk مطمئن شوید که /dev/sda واقعا همان دیسک بوت است، چون این دستور روی دیسک اشتباه، بوت‌سکتور آن دیسک را بازنویسی می‌کند. روی UEFI در خانواده RHEL این دستور پشتیبانی نمی‌شود و باید بسته‌های grub2-efi و shim را دوباره نصب کرده و سپس فایل پیکربندی را بازتولید کنید.
  • Kernel panic - not syncing: VFS: Unable to mount root fs — کرنل به فایل‌سیستم ریشه نرسیده است. معمولا یا مقدار root=UUID= با خروجی blkid نمی‌خواند، یا initramfs درایور لازم را ندارد.
  • رفتن به emergency mode بعد از ویرایش /etc/fstab — شایع‌ترین خطای مدیر سرور. یک سطر اشتباه در fstab کل بوت را متوقف می‌کند. رفع آن فقط از طریق کنسول یا KVM ممکن است: با journalctl -xb سطر مقصر را پیدا کنید، با mount -o remount,rw / ریشه را نوشتنی کنید، سطر را اصلاح کنید و systemctl daemon-reload را اجرا کنید.
  • پر شدن پارتیشن /boot — ساخت initramfs کرنل جدید ناقص می‌ماند و سرور بعد از آپدیت بالا نمی‌آید. با df -h /boot بررسی کنید و کرنل‌های قدیمی را حذف کنید. کرنلی که همین حالا با آن بوت شده‌اید (خروجی uname -r) و دست‌کم یک کرنل سالم دیگر را هرگز حذف نکنید و به جای پاک کردن دستی فایل‌ها، از پکیج‌منیجر استفاده کنید.
  • ناسازگاری UEFI و BIOS بعد از مهاجرت یا کلون دیسک — دیسکی که با GPT و UEFI نصب شده روی سروری با بوت Legacy بالا نمی‌آید و برعکس.

چه کارهایی را نباید انجام دهید

روی سروری که فقط دسترسی SSH دارید و کنسول یا KVM ندارید، هیچ تغییری در GRUB، fstab یا کرنل ندهید. هر خطا در این سه مورد به قطع کامل دسترسی ختم می‌شود و بدون کنسول قابل رفع نیست.

پیش از ریستارت، هر تغییر fstab را با mount -a و سپس systemctl daemon-reload تست کنید. توجه داشته باشید که mount -a تضمین کامل نیست: سطرهای noauto و مسیرهایی که همین حالا mount هستند بررسی نمی‌شوند. برای mount شبکه‌ای هم گزینه‌های nofail و _netdev را اضافه کنید تا در دسترس نبودن استوریج، بوت را متوقف نکند.

فایل /boot/grub2/grub.cfg را دستی ویرایش نکنید، هرگز آخرین کرنلِ سالم را از منوی بوت حذف نکنید، و target پیش‌فرض یک سرور را روی graphical.target نگذارید. در نهایت، اگر مطمئن نیستید تغییری چه اثری روی بوت دارد، ابتدا روی یک سرور آزمایشی یا اسنپ‌شات امتحان کنید، نه روی سرور اصلی.