linux串口write有延迟,linux中tty->write_wait的疑惑(解决)

本文详细解读了Linux内核中串口驱动serial_read函数如何通过wake_up_interruptible操作来通知n_tty_write,确保数据传输流程。涉及核心代码剖析、中断处理和驱动与用户空间通信机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是我的摘自我的串口驱动的一段代码:

static ssize_t serial_read(struct file *file, char * buf,

size_t count, loff_t *ppos)

{

while(count) {

cnt = MIN(count, MIN(info->xmit_cnt,

SERIAL_XMIT_SIZE - info->xmit_tail));

if(cnt <= 0)

break;

if(copy_to_user(buf + rtn, info->xmit_buf + info->xmit_tail,cnt))

//将驱动缓冲区中的数据拷贝到应用层的buf中

{

rtn = -EFAULT;

goto done;

}

rtn += cnt;

count -= cnt;

DOWN(info->tx_lock, flags);

info->xmit_cnt -= cnt;

//为驱动缓冲区腾出了一部分空间

info->xmit_tail += cnt;

info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE - 1);

#if (LINUX_VERSION_CODE >= VERSION_CODE(2,1,0))

info->icount.tx += cnt;

#endif

UP(info->tx_lock, flags);

}

//在结尾处,有这么一句代码,很不解?

wake_up_interruptible(&tty->write_wait);

done:

return rtn;

}

后来读到了ldisc的write函数,代码如下:

static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,

const unsigned char *buf, size_t nr)

{

...

add_wait_queue(&tty->write_wait, &wait);//将当前进程放到等待队列中

while (1) {

set_current_state(TASK_INTERRUPTIBLE);

if (signal_pending(current)) {

retval = -ERESTARTSYS;

break;

}

//进入此处继续执行的原因可能是被信号打断,而不是条件得到了满足。

//只有条件得到了满足,我们才会继续,否则,直接返回!

if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {

retval = -EIO;

break;

}

if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {

while (nr > 0) {

ssize_t num = process_output_block(tty, b, nr);

if (num < 0) {

if (num == -EAGAIN)

break;

retval = num;

goto break_out;

}

b += num;

nr -= num;

if (nr == 0)

break;

c = *b;

if (process_output(c, tty) < 0)

break;

b++; nr--;

}

if (tty->ops->flush_chars)

tty->ops->flush_chars(tty);

} else {

while (nr > 0) {

c = tty->ops->write(tty, b, nr);

//众所周知,此处的write指具体的驱动里write函数

if (c < 0) {

retval = c;

goto break_out;

}

if (!c)

break;

b += c;

nr -= c;

}

}

if (!nr)

break;

//全部写入,返回

if (file->f_flags & O_NONBLOCK) {

retval = -EAGAIN;

break;

}

/*

假如是以非阻塞的方式打开的,那么也直接返回。否则,让出cpu,等条件满足以后再继续执行。

*/

schedule();//执行到这里,当前进程才会真正让出cpu!!!

/*

此处让出cpu以后,什么时候才会继续执行呢?

答案是当某个地方调用wake_up(&tty->write_wait)!

那么什么地方的代码会调用wake_up(&tty->write_wait)呢?

让我们分析一下此处让出cpu的原因:

c = tty->ops->write(tty, b, nr);调用失败!为什么会失败呢?

可能的原因是驱动里申请的的缓冲区满了,所以不能再往里边写了!

终于明白了serial_read函数的wake_up_interruptible(&tty->write_wait);这个语句是为了唤醒此处的代码!

从serial_read函数可以看出,在调用wake_up_interruptible(&tty->write_wait)

函数之前,我们将驱动缓冲区里的数据写入了用户缓冲buf中,这样驱动的缓冲区就又有空间了。

所以,有必要在serial_read中唤醒n_tty_write函数,让它再次尝试执行write动作。

*/

}

break_out:

__set_current_state(TASK_RUNNING);

remove_wait_queue(&tty->write_wait, &wait);

...

}

serial_read()函数中的wake_up_interruptible(&tty->write_wait)其实想表达的意思是:我读完了(把驱动缓冲中的数据拿走了,缓冲区空了),你继续写吧,于是唤醒了n_tty_write,n_tty_write再调用serial_write把用户层传下来的数据写到驱动缓冲区里边去。

