嵌入式系统下console,tty,串口的关系

来源:互联网 发布:js array map index 编辑:程序博客网 时间:2024/06/04 17:57
tty driver其实就是console的低层驱动了,除了和硬件进行交互的代码可以写在这里之外,也可以自己虚拟一个tty 设备出来,配合网络模拟的远程console接口什么的。

tty是一类char设备的通称,它们有相同的特性,比如对^C的处理,驱动使用tty_register_driver注册一个tty。

/dev/console是一个虚拟的tty,它映射到真正的tty上,console有多种含义,这里特指printk输出的设备,驱动使用register_console注册一个console。

console和tty有很大区别:console是个只输出的设备,功能很简单,只能在内核中访问;tty是char设备,可以被用户程序访问。

实际的驱动比如串口对一个物理设备会注册两次,一个是tty,一个是console,并通过在console的结构中记录tty的主次设备号建立了联系。

在内核中,tty和console都可以注册多个。当内核命令行上指定console=ttyS0之类的参数时,首先确定了printk实际使用那个 console作为输出,其次由于console和tty之间的对应关系,打开/dev/console时,就会映射到相应的tty上。用一句话说: /dev/console将映射到默认console对应的tty上。

kernel_1998 :
看到你们的问题后,感觉很有典型性,因此花了点工夫看了一下,做了一些心得贴在这里,欢迎讨论并指正:
1、LINUX下TTY、CONSOLE、串口之间是怎样的层次关系?具体的函数接口是怎样的?串口是如何被调用的?
tty和console这些概念主要是一些虚设备的概念,而串口更多的是指一个真正的设备驱动。
Tty实际是一类终端I/O设备的抽象,它实际上更多的是一个管理的概念,它和tty_ldisc(行规程)和tty_driver(真实设备驱动)组合在一起,目的是向上层的VFS提供一个统一的接口。通过file_operations结构中的tty_ioctl可以对其进行配置。查tty_driver,你将得到n个结果,实际都是相关芯片的驱动。因此,可以得到的结论是(实际情况比这复杂得多):每个描述tty设备的tty_struct在初始化时必然挂如了某个具体芯片的字符设备驱动(不一定是字符设备驱动),可以是很多,包括显卡或串口chip。不知道你的ARM Soc是那一款,不过看情况你们应该用的是常见的chip,这些驱动实际上都有。
而console是一个缓冲的概念,它的目的有一点类似于tty。实际上console不仅和tty连在一起,还和framebuffer连在一起,具体的原因看下面的键盘的中断处理过程。Tty的一个子集需要使用console(典型的如主设备号4,次设备号1—64),但是要注意的是没有console的tty是存在的。
而串口则指的是tty_driver。
举个典型的例子:
分析一下键盘的中断处理过程:
keyboard_interrupt—>handle_kbd_event—>handle_keyboard_event—>handle_scancode
void handle_scancode(unsigned char scancode, int down)
{
……..
tty = ttytab? ttytab[fg_console]: NULL;
if (tty && (!tty->driver_data)) {
……………
tty = NULL;
}
………….
schedule_console_callback();
}
这段代码中的两个地方很值得注意,也就是除了获得tty外(通过全局量tty记录),还进行了console 回显schedule_console_callback。Tty和console的关系在此已经很明了!!!


2、printk函数是把信息发送到控制台上吧?如何让PRINTK把信息通过串口送出?或者说系统在什么地方来决定是将信息送到显示器还是串口?
具体看一下printk函数的实现就知道了,printk不一定是将信息往控制台上输出,设置kernel的启动参数可能可以打到将信息送到显示器的效果。
函数前有一段英文,很有意思:


这段英文的要点:要想对console进行操作,必须先要获得console_sem信号量。如果获得console_sem信号量,则可以“log the output and call the console drivers”,反之,则“place the output into the log buffer and return”,实际上,在代码:
asmlinkage int printk(const char *fmt, ...)
{
va_list args;
unsigned long flags;
int printed_len;
char *p;
static char printk_buf[1024];
static int log_level_unknown = 1;
if (oops_in_progress) {

spin_lock_init(&logbuf_lock);

init_MUTEX(&console_sem);
}

spin_lock_irqsave(&logbuf_lock, flags);

va_start(args, fmt);
printed_len = vsnprintf(printk_buf, sizeof(printk_buf), fmt, args);
va_end(args);

for (p = printk_buf; *p; p++) {
if (log_level_unknown) {
if (p[0] != '<' || p[1] < '0' || p[1] > '7' || p[2] != '>') {
emit_log_char('<');
emit_log_char(default_message_loglevel + '0');
emit_log_char('>');
}
log_level_unknown = 0;
}
emit_log_char(*p);
if (*p == 'n')
log_level_unknown = 1;
}
if (!arch_consoles_callable()) {

spin_unlock_irqrestore(&logbuf_lock, flags);
goto out;
}
if (!down_trylock(&console_sem)) {

spin_unlock_irqrestore(&logbuf_lock, flags);
console_may_schedule = 0;
release_console_sem();
} else {

spin_unlock_irqrestore(&logbuf_lock, flags);
}
out:
return printed_len;
}
实际上printk是将format后的string放到了一个buffer中,在适当的时候再加以show,这也回答了在start_kernel中一开始就用到了printk函数的原因

