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

目录

1.tty框架

2.uart框架

3.自底向上

4.自顶向下

5.关系图

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

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

1.tty框架

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

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

最上面的用户空间会有很多对底层硬件(在本文中就是8250uart设备)的操作,像read,write等。

用户层数据处理步骤如下

  • 用户空间主要是通过设备文件同tty_core交互,tty_core根据用空间操作的类型再选择跟line discipline和tty_driver也就是serial_core交互,例如设置硬件的ioctl指令就直接交给serial_core处理。
  • Read和write操作就会交给line discipline处理,ldisc对数据进行预处理。
  • 处理之后,就会将数据交给serial_core,最后serial_core会调用8250.c的操作。

Line discipline是线路规程的意思,正如它的名字一样,它表示的是这条终端”线程”的输入与输出规范设置,主要用来进行输入/输出数据的预处理。

2.uart框架

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

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

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

3.自底向上

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

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

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

从上面代码可以看出,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联系起来了。(uart_ops<=>uart_driver:8250.c)

第二个需要关注的地方:

  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 };

这样就保证了调用关系的通畅。(tty_operations<=>tty_driver:serial_core.c)

4.自顶向下

说完了从底层注册时所需要注意的地方,现在我们来看看正常的从上到下的调用关系。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 ...
6 }

我们可以看到,此句就已经将指针调用关系赋给了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 ...
46 }

那我们就去看看线路规程调用的是又是谁,代码目录在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 ...
69 }

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

  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 ...
7 }

可以看到啦,这里就将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;
14 }

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

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

5.关系图

以下是线路规程层发送数据使用的接口,其中关键是两个结构体的赋值操作:struct tty_operation uart_ops和struct uart_ops ***_ops:

两个函数集的绑定:

1     • Uart_ops绑定
2 -->serial_imx_probe
3 -->sport->port.ops = &imx_pops; //完成uart_ops对象和port口绑定
4 -->uart_add_one_port(); //这里面会调用uart_ops函数集
5 • Tty_operations绑定
6 -->uart_register_driver
7 -->tty_set_operations(normal, &uart_ops);
8 -->driver->ops = ops; //完成tty_driver和uart_ops的绑定

以下是应用层发送数据的调用流程:

1     -->write    //application
2 -->struct file_operations tty_fops={.write = tty_write,}
3 -->tty_write(struct file *file, const char __user *buf,size_t count, loff_t *ppos)
4 -->do_tty_write(ld->ops->write, tty, file, buf, count)
5 -->ret = write(tty, file, tty->write_buf, size);
6 -->n_tty_write(struct tty_struct *tty, struct file *file,const unsigned char *buf, size_t nr)
7 -->c = tty->ops->write(tty, b, nr);

do_tty_write中ld->ops->write函数集通过tty_register_ldisc()函数来将函数集赋给ld的ops.

