linux U盘插拔检测

来源:互联网 发布:变形金刚倾天柱 乐乎 编辑:程序博客网 时间:2024/06/17 03:36

因为最终要在tiny210上实现此功能,最终选择了hotplug。

http://hi.baidu.com/hdy5200075/item/7751f48647f3d12a100ef3f6这里是hotplug检测U盘的源码,我在qt里将其写到一个hostplug.h文件里。

[cpp] view plain copy
 print?
  1. #ifndef HOSTPLUG_H  
  2. #define HOSTPLUG_H  
  3.   
  4. #include <iostream>  
  5. #include <stdio.h>  
  6. #include <stdlib.h>  
  7. #include <string.h>  
  8. #include <ctype.h>  
  9. #include <sys/un.h>  
  10. #include <sys/ioctl.h>  
  11. #include <sys/socket.h>  
  12. #include <linux/types.h>  
  13. #include <linux/netlink.h>  
  14. #include <errno.h>  
  15. #include <fcntl.h>  
  16.   
  17. static int init_hotplug_sock(void)  
  18. {  
  19.     struct sockaddr_nl snl;  
  20.     const int buffersize = 16 * 1024 * 1024;  
  21.     int retval;  
  22.     memset(&snl, 0x00, sizeof(struct sockaddr_nl));  
  23.     snl.nl_family = AF_NETLINK;  
  24.     snl.nl_pid = getpid();  
  25.     snl.nl_groups = 1;  
  26.     int hotplug_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);  
  27.     if(hotplug_sock == -1)  
  28.     {  
  29.         printf("Error getting socket;%s\n", strerror(errno));  
  30.         return -1;  
  31.     }  
  32.   
  33.     /*set receive buffersize*/  
  34.     setsockopt(hotplug_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));  
  35. <strong><u><span style="color:#ff0000;">    int flags=fcntl(hotplug_sock, F_GETFL,0);  
  36.     fcntl(hotplug_sock, F_SETFL, flags | O_NONBLOCK);</span></u></strong>  
  37.   
  38.   
  39.     retval = bind(hotplug_sock, (struct sockaddr*)&snl, sizeof(struct sockaddr_nl) );  
  40.     if(retval < 0)  
  41.     {  
  42.         printf("bind failed: %s", strerror(errno));  
  43.         close(hotplug_sock);  
  44.         hotplug_sock = -1;  
  45.         return -1;  
  46.     }  
  47.     return hotplug_sock;  
  48. }  
  49.   
  50. #endif // HOSTPLUG_H  
然后添加一个定时器,定时器时间为500ms,即每500ms扫描一次,如下:

[cpp] view plain copy
 print?
  1. void Widget::timerEvent(QTimerEvent *event)  
  2. {  
  3.     static int n = 0;  
  4.     char buf[1024] = {0};   //UEVENT_BUFFER_SIZE*2  
  5.   
  6.   
  7.     if(event->timerId() == timer.timerId())  
  8.     {  
  9.         recv(hotplug_sock, &buf, sizeof(buf), 0); //use a timer to query socket from core -netlink  
  10.         QString result = buf;  
  11.          rectFlag = result;  
  12.         qDebug()<<result;  
  13.         if(result.contains("add"))  
  14.         {  
  15.             n++;  
  16.             if (n>10)  
  17.                 n = 10;  
  18.             ui->progressBar->setValue(n);  
  19.         }  
  20.         else if(result.contains("remove"))  
  21.         {  
  22.             n--;  
  23.             if(n<0)  
  24.                 n = 0;  
  25.             ui->progressBar->setValue(n);  
  26.         }  
  27.   
  28.   
  29.     }  
  30.     else  
  31.         QWidget::timerEvent(event);  
  32. }  
注意这里最关键的就是
[cpp] view plain copy
 print?
  1. recv(hotplug_sock, &buf, sizeof(buf), 0);  

这个函数,接收消息存至buf里。但默认的hotplug_sock是阻塞的,也就是当执行到recv时,程序就会停在这里,直到再次接收到内核新的消息,程序才会往下执行。为此,程序必须改动,一种思路是开一个线程,专门运行recv,停在那也无所谓;另外一种思路是将这个sock改成非阻塞的,改动部分见.h文件里画红线部分!

    int flags=fcntl(hotplug_sock, F_GETFL,0);
    fcntl(hotplug_sock, F_SETFL, flags | O_NONBLOCK);


当内核没有消息时,recv()之后的buf是空的。 交叉编译后,程序至Tiny210里运行良好!


上一张效果图:


