Diskless Booting: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
No edit summary
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(17 intermediate revisions by 14 users not shown)
Line 1: Line 1:
{{Convert}}

"Diskless Booting" is a synonym for booting across a network. The kernel and its modules are downloaded from a computer on the network. This can be very useful for large projects where Bochs is too slow or one has to use a floppy disk, and is used in some corporate environments to enable centralized OS updates.
"Diskless Booting" is a synonym for booting across a network. The kernel and its modules are downloaded from a computer on the network. This can be very useful for large projects where Bochs is too slow or one has to use a floppy disk, and is used in some corporate environments to enable centralized OS updates.


In order to boot up your kernel by network, you need a DHCP server, a TFTP server, and a program acting as client on the other computer.
In order to boot up your kernel by network, you need a DHCP server, a TFTP server, and a program acting as client on the other computer.


==The GRUB Way==
== The GRUB Legacy Way ==


First, you have to create a floppy with [[GRUB]] configured for net support. You can either get a floppy image from http://i30www.ira.uka.de/%7Eud3/mkgrubdisk/ or download a current source release of [GRUB] and <tt>./configure</tt> it with support for your NIC.
First, you have to create a floppy with [[GRUB Legacy]] configured for net support. You can download a current source release of [[GRUB Legacy]] and <tt>./configure</tt> it with support for your NIC.


Although this is the simplest way, GRUB doesn't seem to support all network cards.
Although this is the simplest way, GRUB Legacy doesn't seem to support all network cards.


==The PXELINUX Way==
== The PXELINUX Way ==


Compile syslinux; a <tt>pxelinux.0</tt> file will be created. It is a PXE binary of a simple bootloader-over-tftp, which can be booted by the client computer (not the one with the TFTP server). After setting up DHCP and TFTP accordingly so the file boots, you can use pxelinux to load "memdisk", which comes with syslinux as well.
Compile syslinux; a <tt>pxelinux.0</tt> file will be created. It is a PXE binary of a simple bootloader-over-tftp, which can be booted by the client computer (not the one with the TFTP server). After setting up DHCP and TFTP accordingly so the file boots, you can use pxelinux to load "memdisk", which comes with syslinux as well.


This file is loaded with a <tt>memdisk initrd=grub.ima</tt> syntax, which will cause pxelinux to load memdisk and grub.ima through TFTP. Memdisk will hook interrupt 0x13, and boot the disk image that way. (However, not all [[GRUB]] disk images seem to access floppies through bios. If you've got such an image you're stuck.)
This file is loaded with a <tt>memdisk initrd=grub.ima</tt> syntax, which will cause pxelinux to load memdisk and grub.ima through TFTP. Memdisk will hook interrupt 0x13, and boot the disk image that way. (However, not all [[GRUB Legacy]] disk images seem to access floppies through bios. If you've got such an image you're stuck.)


You should get a <tt>pxelinux.0</tt> file, which can be loaded by, for example, etherboot. Many modern computers allow booting from NICs so you only need the TFTP and DHCP server up.
You should get a <tt>pxelinux.0</tt> file, which can be loaded by, for example, etherboot. Many modern computers allow booting from NICs so you only need the TFTP and DHCP server up.


At this point, you can make changes to the grub.ima disk image, and put a [[GRUB]] config file and your kernel's binaries there.
At this point, you can make changes to the grub.ima disk image, and put a [[GRUB Legacy]] config file and your kernel's binaries there.


Try <tt>mount /tftpboot/grub.ima /mnt/fpy -o loop</tt> under linux, for example.
Try <tt>mount /tftpboot/grub.ima /mnt/fpy -o loop</tt> under linux, for example.


==The Direct Way==
== The gPXE + GRUB Way ==


[[GRUB]] since version 2 can piggy-back on [http://www.etherboot.org/wiki/start gPXE]'s network support, unlike GRUB Legacy that does not support newer network cards. The gPXE project is a currently-maintained, open source, free network bootloader. It is easy to get gPXE ISO, disk, or USB disk images from their website, but there is a workaround you need to apply in order to get GRUB to successfully load.
Both of the options above involve using someone else's code to do the dirty work, which may be undesirable in some situations - licensing conflicts, technical problems (e.g. for "memdisk" the interrupt 0x13 hook won't work in protected mode) and possibly personal pride. Fortunately, writing your own PXE boot code isn't as difficult as it sounds.