[uart]1.Linux中tty框架与uart框架之间的调用关系剖析的更多相关文章

  1. [tty与uart]1.Linux中tty框架与uart框架之间的调用关系剖析

    转自:http://developer.51cto.com/art/201209/357501_all.htm 目录 1.tty框架 2.uart框架 3.自底向上 4.自顶向下 5.关系图 在这期间 ...

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

    转自:http://developer.51cto.com/art/201209/357501.htm 之前本人在"从串口驱动的移植看linux2.6内核中的驱动模型 platform de ...

  3. Linux中tty、pty、pts的概念区别

    转自Linux中tty.pty.pts的概念区别 基本概念: 1> tty(终端设备的统称): tty一词源于Teletypes,或者teletypewriters,原来指的是电传打字机,是通过 ...

  4. 基于Linux的tty架构及UART驱动详解

    更多嵌入式Linux原创,请关注公众号:一口Linux 一.模块硬件学习 1.1. Uart介绍 通用异步收发传输器(Universal Asynchronous Receiver/Transmitt ...

  5. Linux中TTY是什么意思

    终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备.tty是Teletype的缩写.Teletype是最早出现的一种终端 设备,很象电传打字机(或者说就是),是由Telety ...

  6. Linux中tty、pty、pts的概念区别 转载

    基本概念: > tty(终端设备的统称): tty一词源于Teletypes,或teletypewriters,原来指的是电传打字机,是通过串行线用打印机键盘通过阅读和发送信息的东西,后来这东西 ...

  7. Linux中tty是什么(tty1~7)

    tty:终端设备的统称. tty一词源于Teletypes,或者teletypewriters,原来指的是电传打字机,是通过串行线用打印机键盘通过阅读和发送信息的东西,后来这东西被键盘与显示器取代,所 ...

  8. <frameset>框架集中不同<frame>之间的调用【js代码中】

    top:永远指分割窗口最高层次的浏览器窗口;parent:包含当前分割窗口的父窗口,本文将围绕js中top.parent.frame进行讲述及他们的应用案例 引用方法top: 该变量永远指分割窗口最高 ...

  9. Linux中profile、bashrc、bash_profile之间的区别和联系

    /etc/profile:此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行.并从/etc/profile.d目录的配置文件中搜集shell的设置. 英文描述为: # /etc/pr ...

随机推荐

  1. 继承 多态 java相关基础知识

    1:静态语句块.构造语句块(就是只有大括号的那块)以及构造函数的执行顺序 例子: class HelloA { public HelloA() { System.out.println("H ...

  2. (转)广度优先搜索BFS和深度优先搜索DFS

    1. 广度优先搜索介绍 广度优先搜索算法(Breadth First Search),又称为"宽度优先搜索"或"横向优先搜索",简称BFS. 它的思想是:从图中 ...

  3. UIView 添加闪烁的渐变光

    CGRect gradientRect=CGRectMake(- imageView3.bounds.size.width*, * imageView3.bounds.size.width, imag ...

  4. ThinkPHP从零开始(一)安装和配置

    序: 对PHP一无所知的我,将从这里从零开始. 1.下载与安装 ThinkPHP的下载: ThinkPHP中文站下载页面 有 核心版和完整版两种,由于不了解.所以我选择了完整版.    WampSer ...

  5. 使用 TListView 控件(4)

    本例效果图: 代码文件: unit Unit1; interface uses   Windows, Messages, SysUtils, Variants, Classes, Graphics, ...

  6. Codeforces Round #528 Div. 1 自闭记

    整天自闭. A:有各种讨论方式.我按横坐标排了下然后讨论了下纵坐标单调和不单调两种情况.写了15min也就算了,谁能告诉我printf和cout输出不一样是咋回事啊?又调了10min啊?upd:突然想 ...

  7. Day24-KindEditor基本使用和文件操作2

    KindEditor是一套开源的HTML可视化编辑器,主要用于让用户在网站上获得所见即所得编辑效果,兼容IE.Firefox.Chrome.Safari.Opera等主流浏览器. 1. 准备 2. 写 ...

  8. 【题解】 [SCOI2011]糖果 (差分约束)

    懒得复制,戳我戳我 Solution: 首先考虑\(X=1\)的情况,我们其实只用用一下并查集把相等的点合为一个点 然后后面的四个式子我们就可以用差分约束了,就拿\(X=2\)的情况来说吧,我们用\( ...

  9. 960CSS框架,之前有用过 了解下框架基本原理

    http://blog.sina.com.cn/s/blog_8173443e010160b8.html CSS框架已经出现很长时间了,关于这些框架的用处也被我们讨论了很多遍了.有人说,CSS框架不够 ...

  10. svn cleanup

    SVN 本地更新时,由于一些操作中断更新,如磁盘空间不够,用户取消. 可能会造成本地文件被锁定的情况.一般出现这种情况的解决方法: 1.可以使用SVN clean up来清除锁定. 2.如果不是本目录 ...