但遗憾的是,工业是这么做是很不高明的,为了扫描一个U盘要开一个定时器在那扫描,因此最终采用判断/proc/scsi/usb-storage是否存在来判断u盘是否插入。曾考虑过U盘插入后,挂载点/udisk是否存在来判断。但当用户在/udisk目录下时,这时突然拔掉U盘。/udisk就会存在,而且ls查看的结果是报错。这时因为未推出U盘目录就拔掉,linux无法正常卸载造成的。当U盘插入良好时,usb-storage文件夹里会有三个文件,当卸载不成功时,会有两个文件。当卸载成功时,usb-storage这个文件夹会消失。采用像/udisk里写测试文件来判断/udisk是否可用,不可用的话就提示给用户:(自己是写了一个自动挂载的规则,见博客Linux上用udev自动识别并挂载U盘 ,然后判断usb-storage文件夹里文件的个数,是3的话就是有U盘插上了,否则就是U盘拔掉了。

[cpp] view plain copy
 print?
  1. bool Widget::checkSaveFile()  
  2. {  
  3.   
  4.     QString fileName = "/proc/scsi/usb-storage/a.txt";  
  5.     QFile file(fileName);  
  6.     if(!file.open(QFile::WriteOnly|QFile::Text))  
  7.         return false;  
  8.     else  
  9.     {   file.close();  
  10.         file.remove();  
  11.         return true;  
  12.     }  
  13.   
  14. }  
查询U盘状态的槽函数:

[cpp] view plain copy
 print?
  1. void Widget::on_queryButton_clicked()  
  2. {  
  3.     QDir dir("/proc/scsi/usb-storage"); //在板子上,如果检测挂载点,改为:QDir dir("/udisk")检测挂载点  
  4.     QMessageBox box;  
  5.       QString mess;  
  6.     box.setWindowTitle(tc->toUnicode("U盘状态"));      
  7.     qDebug()<<"dir.count() = "<<dir.count();  
  8.   
  9.     if(rectFlag.contains("add"))  
  10.         mess = tc->toUnicode("正在识别,请稍等-----");  
  11.     else if(rectFlag.contains("remove"))  
  12.         mess = tc->toUnicode("正在删除您的U盘-------");  
  13.     else if(dir.exists())  
  14.     {  
  15.         if(dir.count()>2 )  
  16.         {  
  17.            // if(checkSaveFile())  
  18.                 mess = tc->toUnicode("U盘已连接!");  
  19.            // else  
  20.                // mess = tc->toUnicode("您的U盘已插入,但挂载点有问题,不能正常使用!建议拔掉U盘,然后重启!");  
  21.   
  22.         }  
  23.         else if((dir.count() == 2) )  
  24.         {  
  25.             QDir dirMount("/udisk");  
  26.                         if(dirMount.exists())  
  27.                         {  
  28.                           int fd =  system("umount /udisk");  
  29.                         qDebug()<<"fd = "<<fd;  
  30.                         }  
  31.                         else  
  32.                             mess = tc->toUnicode("/udisk No Exist!");  
  33.   
  34. //            if(checkSaveFile())  
  35. //                mess = tc->toUnicode("您未插入U盘。但挂载点可写,不影响使用。如果需要请插入U盘!"); //这种情况从逻辑上讲不可能出现。  
  36. //            else  
  37. //                mess = tc->toUnicode("您未插入U盘。但当前挂载点有问题,建议重启后再插入U盘!");  
  38.         }  
  39.         else  
  40.             mess = tc->toUnicode("U盘连接有故障,请重启后再插入U盘!");  
  41.     }  
  42.   
  43.     else  
  44.         mess = tc->toUnicode("U盘未连接!");  
  45.    // process->start("ls /mnt\n");  
  46.   
  47.     //QString test = QString::number(a, 10);  
  48. //    QString test = process->readAllStandardOutput();  
  49. //    ui->getTextEdit->insertPlainText(test + "\n");  
  50.   
  51.     ui->getTextEdit->insertPlainText(mess + "\n");  
  52.     autoScroll();  
[cpp] view plain copy
 print?
  1. }  
这样就能正常检测U盘了,如果想加进度条就加上。不加,也能正常检测。

      问题又来了,上面采用向/udisk里写测试文件来检测/udisk是否可用,但有时用户会将U盘进行写保护。我试遍所有的方法,用open、opendir、access、stat等来检查异常情况下/udisk的属性与正常状态有何不同,最终也没查出来。也用了ls /udisk > /a.txt,截取ls的内容。但当/udisk出现异常时,报错的内容是板子上报的,并不是ls显示的内容。ls此时显示结果为空。

      其实,与其判断这种误拔U盘的行为,倒不如防止。经过我研究发现,当ls出现/udisk fatal error,只要执行/umount /udisk,手动将这个文件夹卸载,再次插上U盘就可以了。为此,大家看到我上面程序里,当检测dir.count == 2时,检查/udisk是否存在,如果存在则将/udisk卸载。

  这样做基本算完美解决问题了。美中不足的是,当异常出现时,如果板子程序一直在运行,则拔掉U盘再插上无事。如果此时板子重启,在板子重启前就将U盘再次插入到板子,这时候因为咱们的应用程序还未运行,还没有执行 umount /udisk,这个时候程序就检测不出来了。

       要避免这个问题,就采用往U盘里写数据的方法判断,或者如果允许扫描用hostplug查询出来的信息可以得知usb的注册情况,这种思路应该也可以。一个小小的U盘检测,终于告一段落了,实现了x86下、arm平台均可用的qt检测U盘!----------------yanzi1225627

          这里给一个源码资源,是老外写的,用qtcpsocket来监听netlink的消息,老外写的代码就是不一样啊,大家参考吧:

http://download.csdn.net/detail/yanzi1225627/4514740

0 0
原创粉丝点击