Linux中tty框架与uart框架之间的调用关系剖析【转】

转自:http://developer.51cto.com/art/201209/357501.htm

之前本人在"从串口驱动的移植看linux2.6内核中的驱动模型 platform device & platform driver"一文中已经写到了移植的设备是如何通过platform总线来与对应的驱动挂载。

在这期间有一个问题困扰着我,那就是来自用户空间的针对uart设备的操作意图是如何通过tty框架逐层调用到uart层的core驱动,进而又是如何调用到真实对应于设备的设备驱动的,本文中的对应设备驱动就是8250驱动,最近我想将这方面的内容搞清楚。

在说明这一方面问题之前我们先要大致了解两个基本的框架结构,tty框架和uart框架。

首先看看tty框架:

在linux系统中,tty表示各种终端。终端通常都跟硬件相对应。比如对应于输入设备键盘鼠标,输出设备显示器的控制终端和串口终端。

下面这张图是一张很经典的图了,很清楚的展现了tty框架的层次结构,大家先看图,下面给大家解释。

最上面的用户空间会有很多对底层硬件(在本文中就是8250uart设备)的操作,像read,write等。用户空间主要是通过设备文件同tty_core交互,tty_core根据用空间操作的类型再选择跟line discipline和tty_driver也就是serial_core交互,例如设置硬件的ioctl指令就直接交给serial_core处理。Read和write操作就会交给line discipline处理。Line discipline是线路规程的意思。正如它的名字一样,它表示的是这条终端”线程”的输入与输出规范设置,主要用来进行输入/输出数据的预处理。处理之后,就会将数据交给serial_core,最后serial_core会调用8250.c的操作。

下图是同一样一副经典的uart框架图,将uart重要的结构封装的很清楚,大家且看。

一个uart_driver通常会注册一段设备号.即在用户空间会看到uart_driver对应有多个设备节点。例如:

/dev/ttyS0  /dev/ttyS1 每个设备节点是对应一个具体硬件的,这样就可做到对多个硬件设备的统一管理,而每个设备文件应该对应一个uart_port,也就是说:uart_device要和多个uart_port关系起来。并且每个uart_port对应一个circ_buf(用来接收数据),所以 uart_port必须要和这个缓存区关系起来。

1 自底向上

接下来我们就来看看对设备的操作是怎样进行起来的,不过在此之前我们有必要从底层的uart驱动注册时开始说起,这样到后面才能更清晰。

这里我们讨论的是8250驱动,在驱动起来的时候调用了uart_register_driver(&serial8250_reg);函数将参数serial8250_reg注册进了tty层。具体代码如下所示:

  1. int uart_register_driver(struct uart_driver *drv) 
  1. {  
  2.     struct tty_driver *normal = NULL;  
  3.     int i, retval;  
  4.  
  5.     BUG_ON(drv->state);  
  6.  
  7.     /*  
  8.      * Maybe we should be using a slab cache for this, especially if   
  9.      * we have a large number of ports to handle.  
  10.      */ 
  11.     drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);  
  12.     retval = -ENOMEM;  
  13.     if (!drv->state)  
  14.         goto out;  
  15.  
  16.     normal  = alloc_tty_driver(drv->nr);  
  17.     if (!normal)  
  18.         goto out;  
  19.  
  20.     drv->tty_driver = normal;  
  21.  
  22.     normal->owner       = drv->owner;  
  23.     normal->driver_name = drv->driver_name;  
  24.     normal->name        = drv->dev_name;  
  25.     normal->major       = drv->major;  
  26.     normal->minor_start = drv->minor;  
  27.     normal->type        = TTY_DRIVER_TYPE_SERIAL;  
  28.     normal->subtype     = SERIAL_TYPE_NORMAL;  
  29.     normal->init_termios    = tty_std_termios;  
  30.     normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;  
  31.     normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;  
  32.     normal->flags       = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;  
  33.     normal->driver_state    = drv;  // here is important for me, ref uart_open function in this file   
  34.     tty_set_operations(normal, &uart_ops);  
  35.  
  36.     /*  
  37.      * Initialise the UART state(s).   
  38.      */ 
  39.     for (i = 0; i < drv->nr; i++) {  
  40.         struct uart_state *state = drv->state + i;  
  41.  
  42.         state->close_delay     = 500;   /* .5 seconds */ 
  43.         state->closing_wait    = 30000; /* 30 seconds */ 
  44.         mutex_init(&state->mutex);  
  45.  
  46.         tty_port_init(&state->info.port);  
  47.         init_waitqueue_head(&state->info.delta_msr_wait);  
  48.         tasklet_init(&state->info.tlet, uart_tasklet_action,  
  49.                  (unsigned long)state);  
  50.     }  
  51.  
  52.     retval = tty_register_driver(normal);  
  53.  out:  
  54.     if (retval < 0) {  
  55.         put_tty_driver(normal);  
  56.         kfree(drv->state);  
  57.     }  
  58.     return retval;  

