浅析ttyUSB驱动usb_serial_driver-ch341
sudo insmod /lib/modules/2.6.22-14-generic/kernel/drivers/usb/serial/usbserial.ko vendor=0x8086 product=0xd001
安装之后可以通过ttyUSBx与任何usb设备进行数据传输,但是如果usb设备端接收pc下发的数据速度跟不上,比如设备端可能需要处理pc下发的数据用掉一段时间,这时tty->driver->write(tty, b, nr);可能返回0,也就是在执行usb_serial_generic_write()函数时,因为port->write_urb_busy = 1;发送正繁忙,进而会执行schedule让出cpu,虽然使用usbserial.ko模块可以与任何usb设备进行通信,但是当进行大量数据传输时,速度并不乐观,下面会做一个粗略的分析,找到了速度慢的原因,和一个保守的解决方案[gliethttp_20080526]。
usb_register(&ch341_driver)->含有如下两行语句,
drvwrap.driver.bus = &usb_bus_type;
drvwrap.driver.probe = usb_probe_interface;
usb总线发现硬件之后,会在恰当的时候调用__driver_attach()函数,__driver_attach()会调用usb_bus_type.match(),对于usb设备一定返回成功,
然后执行really_probe(dev, drv);暂时将dev->driver = drv;之后执行
device_driver->probe函数,对于usb总线上的设备就是调用usb_probe_interface,
usb_probe_interface函数会搜索所有usb总线上挂接的usb_driver驱动,检查是否和该dev设备的id匹配,如果匹配,那么调用usb_driver->probe()函数,对于usbserial驱动来说,就是调用usb_serial_probe,在该函数中如果
usbserial所属的usb_serial_driver驱动包含probe函数,那么调用之,对于ch341没有,在usb_serial_probe函数中分配完port对应的管道缓冲区之后,执行usb_serial_driver->attach()函数,对于ch341来说就是ch341_attach,使用control控制管道,发送波特率设置参数给ch341串口设备,紧接着调用get_free_serial (serial, num_ports, &minor);
该函数会查找serial_table[i]的空缺位置,然后serial->minor = minor;于是serial有了次设备号minor,
应用程序会调用serial_open()->serial->type->open(port, filp);也就是ch341_open详细的打开serial串口,
对于ch341,就是将波特率配置值发送给串口ch341设备而已,
用户调用serial_write()来向ch341发送串口数据,serial_write()会调用port->serial->type->write(port, buf, count);因为ch341没有定义自己的write写函数,所以会调用usb_serial_generic_write函数实现具体的发送操作,但每次最多只能发送一个输出端点大小的数据,具体发送流程是这样的:
sys_write()->vfs_write()->
drivers/char/tty_io.c->tty_fops.tty_write()->do_tty_write()->
tty_ldisc_N_TTY.write_chan()->调用
tty->driver->write(tty, b, nr)->
serial_write(struct tty_struct * tty, const unsigned char *buf, int count)->
port->serial->type->write(port, buf, count)->
usb_serial_generic_write()
对于open的安装过程简单来说是这样的
sys_open()->tty_open()->
然后设置操作函数集filp->f_op = &tty_fops;
这样操作函数集tty_fops会调用ch341的驱动,因为ch341没有设置read和write函数,所以对于ch341的ttyUSBx写操作,最终调用usb_serial_generic_write,同理读调用usb_serial_generic_read,对于generic类型串口读写函数,每次最大收发字节数为相应端点的端点大小,
tty_write会检测所有的counts待收发数据是否全部完成,如果循环完成了,tty_write返回,进而vfs_write返回,进而sys_write返回,于是read()和write()
对于usb_serial串口发送数据速度超级慢,不是因为usb_serial_generic_write函数速度慢,它的速度已经是全速了,问题主要出在调用usb_serial_generic_write函数的上级函数中,主要原因在这里:
do_tty_write()
...
chunk = 2048;
...
/* Do the write .. */
for (;;) {
size_t size = count;
if (size > chunk)
size = chunk;
ret = -EFAULT;
if (copy_from_user(tty->write_buf, buf, size))
break;
lock_kernel();
ret = write(tty, file, tty->write_buf, size);
//gliethttp_20080526
//write就是
//write_chan(),在write_chan函数中
//while (1) {
// while (nr > 0) {
// c = tty->driver->write(tty, b, nr);
//因为pc主机速度很快,所以c经常会等于0,说明上一次提交的发送数据还没有发送完毕,busy中,所以返回0
// if (c < 0) {
// retval = c;
// goto break_out;
// }
// if (!c)
// break;
// b += c;
// nr -= c;
// }
// schedule();//当c==0时,也就是usb通信管道繁忙时,将会执行schedule
//}
//对于ch341或者其他的usbserial硬件,c等于out端点大小,一般为16字节或者64字节,大多数等于0,
//所以上面copy_from_user(tty->write_buf, buf, size)拷贝了2k的数据,
//将在write(tty, file, tty->write_buf, size);中以端点大小为单位,通过tty->driver->write一组组的循环发送出去
//当c==0时,也就是usb通信管道繁忙时,将会执行schedule,内核调度出本task之后,什么时候返回是未知的,因为当前
//的task没有等待任何事件,所以属于变相告诉kernel,“我现在闲的很,什么事情都没有了,kernel你让其他task执行吧,
//如果没有任何人需要cpu的时候,你就回过头来看看我,不用把我放在心上了”,而事实上本task还有很多数据在堆积,等待发送处理,
//但就是因为没有使用wakeup之类的咚咚机制,来等待事件完成后的即时唤醒,傻傻的让出cpu,
//所以当连续发送大量数据的时候,这种切换出去、切换回来时间不确定性会导致速度指数级下降!
//所以我觉得,在tty->driver->write(tty, b, nr)中使用信号等待机制,等待数据发送完毕,这将使得数据发送速度指数级提升!
//或者直接使用usb_bulk_msg完成1个端点大小数据发送,这样可以一直等到数据发送完毕!
unlock_kernel();
if (ret <= 0)
break;
written += ret;
buf += ret;
count -= ret;
if (!count)
break;
ret = -ERESTARTSYS;
if (signal_pending(current))
break;
cond_resched();
//为了均衡系统,尝试调度出自己,这是在为系统的其他线程做好事,这样的不霸道行为也是降低速度的原因之一!
}
因为是内核研读笔记,比较凌乱,凑活了[gliethttp_20080526]
|