When you run the typical text-mode GNU/Linux installation on your computer, the console is by default the current virtual terminal. Thus, kernel messages fall in the middle of your shell session. This behavior may be irritating if, for example, you are accessing a damaged floppy disk and the kernel spits a line of complaint every few seconds; we'll see how to fix the problem in a while. When you run X, the current virtual terminal is in graphic mode and delivery of kernel messages is disabled for this reason; even if the kernel printed the messages, you wouldn't see them anyways.
The mechanism for delivering messages to the console is implemented
by the printk function, defined in
kernel/printk.c
. The function uses vsprintf
(defined in lib/vsprintf.c
) to create a message string,
places the string in the circular buffer of kernel messages and passes
it to all active console devices if the priority of the message is
less than console_loglevel
. The circular buffer is out of
scope for this article because it is just temporary storage whence
user-space programs can retrieve kernel messages.
The variable console_loglevel
, used to select which
messages are considered to be ``important enough'' to be worth
printing on the system console, defaults to
DEFAULT_CONSOLE_LOGLEVEL
and the system administrator can
change it by writing to /proc/sys/kernel/printk
. A value
of 8, for example, will force all messages down to debug messages to
be printed to the console, and a command like "echo 8 >
/proc/sys/kernel/printk
" would work to enable all messages.
Individual priority values (in the range 0 to 7) are defined in
<linux/kernel.h>
as macros like
KERN_ERROR
and KERN_DEBUG
.
As stated, if the priority of the current message is numerically
less than console_loglevel
, the message is passed to all
active consoles. The actual code for delivering the message scans a
list of console devices and reduces to the few lines shown in listing
1.
if (msg_level < console_loglevel && console_drivers) {
struct console *c = console_drivers;
while(c) {
if ((c->flags & CON_ENABLED) && c->write)
c->write(c, msg, p - msg + line_feed);
c = c->next;
}
}
In the most common Linux configuration there is only one console
device registered, and it corresponds to the virtual terminal. The
relevant code lives in drivers/char/console.c
, and actual
printing of messages is performed by vt_console_print. If you
look in the function, you'll find that it doesn't always print
messages to the foreground virtual terminal but may be configured to
elect a specific virtual terminal for messaging. If the variable
kmsg_redirect
is non-zero, its value is used as the
number of the virtual terminal elected to receive kernel messages. The
variable can be written by invoking ioctl(TIOCLINUX)
(a
Linux-specific tty ioctl command) on a file descriptor associated to a
virtual console. To this aim, I use a simple tool called
setconsole, whose source is included in the sample code
associated to this article. By issuing ``setconsole 1
'',
for example, you can force all kernel messages to be printed to
virtual terminal number 1 instead of the one you are using when the
message is delivered.
If you are running a ``standard'' PC equipped with serial ports,
you can also elect one of your serial ports as a console by passing
a proper console=
command line option to your kernel.
The file Documentation/serial-console.txt
, part of the
kernel source tree, clearly describes both the overall design and
the details you may need, so I won't repeat it here. The only point
worth noting is that there may be several console devices at the
same time (for example, both a serial port and a virtual terminal),
thanks to the loop shown in listing 1.
In order to declare a new console device, your kernel code must
first include <linux/console.h>
. The header defines
struct console
and a few flags used therein.
Once equipped with a struct console
, your code can
simply call register_console
to get inserted in the list
of active consoles. The structure being registered is made up
of the following fields:
console=
command line option.
console=
command-line argument matches the name for this console structure.
Listing 2 shows how the device driver for PC serial ports
(drivers/char/serial.c
) declares and registers the serial
console.
static struct console sercons = {
name: "ttyS",
write: serial_console_write,
device: serial_console_device,
wait_key: serial_console_wait_key,
setup: serial_console_setup,
flags: CON_PRINTBUFFER,
index: -1,
};
void __init serial_console_init(void)
{
register_console(&sercons);
}
Listing 2a shows how the device driver for PC parallel ports
(drivers/char/lp.c
) declares and registers the parallel
console (if enabled). As you can see, the code for registering a
parallel console is a bit more complicated, as the kernel first checks
if the line printer that is being attached is the first one
(!nr
) and refuses to use it as a console otherwise.
static struct console lpcons = {
name: "lp",
write: lp_console_write,
device: lp_console_device,
flags: CON_PRINTBUFFER,
};
static int lp_register(int nr, struct parport *port)
{
[...]
#ifdef CONFIG_LP_CONSOLE
if (!nr) {
if (port->modes & PARPORT_MODE_SAFEININT) {
register_console (&lpcons);
console_registered = port;
printk (KERN_INFO "lp%d: console ready\n", CONSOLE_LP);
} else
printk (KERN_ERR "lp%d: cannot run console on %s\n",
CONSOLE_LP, port->name);
}
#endif
return 0;
}
It's interesting to note that the console.h
header is
also concerned with frame-buffer-consoles; that kind of functionality
(based on the struct consw
data structure) is out of
scope in this column as it deals with mapping the virtual-terminal
text modes to different video hardware implementations.
In order to understand the mechanisms that make up a system console
in Linux, we'll see them on work in an implementation that sends
kernel messages to the network using UDP. In my opinion, bringing the
discussion of serial consoles outside of the realm of serial
communication helps in better understanding the ``console''
abstraction; any doubts on how to bring this discussion to the serial
device can be solved by looking at drivers/char/serial.c
.
This column doesn't show all the details of the UDP console, as
that would make the discussion too heavy to be interesting. Ingo
Molnar's netconsole patches
(http://people.redhat.com/mingo/netconsole-patches/
)
implement a full-featured UDP console. My skeletal UDP console, which
could be a good starting point to grasp the whole mechanism and will
be discussed throughout the rest of this article, is available from my
own FTP and HTTP space
(http://arcana.linux.it/docs/sercons.tar.gz
). Both Ingo
Molnar's patches and my code are licensed according to the GNU GPL.
Sample code availability All sample code referenced in this column and previous ones is available from http://www.linux.it/~rubini/linux-mag/ and ftp://ftp.linux.it/pub/People/rubini/linux-mag . I owe an apology to the readership because last time I promised sample code I haven't been able to deliver it in time. Due to high workload, I usually complete sample code after the deadline for the column (although I already have a proof of the idea and I compile and test everything that's printed in the column itself). Last time I offered sample code (ktftpd.tar.gz) I haven't been able to complete it in time (nor to write any more columns for a while). Now everything is in place and I hope not to so late with deadlines any more. ---> I can't make it shorter than that. I would promise to at least upload a skeleton as "beta", but that would make it longer.
The definition of the UDP console device is, as you may expect,
reminiscent of the serial console just shown. Listing 3 shows the
incantation of struct console
for the UDP console.
struct console udpcons = {
name: "udp",
write: udpcons_write,
wait_key: udpcons_waitkey,
setup: udpcons_setup,
device: NULL, /* no tty device associated to UDP */
flags: CON_ENABLED, /* always enabled */
index: -1, /* unspecified */
};
The purpose of the UDP console device is sending kernel messages through the network. Unlike serial or vt consoles, which are associated to real tty devices, this console has no tty associated, and that's why the device function is not defined.
In a real console device, the device function is used to
return the device number associated to this console as a
kdev_t
value. Only one serial port can be elected as a
console, for example, and the device function defined by the
serial driver is used to tell the caller which one is. The function is
used in drivers/char/tty_io.c
to redirect any access to
/dev/console
. Thus, a process that opens
/dev/console
will actually open a different device,
provided at least one of the active console drivers has a
device function and the device returned is known to the tty
layer.
A few fields in struct console
exist just to
simplify console initialization and customization.
The name field is used in command line parsing, together
with setup. The UDP console is designed to be a module, so
accessing the kernel command line may not make too much sense;
however, to better exemplify how add-on consoles work I chose to
implement both fields. The name of this console is "udp", so
you could use a kernel command line argument of
"console=udp
...".
At system boot, console_setup (in
kernel/printk.c
) is called once for each
console=
kernel command line argument. The function saves
all of these directives in an array. When register_console is
later called, if one of the options is found to match with the
name field, the setup function is called.
A "console
" command-line option can be used to specify
the name (and number) of the console as well as an optional argument
separated by a comma. For serial ports, for example,
"console=ttyS2,9600n8
" will select the third serial port
at a speed of 9600 baud. For the UDP console, you can specify the UDP
port like it was a device number and an optional destination IP; the
device number is saved to the index field of struct
console
and everything after the command is passed as
options
to the setup function. For example you can tell
your kernel "console=udp4000,10.0.0.1
" so that when the
module is later loaded it will use port 4000 and will transmit to host
10.0.0.1. The default UDP port is 2222 and the default destination IP
address is the broadcast address ("255.255.255.255"); if you use those
default values, the route taken by generated packets depend on your
configuration.
int udpcons_setup(struct console *co, char *options)
{
if (co->index > 0)
udpcons_port = co->index;
if (options)
udpcons_addr = in_aton(options);
return 0; /* success */
}
Listing 4 shows the core of the setup function (the real one
is slightly more flexible, and you can see it in the file
udpcons.c
).
The flags field is a bitmask, and linux-2.4 defines three flags:
CON_ENABLED
: the flag is used to tell whether or not
this console device is enabled by default. As shown in listing 1, only
enabled console devices receive kernel messages. The flag is set
automatically by the system if one of the kernel command line options
selects this console and setup and is successful. Moreover, the flag
is set for the first console device that registers itself. That's why
neither the serial nor the parallel consoles don't set the flag by
default in struct console
(see listing 2 and 2a): no
serial or parallel port acts as a console by default unless the user
asks for a serial or parallel console on the kernel command line or
there exists no virtual-terminal device that can act as a default
console.
CON_PRINTBUFFER
: the flags requests buffered messages
to be dumped to this console device. The serial and parallel consoles,
for example, display all kernel boot messages because of this flag:
when a serial or parallel console is registered the flag is set, and
the kernel immediately dumps all kernel messages that were printed
earlier.
CON_CONSDEV
: the console asks to be the preferred
console device. The preferred console device is placed first in the
console list.
Our UDP console sets CON_ENABLED
in order to run even
if no kernel command line option requests an UDP console, while other
flags are not interesting in this context.
The main role of a console device, as seen from the kernel, is
reporting kernel messages to the user, and the write function
as defined in struct console
is the engine of such
reporting. As shown in listing 1, it is called directly from
printk. Since printk can be called at any time, even at
interrupt time, the write function of a console should not
sleep for any noticeable amount of time. To prevent data corruption
due to reentrancy, printk is protected by a spinlock and runs
with interrupts disabled; this unfortunately means that any delay in
the console output function can be detrimental to system operation.
On the other hand, you may want to see all messages that are
printed before a system panic, so for maximum reliability the console
print function should not return before data is actually output. For
this reason, the serial console driver operates in a busy loop and its
write function doesn't return before the last byte has been
transmitted. The same applies to the parallel console driver, with the
caveat that our line printer could get out of paper. In this case, we
have the possibility of either blocking until the printer is feeded
some more paper or losing messages; we can choose the preferred
behaviour by setting CONSOLE_LP_STRICT
to 1
or 0
, respectively (see drivers/char/lp.c
).
Our sample UDP console can't wait for data transmission because it
can't manipulate the network device in a busy loop; it therefore just
builds a network frame and enqueues it in the kernel's transmit
queue. For this reason, you won't be able see UDP packets for any
kernel message that is immediately followed by a kernel
panic. Fortunately, most Linux ports are very conservative in calling
panic (while, for example, the PPC port is not), and you'll be
able to use the UDP console in debugging your own device drivers like
I do.
As far as input is concerned, struct console
also
includes a wait_key function (and an unused read
function). wait_key is only used at system boot time, for
example after asking the user to insert a root floppy. There is no
such feature implemented in the sample UDP console, but the comment in
the placeholder function udpcons_wait_key describes how you
could implement it.
udp-get 2222
".
<andrea.glorioso-at-binary-only-dot-com>
and Davide
Ciminaghi <ciminaghi-at-prosa-dot-it>
for helping revising this
article.