从上面代码可以看出,uart_driver中很多数据结构其实就是tty_driver中的,将数据转换为tty_driver之后,注册tty_driver。然后初始化uart_driver->state的存储空间。
这里有两个地方我们需要特别关注:

第一个是

  1. normal->driver_state    = drv;  

为什么说重要呢,因为真实这一句将参数的ops关系都赋给了serial_core层。也就是说在后面serial_core会根据uart_ops关系找到我们的8250.c中所对应的操作,而我们参数中的ops是在哪被赋值的呢?这个一定是会在8250.c中不会错,所以我定位到了8250.c中的serial8250_ops结构体,初始化如下:

  1. static struct uart_ops serial8250_pops = {  
  2.     .tx_empty   = serial8250_tx_empty,  
  3.     .set_mctrl  = serial8250_set_mctrl,  
  4.     .get_mctrl  = serial8250_get_mctrl,  
  5.     .stop_tx    = serial8250_stop_tx,  
  6.     .start_tx   = serial8250_start_tx,  
  7.     .stop_rx    = serial8250_stop_rx,  
  8.     .enable_ms  = serial8250_enable_ms,  
  9.     .break_ctl  = serial8250_break_ctl,  
  10.     .startup    = serial8250_startup,  
  11.     .shutdown   = serial8250_shutdown,  
  12.     .set_termios    = serial8250_set_termios,  
  13.     .pm     = serial8250_pm,  
  14.     .type       = serial8250_type,  
  15.     .release_port   = serial8250_release_port,  
  16.     .request_port   = serial8250_request_port,  
  17.     .config_port    = serial8250_config_port,  
  18.     .verify_port    = serial8250_verify_port,  
  19. #ifdef CONFIG_CONSOLE_POLL  
  20.     .poll_get_char = serial8250_get_poll_char,  
  21.     .poll_put_char = serial8250_put_poll_char,  
  22. #endif  
  23. }; 

这样一来只要将serial8250_ops结构体成员的值赋给我们uart_dirver就可以了,那么这个过程在哪呢?就是在uart_add_one_port()函数中,这个函数是从serial8250_init->serial8250_register_ports()->uart_add_one_port()逐步调用过来的,这一步就将port和uart_driver联系起来了。

第二个需要关注的地方:

  1. tty_set_operations(normal, &uart_ops); 

此句之所以值得关注是因为.在这里将tty_driver的操作集统一设为了uart_ops.这样就使得从用户空间下来的操作可以找到正确的serial_core的操作函数,uart_ops是在serial_core.c中的:

  1. static const struct tty_operations uart_ops = {  
  2.     .open       = uart_open,  
  3.     .close      = uart_close,  
  4.     .write      = uart_write,  
  5.     .put_char   = uart_put_char,  
  6.     .flush_chars    = uart_flush_chars,  
  7.     .write_room = uart_write_room,  
  8.     .chars_in_buffer= uart_chars_in_buffer,  
  9.     .flush_buffer   = uart_flush_buffer,  
  10.     .ioctl      = uart_ioctl,  
  11.     .throttle   = uart_throttle,  
  12.     .unthrottle = uart_unthrottle,  
  13.     .send_xchar = uart_send_xchar,  
  14.     .set_termios    = uart_set_termios,  
  15.     .set_ldisc  = uart_set_ldisc,  
  16.     .stop       = uart_stop,  
  17.     .start      = uart_start,  
  18.     .hangup     = uart_hangup,  
  19.     .break_ctl  = uart_break_ctl,  
  20.     .wait_until_sent= uart_wait_until_sent,  
  21. #ifdef CONFIG_PROC_FS  
  22.     .read_proc  = uart_read_proc,  
  23. #endif  
  24.     .tiocmget   = uart_tiocmget,  
  25.     .tiocmset   = uart_tiocmset,  
  26. #ifdef CONFIG_CONSOLE_POLL  
  27.     .poll_init  = uart_poll_init,  
  28.     .poll_get_char  = uart_poll_get_char,  
  29.     .poll_put_char  = uart_poll_put_char,  
  30. #endif  
  31. }; 

这样就保证了调用关系的通畅。

2 自顶向下