3、start_kernel中一开始就用到了printk函数(好象是printk(linux_banner什么的),在这个时候整个内核还没跑起来呢。那这时候的printk是如何被调用的?在我们的系统中,系统启动是用的现代公司的BOOTLOADER程序,后来好象跳到了LINUX下的head-armv.s,然后跳到start_kernel,在bootloader 里串口已经是可用的了,那么在进入内核后是不是要重新设置?
Bootloader一般会做一些基本的初始化,将kernel拷贝物理空间,然后再跳到kernel去执行。可以肯定的是kernel肯定要对串口进行重新设置,原因是Bootloader有很多种,有些不一定对串口进行设置,内核不能依赖于bootloader而存在
 
console,uart,tty 的关联关系
console可以是串口,也可以是vga,console确实是只能输出,write,内核打印。
在UNIX系统中,计算机显示器通常被称为控制台终端(Console),它仿真了类型为Linux的一种终端(TERM=Linux),并且有一些设备特殊文件与之相关联:tty0、tty1、tty2等。当你在控制台上登录时,使用的是 tty1。使用Alt+[F1~F6]组合键时,我们就可以切换到tty2、tty3等上面去。tty1~tty6等称为虚拟终端,而tty0则是当前所使用虚拟终端的一个别名,系统所产生的信息会发送到该终端上。因此不管当前正在使用哪个虚拟终端,系统信息都会发送到控制台终端上。你可以登录到不同的虚拟终端上去,因而可以让系统同时有几个不同的会话期存在。只有系统或超级用户root可以向/dev /tty0进行写操作,
serial_em86xx.c实现的是串口uart驱动,也注册了console,在 console_init中调用console初始化,但是如果serial不做console的话,应该就不用注册console,但是串口uart驱动是必须的,就是uart_register_driver,其中涉及最关键的tty_driver,
tty是一类char设备的通称,它们有相同的特性,比如对 [^C]的处理,驱动使用tty_register_driver注册一个tty。

/dev/console是一个虚拟的tty,它映射到真正的tty上,如何映射等会再说。

console 有多种含义,这里特指printk输出的设备,驱动使用register_console注册一个console。 console和tty有很大区别:console是个只输出的设备,功能很简单,只能在内核中访问;tty是char设备,可以被用户程序访问。

实际的驱动比如串口对一个物理设备会注册两次,一个是tty,一个是console,并通过在console的结构中记录tty的主次设备号建立了联系。

在内核中,tty和console都可以注册多个。当内核命令行上指定console=ttyS0之类的参数时,首先确定了printk实际使用那个 console作为输出,其次由于console和tty之间的对应关系,打开/dev/console时,就会映射到相应的tty上。用一句话说: /dev/console将映射到默认console对应的tty上。

顺便说一句,console=ttyS0和/dev/ttyS0包含相同的设备名字完全是巧合,不同也没事。

 

       所以如果是一个单纯的串口通信,可以不用实现console,只要实现uart驱动,包括tty_driver,就可以在应用层调用串口设备实现的接口,比如open(/dev/ttyS0),然后select,用于等待串口上的数据,而在内核层,驱动中,就是用wait_queue等来实现 select的。/dev/tty, /dev/console,/dev/tty是正在使用的虚拟终端,因此在这里tty_open就是ttyS0,因为前台进程的控制终端现在就是ttyS0,/dev/tty可以在用户空间访问,就是用户打印可以在该空间。

serial.c不是必须的,而serial_em86xx.c中提供了与其相似的功能,比如shutdown,startup等方法,以及 console_write等,而console是在内核打印,而在erial_em86xx.c中,已经有串口uart驱动,而且与 tty_driver关联起来,所以tty_open打开的就是ttyS0了。如果当前进程有控制终端(Controlling Terminal)的话,那么/dev/tty就是当前进程的控制终端的设备特殊文件,用户打印。tty_io.c中除了有 tty_init,tty_open之外还有tty_poll,用于实现poll,另外还有用于异步通知的tty_fasync,主要是通过SIGIO信号来通知用户进程,因为串口速度不快,用异步通知也可以。而如果速度要求快的话,就需要在中断中唤醒进程,用户程序用poll,select来等待了。
ldd3中有tty_driver的介绍了。
tty_ldisc是和console有关系的,n_tty,多串口支持,SIGIO。
tty_ldisc下面才是tty_driver。
串口如果是普通功能,就可以不跟tty关联起来,而实现char设备即可。
console是内核打印,在tty_io.c中console_init中有初始化 em86xx_uart_console_init,在serial_em86xx.c中,有register_console,而 register_console的实现是在printk.c,从而也说明了console内核打印和用户打印tty底层实现不一样。而用户程序打印以及输入是tty,代码在行规n_tty.c中,而tty_ldisc的注册也是在console_init开始部分的,
(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);

只不过这里的console_init部分是与定义的 CONFIG_SERIAL_EM86XX_CONSOLE没有关系的,而是与tty有关的,n_tty->tty_io->serial_em86xx,serial_core.也就是说即使没有 CONFIG_SERIAL_EM86XX_CONSOLE,假设console内核打印是在framebuffer,键盘的tty0,但用户程序的打印输入是在/dev/tty,最终是可以是在/dev/ttyS0,从而用户程序就可以通过/dev/ttyS0普通串口通信与串口通信设备通信了,而此时串口驱动可以不实现serial_console功能.


http://blog.sina.com.cn/s/blog_693301190100w5cm.html

0 0
原创粉丝点击