歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> linux U盤插拔檢測

linux U盤插拔檢測

日期:2017/3/3 12:24:33   编辑:Linux技術
因為最終要在tiny210上實現此功能,最終選擇了hotplug。
http://hi.baidu.com/hdy5200075/item/7751f48647f3d12a100ef3f6這裡是hotplug檢測U盤的源碼,我在qt裡將其寫到一個hostplug.h文件裡。
[cpp] view
plain copy
print?
#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));
<strong><u><span > int flags=fcntl(hotplug_sock, F_GETFL,0);
fcntl(hotplug_sock, F_SETFL, flags | O_NONBLOCK);</span></u></strong>
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掃描一次,如下:
[cpp] view
plain copy
print?
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);
}
注意這裡最關鍵的就是
[cpp] view
plain copy
print?
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?
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盤狀態的槽函數:
[cpp] view
plain copy
print?
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();
[cpp] view
plain copy
print?
}
這樣就能正常檢測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
Copyright © Linux教程網 All Rights Reserved