class="tags" href="/tags/QianRuShi.html" title=嵌入式>嵌入式系统class="tags" href="/tags/ZhongDuan.html" title=终端>终端分析
------------------------------------
本文系本站原创,欢迎转载!
转载请注明出处:http://sjj0412.cublog.cn/
------------------------------------------
当我们打开机器或一个class="tags" href="/tags/QianRuShi.html" title=嵌入式>嵌入式系统时c;我们可能都适应了它会显示信息c;我们也理所当然?
娜衔馐怯Ω茫撬蛹扑慊型ü嗌俟乜诓诺酱锵允酒骰虼谥斩讼允镜哪兀兰?
大家都没在意过c;其实这个过程在linux
操作系统中可为千辛万库啊c;当然在其他操作系统也一样c;今天我就来分析从printf
到显示设备的过程。
我们先来看下面这些程序。
1 If (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console
./n");
2 (void) sys_dup(0);
3 (void) sys_dup(0);
第1语句执行后生成第一个文件c;这个文件的文件号为0c;第2
语句过后产生第二个文件c;这个文件的文件号为1c;第3
语句过后产生第三个文件c;这个文件的文件号为2
c;这时这三个文件都指向同一个文件c;那就是设备文件/dev/console
c;这个三个文件就是大名鼎鼎的标准输入c;标准输出c;错误输出文件c;当然他们有不同属?
裕罄床鸥模院蠖哉庑┪募诺牟僮骶褪嵌?dev/console
的操作c;由于所有进程是由这个进程fork
继承而来c;故所有进程都拥有这些文件号c;都可以执行写读操作c;即向显示设备输出内容?
淙肷璞付寥肽谌荨?
在上面语句没有执行前都是通过printk来输出字符c;printk是不通过文件系统输出字符的
c;而直接通过相关驱动输出到显示设备c;执行这些语句后c;大家就可以通过向0
号文件写入或读入内容(write(0,buf,len))来达到输出字符的目的,
也许你会说我们没有用这个write
函数啊c;其实是操作系统帮你调用这个函数的c;也许大家经常在应用程序用printf
c;这个函数就会调用系统调用write(0,buf,len)等等。
应用层分析就到此位置c;但是设备层又来了。
当我们向/dev/console
设备文件写或读入数据c;怎么就在显示设备上(没有在串口中)显示了c;并且在Pc
上它是在显示器上显示c;在arm
等class="tags" href="/tags/QianRuShi.html" title=嵌入式>嵌入式设备上c;往往是在串口中显示(当然要超级class="tags" href="/tags/ZhongDuan.html" title=终端>终端或minicom)c;难道/dev/
console很智能c;对它确实很智能c;要分析这个过程c;就不得不谈到class="tags" href="/tags/ZhongDuan.html" title=终端>终端的概念。
1. 一般class="tags" href="/tags/ZhongDuan.html" title=终端>终端
2. 控制台class="tags" href="/tags/ZhongDuan.html" title=终端>终端
3 pts
控制台class="tags" href="/tags/ZhongDuan.html" title=终端>终端是class="tags" href="/tags/ZhongDuan.html" title=终端>终端的一种c;如/dev/tty0,/dev/tty1c;…./dev/tty8
等虚拟控制台class="tags" href="/tags/ZhongDuan.html" title=终端>终端c;其中/dev/tty0叫当前控制台class="tags" href="/tags/ZhongDuan.html" title=终端>终端c;它通常指向/dev/tty1,…../dev/
tty8中的一个c;/dev/tty
是进程的class="tags" href="/tags/ZhongDuan.html" title=终端>终端c;它可以是控制台class="tags" href="/tags/ZhongDuan.html" title=终端>终端也可以是其他class="tags" href="/tags/ZhongDuan.html" title=终端>终端c;反正它就是进程拥有的class="tags" href="/tags/ZhongDuan.html" title=终端>终端。
现在开始非常重要的class="tags" href="/tags/ZhongDuan.html" title=终端>终端到了c;那就是/dev/console
有很多人将它说成是外接控制台class="tags" href="/tags/ZhongDuan.html" title=终端>终端c;我不理解c;我认为它就是用来输出信息(应用程序
printf使用)的class="tags" href="/tags/ZhongDuan.html" title=终端>终端。我们知道
在pc中我们的控制台就是/tty1,……/tty..,我们应该记得ctrl+alt+fn
来切换控制台吧c;这个时候这些控制台就是对应/dev/tty1……/dev/tty4,
这个时候这些控制台就是用来输出信息的c;照这样说来c;/dev/console应该是指向/dev/
tty*中的一个,事实上是对的c;我们可以在Linux
文本模式下实验c;在第一个控制台c;我们输入
echo “kfdl”>/dev/tty0,echo “kfds”>/dev/tty1c;echo”kfdl”>/dev/console
这三调命令都可以在屏幕下看到信息c;这说明这三个设备文件都引导到了同一个
tty_driverc;tty_class="tags" href="/tags/STRUCT.html" title=struct>struct,
echo “kfdl”>/dev/tty0,echo “kfds”>/dev/tty2c;echo”kfdl”>/dev/console
c;仍然可以看到信息c;但输入/dev/tty1输出不了信息c;为什么,因为这个是控制台2
了啊c;而console有机制可以指向活动的tty,所以可以显示c;当然/dev/tty0
本身就是指向活动的控制台的。
在armclass="tags" href="/tags/QianRuShi.html" title=嵌入式>嵌入式系统中c;
echo “kfdl”>/dev/tty0,echo “kfds”>/dev/tty1c;echo”kfdl”>/dev/console
中只有echo”kfdl”>/dev/console能输出信息c;这是为什么因为在arm
的class="tags" href="/tags/QianRuShi.html" title=嵌入式>嵌入式系统中c;显示信息的不是显示器了c;是串口了c;所以由于console
是引导向显示信息的设备c;而
Tty1-tty4只是控制台(只不过在Pc中恰好就是显示器作为显示它才能显示)c;故只有
consolec;当然如果有显示器等显示设别c;echo “kfdl”>/dev/tty0,echo “kfds”>/
dev/tty1可以在显示器等设备显示c;但在串口是如何都不能显示的。
这时如何实现的c;我们知道当我们打开class="tags" href="/tags/ZhongDuan.html" title=终端>终端设备时c;内核的入口就是
Tty设备文件打开关键是找到tty_driverc;因为它里面有具体tty的操作函数啊。
static int tty_open(class="tags" href="/tags/STRUCT.html" title=struct>struct inode *inode, class="tags" href="/tags/STRUCT.html" title=struct>struct file *filp)
{
class="tags" href="/tags/STRUCT.html" title=struct>struct tty_class="tags" href="/tags/STRUCT.html" title=struct>struct *tty;
int noctty, retval;
class="tags" href="/tags/STRUCT.html" title=struct>struct tty_driver *driver;
int index;
dev_t device = inode->i_rdev;
unsigned short saved_flags = filp->f_flags;
nonseekable_open(inode, filp);
retry_open:
//O_NOCTTY 如果路径名指向class="tags" href="/tags/ZhongDuan.html" title=终端>终端设备c;不要把这个设备用作控制class="tags" href="/tags/ZhongDuan.html" title=终端>终端
//noctty:需不需要更改当前进程的控制class="tags" href="/tags/ZhongDuan.html" title=终端>终端
noctty = filp->f_flags & O_NOCTTY;
index = -1;
retval = 0;
mutex_lock(&tty_mutex);
//设备号(5,0) 即/dev/tty.表示当前进程的控制class="tags" href="/tags/ZhongDuan.html" title=终端>终端
if (device == MKDEV(TTYAUX_MAJOR, 0)) {//tty设备文件的driver的获取方式
tty = get_current_tty();/这就是为什么说tty
代表当前进程class="tags" href="/tags/ZhongDuan.html" title=终端>终端c;代码中就是这样实现的啊
//如果当前进程的控制class="tags" href="/tags/ZhongDuan.html" title=终端>终端不存在,退出
if (!tty) {
mutex_unlock(&tty_mutex);
return -ENXIO;
}
//取得当前进程的tty_driver
driver = tty->driver;
index = tty->index;
filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
/* noctty = 1; */
goto got_driver;
}
#ifdef CONFIG_VT
//设备号(4,0).即/dev/tty0:表示当前的控制台
if (device == MKDEV(TTY_MAJOR, 0)) {
extern class="tags" href="/tags/STRUCT.html" title=struct>struct tty_driver *console_driver;//tty0设备文件的
driver的获取方式
driver = console_driver;
//fg_console: 表示当前的控制台全局变量(console_driver ,fg-
console)设置了它为活动控制台
index = fg_console;
noctty = 1;
goto got_driver;
}
#endif
//设备号(5,1).即/dev/console.表示外接的控制台. 通过regesit_console()
if (device == MKDEV(TTYAUX_MAJOR, 1)) {
driver = console_device(&index);//关键在这里/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;
}
driver = get_tty_driver(device, &index);//tty1~ttyn设备文件driver获取的方式
……….
………….
return 0;
}
class="tags" href="/tags/STRUCT.html" title=struct>struct tty_driver *console_device(int *index)
{
class="tags" href="/tags/STRUCT.html" title=struct>struct console *c;
class="tags" href="/tags/STRUCT.html" title=struct>struct tty_driver *driver = NULL;
for (c = console_drivers; c != NULL; c = c->next) {
continue;
driver = c->device(c, index);//console的tty找到了啊
if (driver)
break;
}
release_console_sem();
return driver;
}
由上面的红色可知c;console设备的driver由console_drivers获取。
它是通过register_console 注册的。
void register_console(class="tags" href="/tags/STRUCT.html" title=struct>struct console * console)
{
………………………
………………………..
if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
console->next = console_drivers;
if (console->next)
console->next->flags &= ~CON_CONSDEV;
} else {
console->next = console_drivers->next;
console_drivers->next = console;//这些就将console挂到console_drivers
链上了啊
}
if (console->flags & CON_PRINTBUFFER) {
/*
* release_console_sem() will print out the buffered messages
* for us.
*/
spin_lock_irqsave(&logbuf_lock, flags);
con_start = log_start;
spin_unlock_irqrestore(&logbuf_lock, flags);
}
………
………
}
由上可知在Pc中console和tty0~tty1显示是一样的c;那它的驱动应该是一样的c;也就是说
console_driver->device返回的tty_driver应该是console_driver
c;是如何实现c;这个就是con_init实现的哦c;
static int __init con_init(void)
{
const char *display_desc = NULL;
class="tags" href="/tags/STRUCT.html" title=struct>struct vc_data *vc;
display_desc = conswitchp->con_startup();
if (!display_desc) {
fg_console = 0;
release_console_sem();
return 0;
}
console_timer.function = blank_screen_t;
if (blankinterval) {
blank_state = blank_normal_wait;
mod_timer(&console_timer, jiffies + blankinterval);
}
/*
* kmalloc is not running yet - we use the bootmem allocator.
*/
for (currcons = 0; currcons < MIN_NR_CONSOLES; currcons++) {
vc_cons[currcons].d = vc = alloc_bootmem(sizeof(class="tags" href="/tags/STRUCT.html" title=struct>struct vc_data));
vc->vc_screenbuf = (unsigned short *)alloc_bootmem(vc->
vc_screenbuf_size);
vc_init(vc, vc->vc_rows, vc->vc_cols,
currcons || !vc->vc_sw->con_save_screen);
}
master_display_fg = vc = vc_cons[currcons].d;
set_origin(vc);
gotoxy(vc, vc->vc_x, vc->vc_y);
printk("Console: %s %s %dx%d",
vc->vc_can_do_color ? "colour" : "mono",
display_desc, vc->vc_cols, vc->vc_rows);
printable = 1;
printk("/n");
release_console_sem();
#ifdef CONFIG_VT_CONSOLE
register_console(&vt_console_driver);//这一句致关重要c;它注册了console
c;挂到了console_driver
#endif
return 0;
}
再看下vt_console_driver的console_device是不是返回console_driver,
static class="tags" href="/tags/STRUCT.html" title=struct>struct console vt_console_driver = {
.name = "tty",
.write = vt_console_print,
.unblank = unblank_screen,
.flags = CON_PRINTBUFFER,
.index = -1,
};
static class="tags" href="/tags/STRUCT.html" title=struct>struct tty_driver *vt_console_device(class="tags" href="/tags/STRUCT.html" title=struct>struct console *c, int *index)
{
*index = c->index ? c->index-1 : fg_console;
return console_driver;
}
正是c;大功告成。
下面来看在arm等class="tags" href="/tags/QianRuShi.html" title=嵌入式>嵌入式系统中c;console
是和串口相关练的c;那么应该也有一个类似的注册console的函数c;经发现c;正是啊
tatic int s3c24xx_serial_initconsole(void)
{
class="tags" href="/tags/STRUCT.html" title=struct>struct s3c24xx_uart_info *info;
class="tags" href="/tags/STRUCT.html" title=struct>struct platform_device *dev = s3c24xx_uart_devs[0];
dbg("s3c24xx_serial_initconsole/n");
/* select driver based on the cpu */
if (dev == NULL) {
printk(KERN_ERR "s3c24xx: no devices for console init/n");
return 0;
}
if (strcmp(dev->name, "s3c2400-uart") == 0) {
info = s3c2400_uart_inf_at;
} else if (strcmp(dev->name, "s3c2410-uart") == 0) {
info = s3c2410_uart_inf_at;
} else if (strcmp(dev->name, "s3c2440-uart") == 0) {
info = s3c2440_uart_inf_at;
} else {
printk(KERN_ERR "s3c24xx: no driver for %s/n", dev->name);
return 0;
}
if (info == NULL) {
printk(KERN_ERR "s3c24xx: no driver for console/n");
return 0;
}
s3c24xx_serial_console.data = &s3c24xx_uart_drv;
s3c24xx_serial_init_ports(info);
register_console(&s3c24xx_serial_console);
return 0;
}
static class="tags" href="/tags/STRUCT.html" title=struct>struct console s3c24xx_serial_console =
{
.name = S3C24XX_SERIAL_NAME,
.device = uart_console_device,
.flags = CON_PRINTBUFFER,
.index = -1,
.write = s3c24xx_serial_console_write,
.setup = s3c24xx_serial_console_setup
};
class="tags" href="/tags/STRUCT.html" title=struct>struct tty_driver *uart_console_device(class="tags" href="/tags/STRUCT.html" title=struct>struct console *co, int *index)
{
class="tags" href="/tags/STRUCT.html" title=struct>struct uart_driver *p = co->data;
*index = co->index;
return p->tty_driver;
}
我们可以知道c;返回的是s3c24xx_uart_drv中的tty_dirverc;这个driver
是何时注册的呢c;
其实这就关系到uart结构
static int __init s3c24xx_serial_modinit(void)
{
int ret;
ret = uart_register_driver(&s3c24xx_uart_drv);
if (ret < 0) {
printk(KERN_ERR "failed to register UART driver/n");
return -1;
}
s3c2400_serial_init();
s3c2410_serial_init();
s3c2440_serial_init();
return 0;
}
int uart_register_driver(class="tags" href="/tags/STRUCT.html" title=struct>struct uart_driver *drv)
{
class="tags" href="/tags/STRUCT.html" title=struct>struct tty_driver *normal = NULL;
int i, retval;
BUG_ON(drv->state);
/*
* Maybe we should be using a slab cache for this, especially if
* we have a large number of ports to handle.
*/
drv->state = kzalloc(sizeof(class="tags" href="/tags/STRUCT.html" title=struct>struct uart_state) * drv->nr, GFP_KERNEL);
retval = -ENOMEM;
if (!drv->state)
goto out;
normal = alloc_tty_driver(drv->nr);
if (!normal)
goto out;
drv->tty_driver = normal;
normal->owner = drv->owner;
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state = drv;
tty_set_operations(normal, &uart_ops);
/*
* Initialise the UART state(s).
*/
for (i = 0; i < drv->nr; i++) {
class="tags" href="/tags/STRUCT.html" title=struct>struct uart_state *state = drv->state + i;
state->close_delay = 500; /* .5 seconds */
state->closing_wait = 30000; /* 30 seconds */
mutex_init(&state->mutex);
}
retval = tty_register_driver(normal);
out:
if (retval < 0) {
put_tty_driver(normal);
kfree(drv->state);
}
return retval;
}
从上面可以看到c;当串口注册时它作为一般的tty_driver注册c;但是由于uart_driver
还是console.datac;且在console设备通过console_device然后uart_driver->device
函数会把uart_driver的tty_driver(上面的normal)赋给console故它还是console的
tty_driver
c;这个可以明白c;串口我们当然也希望当作一般的串口接口c;而不仅仅是作为控制class="tags" href="/tags/ZhongDuan.html" title=终端>终端在?
吨斩酥邢允镜摹?
无论是一般tty_driverc;还是console的tty_driver,它的统一接口是uart_opd
c;这就区别了其他tty_driverc;
Tty_driver->driver_data=uart_driver
Uart_open后
Tty_class="tags" href="/tags/STRUCT.html" title=struct>struct->driver_data=uart_state;
那s3c24xx_serial_initconsolec;和con_init又是在那调用的呢在start_kernel->
console_init调用的
while (call < __con_initcall_end) {
(*call)();
call++;
}
__con_initcall_start是一个初始段c;通过console_initcall注册c;
#define console_initcall(fn) /
static initcall_t __initcall_##fn /
__attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn
console_initcall(s3c24xx_serial_initconsole);
下面讲/dev/tty,/dev/tty0,/dev/console,串口class="tags" href="/tags/ZhongDuan.html" title=终端>终端/tts/0~n(在内核叫做ttySAC0~n
)的产生过程,虚拟控制台class="tags" href="/tags/ZhongDuan.html" title=终端>终端/dev/tty1~ttyn
/dev/ttyc;/dev/tty0,/dev/console都是在tty_init中通过cdev_addc;register_chrdev(
)等添加
的c;而/dev/tty~ttyn是在vty_init中通过tty_driver_register创建的c;并且创建
console_driver->num即MAX_NR_CONSOLESc;在alloc_ttydriver是赋值的c;而/tts/0~n
是在s3c24xx_serial_probe中c;当static inline int s3c2440_serial_init(void)
{
return s3c24xx_serial_init(&s3c2440_serial_drv, &s3c2440_uart_inf);
}
当s3c2440_serial_drv检测到串口设备时就会调用s3c24xx_serial_probe
c;然后它就会通过uart_add_one_port->tty_register_device(由于这个device的dev_t
由它的tty_driver major
指定c;当打开这个设备肯定可以找到这个驱动)创建class="tags" href="/tags/ZhongDuan.html" title=终端>终端的设备c;如果这个些串口class="tags" href="/tags/ZhongDuan.html" title=终端>终端设?
傅膗art_driver->con存在且con.flags=con_enablec;它也同时console_register
c;所以这时console可以指向它们中的任意一个。