转自 http://blog.chinaunix.net/uid-20727076-id-3062401.html
在我的fedora11系统(linux2.6.31)下,这三个设备的具体情况如下:
- [root@localhost dev]# ls -l tty
- crw-rw-rw- 1 root tty 5, 0 2012-01-30 17:26 tty
- [root@localhost dev]# ls -l tty0
- crw--w---- 1 root root 4, 0 2012-01-30 17:26 tty0
- [root@localhost dev]# ls -l console
- crw------- 1 root root 5, 1 2012-01-30 17:26 console
1./dev/tty表示控制终端
如果当前进程有控制终端(Controlling Terminal)的话,那么/dev/tty就是当前进程的控制终端的设备特殊文件。可以使用命令”ps –ax”来查看进程与哪个控制终端相连。对于你登录的shell,/dev/tty就是你使用的终端,设备号是(5,0)。使用命令”tty”可以查看它具体对应哪个实际终端设备。
控制终端可以是Xwindows模式下的伪终端(/dev/pts/*),也可以是控制台虚拟终端(/dev/tty*)
- if (device == MKDEV(5, 0)) {
- tty = get_current_tty();
- if (!tty) {
- mutex_unlock(&tty_mutex);
- return -ENXIO;
- }
- driver = tty_driver_kref_get(tty->driver);
- index = tty->index;
- filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
- /* noctty = 1; */
- /* FIXME: Should we take a driver reference ? */
- tty_kref_put(tty);
- goto got_driver;
- }
"driver"对应到驱动,“index"对应到具体的设备,我们从中也可以看出
/dev/tty有些类似于到实际所使用终端设备的一个链接
。
2./dev/tty0则是当前所使用虚拟终端的一个别名
- # tty(查看当前TTY)
- /dev/tty1
- #echo "test tty0" > /dev/tty0
- test tty0
内核实现如下:
- if (device == MKDEV(4, 0)) {
- extern struct tty_driver *console_driver;
- driver = tty_driver_kref_get(console_driver);
- index = fg_console;
- noctty = 1;
- goto got_driver;
- }
3./dev/console即控制台,是与操作系统交互的设备,系统将一些信息直接输出到控制台上
内核实现如下:
- if (device == MKDEV(5, 1)) {
- struct tty_driver *console_driver = console_device(&index);//如何通过这一句得到tty_driver结构呢,这个结构是哪里来的?我们稍候会提到
- if (console_driver) {
- driver = tty_driver_kref_get(console_driver);
- if (driver) {
- /* Don't let /dev/console block */
- filp->f_flags |= O_NONBLOCK;
- noctty = 1;
- goto got_driver;
- }
- }
- mutex_unlock(&tty_mutex);
- return -ENODEV;
- }
通过/dev/console如何对应到具体的设备呢?
在8250.c里,有如下代码:
- static struct console serial8250_console = {
- .name = "ttyS",
- .write = serial8250_console_write,
- .device = uart_console_device,
- .setup = serial8250_console_setup,
- .early_setup = serial8250_console_early_setup,
- .flags = CON_PRINTBUFFER,
- .index = -1,
- .data = &serial8250_reg,
- };
-
- static int __init serial8250_console_init(void)
- {
- if (nr_uarts > UART_NR)
- nr_uarts = UART_NR;
-
- serial8250_isa_init_ports();
- register_console(&serial8250_console);
- return 0;
- }
register_console会将serial8250_console添加到console_drivers的尾部。
而
console_device
函数遍历console_drivers以得到一个有效的console,得到有效的console以后(在这个例子里,当然会得到
serial8250_console
),然后会调用他的device方法以得到相应的tty_driver结构,
serial8250_console
结构的device方法为uart_console_device,
uart_console_device
的内容如下:
- struct tty_driver *uart_console_device(struct console *co, int *index)
- {
- struct uart_driver *p = co->data;
- *index = co->index;
- return p->tty_driver;
- }
serial8250_reg
是个uart_driver的结构体,内容如下:
- static struct uart_driver serial8250_reg = {
- .owner = THIS_MODULE,
- .driver_name = "serial",
- .dev_name = "ttyS",
- .major = TTY_MAJOR,
- .minor = 64,
- .cons = SERIAL8250_CONSOLE,
- };
注册uart_driver 的时候会分配相应的tty_driver结构
这样通过/dev/console和具体的uart驱动联系起来,通过co->index,又和具体的uart设备联系了起来,这样,内核通过/dev/console就可以操作串口,进行内核信息的输出了。
内核使用/dev/console的地方为kernel初始化的时候:
start_kernel->rest_init->kernel_init->init_post
- static noinline int init_post(void)
__releases(kernel_lock)
{ - ...
- if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
- printk(KERN_WARNING "Warning: unable to open an initial console.\n");
-
- (void) sys_dup(0);
- (void) sys_dup(0);
- ...
将控制台设置为标准输入,后续的两个sys_dup(0),则复制标准输入为标准输出和标准错误输出。
这是为调用init进程做准备,之后内核将通过kernel_execve()调用init进程,这样,init进程的输入、输出和错误输出就全部重定向到/dev/console对应的uart设备了.
因为用户应用程序全部为init进程的子或孙进程,所以,默认情况下,用户应用程序的输入、输出和错误输出也全部重定向到了/dev/console对应的uart设备了。