上世纪,六七十年代,虽然已经有电脑了,但还没有普及,电脑还是极其昂贵的,普通老百姓都买不起这么贵的一台电脑。那时候,UNIX的发明人——汤普森,为了实现多用户能够同时登陆UNIX系统的目的,使用非常廉价的TTY设备连接电脑,通过TTY设备发送和接收来自UNIX系统的信息,实现系统连接多个TTY设备的功能,多个用户可以同时登陆UNIX系统。 Linux继承和发展了TTY功能,不仅仅可以通过串口连接TTY设备,多个用户通过TTY设备登陆linux系统,更支持通过网络连接TTY设备了。接下来,本文会介绍什么为终端,什么为TTY。
终端是一种可以同时显示和接收信息的设备,通俗的来讲,终端是一种用于人机交互的设备。用户通过终端发送信息给系统,终端接收来自系统的反馈信息,显示给用户。例如,对于一台电脑来说,显示器和键盘就是一个终端,键盘接收用户输入的信息,发送给系统,系统接收到信息后,处理该信息,把处理结果发送给显示器,显示出来给用户。
目前,终端的种类有很多,包括控制台终端、虚拟终端、串口终端、软件终端、USB或网络终端和图形终端等。其中,软件终端是指,通过串口或者USB等连接到另外一台电脑,该电脑上的一个软件模拟出一个终端,在该电脑可以实现终端的输入和输出。
电传打印机是一种基于电报技术的远距离信息传送的设备,通常有键盘、收发报器和打印机构等部件。用户敲击键盘的某一个按键,打印机就会自动把该按键代表的信息发送到信道里面,这就是我们所说的发报;电传打印即接收来自信道的电码信号,打印机构打印该信号所代表的字符,这就是收报。
那时候,电传打印机很便宜,使用一根线就可以连接电传打印。发现电传打印机的优点后,汤普森使用串口连接打印机,敲击打印机的键盘发送信息给UNIX系统,UNIX系统接收到信息后,处理用户的信息,把结果发送给打印机,打印机打印UNIX系统的信息到纸上,查看纸上的打印信息,用户可以获取到系统的状态。
电传打印机的英文名为Teletype,缩写为TTY,由于第一台TTY设备为电传打印机,所以现在称呼所有的这类型设备为TTY设备。
除了外接的TTY设备,电脑自己的显示器和键盘也是一个TTY设备,为了显示电脑自身的TTY设备的高贵身份,美其名曰,控制台终端console。与普通的TTY设备比较,console具有管理员权限,打印系统日志信息。
一般情况下,电脑的控制台终端只有一个输出设备(显示器),任一个时刻仅仅只能被一个应用程序占有。现代系统都支持多任务的操作环境,有时候,将控制台终端切换给另一个应用程序前,往往需要保留上一个应用程序的控制台终端上的输出,下一次切到该应用程序的时候,方便查看上一次输出信息。
因此,在控制台终端的基础上,UNIX/Linux系统虚拟出了6个终端——虚拟终端,分别为tty1~tty6,。各个应用程序可以在虚拟终端上独立输出,使用键盘组合键(CTRL+ALT+F1~F6)在各个虚拟终端间切换。
本文的目的是,介绍linux kernel的TTY驱动框架、tty设备和驱动的注册流程、用户空间open、read和write TTY设备节点的驱动操作过程。本文的意义是,通过了解TTY驱动的框架和使用方法,驱动人员熟悉怎么编写自己的TTY设备驱动,对TTY核心层有更深的了解和认识。
TTY框架包括三部分,分别是tty driver、discipline和tty core,它们之间的关系如下图所示:
tty core是TTY的核心层,它抽象tty的核心数据结构,例如tty_struct、tty_driver等,提供接口给下面的discipline和tty driver层,discipline和tty driver使用tty core提供的注册接口,把discipline和tty driver注册到tty core。当用户操作设备节点的时候,tty core接收用户的请求,决定下一步是到discipline,还是tty driver层,例如,用户open、read和write tty设备节点的时候,tty core处理完后,交给discipline处理,最后再到tty driver处理,但ioctl的时候,tty core会直接就到tty driver层,没有经过discipline层。
discipline的中文意思是规范,在这里表示线路的规范。discipline接收tty core或者tty driver的数据,进行一些格式的转换,比如换行等等等,处理完后,再上传给tty core或者传给tty driver层。
tty driver是low level driver,它直接操作硬件,完成硬件的初始化,接收硬件的数据,上报数据给discipline或者tty core层;接收来自tty core或者discipline的数据,发送数据给硬件等等。
上一节介绍了tty的整体框架,tty核心定义了tty需要使用的数据结构,提供各种各样的函数接口给下面的层使用,管理注册到tty核心的tty设备和驱动。本节将会详细的介绍tty核心提供的功能,囊括tty core提供的数据结构和接口等等。
在代码的世界里,数据结构代表程序里面的一个个抽象出来的实体,就好像代码里面的一个个零件,由这些零件才能组成一个健全的程序,由此可见,数据结构是一个程序的基本,使用抽象出来的数据结构,不仅有利于代码的编写和理解,更有利于代码的维护!
Linux的设备驱动模型有两大部分,分别是驱动和设备,每一个驱动都会有对应描述驱动和设备的数据结构。tty框架也一样,tty框架使用了tty_driver和tty_struct结构描述tty驱动和tty设备。
tty driver结构如下:
struct tty_driver { int magic; /* magic number for this structure */ struct kref kref; /* Reference management */ struct cdev *cdevs; struct module *owner; const char *driver_name; const char *name; int name_base; /* offset of printed name */ int major; /* major device number */ int minor_start; /* start of minor device number */ unsigned int num; /* number of devices allocated */ short type; /* type of tty driver */ short subtype; /* subtype of tty driver */ struct ktermios init_termios; /* Initial termios */ unsigned long flags; /* tty driver flags */ struct proc_dir_entry *proc_entry; /* /proc fs entry */ struct tty_driver *other; /* only used for the PTY driver */ /* * Pointer to the tty data structures */ struct tty_struct **ttys; struct tty_port **ports; struct ktermios **termios; void *driver_state; /* * Driver methods */ const struct tty_operations *ops; struct list_head tty_drivers;};
@magic:该tty_driver的幻数;
@kref:该结构的计数,当该计数小于或者等于零的时候,该结构才可以被释放;
@cdevs:该结构注册的字符设备;
@driver_name:tty_driver注册的时候,创建proc文件的名称;
@name:该结构的名称,字符设备的名称也是该名称;
@name_base:和name字段一起构成设备的名称;
@major:该tty_driver的主设备号;
@minor_start:次设备号的开始位;
@num:申请次设备号的数量;
@type:tty_driver的类型;
@subtype:tty_driver的子类型;
@init_termios:终端的初始化参数;
@flags:tty_driver的标志;
以下是指向tty data的结构
@ttys:存放打开的tty_struct;
@ports:存放tty_driver注册的tty_port;
@termios:运行时的终端配置信息;
@driver_state:指向tty_driver的数据,例如,指向uart_driver;
以下是tty_driver的操作函数
@ops:该ops会在打开tty设备节点的时候,赋给tty_struct结构的ops,该ops被discipline层ops调用,最后会打开tty_driver层;
@tty_drivers:链表头,用于挂载到tty_drivers链表;
驱动人员注册一个tty_driver的时候,必须初始化tty_driver的一些必要的字段,然后调用tty core提供的接口注册到tty core。
tty core使用tty_struct结构描述一个tty设备。一般情况下,设备和驱动都会有一个匹配的过程,就好像platform_device和platform_driver通过虚拟总线匹配的过程一样,不过,tty_struct和tty_driver并没有通过总线来进行匹配,仿佛tty core弱化了tty device,驱动人员不需要创建一个tty device,而是,tty_driver注册接口注册一个指定范围设备号的字符设备驱动,再创建并注册对应设备号范围的设备。这样,字符设备驱动就会与新建的设备匹配,当用户空间open一个tty设备节点的时候,调用字符设备驱动的ops的open函数,tty_struct就在该open函数创建的,这意味着,一个tty设备对应着一个tty_struct。
如下所示为tty_struct的定义:
struct tty_struct { int magic; struct kref kref; struct device *dev; struct tty_driver *driver; const struct tty_operations *ops; int index; /* Protects ldisc changes: Lock tty not pty */ struct ld_semaphore ldisc_sem; struct tty_ldisc *ldisc; --cut— --cut-- struct tty_struct *link; struct fasync_struct *fasync; 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 unsigned char closing:1; unsigned char *write_buf; int write_cnt; /* If the tty has a pending do_SAK, queue it here - akpm */ struct work_struct SAK_work; struct tty_port *port;};
@magic:该tty_struct的幻数;
@kref:tty_struct的计数;
@driver:tty_struct的tty_driver;
@ops:open设备节点的时候,tty_driver的ops会赋给该ops;
@index:tty_struct在tty_driver中的索引;
@ldisc:tty_struct的discipline;
@write_wait:写等待队列头,N_TTY discipline的ops->write把当前的进程添加到该等待队列头,进入while(1)循环,如果while循环调用了schedule,当前进程进入休眠,设备节点的下一次open,或者reset discipline的时候,会wake_up该等待队列头的等待队列,以继续进行write操作;
@read_wait:与write_wait相似,所不同的是,是在N_TTY discipline的read函数;
@disc_data:保存discipline用到的数据;
@tty_files:链表头,保存file文件;
@write_buf:写缓冲区;
@write_cnt:申请输出的数量;
@port:tty_struct对应的tty_port;
struct tty_operation结构是tty_driver的操作函数集,tty_driver的字符设备操作函数或者discipline的操作函数会调用到 tty_driver的操作函数集,tty_driver的函数集再调用更低一层的操作函数,比如,对于串口来说,tty_driver的函数集会调用uart_port的操作函数。
tty_operation的定义如下:
struct tty_operations { struct tty_struct * (*lookup)(struct tty_driver *driver, struct inode *inode, int idx); int (*install)(struct tty_driver *driver, struct tty_struct *tty); void (*remove)(struct tty_driver *driver, struct tty_struct *tty); int (*open)(struct tty_struct * tty, struct file * filp); void (*close)(struct tty_struct * tty, struct file * filp); void (*shutdown)(struct tty_struct *tty); void (*cleanup)(struct tty_struct *tty); int (*write)(struct tty_struct * tty, const unsigned char *buf, int count); int (*put_char)(struct tty_struct *tty, unsigned char ch); int (*write_room)(struct tty_struct *tty); int (*ioctl)(struct tty_struct *tty, unsigned int cmd, unsigned long arg); void (*set_termios)(struct tty_struct *tty, struct ktermios * old); void (*stop)(struct tty_struct *tty); void (*start)(struct tty_struct *tty); void (*hangup)(struct tty_struct *tty); int (*break_ctl)(struct tty_struct *tty, int state); void (*flush_buffer)(struct tty_struct *tty);};
@lookup:tty设备节点的open函数使用该回调函数查找tty_struct,如果没有该回调,则直接访问driver->ttys数组,该数组保存着打开的tty_struct;
@install:tty设备节点的open函数新建一个tty_struct的时候,使用该回调把tty_struct安装到tty_driver,如果没有提供该回调,使用tty_standard_insatll函数,把tty_struct存在tty_driver的ttys数组中;
@remove:与install的作用相反,把ttt_struct从tty_driver中去除;
@open:tty设备节点的open函数调用discipline ops的open后,调用该open回调,该回调再调用下一层的回调;
@close:与open相反,close回调在tty设备节点的release函数被调用;
@shutdown:关掉该tty_struct;
@cleanup:tty_struct计数为零,触发queue_release_one_tty被调用,接着release_one_tty被调用,release_one_tty检测tty->ops->cleanup是否存在,如果存在则调用该cleanup回调,该回调释放tty->ops申请的资源;
@write:输出一个buf数据;
@put_char:输出一个字符;
@write_room:返回输出buffer剩下的空间大小;
@ioctl:ioctl接口;
@set_termios:设置tty_struct的配置,在discipline的ioctl会调用该回调;
@stop:暂定tty_struct flow,discipline的ioctl会调用这回调;
@start:开始tty_struct flow,比如,uart driver的start会调用port的start_tx回调,开始发送数据;
@hangup:挂起tty_struct当前flow,包括fulsh buffer,shutdown tty等;
discipline层采用tty_ldisc描述一个discipline,tty_ldisc_ops描述一个discipline的操作函数。tty_ldisc_ops类似于tty_operation,这里不再赘述;tty_ldisc非常简单,作为连接discipline与tty_struct之间的桥梁。
tty_ldisc数据结构定义如下:
struct tty_ldisc { struct tty_ldisc_ops *ops; struct tty_struct *tty;};
@ops:tty_ldisc_ops操作函数集;
@tty:指向该discipline的tty_struct结构;
控制台终端采用struct console结构描述一个终端,因个人对console还不是很熟悉,所以,这里只给出其定义,定义如下:
struct console { char name[16]; void (*write)(struct console *, const char *, unsigned); int (*read)(struct console *, char *, unsigned); struct tty_driver *(*device)(struct console *, int *); void (*unblank)(void); int (*setup)(struct console *, char *); int (*early_setup)(void); short flags; short index; int cflag; void *data; struct console *next;};
uart核心是tty核心的一部分,采用uart_driver结构描述一个uart。uart_driver也是一个tty_driver,是tty_driver更高一层的封装。相对tty_driver,uart_driver封装了tty_driver、console等字段,增加了一些uart需要的字段。
uart_driver定义如下:
struct uart_driver { struct module *owner; const char *driver_name; const char *dev_name; int major; int minor; int nr; struct console *cons; /* * these are private; the low level driver should not * touch these; they should be initialised to NULL */ struct uart_state *state; struct tty_driver *tty_driver;};
@owner:uart_driver的MODULE;
@driver_name:driver名称;
@dev_name:对应于tty_driver的name;
@major:主设备号,用于初始化tty_driver;
@minor:次设备号,初始化tty_driver;
@nr:uart_port的数量;
@cons:指向console;
@state:包含环形buf、uart_port和tty_port指针,是uart_driver与uart_port的桥梁;
@tty_driver:指向tty_driver的指针;
上一节介绍了tty核心的主要数据结构,这一节将会介绍tty的接口。tty核心提供了很多方便的接口。其中,alloc_tty_driver接口申请一个tty_driver;tty_register_driver注册一个tty_driver;tty_register_ldisc注册一个dicsipline;register_console注册一个console;uart_register_driver注册一个uart_driver等等。接下来,将会主要介绍这几个接口。
alloc_tty_driver(unsigned int lines)
参数lines表示tty_driver有几条line。一条line分别占有一个tty_struct、tty_port和termios,所以,该接口根据lines初始化tty_driver->num,申请lines个ttys、terminos和ports内存空间。
tty_register_driver(struct tty_driver *driver)
tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
参数disc表示discipline的序号,new_ldisc表示该discipline的操作函数集。该接口接收这两个参数,把new_ldisc放到tty_ldiscs全局数组中。
register_console(struct console *newcon)
uart_register_driver(struct uart_driver *drv)
该接口注册一个uart_driver到uart core层。该接口为uart_driver->state申请空间,调用alloc_tty_driver申请一个tty_driver,初始化uart需要的一些tty_driver字段,把tty_driver的ops设置为uart使用的uart_ops操作函数,最后调用tty_register_driver注册初始化后的tty_driver。
tty_driver的注册由tty_registeer_driver完成,注册之前,需要使用alloc_tty_driver申请一个tty_driver,然后对其初始化,再传给tty_register_driver接口,剩下的工作就交给它完成就行了。
tty_register_driver接口非常简单,其定义如下:
int tty_register_driver(struct tty_driver *driver){ --cut-- if (!driver->major) { error = alloc_chrdev_region(&dev, driver->minor_start, driver->num, driver->name); if (!error) { driver->major = MAJOR(dev); driver->minor_start = MINOR(dev); } } else { dev = MKDEV(driver->major, driver->minor_start); error = register_chrdev_region(dev, driver->num, driver->name); } if (error < 0) goto err; if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) { error = tty_cdev_add(driver, dev, 0, driver->num); if (error) goto err_unreg_char; } mutex_lock(&tty_mutex); list_add(&driver->tty_drivers, &tty_drivers); mutex_unlock(&tty_mutex); if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) { for (i = 0; i < driver->num; i++) { d = tty_register_device(driver, i, NULL); if (IS_ERR(d)) { error = PTR_ERR(d); goto err_unreg_devs; } } } proc_tty_register_driver(driver); driver->flags |= TTY_DRIVER_INSTALLED; return 0; --cut--}
1.如果driver没有指定major,调用alloc_chrdev_region,动态申请主设备号;
2.否则,调用register_chrdev_region,申请driver指定的设备号;
3.判断driver->flags是否设置了TTY_DRIVER_DYNAMIC_ALLOC标志位,如果设置了,调用tty_cdev_add注册一个字符设备驱动,该字符设备驱动负责处理对应于第1或第2部申请的设备号的设备,tty_cdev_add函数下面再作展开;
4.如果driver->flags设置了TTY_DRIVER_DYNAMIC_DEV标志,调用tty_register_device,注册driver->num个设备;
5.调用proc_tty_register_driver,注册tty_driver的proc文件系统文件;
6.设置driver->flags的TTY_DRIVER_INSTALLED标志位,表示driver已经成功注册到tty core。
tty_cdev_add接口也非常简单,调用cdev_init初始化一个cdev,指明cdev的owner,最后调用cdev_add注册该cdev。该函数的定义如下:
static int tty_cdev_add(struct tty_driver *driver, dev_t dev, unsigned int index, unsigned int count){ /* init here, since reused cdevs cause crashes */ cdev_init(&driver->cdevs[index], &tty_fops); driver->cdevs[index].owner = driver->owner; return cdev_add(&driver->cdevs[index], dev, count);}
discipline层需要注册disc到tty核心,这样打开tty设备的时候,通过tty核心才可以找到该disc。不同的tty设备可能需要不同的disc,tty核心管理着一个tty_ldiscs数组,不同类型的disc以disc类型号为数组的下标存到该数组,获取该disc的时候,以disc类型号找到目标disc。disc的注册接口为tty_register_ldisc,注册一个disc之前,需要初始化一个tty_ldisc_ops,指定该tty_ldisc_ops的disc号,准备好后,调用tty_register_ldisc完成注册。
tty_register_ldisc接口也非常简单,其定义如下:
int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc){ unsigned long flags; int ret = 0; if (disc < N_TTY || disc >= NR_LDISCS) return -EINVAL; raw_spin_lock_irqsave(&tty_ldiscs_lock, flags); tty_ldiscs[disc] = new_ldisc; new_ldisc->num = disc; new_ldisc->refcount = 0; raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags); return ret;}
tty_register_ldisc检查disc类型号disc是否在正确的范围,如果不在正确的范围,返回-EINVAL,如果在,以disc类型号为下标,存放到tty_ldiscs数组中。
内核启动的打印信息通过printk函数打印出来的, printk找到合适的console,console最后依赖tty_driver输出打印信息。console分为两种类型,一种为bootconsole,另外一种为”real”console。bootconsole可以在任何时候注册,但是,当real console注册后,bootconsole将会被自动注销,而且,real console注册后,再注册bootconsole将会失败。
注册console的接口函数为register_console,参数为struct console类型的变量,注册之前,需要初始化好struct console的变量。register_console的定义如下:
void register_console(struct console *newcon){ int i; unsigned long flags; struct console *bcon = NULL; struct console_cmdline *c; if (console_drivers) for_each_console(bcon) if (WARN(bcon == newcon, "console '%s%d' already registered\n", bcon->name, bcon->index)) return; /* * before we register a new CON_BOOT console, make sure we don't * already have a valid console */ if (console_drivers && newcon->flags & CON_BOOT) { /* find the last or real console */ for_each_console(bcon) { if (!(bcon->flags & CON_BOOT)) { pr_info("Too late to register bootconsole %s%d\n", newcon->name, newcon->index); return; } } } if (console_drivers && console_drivers->flags & CON_BOOT) bcon = console_drivers; if (preferred_console < 0 || bcon || !console_drivers) preferred_console = selected_console; if (newcon->early_setup) newcon->early_setup(); /* * See if we want to use this console driver. If we * didn't select a console we take the first one * that registers here. */ if (preferred_console < 0) { if (newcon->index < 0) newcon->index = 0; if (newcon->setup == NULL || newcon->setup(newcon, NULL) == 0) { newcon->flags |= CON_ENABLED; if (newcon->device) { newcon->flags |= CON_CONSDEV; preferred_console = 0; } } } /* * See if this console matches one we selected on * the command line. */ for (i = 0, c = console_cmdline; i < MAX_CMDLINECONSOLES && c->name[0]; i++, c++) { BUILD_BUG_ON(sizeof(c->name) != sizeof(newcon->name)); if (strcmp(c->name, newcon->name) != 0) continue; if (newcon->index >= 0 && newcon->index != c->index) continue; if (newcon->index < 0) newcon->index = c->index; if (_braille_register_console(newcon, c)) return; if (newcon->setup && newcon->setup(newcon, console_cmdline[i].options) != 0) break; newcon->flags |= CON_ENABLED; newcon->index = c->index; if (i == selected_console) { newcon->flags |= CON_CONSDEV; preferred_console = selected_console; } break; } if (!(newcon->flags & CON_ENABLED)) return; /* * If we have a bootconsole, and are switching to a real console, * don't print everything out again, since when the boot console, and * the real console are the same physical device, it's annoying to * see the beginning boot messages twice */ if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) newcon->flags &= ~CON_PRINTBUFFER; /* * Put this console in the list - keep the * preferred driver at the head of the list. */ console_lock(); if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) { newcon->next = console_drivers; console_drivers = newcon; if (newcon->next) newcon->next->flags &= ~CON_CONSDEV; } else { newcon->next = console_drivers->next; console_drivers->next = newcon; } if (newcon->flags & CON_PRINTBUFFER) { /* * console_unlock(); will print out the buffered messages * for us. */ raw_spin_lock_irqsave(&logbuf_lock, flags); console_seq = syslog_seq; console_idx = syslog_idx; console_prev = syslog_prev; raw_spin_unlock_irqrestore(&logbuf_lock, flags); /* * We're about to replay the log buffer. Only do this to the * just-registered console to avoid excessive message spam to * the already-registered consoles. */ exclusive_console = newcon; } console_unlock(); console_sysfs_notify(); /* * By unregistering the bootconsoles after we enable the real console * we get the "console xxx enabled" message on all the consoles - * boot consoles, real consoles, etc - this is to ensure that end * users know there might be something in the kernel's log buffer that * went to the bootconsole (that they do not see on the real console) */ pr_info("%sconsole [%s%d] enabled\n", (newcon->flags & CON_BOOT) ? "boot" : "" , newcon->name, newcon->index); if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) && !keep_bootcon) { /* We need to iterate through all boot consoles, to make * sure we print everything out, before we unregister them. */ for_each_console(bcon) if (bcon->flags & CON_BOOT) unregister_console(bcon); }}
这个函数非常长,也比较复杂,下面一步一步分析这个函数做了哪些工作。
1.扫描console_drivers链表,检查newcon是否已经注册,如果已经被注册,直接返回,否则,继续;
2.如果console_drivers不为空、newcon->flags标志newcon位bootconsole,扫描console_drivers链表,检查是否有”real”console已经注册,如果有,提示已经注册了”real”console,不能再注册bootconsole,然后返回;
3.函数局部变量bcon表示上一个注册的bootconsole,函数最后判断bcon是否不为空,如果不为空,且新注册的console不为bootconsole,将注销所有的bootconsole。如果console_drivers不为空,且console_drivers->flags标志了CON_BOOT,bcon的值为该console_drivers;
4.全局变量preferred_console表示作为printk输出的console,全局变量selected_console表示uboot传给kernel的命令行参数选择输出的console。如果preferred_console小于0,或者bcon不为空,或者console_drivers为空(表示还没有console已经注册),preferred_console的值为selected_console(selected_console为解释uboot命令行选择的console);
5.如果uboot传给kernel的命令行参数没有选择console,即preferred_console和selected_console都为-1,选择第一个注册的console为输出打印信息的console;
6.newcon与命令行console_cmdline选择的console比较,判断newcon是否为console_cmdline选择的console,如果是,且newcon->setup接口存在,调用newcon->setup,进行console自定义的初始化工作,标识newcon为使能状态;如果console_cmdline的第i个与newcon匹配,且i等于selected_console,标志newcon->flags的CON_CONSDEV,表示newcon为uboot命令行选择的最后一个的console;
7.经过前面的处理后,如果newcon->flags没有包含CON_ENABLED标志位,直接返回,表示只有选择了的console才可以注册;
8.如果bcon不为空,且newcon->flags标志了CON_SONSDEV,设置newcon->flags不包含CON_PRINTBUFFER标志位,这样可以防止重复打印内核启动信息;
9.如果newcon->flags包含CON_CONSDEV,把newcon插入链表的表头位置,插入链表后,如果newcon->next存在(即链表不为空),设置链表倒数第二个console的flags不包含CON_CONSDEV。或者,如果console_drivers为空,表示newcon为第一个注册的console,设置console_drivers指向newcon;如果newcon->flags既不包含CON_CONSDEV,而且console_drivers不为空,则把newcon添加到链表表头的位置;
10.全局变量exclusive_console表示打印信息将从该console输出。如果newcon->flags设置了CON_PRINTBUFFER,exclusive_console为newcon;
11.如果bcon不为空,newcon->flags设置了CON_CONSDEV,并且没有设置keep_bootcon,那么,注销所有的bootconsole。
uart是tty设备连接系统的典型工具。uart_driver是tty_driver中的一种,封装了更加多的成员,以适应uart设备。为了简化uart driver的开发, kernel封装了描述uart设备驱动的uart_driver结构体,提供用于注册uart_driver和操作uart设备的接口,管理一些不需要驱动人员关心的信息,负责管理已经注册的uart_driver,这就是uart core,这样大大减少了驱动人员开发uart driver的工作。下图是tty core与uart core之间的关系:
uart core位于tty driver的下面,uart_driver在uart core的下面,uart driver的下面就是硬件了。uart driver注册到uart core,uart core根据uart driver申请一个tty driver,初始化tty driver,把tty driver注册到tty core,因此,uart core是一个中间层,在tty core与uart_driver之间封装一个接口,管理tty_driver的注册。除了管理tty_driver的注册外,uart_core也提供了tty_driver的ops,这表示,uart core提供一套公用的接口,这些接口包含uart一些特有的处理,再调用注册到uart core中的uart_driver的接口,交给uart_driver处理。
uart_driver的注册由uart_register_driver接口完成,该接口接收一个uart_driver类型的参数,注册uart_driver之前,需要初始化好一个uart_driver类型的变量。
uart_register_driver的定义如下:
int uart_register_driver(struct uart_driver *drv){ struct tty_driver *normal; int i, retval; BUG_ON(drv->state); /* * Maybe we should be using a slab cache for this, especially if * we have a large number of ports to handle. */ drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL); if (!drv->state) goto out; normal = alloc_tty_driver(drv->nr); if (!normal) goto out_kfree; drv->tty_driver = normal; normal->driver_name = drv->driver_name; normal->name = drv->dev_name; normal->major = drv->major; normal->minor_start = drv->minor; normal->type = TTY_DRIVER_TYPE_SERIAL; normal->subtype = SERIAL_TYPE_NORMAL; normal->init_termios = tty_std_termios; normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600; normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; normal->driver_state = drv; tty_set_operations(normal, &uart_ops); /* * Initialise the UART state(s). */ for (i = 0; i < drv->nr; i++) { struct uart_state *state = drv->state + i; struct tty_port *port = &state->port; tty_port_init(port); port->ops = &uart_port_ops; port->close_delay = HZ / 2; /* .5 seconds */ port->closing_wait = 30 * HZ;/* 30 seconds */ } retval = tty_register_driver(normal); if (retval >= 0) return retval; --cut--}
1.uart_state的作用后面再说明。这里为uart_driver->state申请空间,调用alloc_tty_driver申请一个tty_driver——normal。alloc_tty_driver接口前面章节已经讲述,主要为tty_driver申请空间,初始化一些字段;
2.初始化normal的名称、设备号、init_termios和ops等。设置init_termios一些串口控制cflag和输入、输出速度,设置normal->ops为uart core提供的uart_ops;
3.每个uart_state都包含着一个tty_port,这里初始化每个uart_state的tty_port,设置tty_port的ops为uart_port_ops,uart_port_ops与uart_ops一样,都为uart core提供的公用操作函数集,这些ops再调用下面uart_driver的ops;设置tty_port的close_delay和closing_wait;
4.现在,已经初始化完normal了,调用我们前面章节已经讨论过的tty_register_driver函数,注册normal到tty core。
上一节,uart_register_driver注册了一个uart_driver,这一节,将介绍如何注册一个uart port。硬件上,串口的端口依附在串口控制器上,软件采用了相似的结构描述串口的结构,uart_driver描述一个串口控制器,uart_port描述一个串口端口。与串口的硬件一样,uart_port同样也是依附在uart_driver上,串口端口正常工作,需要添加该端口到uart_driver上,本节将会介绍如果添加端口uart_port到uart_driver。
添加uart_port的接口为uart_add_one_port,该接口负责把一个uart_port添加到uart_driver,它有两个参数,一个为已经注册的uart_driver,另一个为需要添加的uart_port。
下面为uart_add_one_port的定义:
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport){ struct uart_state *state; struct tty_port *port; int ret = 0; struct device *tty_dev; BUG_ON(in_interrupt()); if (uport->line >= drv->nr) return -EINVAL; state = drv->state + uport->line; port = &state->port; mutex_lock(&port_mutex); mutex_lock(&port->mutex); if (state->uart_port) { ret = -EINVAL; goto out; } state->uart_port = uport; state->pm_state = UART_PM_STATE_UNDEFINED; uport->cons = drv->cons; uport->state = state; /* * If this port is a console, then the spinlock is already * initialised. */ if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) { spin_lock_init(&uport->lock); lockdep_set_class(&uport->lock, &port_lock_key); } uart_configure_port(drv, state, uport); /* * Register the port whether it's detected or not. This allows * setserial to be used to alter this ports parameters. */ tty_dev = tty_port_register_device_attr(port, drv->tty_driver, uport->line, uport->dev, port, tty_dev_attr_groups); if (likely(!IS_ERR(tty_dev))) { device_set_wakeup_capable(tty_dev, 1); } else { printk(KERN_ERR "Cannot register tty device on line %d\n", uport->line); } /* * Ensure UPF_DEAD is not set. */ uport->flags &= ~UPF_DEAD; out: mutex_unlock(&port->mutex); mutex_unlock(&port_mutex); return ret;}
1.uart_port以line来区分不同的uart_port,uart_driver的nr表示uart_driver可以处理的uart_port的数量,line必须小于nr。检查uport->line有没有超出drv->nr,如果超出,表示超出了uart_driver的处理范围,返回-EINVAL;
2.uart_driver为每一个uart_port准备了一个uart_state,uart_state是tty_port与uart_port之间的桥梁。根据uport->line,获取uart_state(该uart_state在uart_driver注册的时候已经初始化,初始化了uart_state的tty_port等),把uart_state的uart_port指针指向添加的uart_port,这样,以后就可以通过uart_state找到该uart_port了;
3.调用uart_configure_port。uart_configure_port根据uport的flags,判断是否需要调用uport->ops->config_port;如果uport->type不是PORT_UNKNOWN,调用uart_change_pm,设置uport为上电的状态,调用uport->ops->set_mctrl,确保modem control line是非激活状态;如果uport带有CON_ENABLED标志,调用register_console接口,注册console;
4.调用tty_port_register_device_attr。tty_port_link_device把tty_port存放到tty_driver->ports中;tty_register_device_attr注册一个以tty_driver->major为主设备号,以uport->line + base为次设备号的设备,该设备将会与tty_driver注册的字符设备驱动匹配,/dev下的tty设备节点的名字为该设备的名称,open该设备节点,以次设备号,最终找到该uport;
5.设置uport->flags不包含UPF_DEAD,表示该uport状态不是dead的状态;
open tty设备节点的时候,会调用设备节点对应的字符设备驱动的open函数。tty_register_driver注册tty_driver的时候,如果tty_driver的flags带有TTY_DRIVER_DYNAMIC_ALLOC标志,tty_register_driver调用tty_cdev_add函数,tty_cdev_add函数初始化字符设备驱动cdev,注册字符设备驱动cdev; tty_driver支持的所有的tty 设备的设备号范围都在cdev注册的设备号范围内,对tty设备的操作都有cdev的操作函数集进行处理。
open tty设备节点调用的是cdev的tty_open函数,该函数的定义如下:
static int tty_open(struct inode *inode, struct file *filp){ struct tty_struct *tty; int noctty, retval; struct tty_driver *driver = NULL; int index; dev_t device = inode->i_rdev; unsigned saved_flags = filp->f_flags; nonseekable_open(inode, filp);retry_open: retval = tty_alloc_file(filp); if (retval) return -ENOMEM; noctty = filp->f_flags & O_NOCTTY; index = -1; retval = 0; mutex_lock(&tty_mutex); /* This is protected by the tty_mutex */ tty = tty_open_current_tty(device, filp); if (IS_ERR(tty)) { retval = PTR_ERR(tty); goto err_unlock; } else if (!tty) { driver = tty_lookup_driver(device, filp, &noctty, &index); if (IS_ERR(driver)) { retval = PTR_ERR(driver); goto err_unlock; } /* check whether we're reopening an existing tty */ tty = tty_driver_lookup_tty(driver, inode, index); if (IS_ERR(tty)) { retval = PTR_ERR(tty); goto err_unlock; } } if (tty) { tty_lock(tty); retval = tty_reopen(tty); if (retval < 0) { tty_unlock(tty); tty = ERR_PTR(retval); } } else /* Returns with the tty_lock held for now */ tty = tty_init_dev(driver, index); mutex_unlock(&tty_mutex); if (driver) tty_driver_kref_put(driver); if (IS_ERR(tty)) { retval = PTR_ERR(tty); goto err_file; } tty_add_file(tty, filp); check_tty_count(tty, __func__); if (tty->driver->type == TTY_DRIVER_TYPE_PTY && tty->driver->subtype == PTY_TYPE_MASTER) noctty = 1;#ifdef TTY_DEBUG_HANGUP printk(KERN_DEBUG "%s: opening %s...\n", __func__, tty->name); #endif if (tty->ops->open) retval = tty->ops->open(tty, filp); else retval = -ENODEV; filp->f_flags = saved_flags; --cut— mutex_lock(&tty_mutex); tty_lock(tty); spin_lock_irq(¤t->sighand->siglock); if (!noctty && current->signal->leader && !current->signal->tty && tty->session == NULL) __proc_set_tty(current, tty); spin_unlock_irq(¤t->sighand->siglock); tty_unlock(tty); mutex_unlock(&tty_mutex); --cut--}
1.tty_alloc_file为file的private_data字段申请内存,private_data将会保存一些私有数据等,例如,tty_struct等;
2.TTYAUX_MAJOR主设备号的设备为控制台终端设备,open主设备号为TTYAUX_MAJOR、次设备号为0的设备(/dev/console),打开的为当前进程的控制台终端设备,即次设备号为0的控制台终端设备指向的是当前进程的控制台终端设备;主设备号为TTY_MAJOR的设备为虚拟终端设备,open这设备号为TTY_MAJOR、次设备号为0的设备(/dev/tty0),打开的为fg_console(虚拟终端,应该是当前活动的虚拟终端),即次设备号为0的虚拟终端设备指向的是当前活动的虚拟终端。tty_open_current_tty检测打开的tty设备节点的设备号device是否为MKDEV(TTYAUX_MAJOR, 0),如果不是,返回NULL,否则,返回当前进程的tty,如果当前进程没有tty,打开就会失败;
3.第2步返回NULL,!tty为真,调用tty_lookup_driver。tty_lookup_driver检测tty设备的类型(虚拟终端设备、控制台终端设备、其它tty设备),默认是通过get_tty_driver获取tty_driver。本文通过get_tty_driver获取tty_driver,get_tty_driver扫描已经注册的tty_driver的链表tty_drivers,找到包含设备号device的tty_driver,返回该tty_driver,并返回该设备号device在该tty_driver设备号中的偏移index,如果没有找到,返回NULL;
4.第3步返回driver和index后,以driver、index和inode为参数,调用tty_driver_lookup_tty,查找driver是否已经打开了该index的tty。tty_driver_lookup_tty检测tty_driver是否有lookup回调函数,如果有返回回调函数lookup的结果,如果没有lookup回调函数,以index为索引,返回数组tty_driver->ttys数组的第index个成员,该数组存放的是tty_driver已经打开的tty_struct。本节讨论的是第一次open tty设备节点的情况,因此,tty_driver_lookup_tty返回NULL;
5.第4步,tty_driver_lookup_tty返回NULL,即tty为NULL,这样,下一步将会调用tty_init_dev,新建一个tty_struct。tty_init_dev申请一个tty_struct,初始化该tty_struct,安装tty_struct到tty_driver,调用ldisc的open函数。下面再具体展开tty_init_dev;
6.tty_add_file把tty_struct添加到file的private_data中,这样,字符设备驱动的其他函数就可以通过file获取到tty_struct;
7.tty->ops在tty_init_dev函数被初始化为tty_driver的ops。判断tty->ops->open是否存在,即判断tty_driver的ops-open是否存在,如果存在,调用tty->ops->open,否则,返回-ENODEV。前面uart_driver注册过程一节,uart_driver注册的时候,初始化tty_driver的ops为uart_ops,因此,在此处,如果是open的是串口类型的tty设备,tty->ops->open存在。下面再详细展开uart_driver的uart_ops open函数;
8.noctty表示该tty是否为控制终端,noctty为1,表示不是控制终端,noctty为0,表示是控制终端。前面的tty_lookup_driver函数,如果打开主设备号为TTY_MAJOR,次设备号为0、或者主设备号为TTYAUX_MAJOR、次设备号为0的设备,设noctty为1。如果noctty不为1、current->signal->leader为真、current->signal->tty不存在和tty->session为NULL,调用__proc_set_tty,设置当前进程的tty为tty_struct。
tty_open没有找到tty的时候,调用tty_init_dev,初始化一个tty。tty_init_dev的定义如下:
struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx) { struct tty_struct *tty; --cut-- tty = alloc_tty_struct(); if (!tty) { retval = -ENOMEM; goto err_module_put; } initialize_tty_struct(tty, driver, idx); tty_lock(tty); retval = tty_driver_install_tty(driver, tty); if (retval < 0) goto err_deinit_tty; if (!tty->port) tty->port = driver->ports[idx]; --cut-- tty->port->itty = tty; retval = tty_ldisc_setup(tty, tty->link); if (retval) goto err_release_tty; --cut—}
1.调用alloc_tty_struct,alloc_tty_struct申请一个tty_struct的内存空间;
2.initialize_tty_struct初始化tty_struct,初始化各个锁。initialize_tty_struct调用tty_ldisc_init,初始化tty的discipline为N_TTY类型。tty_ldisc_init以tty和N_TTY为参数,调用tty_ldisc_get,tty_ldisc_get以N_TTY为索引,在tty_ldiscs数组(已经注册的ldops都存放在该全局数组)中找到目的ldops,新建一个tty_ldisc,为其申请空间,初始化tty_ldisc的ops为刚刚获取到的ldops,初始化tty字段指向参数tty,即我们在initialize_tty_struct新建的tty_struct;tty_ldisc_get返回新建的tty_ldisc后,tty_ldisc_init初始化tty的ldisc指向返回的ldisc。initialize_tty_struct初始化完discipline、各个链表头和锁后,初始化tty的ops为tty_driver的ops,所以以后调用tty的ops,实际是调用tty_driver的ops;
3.tty_driver_install_tty判断tty_driver是否提供intstall回调函数,如果有,调用install回调,让tty_driver自己处理tty的安装,否则,调用tty_standard_install,使用tty_driver->init_termios或者tty_driver->termios初始化tty的termios,以tty->index(设备接待的设备号在tty_driver中的偏移),把tty保存在tty_driver的ttys数组中,之后,可以在该数组找到tty;
4.如果tty->port不存在,以index为索引,获取tty_driver的ports数组中的index成员,tty的port指向该成员,这意味着,打开这个tty最后打开的是该port;tty->port->itty指向tty;
5.tty初始化完后,tty_ldisc_setup调用ldisc ops的open回调函数,交给ldisc做一些初始化工作;
上一节,tty->ops初始化为tty_driver的ops,调用tty->ops->open,最后调用的是tty_driver->ops->open。本节将会基于uart_driver展开tty_driver->ops->open流程。由uart_driver注册过程一节得知,tty_driver的ops为uart_ops,tty_driver->ops->open为uart_open函数。下面为uart_open函数的定义:
位置:drivers/tty/serial/serial_core.c
static int uart_open(struct tty_struct *tty, struct file *filp){ struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state; int retval, line = tty->index; struct uart_state *state = drv->state + line; struct tty_port *port = &state->port; --cut-- port->count++; --cut-- tty->driver_data = state; state->uart_port->state = state; state->port.low_latency = (state->uart_port->flags & UPF_LOW_LATENCY) ? 1 : 0; tty_port_tty_set(port, tty); --cut-- /* * Make sure the device is in D0 state. */ if (port->count == 1) uart_change_pm(state, UART_PM_STATE_ON); /* * Start up the serial port. */ retval = uart_startup(tty, state, 0); --cut--}
1.uart_driver注册的时候,tty_driver的driver_state指向uart_driver,所以,uart_open通过tty_drvier获取到uart_driver,通过uart_driver获取到uart_state,再从uart_state得到tty_port和uart_port等;
2.增加port的count计数,这样,下面可以通过判断 count是否为1,再决定uart pm state为UART_PM_STATE_ON。设定tty的driver_data指向state,uart_port的state指向state。tty_port_tty_set设置tty_port的tty指向tty;
3.如果port->count为1,调用uart_change_pm,设置uart的电源状态为UART_PM_STATE_ON状态,uart_change_pm判断uart_state的pm_state是否与目的state一样,如果不一样、uart_port->ops->pm回调函数存在,调用该回调函数设置目的电源状态,并把成功设置后的state保存在uart_state的state字段里;
4.uart_startup调用uart_port_startup,为uart_state申请环形buf空间,调用uart_port->ops->startup,交给更加底层的upart_port完成硬件的初始化,调用uart_change_speed设置uart_port的termios;下面将会详细展开uart_port_startup函数;
uart_startup的主体工作是由uart_port_startup完成,uart_port_startup有三个参数,分别为tty_struct、uart_state和init_hw。uart_port_startup的定义如下:
位置:drivers/tty/serial/serial_core.c。
static int uart_port_startup(struct tty_struct *tty, struct uart_state *state, int init_hw){ --cut-- if (!state->xmit.buf) { /* This is protected by the per port mutex */ page = get_zeroed_page(GFP_KERNEL); if (!page) return -ENOMEM; state->xmit.buf = (unsigned char *) page; uart_circ_clear(&state->xmit); } retval = uport->ops->startup(uport); if (retval == 0) { if (uart_console(uport) && uport->cons->cflag) { tty->termios.c_cflag = uport->cons->cflag; uport->cons->cflag = 0; } /* * Initialise the hardware port settings. */ uart_change_speed(tty, state, NULL); if (init_hw) { /* * Setup the RTS and DTR signals once the * port is open and ready to respond. */ if (tty->termios.c_cflag & CBAUD) uart_set_mctrl(uport, TIOCM_RTS | TIOCM_DTR); } --cut-- } --cut--}
1.get_zeroed_page申请一页初始化为0的内存页,state->xmit.buf指向该内存页;uart_circ_clear初始化state->xmit的head和tail指针,head和tail指针构成了一个环形buffer的功能;
2.调用uart_port->ops->startup函数,交给具体的、更底层的uart_port完成硬件的初始化;
3.uart_change_speed根据tty->termios的c_cflag,清除或者设置tty_port的flag,最后调用uart_port->ops->set_termios,最终设置硬件;
4.如果指定了init_hw,设置RTS和DTR信号;
tty open流程图如下:
tty write接收用户的输入,把数据发送出去。由上一节的tty_open,可以知道,处理tty设备节点操作由注册的字符设备驱动cdev完成。tty write对应的操作函数为tty_write,tty_write先经过discipline,discipline处理一些特殊字符的处理、输出,接着接着调用tty_driver ops的write函数,tty_driver->ops->write最后调用到具体的port的函数。
首先,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); struct tty_ldisc *ld; ssize_t ret; --cut-- if (tty->ops->write_room == NULL) printk(KERN_ERR "tty driver %s lacks a write_room method.\n", tty->driver->name); ld = tty_ldisc_ref_wait(tty); if (!ld->ops->write) ret = -EIO; else ret = do_tty_write(ld->ops->write, tty, file, buf, count); tty_ldisc_deref(ld); return ret;}
1.tty_write检测tty ops是否存在write_room回调函数,write_room函数返回的是当前可以支持的写的空间大小,使用tty_write一般需要实现write_room回调函数,如果没有实现,输出警告;
2.检测discipline有没有实现write回调函数,如果没有,直接返回错误,实现tty_write功能,必须提供健全的discipline的write回调函数;
3.do_tty_write检测tty->write_cnt的大小,如果write_cnt小于chunk,从新申请一块内存给tty->write_buf,write_buf是作为临时存放用户数据的buf;接着,do_tty_write拷贝用户空间数据到write_buf,调用参数write函数,该write参数就是discipline的write回调函数,本节使用的discipline为N_TTY类型的discipline,N_TTY类型的discipline的write回调函数为n_tty_write;
4.n_tty_write首先调用process_echoes输出一些特殊字符,然后进入while循环,本节加入O_OPOS(tty)成立,while循环进入process_output_block函数,process_out_block检测用户数据是否包含特殊字符,如果包含了特殊字符,不再发送特殊字符以下的数据(包括特殊字符),留给process_output处理,否则,调用tty->ops->write函数;
5.process_output检测到特殊字符,直接调用tty->ops->write输出特殊字符,如果不是,调用 tty_put_char,tty_put_char检测是否存在put_char回调,如果有,使用该回调输出单个字符;
6.与上一节tty_open一样,本节讨论的是uart类型的tty_driver。第四步最后提到的tty->ops->write回调函数为uart_write。uart_write把write_buf的数据拷贝到circ环形buf里面。最后调用uart_start,启动传输,这样,中断的时候,自动从该环形buf拿数据发送。
tty_write的流程图如下所示:
tty read与tty write不一样,tty read的数据是,硬件检测到数据的到来,产生中断,通知软件,软件从寄存器里面读取数据出来,再一层一层上报,最终送到tty read,tty read返回数据给用户空间。
与前面的章节一样,这里以uart为例,讨论数据的接收流程。硬件产生中断后,软件从寄存器读取接收到的数据 ,调用tty_insert_flip_char,把数据往上一层提报。tty_insert_flip_char进行tty_port的flag的一些检测,检测完后,调用tty_insert_flip_string_flags,主体工作由该函数完成。下面进入该函数看看。
tty_insert_flip_string_flags函数的定义如下:
int tty_insert_flip_string_flags(struct tty_port *port, const unsigned char *chars, const char *flags, size_t size){ int copied = 0; do { int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE); int space = tty_buffer_request_room(port, goal); struct tty_buffer *tb = port->buf.tail; if (unlikely(space == 0)) break; memcpy(char_buf_ptr(tb, tb->used), chars, space); memcpy(flag_buf_ptr(tb, tb->used), flags, space); tb->used += space; copied += space; chars += space; flags += space; /* There is a small chance that we need to split the data over several buffers. If this is the case we must loop */ } while (unlikely(size > copied)); return copied;}
该函数非常简单,只有寥寥几行代码。函数把接收到的字符数据chars和标志flags拷贝到tty buf。
软件调用完tty_insert_flip_char后,调用tty_flip_buffer_push,tty_flip_buffer_push调度tty buf的工作队列,工作队列调用flush_to_ldisc处理函数,把数据往discipline层上报。flush_to_ldisc调用receive_buf,receive_buf检测discipline是否存在receive_buf2函数,如果存在调用该回调函数,否则调用discipline的receive_buf回调函数。N_TTY类型的discipline存在receive_buf2回调函数,所以,下一步进入的是receive_buf2回调函数。N_TTY discipline的receive_buf2回调函数为n_tty_receive_buf函数,该函数只是调用n_tty_receive_buf_common函数,n_tty_receive_buf_common再调用__receive_buf函数,__receive_buf再调用n_tty_receive_buf_standard或者n_tty_receive_buf_fast,无论调用哪个函数,最终都是调用put_tty_queue,把tty buf的数据放到ldata buf。
经过上面那么多层的函数调用,数据最终的位置为ldata buf。这样,用户空间调用tty_read的时候,tty_read函数调用的是discipline的read回调,该read回调从ldata buf获取数据,把数据拷贝到用户空间,实现数据的读取。
本节涉及太多的函数了,因此,本节只列出了一小部分的函数,具体的函数调用流程,可以参考下一节的流程图。
tty read流程图如下所示:
联系客服