需要注意的是:正常的进程调度也会让调用n_tty_write的进程重新得到cpu!

代码摘自:linux-2.6.29.RT.x

xin.jin

阅读(3277) | 评论(0) | 转发(0) |

static void pl011_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) { struct uart_amba_port *uap = container_of(port, struct uart_amba_port, port); unsigned int lcr_h, old_cr; unsigned long flags; unsigned int baud, quot, clkdiv; unsigned int bits; if (uap->vendor->oversampling) clkdiv = 8; else clkdiv = 16; /* * Ask the core to calculate the divisor for us. */ baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / clkdiv); #ifdef CONFIG_DMA_ENGINE /* * Adjust RX DMA polling rate with baud rate if not specified. */ if (uap->dmarx.auto_poll_rate) uap->dmarx.poll_rate = DIV_ROUND_UP(10000000, baud); #endif if (baud > port->uartclk/16) quot = DIV_ROUND_CLOSEST(port->uartclk * 8, baud); else quot = DIV_ROUND_CLOSEST(port->uartclk * 4, baud); switch (termios->c_cflag & CSIZE) { case CS5: lcr_h = UART01x_LCRH_WLEN_5; break; case CS6: lcr_h = UART01x_LCRH_WLEN_6; break; case CS7: lcr_h = UART01x_LCRH_WLEN_7; break; default: // CS8 lcr_h = UART01x_LCRH_WLEN_8; break; } if (termios->c_cflag & CSTOPB) lcr_h |= UART01x_LCRH_STP2; if (termios->c_cflag & PARENB) { lcr_h |= UART01x_LCRH_PEN; if (!(termios->c_cflag & PARODD)) lcr_h |= UART01x_LCRH_EPS; if (termios->c_cflag & CMSPAR) lcr_h |= UART011_LCRH_SPS; } if (uap->fifosize > 1) lcr_h |= UART01x_LCRH_FEN; bits = tty_get_frame_size(termios->c_cflag); spin_lock_irqsave(&port->lock, flags); /* * Update the per-port timeout. */ uart_update_timeout(port, termios->c_cflag, baud); /* * Calculate the approximated time it takes to transmit one character * with the given baud rate. We use this as the poll interval when we * wait for the tx queue to empty. */ uap->rs485_tx_drain_interval = DIV_ROUND_UP(bits * 1000 * 1000, baud); pl011_setup_status_masks(port, termios); if (UART_ENABLE_MS(port, termios->c_cflag)) pl011_enable_ms(port); if (port->rs485.flags & SER_RS485_ENABLED) termios->c_cflag &= ~CRTSCTS; old_cr = pl011_read(uap, REG_CR); if (termios->c_cflag & CRTSCTS) { if (old_cr & UART011_CR_RTS) old_cr |= UART011_CR_RTSEN; old_cr |= UART011_CR_CTSEN; port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; } else { old_cr &= ~(UART011_CR_CTSEN | UART011_CR_RTSEN); port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS); } if (uap->vendor->oversampling) { if (baud > port->uartclk / 16) old_cr |= ST_UART011_CR_OVSFACT; else old_cr &= ~ST_UART011_CR_OVSFACT; } /* * Workaround for the ST Micro oversampling variants to * increase the bitrate slightly, by lowering the divisor, * to avoid delayed sampling of start bit at high speeds, * else we see data corruption. */ if (uap->vendor->oversampling) { if ((baud >= 3000000) && (baud < 3250000) && (quot > 1)) quot -= 1; else if ((baud > 3250000) && (quot > 2)) quot -= 2; } /* Set baud rate */ pl011_write(quot & 0x3f, uap, REG_FBRD); pl011_write(quot >> 6, uap, REG_IBRD); /* * ----------v----------v----------v----------v----- * NOTE: REG_LCRH_TX and REG_LCRH_RX MUST BE WRITTEN AFTER * REG_FBRD & REG_IBRD. * ----------^----------^----------^----------^----- */ pl011_write_lcr_h(uap, lcr_h); pl011_write(old_cr, uap, REG_CR); spin_unlock_irqrestore(&port->lock, flags); }
最新发布
03-14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值