gPXE supports multiboot, but if it detects a multiboot image then it will not provide PXE services. Unfortunately, it detects your generated GRUB image as multiboot, and the only way I found to get around this was to recompile gPXE without multiboot support. Recompiling gPXE is easy: unpack it, <tt>cd src; make</tt>. Before you do that, you will want to edit <tt>src/config/defaults/pcbios.h</tt> and comment out the line that defines <tt>IMAGE_MULTIBOOT</tt>. After compiling you should be left with <tt>bin/gpxe.{dsk,iso,usb}</tt> which you can write to disk or CD.
The first step is to download the PXE specification from the internet and take a look at it. At first glance this specification can be rather daunting, but don't let that stop you - most of it relates to BIOS and network cards and can be safely ignored. The important part is in chapter 3, the PXE API.


To create a GRUB PXE bootable image, you can follow the advice in the [http://www.gnu.org/software/grub/manual/grub.html#Network GRUB manual's Network chapter]. Several of the options to <tt>grub-mkimage</tt> did not exist in past versions and you need to upgrade if they aren't supported. If you compile from source, you can get all the <tt>*.{lst,mod,img}</tt> files you need in the source directory. In the case you compile your own GRUB you do not need to install it, just do commands like this:
Basically, "somehow" your PXE boot code gets into the client computer and it doesn't really matter how (networking code in the client machine's ROM arranges this with help from the DHCP server and TFTP server). Your code is loaded at 0x0007C00 and started in real mode, just like a normal floppy boot sector. The difference is that you don't need the "0xAA55" magic value and the size of your code can be up to 32 KB (no more trouble squeezing everything into 512 bytes).


<syntaxhighlight lang="text">
When your code is started, the BIOS gives it a valid stack (with at least 1.5 KB of free stack space) and passes some parameters to your code on this stack and in registers. See section 4.4.5 "_Client State at Bootstrap Execution Time (Remote.0)_" in the specification for details. There's only 2 things that are important here - the address of an older "PXENV+ structure" and the address of the newer "PXE structure". These structures are used to find the PXE API entry point/s and a few other things.
./grub-mkimage -d . --format=i386-pc --output=core.img --prefix="(pxe)/boot/grub" pxe pxecmd
cat pxeboot.img core.img > grub2pxe
</syntaxhighlight>


The final thing you need to do is setup a DHCP/BOOTP/TFTP server. I used <tt>dnsmasq</tt> which came preinstalled on my workstations, and seems to be widely available in distributions. It can easily be configured on the command line or in <tt>/etc/dnsmasq.conf</tt> which uses the same syntax as the long-form command-line options but without the leading dashes. You will need the following options:
The PXE entry structure was introduced in version 2.1 of the specification, while the PXENV+ structure is becoming obsolete. This means that only one of these structures may be present depending on how old your ROM's code is. The specification states that if the !PXE structure is present then your code should use the PXE entry point (not the entry point from the PXENV+ structure, which may be present for backwards compatability). Both of these structures contain a _segment:offset_ for their own PXE API entry point. The calling conventions you need use to access the PXE API depend on which structure is are present.


<syntaxhighlight lang="text">
For the PXE entry point you use "_Pascal_" calling conventions (push the parameters on the stack, far call the entry point, then clean up the stack), while the older PXENV+ entry point uses registers instead. Apart from this the PXE API is the same, and fortunately all PXE API functions have the same parameters - the "_opcode_" (or API function number) and the _segment:offset_ for a structure containing data for the function.
interface=... # be careful what interface the dhcp server runs on!
bind-interfaces # *really* only bind that interface
dhcp-range=a.b.c.d,e.f.g.h # whatever your private network uses
dhcp-boot=boot/grub/grub2pxe # tells machine to boot grub
dhcp-no-override # some kind of workaround that gpxe needs
enable-tftp
tftp-root=/tftp # or wherever
</syntaxhighlight>


and there are other options to explore as well. Now make sure that you take <tt>grub2pxe,*.lst,*.mod</tt> from the GRUB source and put them in <tt>/tftp/boot/grub</tt> or equivalent. Also put your <tt>grub.cfg</tt> file there. The format of that is fairly simple. Here's the essence of what I use:
For the PXE entry point you'd use something like this:


<syntaxhighlight lang="text">
<pre>
set timeout=0
push structureSegment
set default=0
push structureOffset
menuentry "MY OS" {
push opcode
set root=(pxe)
call far [PXEAPIentry]
multiboot /kernel
add sp,6
module /shell
</pre>
module /test
}
</syntaxhighlight>


Make sure your kernel and modules appear in the tftp root, and you should be set to boot using your gPXE media, over a private network connection hooked up between workstations.
For the older PXENV+ entry point you'd use something like this instead:


==The Direct Way==
<pre>
mov ds,structureSegment
mov di,structureOffset
mov bx,opcode
call far [PXEAPIentry]
</pre>


