Arm平臺 Qt檢測U盤插拔(三)hotplug-recv()阻塞

轉自:http://blog.csdn.net/yanzi1225627/article/details/7889717

#ifndef HOSTPLUG_H
#define HOSTPLUG_H

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/types.h>
#include <linux/netlink.h>
#include <errno.h>
#include <fcntl.h>

static int init_hotplug_sock(void)
{
    struct sockaddr_nl snl;
    const int buffersize = 16 * 1024 * 1024;
    int retval;
    memset(&snl, 0x00, sizeof(struct sockaddr_nl));
    snl.nl_family = AF_NETLINK;
    snl.nl_pid = getpid();
    snl.nl_groups = 1;
    int hotplug_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
    if(hotplug_sock == -1)
    {
        printf("Error getting socket;%s\n", strerror(errno));
        return -1;
    }

    /*set receive buffersize*/
    setsockopt(hotplug_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));
  int flags=fcntl(hotplug_sock, F_GETFL,0);
    fcntl(hotplug_sock, F_SETFL, flags | O_NONBLOCK);
    retval = bind(hotplug_sock, (struct sockaddr*)&snl, sizeof(struct sockaddr_nl) );
    if(retval < 0)
    {
        printf("bind failed: %s", strerror(errno));
        close(hotplug_sock);
        hotplug_sock = -1;
        return -1;
    }
    return hotplug_sock;
}

#endif // HOSTPLUG_H

然後添加一個定時器,定時器時間爲500ms,即每500ms掃描一次,如下:

void Widget::timerEvent(QTimerEvent *event)
{
    static int n = 0;
    char buf[1024] = {0};   //UEVENT_BUFFER_SIZE*2


    if(event->timerId() == timer.timerId())
    {
        recv(hotplug_sock, &buf, sizeof(buf), 0); //use a timer to query socket from core -netlink
        QString result = buf;
         rectFlag = result;
        qDebug()<<result;
        if(result.contains("add"))
        {
            n++;
            if (n>10)
                n = 10;
            ui->progressBar->setValue(n);
        }
        else if(result.contains("remove"))
        {
            n--;
            if(n<0)
                n = 0;
            ui->progressBar->setValue(n);
        }


    }
    else
        QWidget::timerEvent(event);
}

注意這裏最關鍵的就是

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是否可用,不可用的話就提示給用戶:

bool Widget::checkSaveFile()
{

    QString fileName = "/proc/scsi/usb-storage/a.txt";
    QFile file(fileName);
    if(!file.open(QFile::WriteOnly|QFile::Text))
        return false;
    else
    {   file.close();
        file.remove();
        return true;
    }

}

查詢U盤狀態的槽函數

void Widget::on_queryButton_clicked()
{
    QDir dir("/proc/scsi/usb-storage"); //在板子上,如果檢測掛載點,改爲:QDir dir("/udisk")檢測掛載點
    QMessageBox box;
      QString mess;
    box.setWindowTitle(tc->toUnicode("U盤狀態"));   
    qDebug()<<"dir.count() = "<<dir.count();

    if(rectFlag.contains("add"))
        mess = tc->toUnicode("正在識別,請稍等-----");
    else if(rectFlag.contains("remove"))
        mess = tc->toUnicode("正在刪除您的U盤-------");
    else if(dir.exists())
    {
        if(dir.count()>2 )
        {
           // if(checkSaveFile())
                mess = tc->toUnicode("U盤已連接!");
           // else
               // mess = tc->toUnicode("您的U盤已插入,但掛載點有問題,不能正常使用!建議拔掉U盤,然後重啓!");

        }
        else if((dir.count() == 2) )
        {
            QDir dirMount("/udisk");
                        if(dirMount.exists())
                        {
                          int fd =  system("umount /udisk");
                        qDebug()<<"fd = "<<fd;
                        }
                        else
                            mess = tc->toUnicode("/udisk No Exist!");

//            if(checkSaveFile())
//                mess = tc->toUnicode("您未插入U盤。但掛載點可寫,不影響使用。如果需要請插入U盤!"); //這種情況從邏輯上講不可能出現。
//            else
//                mess = tc->toUnicode("您未插入U盤。但當前掛載點有問題,建議重啓後再插入U盤!");
        }
        else
            mess = tc->toUnicode("U盤連接有故障,請重啓後再插入U盤!");
    }

    else
        mess = tc->toUnicode("U盤未連接!");
   // process->start("ls /mnt\n");

    //QString test = QString::number(a, 10);
//    QString test = process->readAllStandardOutput();
//    ui->getTextEdit->insertPlainText(test + "\n");

    ui->getTextEdit->insertPlainText(mess + "\n");
    autoScroll();

這樣就能正常檢測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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章