SerialConsoles and Consoles in General

http://ar.linux.it/docs/sercons/sercons.html

SerialConsoles and Consoles in General

       在讨论了串口驱动的架构以及如何通过串行设备和PPP以及SLIP协议交互之后,http://ar.linux.it/docs/serial/serial.html这个月我们要看一下,如何使用串口作为控制台.幸运的是,控制台管理是独立于底层的具体的串行端口的,并且它自身也比较有趣.因此,这篇文章不会过多的讨论串行端口,而是主要讲解控制台管理本身的内容,同时最后会讲解一个UDP控制台的实现,来说明2.4中的控制台管理是如何实现的.代码通过了2.4.4内核测试.

1,控制台的思想(由来)

曾几何时,电脑是一个大盒子加上很多的终端,这些终端中的一个担负着一个特殊的角色,那就是”系统控制台”,这个终端是唯一的一个特殊终端-它可以单用户模式登录来进行系统恢复,也是唯一一个可以接受系统的错误消息.

现代的电脑的硬件配置大部分是显示器和键盘,并不使用很多的的字符终端,所以现在的”系统控制台”在硬件上已经演变为一个本地的键盘和显示器.

当然,另外一种情况,主机没有自己的显示硬件的时候,”系统控制台”的角色往往就由串行端口来担负了:在电脑插一条串口线,就可以远程登录,可以获取错误诊断信息.

虽说这样使得看起来“系统控制控制台”类似于”控制终端”,他们都能输入输出.

但是,启动一个shell和传输内核消息使用的完全不同的机制.前者,是通过init进程来启动的,这部分的讨论超出了本文的范围.我们这部分内核相关的章节,主要关心后者:内核消息是如何传递给当前活动的系统管理源.注意我不会讨论用户程序如何去手机内核消息(比如klogdsyslogd),我会把重心放在内核消息发送到”控制台设备”,这个控制台设备可能是串行端口或者其他设备.

2,默认的控制台

当你运行一个经典的字符模式的GNU/Linux系统的时候,控制台默认往往是当前的虚拟终端(真正的终端是用串口线连出去的,这里用显示器来模拟,当然是虚拟的终端了).因此,内核消息就落到了你的shell区域,这种行为可能让人很恼火,比如你在访问一块坏掉的软盘,结果每隔几秒,它就发出几行抱怨信息(就是内核消息不时的打断你,输出到你的shell,的确,我曾经alt+f1的时候,进去后,内核自动发出I/O错误的信息什么的),下面我们会讲怎么解决这个问题.让你使用X的时候,当前的虚拟终端是图形模式,传输内核消息的功能被禁用了,即使内核传输了内核消息,我们也看不到.

传输内核消息机制到控制台的具体实现是函数printk,kernel/printk.c中定义.这个函数使用vsprintf(lib/vsprintf.c中定义)创建一个消息字符串,把字符串放在一个存放内核消息的循环缓冲区中,然后把它发送给所有级别小于console_loglevel的控制台设备(也就是说,控制台设备可以由多个),循环缓冲区的讨论超出了本文的范围,因为它仅仅是临时缓存从而让用户程序可以接受到内核消息.

变量console_loglevel,用于选择什么消息足够重要,值得发送到系统控制台,默认的”重要程度级别”是DEFAULT_CONSOLE_LOGLEVEL,系统管理员可以通过向/proc/sys/kernel/printk这个文件写值来修改默认的级别.举例来说,使用8这个值可以把所有低于debug级别的消息都发送到控制台上,使用命令"echo8 > /proc/sys/kernel/printk"就可以达到这个效果.其他优先级的值(0-7)使用宏KERN_ERRORKERN_DEBUG等定义在linux/kernel.h

如上所述,如果当前的消息优先级低于console_loglevel,那么所有的消息会发送的所有的控制台设备.发送消息的具体代码会扫面一个控制台设备的链表,缩减版的示意代码如下(list 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;
 }
}


在通常的linux配置中,只包含一个控制台设备,并且对应一个虚拟终端.相关代码在drivers/char/console.c,对应的,使用vt_onsole_print函数来打印消息(console设备的打印函数),查看这个函数的实现我们可以看到,内核消息并不是一定发送到前台的活动的虚拟终端(tty0(表示当前活动的虚拟终端)),如果变量kmsg_redirect不是0(而是i),那么内核消息就会发送到第i个虚拟终端上.

