在看过很多大神的文章,加上自己研究了一些tty驱动代码之后,算是对tty驱动有了一定的认识。就像研究tcp/ip协议栈一样,我喜欢从数据流动的角度来分析,下面就介绍下我对tty设备中数据流动的薄见。
先应用一张别人的数据流通图,如下,个人觉得这张图将tty设备驱动的大致轮廓描述的比较清晰,数据的流动也比较清晰的展现了。
http://blog.chinaunix.net/photo/94212_100730152614.png
用户在用户空间使用open系统调用打开了一个tty设备之后,得到设备文件名,然后可以通过write和read系统调用来进行读写数据。我们注意到在tty线路规程ldisc->ops中油两个read函数分别为read和receive_read函数,两个函数分别是从上和从下调用的。什么意思呢,就是说read是提供给上层代码调用,receive_read是提供给底层代码调用的,下面会有介绍,我们首先来介绍read的数据流动过程:
如果对tty驱动有所了解的应该知道,在tty设备驱动里只有write函数,并没有read函数。在tty驱动中,当硬件产生一个数据中断之后,也就是硬件收到数据后会发出一个中断信号,系统捕捉到这个中断信号之后调用tty驱动中的中断处理函数,将硬件fifo队列的数据保存到tty_struct临时缓存当中(实际为tty_struct中的struct tty_headbuf buf成员,其head指针指向临时缓存的链表头,tail指向待操作临时链表),此处用到的函数是tty_insert_filp_char。下面是涉及到的几个数据结构:
struct tty_struct {
int magic;
struct kref kref;
struct device *dev;
struct tty_driver *driver;
const struct tty_operations *ops;
int index;
....................
struct tty_struct *link;
struct fasync_struct *fasync;
struct tty_bufhead buf;/* Locked internally */
int alt_speed;/* For magic substitution of 38400 bps */
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
struct work_struct hangup_work;
void *disc_data;
void *driver_data;
struct list_head tty_files;
#define N_TTY_BUF_SIZE 4096
/*
* The following is data for the N_TTY line discipline. For
* historical reasons, this is included in the tty structure.
* Mostly locked by the BKL.
*/
unsigned int column;
unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
<span style="color:#FF0000;"> raw:在n_tty_set_termios函数中根据tty_driver->termios.c_iflag等值初始化
real_raw:当tty_driver->flags中设置为TTY_DRIVER_REAL_RAW时置1
icanon:在tty_open函数中会调用到n_tty_set_termios,然后根据tty_driver->tty_drv->init_termios.c_lflag的值初始化icanon
</span> unsigned char echo_overrun:1;
unsigned short minimum_to_wake;
unsigned long overrun_time;
int num_overrun;
unsigned long process_char_map[256/(8*sizeof(unsigned long))];
char *read_buf;
int read_head;
int read_tail;
int read_cnt;
..........
/* If the tty has a pending do_SAK, queue it here - akpm */
struct work_struct SAK_work;
struct tty_port *port;
};
struct tty_bufhead{
struct delayed_work work;
spinlock_t lock;
struct tty_buffer *head;
struct tty_buffer *tail;
struct tty_buffer *free;
int memory_used;
};
struct tty_buffer{
struct tty_buffer *next;
char *char_buf_ptr;
unsigned char *flag_buf_ptr;
int used;
int size;
int commit;
int read;
/*Data points here*/
unsigned long data[0];
};
将数据拷贝到临时缓存之后,紧接着调用tty_filp_buffer_push函数,将临时缓存中的数据拷贝到tty_struct的成员变量tty->read_buf,用户便可以读取数据了。注意在tty_filp_buffer_push函数中会调用flush_to_ldisc函数完成数据到read_buf的拷贝,在flush_to_ldisc中会调用到上面提到的ldisc->ops->receive_buf函数完成实际的工作。
正如这位老兄在这里所提问的:http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=3761128
他分析所列出的这一串函数调用:serial_in()-->serial8250_handle_port()--->receive chars()--->uart_insert_char()---->tty_insert_flip_char()-->tty_flip_buffer_push()-->flush_to_ldisc()--->n_tty_receive_buf()--->n_tty_receive_char()
这一串函数的调用正是上面我们分析的结果,是将数据从底层硬件收上来传递到线路规程这一层的过程。在这一过程中当然不会调用到tty_read函数,这是数据从下往上的过程。当数据送达到线路规程的缓存中之后,用户程序调用的read函数,会调用到tty_read,在tty_read函数中调用ldisc->ops->read函数,将数据从线路规程这一层的缓存中取走。
http://www.amobbs.com/thread-5521150-1-1.html
http://www.linuxidc.com/Linux/2011-02/32060p4.htm
http://blog.chinaunix.net/uid-21273878-id-1828727.html
http://blog.csdn.net/tommy_wxie/article/details/17003997
http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=3761128