程序员人生 网站导航

E:安桌层及文件系统层的PRINTf输出原理

栏目:综合技术时间:2016-10-12 09:35:12

/device/console操控台原理分析,通过调用此操控台来输出信息,同时这儿触及到/device/console调用TTY,然后TTY调用低层串口的分析 安桌LOG输出原理)

LINUX内核源码版本:linux⑶.0.86  

 

/dev/console即控制台,是与操作系统交互的装备,系统将1些调试信息直接输出到控制台上,是TTY装备的1个子集

 

Tty:teltypewriters  Teletypes简称电传打印机:终端是1种字符型装备,它有多种类型,通常使用tty来简称各种类型的终端装备ConsoleTTY装备的1种。Console调用到最低层就是TTY装备驱动。

TTY CONSOLE UART关系图(由我的平台有4路串口,要分析到USER空间/dev/console最后是通过那1路口输出的,注意和内核空间的printkconsole操控台输出端口是不是1致,分析原理。由前面几节分析可知printk输出是由uboot传入的参数来肯定那1路串口输出的的。Console=ttySAC0,115200n80)

分析代码前根据网络资料得出的1张图:

 

自己分析代码后得到的图:

 

 

红色部份是分析了所有代码后得出的结论:

下面进程很杂乱,到现在还是没有分析清楚,只能是明白了个大概,由于结构体太多,没交繁复杂。现在还是乱。只能再次分析1下流程(只针对/dev/console的写进程。我们要知道用户空间的console是支持输入输出的。Kernel空音的console只支持输出信息。这是很重要的区分。)。

/dev/console

  1. 装备打开。得到tty_struct结构体,并且赋值在file中1个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注册函数时1个指针链表)->driver = c->device(c, index);

  2. 写操作//把数据写入file文件中的private_data指向的tty_struct中的1xmit变量(这儿有好层转换),同时打开中断。产生中断条件。

    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这3个函数由用户空间来调用。当用户空间调打开读写函数时会调用上面注册的驱动的操作函数对应的函数。所以接下来分析Open write read

    Open:tty_open

    struct tty_driver *console_driver = console_device(&index)->console_drivers(内核层register_console注册函数时1个指针链表)->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= ⑴,

    .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_drivers3c24xx_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/consoletty_drvier是同1个变量。所以在触发中断发送时能正确的发送数据。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

    上面只是注册了1个tty装备。Tty实际上是1多个的。最后是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装备的注册,这儿主要是串串的。所有的tty1字符输输入输出装备都可以用tty_register_driver来注册。可以注册多个。上面/dev/console只是利用了其中的1类来解决问题。Tty装备是可以单独来解决问题的。用tty_register_driver注册。但我们分析的串口部份是用的tty_init来注册装备。然后通过打开函数来与tty_structtty_drvier关联解决问题。大致分析了代码发/dev/conosle/  /dev/tty逻辑差不多。细节就不去分析了。可以明确的是对这两个装备的输入输出都是由uboot传入参数来决是那1个口的。前1个用于系统信息调试 ,后1个应当是作其它功能。对这装备当作1般装备来理解。只是加入很多中间层,所以分析起来比较麻烦。暂不去细究。分析了好几天还是乱。不是清晰的逻辑。

     

    static const struct file_operations tty_fops = {

    .llseek= no_llseek,

    .read= tty_read,

    .write= tty_write,

    .poll= tty_poll,

    .unlocked_ioctl

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