说完了从底层注册时所需要注意的地方,现在我们来看看正常的从上到下的调用关系。tty_core是所有tty类型的驱动的顶层构架,向用户应用层提供了统一的接口,应用层的read/write等调用首先会到达这里。此层由内核实现,代码主要分布在drivers/char目录下的n_tty.c,tty_io.c等文件中,下面的代码:

  1. static const struct file_operations tty_fops = {  
  2.     .llseek        = no_llseek,  
  3.     .read        = tty_read,  
  4.     .write        = tty_write,  
  5.     .poll        = tty_poll,  
  6.     .unlocked_ioctl    = tty_ioctl,  
  7.     .compat_ioctl    = tty_compat_ioctl,  
  8.     .open        = tty_open,  
  9.     .release    = tty_release,  
  10.     .fasync        = tty_fasync,  
  11. }; 

就是定义了此层调用函数的结构体,在uart_register_driver()函数中我们调用了每个tty类型的驱动注册时都会调用的tty_register_driver函数,代码如下:

  1. int tty_register_driver(struct tty_driver * driver)  
  2. {  
  3.     ...  
  4.     cdev_init(&driver->cdev, &tty_fops);  
  5.     ...  

我们可以看到,此句就已经将指针调用关系赋给了cdev,以用于完成调用。在前面我们已经说过了,Read和write操作就会交给line discipline处理,我们在下面的代码可以看出调用的就是线路规程的函数:

  1. static ssize_t tty_read(struct file *file, char __user *buf, size_t count,  
  2.             loff_t *ppos)  
  3. {  
  4.     ...  
  5.     ld = tty_ldisc_ref_wait(tty);  
  6.     if (ld->ops->read)  
  7.         i = (ld->ops->read)(tty, file, buf, count);  
  8.         //调用到了ldisc层(线路规程)的read函数  
  9.     else 
  10.         i = -EIO;  
  11.     tty_ldisc_deref(ld);  
  12.     ...  
  13. }  
  14. static ssize_t tty_write(struct file *file, const char __user *buf,  
  15.                         size_t count, loff_t *ppos)  
  16. {  
  17.     ...  
  18.     ld = tty_ldisc_ref_wait(tty);  
  19.     if (!ld->ops->write)  
  20.         ret = -EIO;  
  21.     else 
  22.         ret = do_tty_write(ld->ops->write, tty, file, buf, count);  
  23.     tty_ldisc_deref(ld);  
  24.     return ret;  
  25. }  
  26. static inline ssize_t do_tty_write(  
  27.     ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),  
  28.     struct tty_struct *tty,  
  29.     struct file *file,  
  30.     const char __user *buf,  
  31.     size_t count)  
  32. {  
  33.     ...  
  34.     for (;;) {  
  35.         size_t size = count;  
  36.         if (size > chunk)  
  37.             size = chunk;  
  38.         ret = -EFAULT;  
  39.         if (copy_from_user(tty->write_buf, buf, size))  
  40.             break;  
  41.         ret = write(tty, file, tty->write_buf, size);  
  42.         //调用到了ldisc层的write函数  
  43.         if (ret <= 0)  
  44.             break;  
  45.     ...  

那我们就去看看线路规程调用的是又是谁,代码目录在drivers/char/n_tty.c文件中,下面的代码是线路规程中的write函数:

  1. static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,  
  2.                const unsigned char *buf, size_t nr)  
  3. {  
  4.     ...  
  5.     add_wait_queue(&tty->write_wait, &wait);//将当前进程放到等待队列中  
  6.     while (1) {  
  7.         set_current_state(TASK_INTERRUPTIBLE);  
  8.         if (signal_pending(current)) {  
  9.             retval = -ERESTARTSYS;  
  10.             break;  
  11.         }  
  12.         //进入此处继续执行的原因可能是被信号打断,而不是条件得到了满足。  
  13.         //只有条件得到了满足,我们才会继续,否则,直接返回!  
  14.         if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {  
  15.             retval = -EIO;  
  16.             break;  
  17.         }  
  18.         if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {  
  19.             while (nr > 0) {  
  20.                 ssize_t num = process_output_block(tty, b, nr);  
  21.                 if (num < 0) {  
  22.                     if (num == -EAGAIN)  
  23.                         break;  
  24.                     retval = num;  
  25.                     goto break_out;  
  26.                 }  
  27.                 b += num;  
  28.                 nr -= num;  
  29.                 if (nr == 0)  
  30.                     break;  
  31.                 c = *b;  
  32.                 if (process_output(c, tty) < 0)  
  33.                     break;  
  34.                 b++; nr--;  
  35.             }  
  36.             if (tty->ops->flush_chars)  
  37.                 tty->ops->flush_chars(tty);  
  38.         } else {  
  39.             while (nr > 0) {  
  40.                 c = tty->ops->write(tty, b, nr);  
  41.                 //调用到具体的驱动中的write函数  
  42.                 if (c < 0) {  
  43.                     retval = c;  
  44.                     goto break_out;  
  45.                 }  
  46.                 if (!c)  
  47.                     break;  
  48.                 b += c;  
  49.                 nr -= c;  
  50.             }  
  51.         }  
  52.         if (!nr)  
  53.             break;  
  54.         //全部写入,返回  
  55.         if (file->f_flags & O_NONBLOCK) {  
  56.             retval = -EAGAIN;  
  57.             break;  
  58.         }  
  59.         /*   
  60.         假如是以非阻塞的方式打开的,那么也直接返回。否则,让出cpu,等条件满足以后再继续执行。  
  61.         */          
  62.  
  63.         schedule();//执行到这里,当前进程才会真正让出cpu!!!  
  64.     }  
  65. break_out:  
  66.     __set_current_state(TASK_RUNNING);  
  67.     remove_wait_queue(&tty->write_wait, &wait);  
  68.     ...  

在上面我们可以看到此句:

  1. c = tty->ops->write(tty, b, nr); 

此句很明显告诉我们这是调用了serial_core的write()函数,可是这些调用关系指针是在哪赋值的,刚开始我也是郁闷了一段时间,不过好在我最后还是找到了一些蛛丝马迹。其实就是在tty_core进行open的时候悄悄把tty->ops指针给赋值了。具体的代码就在driver/char/tty_io.c中,调用关系如下所示:

tty_open -> tty_init_dev -> initialize_tty_struct,initialize_tty_struct()函数的代码在下面:

  1. void initialize_tty_struct(struct tty_struct *tty,  
  2.         struct tty_driver *driver, int idx)  
  3. {  
  4.     ...  
  5.     tty->ops = driver->ops;  
  6.     ...  

可以看到啦,这里就将serial_core层的操作调用关系指针值付给了tty_core层,这样tty->ops->write()其实调用到了具体的驱动的write函数,在这里就是我们前面说到的8250驱动中的write函数没问题了。从这就可以看出其实在操作指针值得层层传递上open操作还是功不可没的,这么讲不仅仅是因为上面的赋值过程,还有下面这个,在open操作调用到serial_core层的时候有下面的代码:

  1. static int uart_open(struct tty_struct *tty, struct file *filp)  
  2. {  
  3.     struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state; // here just tell me why uart_open can call 8250  
  4.     struct uart_state *state;  
  5.     int retval, line = tty->index;  
  6.  
  7.     ……  
  8.  
  9.         uart_update_termios(state);  
  10.     }  
  11.  
  12.  fail:  
  13.     return retval;  

在此函数的第一句我们就看到了似曾相识的东西了,没错就是我们在uart_register_driver()的时候所做的一些事情,那时我们是放进去,现在是拿出来而已。

这样一来,我们先从底层向上层分析上来后,又由顶层向底层分析下去,两头总算是接上头了,我很高兴,不是因为我花了近两个小时的时间终于写完了这篇博客,而是我是第一次通过这篇博客的写作过程弄清楚了这个有点小复杂的环节,当然有谬误的地方还是希望大家能慷慨指出。

分享知识,共同进步~

 

原文链接:http://blog.csdn.net/bonnshore/article/details/7996730

【编辑推荐】

  1. 夏雪:蜀汉成败与架构思考
  2. 将 Linux 作为一个科学计算平台进行探索
  3. Linux 灾难恢复
  4. 互联网创业的准备:架构
  5. #微架构设计#快速表态存储设计
时间: 2024-09-20 05:55:31

Linux中tty框架与uart框架之间的调用关系剖析【转】的相关文章

C# 子窗体中调用父窗体中的方法(或多窗体之间方法调用)

本文转载:http://www.csframework.com/archive/2/arc-2-20110805-1771.htm 文章侧重点主要是讨论"窗体"与"窗体"之间方法的调用,以及"MDI父窗体"与"Chilid子窗体"之间如何相互的调用方法. C# 子窗体中调用父窗体中的方法(或多窗体之间方法调用) 看似一个简单的功能需求,其实很多初学者处理不好的,很多朋友会这么写: C# Code: //父窗体是是frmPare

mfc-VC中的MFC的基本对话框之间的调用如何始终保持一个对话框

问题描述 VC中的MFC的基本对话框之间的调用如何始终保持一个对话框 比如说两个对话框,现在对话框A中点击B按钮就弹出了B对话框,但是A对话框没有消失,AB对话框同时存在,如何实现弹出B对话框之后A对话框就自己退出.也就是说始终保持一个对话框 解决方案 你在打开B对话框后,OnOK()等让对话框A关闭 解决方案二: 如果你想做那种向导程序,可以参考:http://www.codeproject.com/Articles/567/Windows-Style-Wizards 解决方案三: 在B按钮中

&lt;frameset&gt;框架集中不同&lt;frame&gt;之间的调用【js代码中】

top:永远指分割窗口最高层次的浏览器窗口;parent:包含当前分割窗口的父窗口,本文将围绕js中top.parent.frame进行讲述及他们的应用案例 引用方法top: 该变量永远指分割窗口最高层次的浏览器窗口.如果计划从分割窗口的最高层次开始执行命令,就可以用top变量. parent: 该变量指的是包含当前分割窗口的父窗口.如果在一个窗口内有分割窗口,而在其中一个分割窗口中又包含着分割窗口,则第2层的分割窗口可以用parent变量引用包含它的父分割窗口. 附:Window对象.Pare

Linux中使用ln命令在文件之间建立连接的用法讲解

  在Unix世界里有两个'link'(连接)概念,一般称之为硬连接和软连接.一个硬连 接仅仅是一个文件名.(一个文件可以有好几个文件名,只有将最后一个文件名从 磁盘上删除,才能把这个文件删掉.文件名的个数是由ls(1)来确定的.所有的文件 名都处于同一个状态,也就没有什么lq源名字rq 之说.通常文件系统里的一个 文件的所有名字包含着一样的数据信息,不过这样也不是必需的.)一个软连接 (或符号连接)是完全不同的:它是一个包含了路径信息的小小的指定文件.因此, 软连接可以指向不同文件系统里的文件

linux中直接进行系统调用和通过C库调用的示例

深入了解LINUX,这方面内容不可少,这段时间再补补.. #include <syscall.h> #include <unistd.h> #include <stdio.h> #include <sys/types.h> int main(void) { long ID1, ID2; /*直接系统调用*/ ID1 = syscall(SYS_getpid); printf("syscall(SYS_getpid)=%ld\n", ID1

网页设计中如何使用嵌套的框架集

在另一个框架集之内的框架集称作嵌套的框架集.一个框架集文件可以包含多个嵌套的框架集.大多数使用框架的Web页实际上都使用嵌套的框架,并且在Dreamweaver 中大多数预定义的框架集也使用嵌套.如果在一组框架里,不同行或不同列中有不同数目的框架,则要求使用嵌套的框架集. 例如,最常见的框架布局在顶行有一个框架(框架中显示公司的徽标),并且在底行有两个框架(一个导航框架和一个内容框架).此布局要求嵌套的框架集:一个两行的框架集,在第二行中嵌套了一个两列的框架集. 一个两行的框架集,在第二行中嵌套

PowerPoint 2013中为视频添加标牌框架

PowerPoint 2013中为视频添加标牌框架         1.在幻灯片中选择视频,在"视频工具-格式"选项卡的"调整"组中单击"标牌框架"按钮,在打开的下拉列表中选择"文件中的图像"选项,如图1所示. 图1 选择"文件中的图像"选项 2.打开"插入图片"窗口,单击"来自文件"按钮,如图2所示.此时将打开"插入图片"对话框,选择需要使用的图

iOS中制作可复用的框架Framework

iOS中制作可复用的框架Framework         在iOS开发中,我们时常会使用一些我们封装好的管理类,框架类,方法类等,我们在实现这些文件时,可能还会依赖一些第三方库或者系统库.如果每次我们复用这些代码时,都要将关联的这些东西进行导入,甚至还要进行arc和mrc的编译设置,会浪费我们很大的精力.除此之外,如果项目需要多人合作,你可能也并不希望你的源代码暴漏在所有人的面前,这个时候,我们就可以使用静态库或者动态库的方式来对我们的代码进行包装,便于复用.静态库的制作方法在一篇旧的博客中有

c++ 强制转换-64位linux中long和int之间的转换

问题描述 64位linux中long和int之间的转换 强制转换不是不可以,只是我在Makefile中加了编译选项,是不允许强制转换的 现在唯一想到的办法就是用位运算 我大概是这样写的 long s = 123; int i = 0xffffffff ; int j = i & s; //这里的s得看机器的大端法还是小端法,必要的时候需要移位 但这样写还是不行 int j = i & s; 还是有问题,因为不允许强制转换... - -! 请问有人能告诉我该怎么办么..(能否将long的前/