linux uart终端 - 调用关系

内核函数调用关系可以通过dump_stack()函数分析出来


uart设备是tty设备的一种,主从设备号的分配依赖于平台实现,在我的mx51板上,主设备号是217,从设备号范围为16-23

终端驱动设备号分配信息可以通过proc文件系统查看:cat /proc/tty/driver


uart serial是tty设备的一种,而tty设备又是char 设备。因此,tty驱动向上层提供file_operations实现,uart core驱动对tty层实现tty_operations,特定的uart驱动比如uart mxc向uart core提供uart_ops实现。uart core和uart driver可以看成特定的tty驱动。

接口关系如下:

linux uart终端 - 调用关系_第1张图片

从接口关系图可以看出,用户对uart设备操作的调用关系非常简单,

file_operations => [tty_ldisc_ops] => tty_operations => uart ops

其中tty_ldisc_ops线路规程并不是必要的,依赖于应用层设置是否使用ldisc处理数据。


几个常用文件操作调用关系

open 操作

[cpp]  view plain copy
  1. [<c01a5adc>] (uart_open+0x48/0x4a8) from [<c018d514>] (tty_open+0x320/0x4c4)  
  2. [<c018d514>] (tty_open+0x320/0x4c4) from [<c00acb04>] (chrdev_open+0x20c/0x234)  
  3. [<c00acb04>] (chrdev_open+0x20c/0x234) from [<c00a81c4>] (__dentry_open+0x190/0x2a8)  
  4. [<c00a81c4>] (__dentry_open+0x190/0x2a8) from [<c00a8398>] (nameidata_to_filp+0x3c/0x50)  
  5. [<c00a8398>] (nameidata_to_filp+0x3c/0x50) from [<c00b4070>] (do_last+0x50c/0x650)  
  6. [<c00b4070>] (do_last+0x50c/0x650) from [<c00b5ce0>] (do_filp_open+0x180/0x514)  
  7. [<c00b5ce0>] (do_filp_open+0x180/0x514) from [<c00a7f58>] (do_sys_open+0x58/0x10c)  
  8. [<c00a7f58>] (do_sys_open+0x58/0x10c) from [<c000844c>] (kernel_init+0xcc/0x16c)  

write 操作

[cpp]  view plain copy
  1. [<c01a5c44>] (uart_write+0x48/0x138) from [<c018e750>] (n_tty_write+0x2c8/0x3b4)  
  2. [<c018e750>] (n_tty_write+0x2c8/0x3b4) from [<c018baf8>] (tty_write+0x180/0x214)  
  3. [<c018baf8>] (tty_write+0x180/0x214) from [<c00a9f08>] (vfs_write+0xac/0x154)  
  4. [<c00a9f08>] (vfs_write+0xac/0x154) from [<c00aa05c>] (sys_write+0x3c/0x68)  
  5. [<c00aa05c>] (sys_write+0x3c/0x68) from [<c0026f80>] (ret_fast_syscall+0x0/0x30)  

ioctl操作

[cpp]  view plain copy
  1. [<c01a5470>] (uart_ioctl+0x34/0x358) from [<c018c8e0>] (tty_ioctl+0x8e8/0x990)  
  2. [<c018c8e0>] (tty_ioctl+0x8e8/0x990) from [<c00b7488>] (vfs_ioctl+0x2c/0xac)  
  3. [<c00b7488>] (vfs_ioctl+0x2c/0xac) from [<c00b7b40>] (do_vfs_ioctl+0x540/0x5a0)  
  4. [<c00b7b40>] (do_vfs_ioctl+0x540/0x5a0) from [<c00b7bec>] (sys_ioctl+0x4c/0x6c)  
  5. [<c00b7bec>] (sys_ioctl+0x4c/0x6c) from [<c0026f80>] (ret_fast_syscall+0x0/0x30)  


read操作

[cpp]  view plain copy
  1. [<c018eaa0>] (n_tty_read+0xac/0x72c) from [<c018a3f0>] (tty_read+0x7c/0xc0)  
  2. [<c018a3f0>] (tty_read+0x7c/0xc0) from [<c00aa130>] (vfs_read+0xa8/0x150)  
  3. [<c00aa130>] (vfs_read+0xa8/0x150) from [<c00aa284>] (sys_read+0x3c/0x68)  
  4. [<c00aa284>] (sys_read+0x3c/0x68) from [<c0026f80>] (ret_fast_syscall+0x0/0x30)  


