Starting up a Linux box is entirely different from starting up a Windows or MacOS machine. To take advantage of all that Linux has to offer, you need to understand what run levels are and how they work. We walk you through it.
One of the nicest things about purely GUI-based systems like the MacOS and Windows, is that they start up cleanly. You turn the key, you hear a few cool noises (courtesy of Brian Eno and Grandfather Clock), a few icons pop up, and suddenly you materialize as a driver on the information interstate.
Needless to say, things are somewhat different with Linux. Instead of a cute musical tune and a bunch of icons, Linux hits you with information overload — screen after screen of information about devices, interrupts, and networking status, followed by a stately parade of status messages about services that you may or may not care about (or even understand). In this regard, Linux resembles a car sold with a spare tire, jack, shop manual, toolkit, and complete schematics of the wiring harness.
This article will help you understand all the information Linux throws at you when it starts up. We’ll examine the way Linux allows you to make different sets of services available to users at boot time. We’ll also look at how to control the services that are available once your system is up and running. You may never have to change anything, but it’s still comforting to know exactly what’s going on and that you have the flexibility to change things if you ever need to.
Getting the Boot
The first thing a computer does when it starts up is to conduct a basic self-test (known as POST — Power On Self Test) to determine things such as how much memory it has, what drives it has, whether a keyboard and mouse are present, etc. After completing the self-test, the system reads a block of information from the device it is booting from (known as a boot block), which is usually (but not always) your hard disk. The boot block is always found at the same location — track 0, cylinder 0, and head 0 of the device the system is booting from. You can modify the settings of your computer’s Basic Input Output System (BIOS) if wish to change your boot device, but describing how to do that is specific to each BIOS.
The boot block contains a program loader (usually LILO [LInux LOader] or a variant thereof) that has knowledge about how the partitions on the boot disk are organized, what operating systems your computer can run, where to look on the disk for each of them, and so on. Everything that comes up on your screen before the LILO: prompt is your computer testing itself and its hardware — everything after that has something to do with Linux. (Assuming you’re running Linux, of course.)
At the LILO: prompt, you can press the <Return> key to cause your system to boot the default operating system, “linux,” or press the <Tab> key to see the list of operating systems that your computer can currently boot. The list of operating systems that LILO knows about and can find on your system is located in the file /etc/lilo.conf.
So here’s where things really start to get interesting! LILO loads the selected or default operating system (actually, the Linux kernel) into memory and then transfers control to that kernel. (LILO also does the same thing when you boot a system other than Linux.)
As the kernel executes, it does the same sorts of hardware probes that your BIOS and motherboard did when you first switched on your system, but this time with an eye towards using all of them as key underpinnings of your Linux system. The kernel then enters into the part of the boot procedure that we will look at in the rest of this article. But first, let’s step back and explore a little bit of additional Linux/Unix theory. (This sort of thing is why I get invited to all the cool Linux/Unix parties.)
Linux/Unix Run Levels
In order to really understand what happens while your system is booting up, you need to be familiar with the concept of “run levels.” Run levels represent the mode in which your computer is operating. They are defined by the set of network and system services available on a system at any given time. Linux systems feature several different, mutually-exclusive modes of operation that you may have heard of: single-user mode, single-user mode with networking, multi-user mode, multi-user mode running a graphical console, and so on. These are the basic Linux run levels.
To better describe run levels, let’s go back to that car analogy we made earlier — different run levels are exactly like the different phases of working on your car. There’s the “it’s up on blocks” phase, where you can remove the tires, get under the car, or work on the engine. There’s the “it’s idling in the driveway” phase, where you can tune up the engine and add oil and transmission fluid without someone trying to drive it away while you’re still working on it. And, finally, there’s the “Let’s take it out on the highway” phase, when you’re back in business.
So run levels are just different ways that your Linux system makes itself available to you as both a user and as an administrator. As a user you won’t normally be aware of run levels, but the multi-user run levels make available the services you would expect to find when using a Linux system — printing, networking, and other high-level services. As an administrator, the single-user run levels let you add or modify hardware, disable existing services or add new ones, change the order in which services start, and so on, without having to worry that a user will log onto the system while you’re working on it and before you’re finished fine-tuning it.
Run levels and the files associated with them are one area where Linux systems vary a great deal, so for the purposes of this article we’ll look at a Red Hat system, which offers the following run levels:
0 Halt. Shuts down the system and turns it off, when possible.
1 Single user mode. Used to perform administrative tasks, such as adding, partitioning, and formatting disks, changing network information, and so on.
2 Multiuser, without networking. (The same as run level 3, if you do not have networking.)
3 Full multiuser mode, with networking support.
5 Full multiuser user mode with an X11 graphical login process running on the console.
6 Reboot. Terminates all running process-es and reboots the system to its default run level.
Starting Up Different Types of Unix Systems
The Unix operating system upon which Linux is based has a long and sordid history. Unix was originally developed in 1969 at AT&T’s Bell labs. A copy of the Unix source code made it’s way to the University of California at Berkeley; things got pretty wild soon after that.
The Unix developed at Berkeley quickly began to diverge from the Unix developed at AT&T; this situation created two basic evolutionary paths for Unix. There is the BSD (Berkeley Software Distribution) path, of which free versions of Unix like FreeBSD and NetBSD represent the most current releases, and there is the AT&T-driven SysV (System 5) path, represented by systems such as Sun Microsystems’ Solaris. Linux is something of a hybrid, but when it comes to startup scripts, it falls very much into the SysV camp.
BSD systems use a simple, but monolithic, set of command files to determine what services are available at different run levels. In general, the monster command file /etc/rc (Run Command) and much smaller /etc/rc.boot files use dynamic settings and variables set in the configuration file /etc/rc.conf to invoke several other command files, including a system-specific command file known as /etc/rc.local.
SysV systems use a much more complex set of command files, and directories of command files, to determine what services are available at different run levels; /etc/rc.d, /etc/ rc.d/rc.sysinit, and /etc/rc.d/init.d run level-specific subdirectories with names like /etc/rc.d/rc3.d (and so on).
System administrators who are used to the elegant simplicity of the BSD startup files may have that “deer in the headlights” feeling when first confronted with the apparent complexity of the SysV startup and shutdown procedures. But trust us — 50 million Elvis fans can’t be wrong. The SysV/Linux model works and is quite flexible once you’re comfortable with it.
Init — The Parent of All Processes
In the beginning, there was one process running on a Linux system and it was good, but hardly good enough to keep anyone interested. This first process is known as the init (initialization) process; it is the parent of all other processes on a Unix/Linux system. The init process is the first process the kernel starts after it has finished with all of its hardware probes.
THE /etc/inittab FILE
Once your system has found an operating system that it can boot and has loaded that operating system, it starts the init process, which first examines the file /etc/inittab (Init Table) to determine what to do next. The following is a sample section of an /etc/ inittab file:
The lines in the /etc/inittab file have the following format: identifier:run-level:action:command-to-run. The command files in the “command-to-run” column of the /etc/ inittab file are actually Unix shell (/bin/sh) scripts. However, since these scripts contain large sequences of commands, thinking of them as “command files” helps clarify their purpose and use without introducing yet another new term.
In fact, you may have noticed that all of these command files are named rc.<some number>. Well, the rc stands for “run command,” giving us yet another excuse to call these “command files” instead of just scripts.
The line in the /etc/inittab file with the action initdefault tells the init process that the default run level for your machine is run level 3, which is a multi-user run level at which all network services are enabled. The init process saves this information for future use and continues reading through the /etc/inittab file. Without some way of specifying the run level that your system should aspire to, your computer wouldn’t know what command files to run, what services to start, and so on.
Next, the init process finds the line with the action sysinit (system initialization) and executes the command file identified in that line, in this case /etc/rc.d/rc.sysinit. After executing the /etc/rc.d/rc.sysinit script, the init process will then begin to execute the commands associated with the default run level.
The next few lines in the /etc/inittab file are specific to different run levels. Each of these lines runs a single script — </etc/rc.d/rc> — which takes a number from 1-6 as an argument that tells it which run level the system wishes to enter. The most common action for these run-level-specific entries in the /etc/inittab file is wait, which means that the init process executes the command file for the specified run level and then waits for that run level to terminate. For a complete list of actions in the /etc/inittab file, see the online manual page for inittab by issuing the command man inittab.
One other entry in the /etc/inittab file that’s worth mentioning is the line with the action ctrlaltdel, which tells the init process how to handle receiving the classic three-finger-salute of PC computing, control-alt-delete. If you want your system to ignore control-alt-delete, don’t specify a command or command file in this entry. The default command is to shut the system down in three seconds and reboot it.
THE /etc/rc.d/rc.sysinit COMMAND FILE
The command that is identified in the system initialization entry in /etc/inittab is executed only once by the init process each time your system boots. In general, this script runs a bunch of commands that perform the following tasks:
- Determines if your system is on a network based on the contents of the file /etc/sysconfig/network;
- Mounts the /proc filesystem used internally by Linux to track the status of various processes on your system;
- Sets the Linux system clock based on the settings in your BIOS and the time zone and other settings you identified when you installed Linux;
- Starts virtual memory on your system by activating any swap partitions identified in your /etc/fstab (File System table) file;
- Sets your system’s host name for networking and system-wide authentication mechanisms, such as NIS (the Network Information Service, also known as the Yellow Pages to graying system administrators), NIS+ (an enhanced version of NIS), and so on;
- Checks the root filesystem for your system for serious problems and mounts it if no problems were found;
- Checks the other filesystems identified in your /etc/fstab file, as appropriate;
- Identifies any special routines that may be required for the operating system to use the hardware in your computer, configures any older plug-and-play devices that may be attached, and activates other basic system services, such as sound;
- Checks the status of any specialized disk devices you may be using, such as RAID (Redundant Array of Inexpensive Disks) drives;
- Mounts all of the filesystems identified in your /etc/ fstab file;
- Performs various other system accounting tasks.
THE /etc/rc.d/init.d DIRECTORY
The /etc/rc.d/init.d directory contains all of the command files that actually start and stop the services that are associated with all of the run levels. Storing all of the commands associated with all of the run levels in a single directory makes it easy to associate any given service with multiple run levels.
This one-to-many association between commands and run levels is made possible because each run level has its own directory containing the command files associated with that run level. But as we’ll see in a minute, the commands “stored” in the run-level-specific directories are all just symbolic links to the “one true copy” of these command files that are stored in the /etc/rc.d/ init.d directory.
All of the command files in the /etc/rc.d/init.d directory have short names that are descriptive of the services with which they are associated. For example, the file /etc/rc.d/init.d/amd starts and stops the automount daemon, which mounts NFS hosts and devices whenever required.
The scripts in /etc/rc.d/ init.d start or stop the services that they are associated with based on the specific arguments with which they are called. For example, the /etc/rc.d/init.d/amd start command starts the automount daemon, while the /etc/rc.d/init.d/ amdstop command stops the automount daemon. Some of these command files accept other arguments, such as restart or status. The restart command-line argument terminates any processes associated with that command file and then starts the service over again. The status command-line argument lists any processes associated with that command file, and then exits without changing anything.
A process is a computer-science term for a single program that is running on your computer system. Processes have a hierarchical relationship; in order to start a new process, it must be “spawned” by an existing process. A process can start any number of other processes. Such a process is known as the “parent” of those other processes. Each process has its own “process number” that allows it to be uniquely identified.
It’s worth pointing out that the init process is something of a special case. Because it is the first process to be run on any Unix or Linux system, it is spawned by the kernel as it boots up. It is given the process number 0; it then proceeds to ultimately spawn (directly or indirectly) all of the other processes that are available on your system.
The term “process” is often used loosely; you’ll hear people using the terms “process” and “procedure” interchangeably, which can be confusing. A procedure is a sequence of steps performed to achieve a specific goal. Some or all of these steps may require starting or stopping specific processes on your system.
Command Files for Different Run Levels
The list of services associated with each Linux run level is stored in a directory associated with that run level. For example, the services associated with Linux’ basic multi-user run level, level 3, are listed in the directory /etc/rc.d/rc3.d. As mentioned earlier, the entries in this directory are actually symbolic links to the command files in /etc/rc.d/init.d that actually start and stop various services.
Now here’s where the really clever bits kick in:
> First, the names of the symbolic links that identify services that must be started when you enter a certain run level begin with an uppercase S (for “Start”). Similarly, the symbolic links identifying services that must be stopped when leaving a certain run level begin with an uppercase K (for “Kill”).
> Second, the names of the symbolic links contain numbers that specify the order in which the services they refer to will be started.
Before the top of your head blows off, lets look at an example that should help tie this all together. Assume that there are two services, foo and bar, that must be started whenever Linux enters run level 3, and that the service bar can only be started after the service foo is already running. Linux makes it easy to encode this information. The directory /etc/rc.d/ rc3.d would contain a symbolic link called S1foo to the command file /etc/rc.d/init.d/foo. The directory /etc/rc.d/ rc3.d would also contain a symbolic link called S2bar to the command file /etc/rc.d/init.d/bar. That’s all there is to it!
When you start your system with the goal of being in run level 3, the init command changes its working directory to /etc/rc.d/rc3.d, and begins to execute the command files beginning with the letter ‘S’ in numeric order. For example, the first command file it executes is S1foo, which points to /etc/rc.d/init.d/foo. After starting that service, the init process next executes S2bar, which points to /etc/ rc.d/init.d/ bar. Voila! The right thing happens, and it all just works!
Displaying the Login Prompt
After the init process has executed all of the commands, files, and scripts identified in the previous few sections, the last few processes it starts are /sbin/mingetty processes which display the familiar banner and login message on your monitor or any other terminals connected to your system. The system is now fully up and running and ready for you to log in.
Changing Run Levels and Shutting Down
At this point you may be asking yourself “OK, so I can boot into a given run level, but what if I want to change run levels once the system has already booted up?” Well, that’s not a problem at all. Red Hat offers three different ways to change run levels. Two of these have limited uses (the shutdown command and booting into a specific run level), while the third is a general-purpose command for changing from any run level to any other run level.
The shutdown command (/sbin/shutdown) changes the run level to 6 (the run level preparatory to shutting down), then switches to run level 0 (halt and power down). You can pass arguments to the shutdown command such as -h (halt) or -r (reboot), telling it which of these run levels (0 or 6) to execute. If you do not specify either of the switches in your shutdown command, your system will automatically reboot in single-user mode.
Booting once into a specific run levels is done by passing the run level you want to boot into as an argument to your boot loader, usually LILO. You can also use the -s switch to cause the system to come up in single user mode so that you can perform administrative tasks. Sample command lines to do this from the LILO: prompt would be:
The first of these examples boots your system into single-user mode. As an example of booting your system into a specific run level, the second of these boots your system into run level 5. Being able to specify a run-level to LILO makes it easy to test other run levels without having to change the run-level entry on the initdefault line in the /etc/inittab file.
However, the most general purpose approach to changing run levels is by using the telinit command (/sbin/telinit). The telinit command is actually linked to the init command, but is meant to be used by system administrators, rather than run as an automatic part of the system boot procedure. To switch to a different run level, execute the command telinit run-level, where run-level is one of the run levels specified in the /etc/inittab file. This first runs all of the K* commands in the /etc/rc.d/rec.run-level directory associated with the current run level, then switches to the specified run level, which runs all of the S* commands in the /etc/rc.d/rec.run-level directory associated with the new run level.
It’s important to realize that changing to a specified run level doesn’t execute the commands associated with lower run levels, but just executes the start-up commands associated with the new run level. It’s easy to see why this is so — if Linux executed the commands associated with lower run levels each time it move to a higher run level, it would always execute the commands for run level 0 first, which would turn the system off.
Once you’re in a specific run level as a result of having used the /sbin/telinit, you can manually start commands associated with other run levels by issuing them directly from the console. For example, you could issue the command /etc/rc.d/init.d/inetd start to start the Internet super-daemon from a run level that doesn’t automatically run that command file. This can actually be very useful for testing out various services.
Once you’re done experimenting with run levels and configuring you system, you can issue the reboot command (/sbin/reboot) to restart the system into its default run level, and your system will be up and running.
While it may appear complex, understanding run levels, the commands that your Linux system uses to switch run levels, and the order in which your Linux system executes those commands is very empowering. Not only will you feel better about all of the data that Linux displays on the screen as it starts, but you will be able to customize your system’s boot process if you so desire.
Understanding run levels gives you the power to easily customize your system to start any processes you want, at any run level, in any order. And that’s what Linux is all about — having the flexibility and freedom you need to make your system work the way you want it to.
Bill von Hagen is president of Move2Linux.com, which ports mainframe and midrange COBOL applications to Linux. He can be reached at email@example.com.