浅析ttyUSB驱动usb_serial_driver-ch341sudo 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// 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]
10-08 16:16