{{Main|PXE}}
Therefore, one of the first things your code will need to do is to detect which entry point to use and setup a generic interface so that the rest of your code doesn't need to care which calling conventions the PXE API needs. For example:


Both of the options above involve using someone else's code to do the dirty work, which may be undesirable in some situations - licensing conflicts, technical problems (e.g. for "memdisk" the interrupt 0x13 hook won't work in protected mode) and possibly personal pride. Fortunately, writing your own PXE boot code isn't as difficult as it sounds.
<pre>
callPXEAPI:
cmp [PXEAPItype],0
je .olderAPI
push ds
push di
push bx
call far [PXEAPIentry]
add sp,6
ret


At first glance the PXE specification can look rather daunting, however most of it relates to BIOS and network cards and can be safely ignored. The important part is in chapter 3, the PXE API.
.olderAPI:
call far [PXEAPIentry]
ret
</pre>


The PXE API is capable of doing raw UDP connections among several other things, however, for a start, you might just want to read files using TFTP.
In any case, the PXE API will leave a status flag in AX (0x0000 = OK, 0x0001 = Failed). If the function failed the PXE API will set more specific flags in the "status" field of the structure you passed to the function.


==See Also==
Now you should be able to call the PXE API. The next step is to get some information about the network. Specifically, you will need the TFTP server's IP address, but you may also want the DHCP server's IP address, the client machine's IP address and the client's MAC address. To get this information (and more) you need to call the PXE API's "Get Cached Info" function. Setup the "t_PXENV_CACHED_INFO" structure as described in the specification and call the PXE API with the address of this structure and 0x0071 as the opcode parameter. For the packet type field in this structure, use "type = 2" and leave everything else set to zero (if you just want the address where the information already is, instead of having the information copied to your own buffer).
===External Links===
*[http://osdev.berlios.de/netboot.html Tutorial on network booting]
*[http://home.dei.polimi.it/fornacia/progetto_minazzi/index.html#HOW%20TO%20INSTALL%20A%20TFTP%20SERVER Setting up a TFTP server]
*[http://tftpd32.jounin.net/ TFTP32 server]
*[http://download.intel.com/design/archives/wfm/downloads/pxespec.pdf PXE Specification]


[[Category:Booting]]
If this worked correctly you should have the segment and offset for a "cached" TCPI/IP packet that was sent to the BIOS's code by the DHCP server. This data contains everything you need, and you should be able to extract the TFTP server's IP address from it (the "sip" field in the "bootph" structure).
[[Category:Bootloaders]]

Now that you know the IP address for the TFTP server you can use the PXE API to access the TFTP server. The functions for this are "TFTP Open", "TFTP Read" and "TFTP Close" (similar to common file I/O operations), or alternatively you could use the "TFTP Read File" function.

After you've read any files your OS needs to boot (and you're finished with networking) it's good to shut down PXE and reclaim any memory it was using. Unfortunately the PXE Specification isn't very clear on how this is meant to be done (which functions in which order), so I've assumed the code for PXE booting Linux is correct.

PXE support is unloaded by using the "PXEAPI_UNDI_SHUTDOWN" function to restore the network adapter to it's default state, followed by the "PXENV_UNLOAD_STACK" function to unhook any IRQ handlers. Lasty, PXE needs to prepare itself to be removed from memory (e.g. unhook interrupt 0x1A) using the "PXENV_STOP_UNDI" function (for PXE) or the "PXENV_UNDI_CLEANUP" function (for PXENV+).

Once PXE is unloaded, memory can be reclaimed by checking the UNDI code segment and UNDI data segment start addresses and sizes (in the PXENV+ data structure), calculating "start address + size" and selecting the highest value. This value is the "new" number of bytes of RAM starting at 0x00000000 (or the amount of conventional memory), and can be used directly and/or the value at 0x40:0x13 (or 0x00000413) can be changed to reflect the new number of 1 KB blocks of conventional memory. Please note that it's probably a good idea to calculate the amount of conventional memory before you unload PXE, because some of the functions above may clear the segment addresses and sizes in the PXENV+ structure.

That is all you really need to know for booting from PXE (however the PXE API is capable of doing raw UDP connections and other things).

==External Links==
*[http://osdev.berlios.de/netboot.html Tutorial on network booting]
*[http://www.gnu.org/software/grub/manual/html_node/Diskless.html#Diskless Official GRUB FAQ]
*[http://www.elet.polimi.it/upload/fornacia/progetto_minazzi/index.html#HOW%20TO%20INSTALL%20A%20TFTP%20SERVER Setting up a TFTP server]

Latest revision as of 04:20, 9 June 2024

"Diskless Booting" is a synonym for booting across a network. The kernel and its modules are downloaded from a computer on the network. This can be very useful for large projects where Bochs is too slow or one has to use a floppy disk, and is used in some corporate environments to enable centralized OS updates.

In order to boot up your kernel by network, you need a DHCP server, a TFTP server, and a program acting as client on the other computer.

The GRUB Legacy Way

First, you have to create a floppy with GRUB Legacy configured for net support. You can download a current source release of GRUB Legacy and ./configure it with support for your NIC.

Although this is the simplest way, GRUB Legacy doesn't seem to support all network cards.

The PXELINUX Way

Compile syslinux; a pxelinux.0 file will be created. It is a PXE binary of a simple bootloader-over-tftp, which can be booted by the client computer (not the one with the TFTP server). After setting up DHCP and TFTP accordingly so the file boots, you can use pxelinux to load "memdisk", which comes with syslinux as well.

This file is loaded with a memdisk initrd=grub.ima syntax, which will cause pxelinux to load memdisk and grub.ima through TFTP. Memdisk will hook interrupt 0x13, and boot the disk image that way. (However, not all GRUB Legacy disk images seem to access floppies through bios. If you've got such an image you're stuck.)

You should get a pxelinux.0 file, which can be loaded by, for example, etherboot. Many modern computers allow booting from NICs so you only need the TFTP and DHCP server up.

At this point, you can make changes to the grub.ima disk image, and put a GRUB Legacy config file and your kernel's binaries there.

Try mount /tftpboot/grub.ima /mnt/fpy -o loop under linux, for example.

The gPXE + GRUB Way

GRUB since version 2 can piggy-back on gPXE's network support, unlike GRUB Legacy that does not support newer network cards. The gPXE project is a currently-maintained, open source, free network bootloader. It is easy to get gPXE ISO, disk, or USB disk images from their website, but there is a workaround you need to apply in order to get GRUB to successfully load.

gPXE supports multiboot, but if it detects a multiboot image then it will not provide PXE services. Unfortunately, it detects your generated GRUB image as multiboot, and the only way I found to get around this was to recompile gPXE without multiboot support. Recompiling gPXE is easy: unpack it, cd src; make. Before you do that, you will want to edit src/config/defaults/pcbios.h and comment out the line that defines IMAGE_MULTIBOOT. After compiling you should be left with bin/gpxe.{dsk,iso,usb} which you can write to disk or CD.

To create a GRUB PXE bootable image, you can follow the advice in the GRUB manual's Network chapter. Several of the options to grub-mkimage did not exist in past versions and you need to upgrade if they aren't supported. If you compile from source, you can get all the *.{lst,mod,img} files you need in the source directory. In the case you compile your own GRUB you do not need to install it, just do commands like this:

./grub-mkimage -d . --format=i386-pc --output=core.img --prefix="(pxe)/boot/grub" pxe pxecmd
cat pxeboot.img core.img > grub2pxe

The final thing you need to do is setup a DHCP/BOOTP/TFTP server. I used dnsmasq which came preinstalled on my workstations, and seems to be widely available in distributions. It can easily be configured on the command line or in /etc/dnsmasq.conf which uses the same syntax as the long-form command-line options but without the leading dashes. You will need the following options:

interface=...                # be careful what interface the dhcp server runs on!
bind-interfaces              # *really* only bind that interface
dhcp-range=a.b.c.d,e.f.g.h   # whatever your private network uses
dhcp-boot=boot/grub/grub2pxe # tells machine to boot grub
dhcp-no-override             # some kind of workaround that gpxe needs
enable-tftp
tftp-root=/tftp              # or wherever

and there are other options to explore as well. Now make sure that you take grub2pxe,*.lst,*.mod from the GRUB source and put them in /tftp/boot/grub or equivalent. Also put your grub.cfg file there. The format of that is fairly simple. Here's the essence of what I use:

set timeout=0
set default=0
menuentry "MY OS" {
  set root=(pxe)
  multiboot /kernel
  module    /shell
  module    /test
}

Make sure your kernel and modules appear in the tftp root, and you should be set to boot using your gPXE media, over a private network connection hooked up between workstations.

The Direct Way

Main article: PXE

Both of the options above involve using someone else's code to do the dirty work, which may be undesirable in some situations - licensing conflicts, technical problems (e.g. for "memdisk" the interrupt 0x13 hook won't work in protected mode) and possibly personal pride. Fortunately, writing your own PXE boot code isn't as difficult as it sounds.

At first glance the PXE specification can look rather daunting, however most of it relates to BIOS and network cards and can be safely ignored. The important part is in chapter 3, the PXE API.

The PXE API is capable of doing raw UDP connections among several other things, however, for a start, you might just want to read files using TFTP.

See Also

External Links