这个kmsg_redirect变量可以通过在用户空间使用ioctl(TIOCLINUX)操作一个关联到对应的虚拟控制台的文件描述符来改变.

这里想规范几个概念:

控制台:内核和外界交互的唯一出口,是一个接口,只有一个控制台

控制台设备:逻辑设备,可以有多个,用串口作为控制台的物理映射(具体实现),那么串口就是控制台设备,需要相关的注册函数,把本身注册为控制台设备

虚拟终端:LCD相关ALT+Fn 1-6 是字符虚拟终端7是图形界面虚拟终端,都是虚拟的,因为终端只有一种,串口叫做终端!!!!

因为很多电脑不用串口连出终端来,而是使用LCD+键盘,所以叫做虚拟(LCD虚拟)的终端.

还有个shell,还没弄明白

串口,终端:物理设备,用线连出来.


为了达到这个目的,我使用了一个名为setconsole简单的工具,源码在这篇文章的例子程序中,通过使用”setconsole1”命令,你可以强制使所有的内核消息打印到1号虚拟终端上,而不是打印到你所使用的那个虚拟终端上(默认情况下,是打印当前使用的虚拟终端???tty0??)

如果你在使用一台配备了串口的”标准”PC,那么你可以通过传递”console=”命令行选项到内核的方式,选择一个串口作为控制台(设备).内核源码的一部分—文件Documentation/serial-console.txt非常清晰的描述了总体的设计,以及你可能需要了解的细节,所以我这里不再重复描述.需要注意的一点是,同一时刻可能有多个控制台设备同时存在(比如说,一个串口控制台(设备),和一个虚拟终端控制台(设备)),从上面的代码列表list1可以看出这点.


3,声明和选择一个控制台(设备)

为了声明(注册)一个新的控制台设备,你的内核代码需要包含头文件<linux/console.h>,头文件定义了structconsole和这个结构体里面使用的一些标志.

一旦注册了一个structconsole(就是说一旦你配置好了了一个console结构体),你的代码就可以简单的调用register_console把本设备添加到已有的控制台设备列表中,被注册的结构体(也就是console)包含下面几个成员:

.name :控制台设备的名称,用于分析命令行中的”console=”参数

.write() :用于打印内核消息的函数

.wait_key() :这个函数可能在系统启动启动期间被调用,强制等待直到用户按下一个键

.device() :这个函数返回当前正在用做控制台设备的tty设备的设备号(如果表示tty社别呢??)

.unblank() :这个函数(如果被定义了的话),用于开启屏幕(麻意思?)

.setup() :这个函数,是当命令行参数”console =” 和本控制台设备匹配的时候,调用

.flags :各种各样的console标志

.index :在控制台设备数组(列表)中的索引值


代码list2展示了pc的串口的设备驱动(drivers/char/serial.c)注册并申请了串口控制台(控制台设备):

staticstruct 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);
}


代码list2a展示了一个pc的并行口(drivers/char/lp.c)声明和注册并行控制台设备.我们可以看到,注册一个并行控制台设备有一点点复杂,因为内核会首先检查一下连接到电脑上的行式打印机是否是第一个(0),如果是,就拒绝使用他作为控制台设备.

staticstruct 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;
}

比较有意思的一点是console.h头文件和frame-buffer-consoles有关联,这部分功能(基于structconsw 结构体)超出了本章节的范围,它处理的是把虚拟字符终端映射到不同的显示硬件上去.


4,写一个控制台设备驱动

为了更好的理解linux下组成系统控制台的机制,我们看一下一个控制台设备驱动的实现:使用UDP发送内核消息到网络上.

在我看来,把对串行控制台的讨论彻底离开串口通信的具体过程,能帮助更好的理解”控制台”这个抽象概念.

串口设备作为控制台设备的相关代码都包含在drivers/char/serial.c

本章节并没有涵盖UDP控制台的所有细节,因为这将让这个研究太庞大而让人失去兴趣.IngoMolnar的网络控制台讲解了一个完整的UDP控制台.http://people.redhat.com/mingo/netconsole-patches/

后面的就不翻译了...Myskeletal UDP console, which could be a good starting point to graspthe whole mechanism and will be discussed throu




你可能感兴趣的:(SerialConsoles and Consoles in General)