void tasklet_schedule(struct tasklet_struct *t);
调度tasklet执行,如果tasklet在运行中被调度,它在完成后会再次运行;这保证了在其他事件被处理当中发生的事件受到应有的注意。这个做法也允许一个tasklet重新调度它自己。
void tasklet_hi_schedule(struct tasklet_struct *t);
和tasklet_schedule()类似,只是在更高优先级执行。当软中断处理运行时, 将在其他软中断之前tasklet_hi_schedule(),只有具有低响应周期要求的驱动才应使用这个函数, 可避免其他软件中断处理引入的附加周期。
void tasklet_hi_schedule_first(struct tasklet_struct *t);
此函数的主要作用是将参数t代表的软中断添加到向量tasklet_hi_vec的头部,并触发一个软中断。而tasklet_hi_schedule()则是将参数t代表的软中断
添加到向量tasklet_hi_vec的尾部,因此tasklet_hi_schedule_first()添加的tasklet比tasklet_hi_schedule()的优先级更高。
tasklet_schedule使用TASKLET_SOFTIRQ软中断索引号,tasklet_hi_schedule和tasklet_hi_schedule_first()使用HI_SOFTIRQ软中断索引号。
在Linux支持的多种软中断中,HI_SOFTIRQ具有最高的优先级。
4) tasklet处理函数的实现
// TODO: custom a new macro to avoid warnings #define my_container_of(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member))) static struct tasklet_struct s485_tasklet; void serial8250_485_do_tasklet(unsigned long); void serial8250_485_do_tasklet(unsigned long param) { struct uart_port *port; struct uart_state *state; struct tty_struct *tty; struct ktermios termios; unsigned int baud; int bit_width; port = (struct uart_port *)param; #if 0 struct circ_buf *xmit = &port->state->xmit; unsigned long flags; unsigned int lsr; while (1) { spin_lock_irqsave(&port->lock, flags); lsr = serial_port_in(port, UART_LSR); spin_unlock_irqrestore(&port->lock, flags); if (uart_circ_empty(xmit) && ((lsr & BOTH_EMPTY) == BOTH_EMPTY)) { break; } } #else while (port->ops->tx_empty(port) != TIOCSER_TEMT) { ; } #endif state = my_container_of(port, struct uart_state, uart_port); tty = my_container_of(state, struct tty_struct, driver_data); termios = tty->termios; baud = uart_get_baud_rate(port, &termios, NULL, 1200, 115200); bit_width = (baud > 0) ? 1000000/baud : 0; bit_width = (bit_width > 50) ? (bit_width-50) : 0; // Measured delay value is 50 us udelay(bit_width); // a stop bit gpio_485_set_value(false); }注意:上述代码中udelay(bit_width)是为了延迟一个stop bit的时间
用示波器测一下,485收发方向切换非常准时,微秒级别的延迟,可以接受
tasklet
tasklet执行于软中断上下文,执行时机通常是在顶半部返回的时候。tasklet处理函数中不可睡眠。
工作队列
工作队列执行于进程上下文(内核线程)。工作队列处理函数中可以睡眠。
软中断(softirq)
tasklet是基于软中断(softirq)实现的。softirq通常在内核中使用,驱动程序不宜直接使用softirq。
总体来说,中断优先级高于软中断,软中断优先级高于各线程。
在本例中,曾尝试使用工作队列,测得延迟仍有几毫秒至十几二十毫秒(记不清楚了),无法解决问题。
而使用tasklet则能将延迟控制得非常精确。从这一点也反映了进程上下文和软中断上下文的不同之处。
tasklet处理函数中调用了自旋锁,忙等判断发送结束时刻,操作系统将串口缓冲区数据全部扔给串口芯片到串口线上一包数据传输完成,这个过程存在一个时间段,在这个时间段内,处于忙等状态,这会影响系统性能。优化方向是:研究是否能利用moderm的线控状态,在传输线上数据传输完成的时刻,触发一个中断,在此中断处理中将485切换为接收状态。
应用程序串口接收的read()函数一直处于阻塞状态,直到数据在信号线中传输完毕驱动层中有数据可读。优化方向是:由驱动层接收在接收起始时刻和结束时刻分别向应用层发一个信号,结束时刻定在串口接收超时中断时刻,这样应用程序可以获知串口线何时处于接收忙碌状态。这样会使对485的支持机制更加完善,应用层有更多的控制空间。
四、参考资料