源地址:http://blog.sina.com.cn/s/blog_633f46290100k4yt.html
1:console的过程描述
例如pmon下其内核命令 g console=ttyS0,115200 root=/dev/sda1 init=/bin/sh rw
对console的过程讨论主要是讨论console=ttyS0 如何影响选取哪种console?
1)在kernel/printk.c中的
__setup("console=", console_setup);
给出了用于解释console=ttyS0的函数console_setup
console_setup调用的__add_preferred_console确定了ttyS0在console_cmdline[]的索引号selected_console
2)其次,各种外围的驱动调用kernel/printk.c中的register_console来注册其console,register_console有如下语句:
1196 if (i == selected_console) { 1198 console->flags |= CON_CONSDEV; 1199 preferred_console = selected_console; 1200 }
在i的循环中当regist的console名字与console_cmdline[selected_console]的名字相同时console->flags |= CON_CONSDEV
如下语句:
1223 if ((console->flags & CON_CONSDEV) || console_drivers == NULL) { 1224 console->next = console_drivers; 1225 console_drivers = console; 1226 if (console->next) 1227 console->next->flags &= ~CON_CONSDEV; 1228 } else { 1229 console->next = console_drivers->next; 1230 console_drivers->next = console; 1231 }
可知,在console_drivers链表中,只有与console_cmdline[selected_console]的名字相同console放在链表首位。
由此可得console=ttyS0实际上是保证console_drivers链表的首个console是名称为ttyS的console
2:tty_driver的选择
对tty_driver的说明主要通过如何根据不同的tty选择相应的tty_driver表示
1)tty_driver的注册实例
通过一个具体的tty_driver的注册例子来表示
在drivers/char/vt.c的vty_init函数中
2965 console_driver->owner = THIS_MODULE; 2966 console_driver->name = "tty"; 2967 console_driver->name_base = 1; 2968 console_driver->major = TTY_MAJOR; 2969 console_driver->minor_start = 1; 2970 console_driver->type = TTY_DRIVER_TYPE_CONSOLE; 2971 console_driver->init_termios = tty_std_termios; 2972 if (default_utf8) 2973 console_driver->init_termios.c_iflag |= IUTF8; 2974 console_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS; 2975 tty_set_operations(console_driver, &con_ops); 2976 if (tty_register_driver(console_driver)) 2977 panic("Couldn't register console driver\n");
其中tty_set_operations(console_driver, &con_ops);是设置tty_driver使用的一系列操作,con_ops是tty_operations类型,tty_register_driver(console_driver)即是注册tty_driver
2)tty_driver注册的作用,drivers/char/tty_io.c tty_register_driver中如下语句:
3534 mutex_lock(&tty_mutex); 3535 list_add(&driver->tty_drivers, &tty_drivers); 3536 mutex_unlock(&tty_mutex);
即是将各种tty_driver串联成一个链表tty_drivers
3)如何根据文件类型选择相应的tty_driver:
对于tty设备文件操作,调用的是tty_fops操作结构体(在drivers/char/tty_io.c中)的操作,其声明如下:
810 static const struct file_operations tty_fops = { 811 .llseek = no_llseek, 812 .read = tty_read, 813 .write = tty_write, 814 .poll = tty_poll, 815 .unlocked_ioctl = tty_ioctl, 816 .compat_ioctl = tty_compat_ioctl, 817 .open = tty_open, 818 .release = tty_release, 819 .fasync = tty_fasync, 820 };
其对tty_driver的选择是在tty_open中的
tty_open 调用_tty_open再调用__tty_open,__tty_open中有如下语句:
2190 if (device == MKDEV(TTYAUX_MAJOR, 0)) { 2192 tty = get_current_tty(); 2193 if (!tty) { 2194 mutex_unlock(&tty_mutex); 2195 return -ENXIO; 2196 } 2197 driver = tty->driver; 2198 index = tty->index; 2199 filp->f_flags |= O_NONBLOCK; 2200 2201 goto got_driver; 2202 } 2203 #ifdef CONFIG_VT 2204 if (device == MKDEV(TTY_MAJOR, 0)) { 2205 extern struct tty_driver *console_driver; 2207 driver = console_driver; 2208 index = fg_console; 2209 noctty = 1; 2210 goto got_driver; 2211 } 2212 #endif 2213 if (device == MKDEV(TTYAUX_MAJOR, 1)) { 2215 driver = console_device(&index); 2216 if (driver) { 2217 2218 filp->f_flags |= O_NONBLOCK; 2219 noctty = 1; 2220 goto got_driver; 2221 } 2222 mutex_unlock(&tty_mutex); 2223 return -ENODEV; 2225 2226 driver = get_tty_driver(device, &index); 2227 if (!driver) { 2228 mutex_unlock(&tty_mutex); 2229 return -ENODEV; 2230 }
可见,通过device与MKDEV的对比选择相应的tty_driver,前两个分支显而易见,对于driver=console_device(&index)
在kernel/printk.c中如下,
1087 struct tty_driver *console_device(int *index) 1088 { 1089 struct console *c; 1090 struct tty_driver *driver = NULL; 1091 1092 acquire_console_sem(); 1093 for (c = console_drivers; c != NULL; c = c->next) { 1094 if (!c->device) 1095 continue; 1096 driver = c->device(c, index); 1097 if (driver) 1098 break; 1099 } 1100 release_console_sem(); 1101 return driver; 1102 }
即console_device选择console_drivers最先有效的console的相关tty_driver,c->device中的device是console的一个成员函数,用于设
备和tty_driver的映射。
由__tty_open中选择driver后,再通过
2234 retval = init_dev(driver, index, &tty);
的语句,使得tty_driver于tty相关联。
如上的整个过程 构成了tty_driver的选择
3:键盘的显示
1)键盘的中断处理
以i8042,型号为at的键盘为,在drivers/char/serio/i8042.c中于两句注册中断的语句
1129 error = request_irq(I8042_AUX_IRQ, i8042_interrupt, IRQF_SHARED, 1130 "i8042", i8042_platform_device);
注册ps2 鼠标中断,一般中断号是12
1155 error = request_irq(I8042_KBD_IRQ, i8042_interrupt, IRQF_SHARED | IRQF_DISABLED, 1156 "i8042", i8042_platform_device);
注册ps2键盘中断,一般中断号是1
可见键盘的中断处理程序是i8042_interrupt --------------------------drivers/input/serio/i8042.c中
i8042继续调用函数 serio_interrupt --------------------------drivers/input/serio/serio.c
serio_interrupt 继续调用ret = serio->drv->interrupt(serio, data, dfl);
其中serio->drv是struct serio_driver* 类型。根据键盘型号知道其指向的是
1142 static struct serio_driver atkbd_drv = { ------------------drivers/input/keyboard/atkbd.c 1143 .driver = { 1144 .name = "atkbd", 1145 }, 1146 .description = DRIVER_DESC, 1147 .id_table = atkbd_serio_ids, 1148 .interrupt = atkbd_interrupt, 1149 .connect = atkbd_connect, 1150 .reconnect = atkbd_reconnect, 1151 .disconnect = atkbd_disconnect, 1152 .cleanup = atkbd_cleanup, 1153 };
因此其最终调用的是 atkbd_interrupt ----------------------------drivers/input/keyboard/atkbd.c
atkbd_interrupt字符处理调用的是 input_event----------------------drivers/input/input.c
input_event调用的是 input_handle_event---------------------------drivers/input/input.c
input_handle_event 调用的是 input_pass_event (对于字符处理)-----drivers/input/input.c
input_pass_event 最终调用handle->handler->event
其中handle->handler 是struct input_handler*类型
键盘调用的struct input_handler* 在drivers/char/keyboard.c中定义如下:
1408 static struct input_handler kbd_handler = { 1409 .event = kbd_event, 1410 .connect = kbd_connect, 1411 .disconnect = kbd_disconnect, 1412 .start = kbd_start, 1413 .name = "kbd", 1414 .id_table = kbd_ids, 1415 };
因此最终调用的是 kbd_event ----------------------------------drivers/char/keyboard.c
kbd_event字符处理调用 kbd_keycode-----------------------------drivers/char/keyboard.c
kbd_keycode字符处理的语句是(*k_handler[type])(vc, keysym & 0xff, !down);
其中k_handler定义如下--------------------------------------------drivers/char/keyboard.c
78 #define K_HANDLERS\ 79 k_self, k_fn, k_spec, k_pad,\ 80 k_dead, k_cons, k_cur, k_shift,\ 81 k_meta, k_ascii, k_lock, k_lowercase,\ 82 k_slock, k_dead2, k_brl, k_ignore 83 84 typedef void (k_handler_fn)(struct vc_data *vc, unsigned char value, 85 char up_flag); 86 static k_handler_fn K_HANDLERS; 87 k_handler_fn *k_handler[16] = { K_HANDLERS }; 88 EXPORT_SYMBOL_GPL(k_handler);
如上对于字符处理一共有16中不同的函数,对于一般的字符显示调用的是k_self,
最终字符显示调用的是
299 static void put_queue(struct vc_data *vc, int ch)-----------drivers/char/keyboard.c
2)字符显示put_queue的参数解释
字符显示put_queue函数如下:
299 static void put_queue(struct vc_data *vc, int ch) 300 { 301 struct tty_struct *tty = vc->vc_tty; 302 303 printk("put_queue %c,%d, tty:%x\n" ,(char)ch,ch,tty); 304 if (tty) { 305 tty_insert_flip_char(tty, ch, 0); 306 con_schedule_flip(tty); 307 } 308 }
除了键盘传入的ch字符,如何获得vc?
kbd_keycode传给put_queue是如下获得的:
struct vc_data *vc = vc_cons[fg_console].d;
vc_cons 是struct vc类型的数组.在drivers/char/vt.c中定义,con_init初始化
对于遇到的通过串口启动系统,板上键盘无法在串口上显示字符问题的原因是
console=ttyS0,时,串口调用的tty_driver 是uart_driver,uart_open函数没有对vc_cons[fg_console].d->vc_tty赋值,因此put_queue无法打印字符
而console=tty时,串口调用的是console_driver , 其中 cons_open 函数有对vc_cons[fg_console].d->vc_tty赋值,因此put_queue>可以显示