linux 串口输出的那些事儿

linux console输出的那些事儿

小问题:

同样是输出到console,为什么用户态printf打印会产生串口中断而内核printk打印不产生中断?
答案在末尾

1.串口的基本原理

1.1 中断:

中断包括接收中断和发送中断,中断号为同一个,可以根据情况设置寄存器分别使能不同的中断。
如下UART_IER_THRI为发送中断使能位, 表示发送为空时产生中断,Transmitter holding register empty,发送中断发送时按需使能
UART_IER_RDI为接收中断使能位, 表示接收有数据时产生中断,Received data available, 接收中断一般默认一直使能

由于发送中断和接收中断的中断号为同一个,那么在handler里面需要根据中断状态位来分别处理发送和接收。
如serial8250_handle_irq 根据“1.3 LSR状态寄存器”中UART_LSR_DR表示有数据接收,则调用接收流程;
根据“1.3 LSR状态寄存器”中 UART_LSR_THRE表示发送为空,则调用发送流程。

中断使能寄存器
#define UART_IER	1	/* Out: Interrupt Enable Register */
#define UART_IER_MSI		0x08 /* Enable Modem status interrupt */
#define UART_IER_RLSI		0x04 /* Enable receiver line status interrupt */
#define UART_IER_THRI		0x02 /* Enable Transmitter holding register int. */
#define UART_IER_RDI		0x01 /* Enable receiver data interrupt */

1.2 FIFO:

这里读写的fifo深度由硬件决定的,普通的只有一个字节;
如下为fifo的地址,读取UART_RX得到未被读取的fifo中的最新的字节;
写入UART_TX自动有序填充fifo;
这里为什么发送和接收都是一个地址? 个人理解因为读和写是两个动作,读操作的是接收FIFO,写操作的是发送FIFO

#define UART_RX		0	/* In:  Receive buffer */
#define UART_TX		0	/* Out: Transmit buffer */

1.3 LSR状态寄存器

UART_LSR_THRE 此状态位表示发送fifo是否为空,跟踪此状态可以在发送时poll此状态来死等发送,内核 serial8250_console_write就是使用的此方式;
UART_LSR_DR 此状态位表示接收是否还有数据,根据此状态在接收的时候可以连续接收多个字节。

#define UART_LSR	5	/* In:  Line Status Register */
#define UART_LSR_FIFOE		0x80 /* Fifo error */
#define UART_LSR_TEMT		0x40 /* Transmitter empty */
#define UART_LSR_THRE		0x20 /* Transmit-hold-register empty */
#define UART_LSR_BI		0x10 /* Break interrupt indicator */
#define UART_LSR_FE		0x08 /* Frame error indicator */
#define UART_LSR_PE		0x04 /* Parity error indicator */
#define UART_LSR_OE		0x02 /* Overrun error indicator */
#define UART_LSR_DR		0x01 /* Receiver data ready */

2. 读写原理

2.1 写

2.1.1 poll方式写:

写入fifo,等待状态寄存器UART_LSR_THRE,再写入fifo,依次重复进行,缺点是等待时间较长,优点是不依赖中断,内核printk使用此方式。

2.1.2 中断方式写:

初始化默认关闭UART_IER_THRI发送空中断,发送时先将数据写入内存循环circ_buf(串口默认1 page),然后开启发送空中断,在中断处理函数中根据fifo的大小n一次性从circ_buf搬移n个字符到fifo,发送完成会继续产生中断,直到circ_buf为空,则关闭UART_IER_THRI发送空中断。

2.2 读

读:接收中断默认开启,当有数据产生时产生中断,handler里读取fifo数据, 知道UART_LSR_DR表征没有数据可以读。

3. 串口速度

通常的打印一行有100+个字符
波特率9600 = 9600bit/s, 约合1ms发送一个1.2个字符,发送每个字符消耗833us(加校验粗略估计1ms), 发送单行100+ms。
波特率115200 = 115200bit/s 约合1ms发送14.4个字符,发送每个字符消耗69us(加校验粗略估计0.1ms), 发送单行10+ms。

4. console串口驱动提供的输出接口:

串口驱动提供了两类输出接口:

1、serial8250_console_write 接口, 里面通过写单个字符到FIFO,等待状态,再写出,再等待死循环方式。内核使用此接口。

2、serial8250_start_tx接口,先写出到公共的内存buffer,再通过如下两种方式之一输出

2.1 DMA方式,内容整体输出到串口

2.2 中断方式,在start_tx接口中使能“输出FIFO空”中断,输出FIFO为空则产生中断,在中断hangdler实际写单个字符到FIFO。FIFO传输完毕后,继续产生中断,中断hangdler实际写单个字符到FIFO。

5. printk与printf的区别

比较项目内核输出printk用户输出printf
简单原理关中断while循环死等串口FIFO状态输出写到内存circ_buffer,使能发送空中断实际发送
直接输出
是否依赖中断
堆栈如下内核写的堆栈如下用户态写的堆栈

5.1 直接输出解释

注:开源内核printk在默认情况下是直接输出,当然由于多核情况下,可能由于有多个printk在同时打印,第一个直接打印,第二个阻塞spin等待,后续的发现有等待,那么写入ring buffer后就直接返回了。
在加入rt补丁的内核中,可能存在printk线程,默认打印都只打印到ring buffer,然后唤醒printk线程进行异步输出的,当然在earlycon和oops中例外,earlycon是因为线程还没有创建,oops是因为可能printk线程已经不能运行,那么都需要同步直接输出。

内核写的堆栈
serial8250_console_write
 => call_console_drivers.isra.1.constprop.12
 => console_unlock
 => vprintk_emit
 => vprintk_default
 => printk
用户态写的堆栈
 serial8250_start_tx  正常只是使能UART_IER_THRI位, 实际在中断handler里面发送
 => uart_write
 => n_tty_write
 => tty_write
 => redirected_tty_write
 => __vfs_write
 => vfs_write
 => SyS_write
 => el0_svc_naked

6. printk 发送的细节

开源printk输出:
输出是根据回车键进行输出的,遇到回车键那么认为需要立即输出,当然也不是所有的printk都是立即输出的,遵循“5.1 直接输出解释”。

rt printk线程输出:
在存在printk线程[printk],为普通优先级。
printk_kthread_func 的msg_print_text 每次从ring buffer里面取LOG_LINE_MAX(1k)数据进行format,然后调用call_console_drivers进行输出,在con->write(con, text, len);写的内部是关闭了中断的,这个write函数即serial8250_console_write,因为这个write要考虑在任意上下文使用,例如中断,异常等,所以是关中断运行的,打印过多或串口速率过慢会导致内核最大关中断时间过长。如果关中断时间影响了中断的响应可以在con->write前进行分段打印。


同样是输出到console,为什么用户态printf打印会产生串口中断而内核printk打印不产生中断?

答案:

究其原因是因为串口是一个低速设备,如在9600波特率下,发送一个字符后,需要接近1ms后才能完成,1ms对于上G HZ的cpu来说是很长很长的时间,
所以需要2.1.2 中断方式写,在实际串口发送的过程中,cpu可以干其他事情,发完fifo为空了再中断通知cpu,cpu继续填充数据;
而内核的printk接口由于需要在任意上下文适用,不能依赖中断,更不能在printk的过程中开中断,所以采用了2.1.1 poll方式死等状态发送。

参考资料

https://www.lammertbies.nl/comm/info/serial-uart#LCR 串口寄存器解释
https://ece353.engr.wisc.edu/serial-interfaces/uart-advanced-features/ 各个缓冲及作用解释

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值