串口终端的read操作

从上面的调用关系,我们发现read操作并没有调用uart层的函数,而仅仅调用到n_tty_read中。

这是因为read操作和 write ioctl操作不同,处理流程如下:

1. read操作在没有数据的情况下会阻塞在n_tty_read(或者其他的LDISC read函数)

2. uart 底层驱动通过中断从硬件电路获取数据,然后唤醒阻塞在ty->read_wait上测进程

3. 阻塞的read操作别唤醒后,处理到来的数据

因此n_tty_ldisc和uart 驱动之间是通过等待队列和唤醒操作进行协调的。

首先我们看看n_tty_read的代码中read操作的阻塞过程

[cpp]  view plain copy
  1. 1705 static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,  
  2. 1706              unsigned char __user *buf, size_t nr)  
  3. 1707 {  
  4. 1708     unsigned char __user *b = buf;  
  5. 1709     DECLARE_WAITQUEUE(wait, current);  
  6.   
  7. 1759     add_wait_queue(&tty->read_wait, &wait);  
  8.   
  9. 1796             if (file->f_flags & O_NONBLOCK) {  
  10. 1797                 retval = -EAGAIN;  
  11. 1798                 break;  
  12. 1799             }  
  13. 1800             if (signal_pending(current)) {  
  14. 1801                 retval = -ERESTARTSYS;  
  15. 1802                 break;  
  16. 1803             }  
  17. 1804             /* FIXME: does n_tty_set_room need locking ? */  
  18. 1805             n_tty_set_room(tty);  
  19. 1806             timeout = schedule_timeout(timeout);  
  20. 1807             continue;  
  21. 1808         }  
  22. 1809         __set_current_state(TASK_RUNNING);  

1709 声明一个等待队列元素

1759 把当前进程加入到等待队列tty->read_wait

1796 如果文件打开方式不是block的,那么不需要调度当前进程

1800 ~ 1802 如果当前进程有信号处理,那么我们也不要阻塞进程,而是返回-ERESTARTSYS,当应用层看到这个返回值后,表明有信号需要处理

1806 调度当前进程

1807 进程被重新调度会来


然后我们再看uart driver如何在接收数据后唤醒read进程,以mx51开发板为例,drivers/serial/mxc_uart.c中的接收中断处理函数

[cpp]  view plain copy
  1.  331 static void mxcuart_rx_chars(uart_mxc_port *umxc)  
  2.  332 {  
  3.  333     volatile unsigned int ch, sr2;  
  4.  334     unsigned int status, flag, max_count = 256;  
  5.  335   
  6.  336     sr2 = readl(umxc->port.membase + MXC_UARTUSR2);  
  7.  337     while (((sr2 & MXC_UARTUSR2_RDR) == 1) && (max_count-- > 0)) {  
  8.  338         ch = readl(umxc->port.membase + MXC_UARTURXD);  
  9.   
  10.  387         uart_insert_char(&umxc->port, status, MXC_UARTURXD_OVRRUN, ch,  
  11.  388                  flag);  
  12.  389           ignore_char:  
  13.  390         sr2 = readl(umxc->port.membase + MXC_UARTUSR2);  
  14.  391     }  
  15.  392     tty_flip_buffer_push(umxc->port.state->port.tty);  
  16.  393 }   

337 while循环从uart寄存器读取数据

387 把读入的字符ch保存到flip buffer中

392 已经接收完数据或者接收到足够多的数据,翻转flip_buffer。tty_flip_buffer_push ==> flush_to_ldisc ==> disc->ops->receive_buf,disc->ops->receive_buf对于N_TTY线路规程来说,正是n_tty_receive_buf


函数n_tty_receive_buf代码

[cpp]  view plain copy
  1. 1352 static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,  
  2. 1353                   char *fp, int count)  
  3. 1354 {  
  4.   
  5. 1412     if (!tty->icanon && (tty->read_cnt >= tty->minimum_to_wake)) {  
  6. 1413         kill_fasync(&tty->fasync, SIGIO, POLL_IN);  
  7. 1414         if (waitqueue_active(&tty->read_wait))  
  8. 1415             wake_up_interruptible(&tty->read_wait);  
  9. 1416     }  

这里代码比较多,不要废话直奔主题

1415 唤醒tty->read_wait上的等待进程,n_tty_read就可以继续处理了

你可能感兴趣的:(linux uart终端 - 调用关系)