(/device/console操控台原理分析,通过调用此操控台来输出信息,同时这儿涉及到/device/console调用TTY,然后TTY调用低层串口的分析 安桌LOG输出原理)
LINUX内核源码版本:linux-3.0.86
/dev/console即控制台,是与操作系统交互的设备,系统将一些调试信息直接输出到控制台上,是TTY设备的一个子集
Tty:teltypewriters Teletypes简称电传打印机:终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备。Console是TTY设备的一种。Console调用到最低层就是TTY设备驱动。
TTY CONSOLE UART关系图(由我的平台有4路串口,要分析到USER空间/dev/console最后是通过那一路口输出的,注意和内核空间的printk的console操控台输出端口是否一致,分析原理。由前面几节分析可知printk输出是由uboot传入的参数来确定那一路串口输出的的。Console=ttySAC0,115200n8第0路)
分析代码前根据网络资料得出的一张图:
自己分析代码后得到的图:
结合硬件开发板分析得到正确的图:
结合硬件开发板分析得到正确的图:
红色部分是分析了所有代码后得出的结论:
下面过程很杂乱,到现在还是没有分析清楚,只能是明白了个大概,因为结构体太多,没交繁复杂。现在还是乱。只能再次分析一下流程(只针对/dev/console的写过程。我们要知道用户空间的console是支持输入输出的。Kernel空音的console只支持输出信息。这是很重要的区别。)。
/dev/console
-
设备打开。得到tty_struct结构体,并且赋值在file中一个private_data中。同时由于
struct tty_driver *console_driver = console_device(&index);得来是由uboot传入的参数来决定的。因此这儿/dev/console输出0串口来决定。由其它节部分的参数解析部分得知。
struct tty_driver *console_driver = console_device(&index)->console_drivers(内核层register_console注册函数时一个指针链表)->driver = c->device(c, index);
-
写操作//把数据写入file文件中的private_data指向的tty_struct中的一xmit变量(这儿有好层转换),同时打开中断。产生中断条件。
Write->[ld->ops->write=n_tty_write]->[tty->ops=driver->ops]=[tty_operations uart_ops]=[.write= uart_write,]
3.中断发送://中断的注册是在open函数中执行的。这儿自动过来执行的。
ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
s3c24xx_serial_portname(port), ourport);---->
s3c24xx_serial_tx_chars->wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);发送数据
//下面是上面结论分析过程。
/dev/console字符设备注册流程分析:
#define fs_initcall(fn)__define_initcall("5",fn,5)
chr_dev_init->tty_init->MKDEV(TTYAUX_MAJOR, 1)->console_fops操作函数
static const struct file_operationsconsole_fops = {
.llseek= no_llseek,
.read= tty_read,
.write= redirected_tty_write,
.poll= tty_poll,
.unlocked_ioctl= tty_ioctl,
.compat_ioctl= tty_compat_ioctl,
.open= tty_open,
.release= tty_release,
.fasync= tty_fasync,
};
完成了对设备的注册,然后是打开设备和读写设备了,这个部分应该是用户空间来完成的。我们不去看相关代码,只从通用的打开读写通用操作方法来作分析。Open write read这三个函数由用户空间来调用。当用户空间调打开读写函数时会调用上面注册的驱动的操作函数对应的函数。所以接下来分析Open write read
Open:tty_open
struct tty_driver *console_driver = console_device(&index)->console_drivers(内核层register_console注册函数时一个指针链表)->driver = c->device(c, index);
retval = tty_alloc_file(filp);
struct tty_driver *console_driver = console_device(&index);
-
tty = tty_init_dev(driver, index, 0);//这个函数里去构建LDISC结构体了,叫做线规程/线路规程line discipline
/*
//ldisc构建过程:
tty_init_dev(driver, index, 0);//tty_driver 0 0
initialize_tty_struct(tty, driver, idx);//tty_struct tty_driver 0
tty_ldisc_init(tty);
Ld->ops=tty_ldiscs[disc]=tty_ldiscs[0]
/*
ty_ldiscs怎么得来的是关键:
start_kernel->console_init()->/* Setup the default TTY line discipline. */
tty_ldisc_begin();->/* Setup the default TTY line discipline. */
(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);->->
N_TTY=0
struct tty_ldisc_ops tty_ldisc_N_TTY = {
.magic = TTY_LDISC_MAGIC,
.name = "n_tty",
.open = n_tty_open,
.close = n_tty_close,
.flush_buffer = n_tty_flush_buffer,
.chars_in_buffer = n_tty_chars_in_buffer,
.read = n_tty_read,
.write = n_tty_write,
.ioctl = n_tty_ioctl,
.set_termios = n_tty_set_termios,
.poll = n_tty_poll,
.receive_buf = n_tty_receive_buf,
.write_wakeup = n_tty_write_wakeup
};
tty_ldiscs[disc] = new_ldisc;
tty_ldiscs[0] = tty_ldisc_N_TTY ;等于上面TTY设备
通过上面分析可知
Ld->ops=tty_ldiscs[disc]=tty_ldiscs[0]=tty_ldisc_N_TTY
*/
tty->ldisc = ld;
tty_add_file(tty, filp);
struct tty_file_private *priv = file->private_dat=tty_struct;因此通过file文件中的private_data来操作tty_sruct。这个结构体时里面包含tty_driver tty->ldisc多个变量。
*/
tty_add_file(tty, filp);
上面代码完成通过文件的struct tty_file_private *priv = file->private_data变量来保存tty_struct(同时TTY结构体包含tty_drivers)驱动变量。
我们代码的console_drivers如下:
static 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,
.data= &s3c24xx_uart_drv,
};
struct tty_driver *uart_console_device(struct console *co, int *index)
{
struct uart_driver *p = co->data;
*index = co->index;//0通过UBOOT传入参数等到0值
returnp->tty_driver;
}
static struct uart_driver s3c24xx_uart_drv = {
.owner= THIS_MODULE,
.driver_name= "s3c2410_serial",
.nr= CONFIG_SERIAL_SAMSUNG_UARTS,
#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE_SWITCH
.cons= NULL,
#else
.cons= S3C24XX_SERIAL_CONSOLE,
#endif
.dev_name= S3C24XX_SERIAL_NAME,
.major= S3C24XX_SERIAL_MAJOR,
.minor= S3C24XX_SERIAL_MINOR,
};
p->tty_driver是s3c24xx_uart_drv结构体的成员,但在上面数组的初始化中并没有初始化此值,因此可以肯定是在在其它地方初始化的tty_driver的。
下面来分析tty_driver的初始化流程:
s3c24xx_serial_modinit->uart_register_driver(&s3c24xx_uart_drv)->:
struct tty_driver *normal;
drv->tty_driver = normal;
tty_set_operations(normal, &uart_ops); //driver->ops = op;
/*
static const struct tty_operations uart_ops = {
.open= uart_open,
.close= uart_close,
.write= uart_write,
.put_char= uart_put_char,
.flush_chars= uart_flush_chars,
.write_room= uart_write_room,
.chars_in_buffer= uart_chars_in_buffer,
.flush_buffer= uart_flush_buffer,
.ioctl= uart_ioctl,
.throttle= uart_throttle,
.unthrottle= uart_unthrottle,
.send_xchar= uart_send_xchar,
.set_termios= uart_set_termios,
.set_ldisc= uart_set_ldisc,
.stop= uart_stop,
.start= uart_start,
.hangup= uart_hangup,
.break_ctl= uart_break_ctl,
.wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
.proc_fops= &uart_proc_fops,
#endif
.tiocmget= uart_tiocmget,
.tiocmset= uart_tiocmset,
.get_icount= uart_get_icount,
#ifdef CONFIG_CONSOLE_POLL
.poll_init= uart_poll_init,
.poll_get_char= uart_poll_get_char,
.poll_put_char= uart_poll_put_char,
#endif
};
tty->ops = driver->ops;
struct tty_driver *driver;
struct tty_driver *console_driver = console_device(&index);
tty->ops=driver->ops->console_drivers->driver = c->device(c, index)->
tty_operations uart_ops=tty_operations uart_ops;--->tty->ops=driver->ops=tty_operations uart_ops
*/
通过上面代码分析可知:用操作/dev/console写函数时
.write= redirected_tty_write,
static ssize_t tty_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
struct tty_struct *tty = file_tty(file);//包含tty_driver结构体。
ld = tty_ldisc_ref_wait(tty);
ld->ops->write//因此找到这个函数才是我们的根本,看是如何实现的。????????????????
通过前面分析知:
Ld->ops=tty_ldiscs[disc]=tty_ldiscs[0]=tty_ldisc_N_TTY
因此ld->ops->write=n_tty_write//tty_ldisc_N_TTY中的函数完成线程规程检查
最后还是调用tty->ops->write(tty, b, nr);来输出数据。规则检查部分不能把数据输出的串口
综上:对于/dev/console设备的写函数的执行流程如下:
Write->[ld->ops->write=n_tty_write]->[tty->ops=driver->ops]=[tty_operations uart_ops]=[.write= uart_write,]
static int uart_write(struct tty_struct *tty,
const unsigned char *buf, int count)
__uart_start(tty);
port->ops->start_tx(port);//ops= s3c24xx_serial_ops;
static struct uart_ops s3c24xx_serial_ops = {
.pm= s3c24xx_serial_pm,
.tx_empty= s3c24xx_serial_tx_empty,
.get_mctrl= s3c24xx_serial_get_mctrl,
.set_mctrl= s3c24xx_serial_set_mctrl,
.stop_tx= s3c24xx_serial_stop_tx,
.start_tx= s3c24xx_serial_start_tx,
.stop_rx= s3c24xx_serial_stop_rx,
.enable_ms= s3c24xx_serial_enable_ms,
.break_ctl= s3c24xx_serial_break_ctl,
.startup= s3c24xx_serial_startup,
.shutdown= s3c24xx_serial_shutdown,
.set_termios= s3c24xx_serial_set_termios,
.type= s3c24xx_serial_type,
.release_port= s3c24xx_serial_release_port,
.request_port= s3c24xx_serial_request_port,
.config_port= s3c24xx_serial_config_port,
.verify_port= s3c24xx_serial_verify_port,
.wake_peer= s3c24xx_serial_wake_peer,
};
s3c24xx_serial_start_tx//这个函数是打开中断 我们/dev/console应该是通过中断来把数据发送出去的。数据存在struct circ_buf xmit结构体中。这个函数只是把数据缓存在变量中和打开串口,发送数据是在中断中进行的。tty->ops->open(tty, filp);====s3c24xx_serial_startup打开中断ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
s3c24xx_serial_portname(port), ourport);注册串口发送函数。
static irqreturn_t
static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
struct s3c24xx_uart_port *ourport = id;
struct circ_buf *xmit = &port->state->xmit;//这个是在s3c24xx_serial_start_tx中去指定的。
/*
ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
s3c24xx_serial_portname(port), ourport);*/
上面中断函数的id与/dev/console的tty_drvier是同一个变量。所以在触发中断发送时能正确的发送数据。wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
/dev/tty字符设备注册流程分析:
#define fs_initcall(fn)__define_initcall("5",fn,5)
chr_dev_init->tty_init->MKDEV(TTYAUX_MAJOR, 0)->tty_fops
上面只是注册了一个tty设备。Tty其实是一多个的。最后是tty_register_driver-》for (i = 0; i < driver->num; i++) {
d = tty_register_device(driver, i, NULL);}-》device_create(tty_class, device, dev, NULL, name);
上面完成多个设备的注册tty设备的注册,这儿主要是串串的。所有的tty一字符输输入输出设备都可以用tty_register_driver来注册。可以注册多个。上面/dev/console只是利用了其中的一类来解决问题。Tty设备是可以单独来解决问题的。用tty_register_driver注册。但我们分析的串口部分是用的tty_init来注册设备。然后通过打开函数来与tty_struct和tty_drvier关联解决问题。大致分析了代码发/dev/conosle/ /dev/tty逻辑差不多。细节就不去分析了。可以明确的是对这两个设备的输入输出都是由uboot传入参数来决是那一个口的。前一个用于系统信息调试 ,后一个应该是作其它功能。对于这设备当作一般设备来理解。只是加入很多中间层,所以分析起来比较麻烦。暂不去细究。分析了好几天还是乱。不是清晰的逻辑。
static const struct file_operations tty_fops = {
.llseek= no_llseek,
.read= tty_read,
.write= tty_write,
.poll= tty_poll,
.unlocked_ioctl= tty_ioctl,
.compat_ioctl= tty_compat_ioctl,
.open= tty_open,
.release= tty_release,
.fasync= tty_fasync,
};
兴趣交流群抠抠: 461283592