Linux下在用户空间实现设备热插拔检测

一,需求背景

      相信大家都知道,嵌入式设备通常会具备一个基本的功能,那就是借助可移动块设备(U盘等)实现升级、日志等数据导出的功能,原因大概有这么几点:

      1,嵌入式设备通常是没有界面操作的,无法在设备本体上人工触发升级或者将日志转移到其他地方

      2,可以通过服务器远程升级或者将日志上传到服务器,但这终归是没有块设备升级或者导出来得方便,考虑网络不理想、升级程序包或者日志文件通常比较大,上传和下载都比较耗时。

     基于以上两点,相信可以理解为什么嵌入式设备需要支持“块设备”升级和导出数据的功能了,那么要实现此功能,关键点有两个:

     a, 当块设备插入时,我们得知道块设备接入了,并且将其挂载到一个可访问的目录下,

     b, 当块设备拔出时,我们得知道块设备移除了,并且卸载之前所挂载的目录

    换句话说,我们得检测块设备的热插拔事件,并进行相应的挂载和卸载操作,当然实现方式比较多,这里介绍一种比较优雅的方式,不仅仅是针对块设备,只要是外部硬件设备的插拔,比如USB、SCSI、串口等接口方式接入的设备,都可以检测其热插拔。

二、方案设计

     linux下有一句话叫“一切皆文件”,对于硬件设备也不例外,外部硬件设备接入后,会在系统/dev目录下产生相应类型的设备文件,当外部设备移除后,该设备文件会被删除。比如块设备在接入后,在系统/dev目录下产生的设备文件是虚拟的“块设备文件”,不能直接进行读写访问,需要挂载到嵌入式设备本身的文件系统才能进行读写访问,如果是手柄、触摸屏幕、键盘、鼠标等输入设备接入,在系统中产生的是字符设备文件,那么是直接可以进行读写的,比如Android手机触摸屏在系统中就是一个“字符设备文件”,当手指触摸屏幕,产生硬件中断事件,内核接收并打包该事件,将其写入对应的字符设备文件,应用层通过读取该字符设备文件来获取各种触摸输入事件。

     前面我们说了,硬件设备接入后,会在系统/dev目录下产生对应的设备文件,移除时,设备文件会被删除,那么这个文件是由谁创建和删除的呢?其实这个就是实现热插拔检测的关键,实际上硬件热插拔事件在linux系统中有一套上报机制,称为uevent机制,其大致流程如下:

                                                          Linux下在用户空间实现设备热插拔检测_第1张图片

     现在我们来回答外部硬件设备在/dev目录下对应的设备文件是由谁创建的,实际上在linux系统启动时,会创建一个守护进程udevd,该进程运行在用户空间,通过监听内核发送的uevent来执行相应的热插拔动作响应,这个动作响应就是在/dev目录下创建或删除响应的设备文件,udevd守护进程还有一个功能,就是根据其在/etc目录下的uevent规则文件(比如/etc/udev/rules.d/50-udev.rules)进行匹配,比如规则文件内容有这么一行

ACTION=="add", SUBSYSTEM=="?*", ENV{MODALIAS}=="?*", RUN+="/sbin/modprobe $env{MODALIAS}"
那么当收到uevent的add(即硬件接入)事件后,shell就会自动加载运行在MODALIAS中定义的模块,那么我们可以在模块中实现对硬件设备的访问,但这种方式只适用于系统启动前,硬件设备已经接入了,不适合热插拔处理,而且不太优雅,需要更改配置文件,在Android系统下,是ueventd这个守护进程在接收uevent事件,源码位于system/core/init/ueventd.c,他读取的配置文件是ueventd.rc。

    可能有人会问,内核如何将uevent发送到用户空间,其实这仅仅是个linux操作系统内部的数据传输的问题,这里采用了一种特殊的socket,linux下专有名词叫Netlink,Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 netlink。前面说的用户空间的守护进程udev内部实际上就是利用Netlink来实现uevent事件的接收的。

   有了Netlink以及udev创建或者删除设备文件节点的支持,那么相信实现设备热插拔检测以及块设备的挂载和卸载也不是什么难事,基本算法分两步:

   1,利用Netlink接收uevent事件

   2,当接收到硬件插入事件时,将udev在/dev目录下创建的块设备文件挂载到嵌入式设备的某个目录下

        当接收到硬件移除事件时,将之前设备挂载的目录卸载掉

三、编码设计

  借鉴了linux内核以及Andorid部分源码,linux内核对uevent事件类型的定义在include/linux/kobject.h中,定义如下:

enum kobject_action {
    KOBJ_ADD,
    KOBJ_REMOVE,
    KOBJ_CHANGE,
    KOBJ_MOVE,
    KOBJ_ONLINE,
    KOBJ_OFFLINE,
    KOBJ_MAX
};

对应的事件定义如下:

static const char *kobject_actions[] = {
    [KOBJ_ADD] =        "add",
    [KOBJ_REMOVE] =     "remove",
    [KOBJ_CHANGE] =     "change",
    [KOBJ_MOVE] =       "move",
    [KOBJ_ONLINE] =     "online",
    [KOBJ_OFFLINE] =    "offline",
};

Android系统ueventd守护进程利用Netlink接收uevent事件的源码在system/core/init/devices.c中,如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define UEVENT_MSG_LEN 4096
struct luther_gliethttp {
    const char *action;
    const char *path;
    const char *subsystem;
    const char *firmware;
    int major;
    int minor;
};
static int open_luther_gliethttp_socket(void);
static void parse_event(const char *msg, struct luther_gliethttp *luther_gliethttp);

int main(int argc, char* argv[])
{
    int device_fd = -1;
    char msg[UEVENT_MSG_LEN+2];
    int n;

    device_fd = open_luther_gliethttp_socket();
    printf("device_fd = %d\n", device_fd);

    do {
        while((n = recv(device_fd, msg, UEVENT_MSG_LEN, 0)) > 0) {
            struct luther_gliethttp luther_gliethttp;

            if(n == UEVENT_MSG_LEN)
                continue;

            msg[n] = '\0';
            msg[n+1] = '\0';

            parse_event(msg, &luther_gliethttp);
        }
    } while(1);
}

static int open_luther_gliethttp_socket(void)
{
    struct sockaddr_nl addr;
    int sz = 64*1024;
    int s;

    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_pid = getpid();
    addr.nl_groups = 0xffffffff;

    s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
    if (s < 0)
        return -1;

    setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz));

    if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
        close(s);
        return -1;
    }

    return s;
}

static void parse_event(const char *msg, struct luther_gliethttp *luther_gliethttp)
{
    luther_gliethttp->action = "";
    luther_gliethttp->path = "";
    luther_gliethttp->subsystem = "";
    luther_gliethttp->firmware = "";
    luther_gliethttp->major = -1;
    luther_gliethttp->minor = -1;


    printf("========================================================\n");
    while (*msg) {

        printf("%s\n", msg);

        if (!strncmp(msg, "ACTION=", 7)) {
            msg += 7;
            luther_gliethttp->action = msg;
        } else if (!strncmp(msg, "DEVPATH=", 8)) {
            msg += 8;
            luther_gliethttp->path = msg;
        } else if (!strncmp(msg, "SUBSYSTEM=", 10)) {
            msg += 10;
            luther_gliethttp->subsystem = msg;
        } else if (!strncmp(msg, "FIRMWARE=", 9)) {
            msg += 9;
            luther_gliethttp->firmware = msg;
        } else if (!strncmp(msg, "MAJOR=", 6)) {
            msg += 6;
            luther_gliethttp->major = atoi(msg);
        } else if (!strncmp(msg, "MINOR=", 6)) {
            msg += 6;
            luther_gliethttp->minor = atoi(msg);
        }


        while(*msg++)
            ;
    }

    printf("event { '%s', '%s', '%s', '%s', %d, %d }\n",
                    luther_gliethttp->action, luther_gliethttp->path, luther_gliethttp->subsystem,
                    luther_gliethttp->firmware, luther_gliethttp->major, luther_gliethttp->minor);
}

       下面来实现我们自己的编码,首先要明确的是,我们只需要关心add和remove两种事件即可,对应硬件的插和拔,而且只需要检测块设备即可,这里由于项目需要,增加了对tty串口设备的检测,不需要可以去掉,总之,你想检测什么类型的设备,只需要增加响应的设备协议解析逻辑即可。

       HotPlugObserver.h中定义对外使用的接口和数据接口,如下

/*************************************************

Author:lijuncheng

Emial:[email protected]

Date:2020-01-11

Description: this file mainly defines some interfaces about observing hardware hot plug and relative data structures 

**************************************************/

#ifndef HOT_PLUG_OBSERVER_H
#define HOT_PLUG_OBSERVER_H

enum ObserveDeviceType
{
	ObserveDeviceType_Block,    ///< observe block devices , such as U disk , mobile disk etc 
	ObserveDeviceType_Tty,      ///< observe tty devices , such as serial or fake terminal device etc
	ObserveDeviceType_All       ///< observe all devices above
};

enum DevType
{
    DevType_Block,    ///< block devices , such as U disk , mobile disk etc
    DevType_Tty       ///< tty devices , such as serial or fake terminal device etc
};

enum DevAction
{
	DevAction_Add,             ///< add device action , such as plugin a U disk
	DevAction_Remove           ///< remove device action, such as remove a U disk
};

/*****************************************************************************
  function name    : ObserveCallback
  used for         :callback the observed device information
  input param      :devType see enum DevType, devAction see enum DevAction ,devPath is device filse system access path , if device is block device , then devPath is the mounted path
  ouput param      :no
  return value     :no
******************************************************************************/
typedef void (*ObserveCallback)(const DevType devType,const DevAction devAction,const char * devPath);

#ifdef __cplusplus
extern "C" {
#endif

/*****************************************************************************
  function name    : initHotPlugObserver
  used for         :initialize hot plug observer to lisen devices' change
  input param      :no
  ouput param      :no
  return value     :no
******************************************************************************/
void initHotPlugObserver();

/*****************************************************************************
  function name    : unInitHotPlugObserver
  used for         :uninitialize hot plug observer to release some resources
  input param      :no
  ouput param      :no
  return value     :no
******************************************************************************/
void unInitHotPlugObserver();

/*****************************************************************************
  function name    : registerObserveCallback
  used for         :resister a callback function to observe device's change
  input param      :observeDeviceType see enum ObserveDeviceType, observeCallback is the callback function
  ouput param      :no
  return value     :no
******************************************************************************/
void registerObserveCallback(const ObserveDeviceType observeDeviceType,const ObserveCallback observeCallback);

/*****************************************************************************
  function name    : unregisterObserveCallback
  used for         :unresister a callback function ,then you will not receive device's change
  input param      :observeDeviceType see enum ObserveDeviceType, observeCallback is the callback function
  ouput param      :no
  return value     :no
******************************************************************************/
void unregisterObserveCallback(const ObserveDeviceType observeDeviceType, const ObserveCallback observeCallback);

#ifdef __cplusplus
}
#endif

#endif //HOT_PLUG_OBSERVER_H

HotPlugObserver.cpp 定义了相关实现,如下所示:

#include "HotPlugObserver.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define UEVENT_MSG_LEN 4096

#define DEV_BASE_PATH   "/dev/"
#define MOUNT_BASE_PATH "/mnt/"
#define DEV_PATH_LENGTH 256

#define SUCCESS 0
#define FAIL -1

struct HotPlugEvent {
	const char* action;
	const char* subsystem;
	const char* devname;
	const char* devtype;
};

static std::mutex g_observeTypeToCallbackMapMutex;
static std::multimap g_observeTypeToCallbackMap;

static std::mutex g_HotPlugInitMutex;
static bool g_isHotPlugObserved = false;

static void observeHotPlug();

void initHotPlugObserver()
{
	printf(" initHotPlugObserver \n");

	g_HotPlugInitMutex.lock();

	if (!g_isHotPlugObserved)
	{
		g_isHotPlugObserved = true;
		std::thread t(observeHotPlug);
                t.detach();
	}

	g_HotPlugInitMutex.unlock();
}

void unInitHotPlugObserver()
{
	printf(" unInitHotPlugObserver \n");

	g_HotPlugInitMutex.lock();

	if (g_isHotPlugObserved)
	{
		g_isHotPlugObserved = false;
	}

	g_HotPlugInitMutex.unlock();

	g_observeTypeToCallbackMapMutex.lock();
	g_observeTypeToCallbackMap.clear();
	g_observeTypeToCallbackMapMutex.unlock();
}

void registerObserveCallback(const ObserveDeviceType observeDeviceType, const ObserveCallback observeCallback)
{
	if (nullptr == observeCallback)
	{
		return;
	}

	g_observeTypeToCallbackMapMutex.lock();

	bool observeCallbackExist = false;

	auto observeCallbackPairs = g_observeTypeToCallbackMap.equal_range(observeDeviceType);

	for (auto p = observeCallbackPairs.first; p != observeCallbackPairs.second; ++p)
	{
		if (p->second == observeCallback)
		{
			observeCallbackExist = true;
			break;
		}
	}

	printf("registerObserveCallback observeCallback=%p exist=%d \n", observeCallback, observeCallbackExist);

	if (!observeCallbackExist)
	{
		g_observeTypeToCallbackMap.insert(std::pair(observeDeviceType, observeCallback));
	}

	g_observeTypeToCallbackMapMutex.unlock();
}

void unregisterObserveCallback(const ObserveDeviceType observeDeviceType, const ObserveCallback observeCallback)
{
	if (nullptr == observeCallback)
	{
		return;
	}

	g_observeTypeToCallbackMapMutex.lock();

	auto observeCallbackPairs = g_observeTypeToCallbackMap.equal_range(observeDeviceType);

	for (auto p = observeCallbackPairs.first; p != observeCallbackPairs.second; ++p)
	{
		if (p->second == observeCallback)
		{
			printf("unregisterObserveCallback find observeCallback=%p then erase \n", observeCallback);
			g_observeTypeToCallbackMap.erase(p);
			break;
		}
	}

	g_observeTypeToCallbackMapMutex.unlock();
}

static int openHotPlugEventSocket();

static void parseHotPlugEvent(const char* msg, struct HotPlugEvent* hotPlugEvent);

static int parseDevAction(const char* eventAction, DevAction* devAction);

static int parseDevType(const char* devSubsystem, DevType* devType);

static int parseDevNode(const char* devname, char* devNode);

static int mountDevNodeToDevPath(const char* devNode, const char* devPath);

static int umountDevPath(const char* devPath);

static void callbackDevEvent(DevType devType, DevAction devAction, const char* devPath);

static void observeHotPlug()
{
	printf(" observeHotPlug \n");

	///< 1, open HotPlugEvent Socket
	int hotPlugEventSocketFd = openHotPlugEventSocket();

	if (FAIL == hotPlugEventSocketFd)
	{
		g_HotPlugInitMutex.lock();

		if (g_isHotPlugObserved)
		{
			g_isHotPlugObserved = false;
		}
		g_HotPlugInitMutex.unlock();

		return;
	}

	///< 2 , receive hot plug socket data and parse it to be HotPlugEvent
	char msg[UEVENT_MSG_LEN + 2];
	int n = 0;
	struct HotPlugEvent hotPlugEvent = {0};

	DevAction devAction = DevAction_Add;
	DevType devType = DevType_Block;
	char devNode[DEV_PATH_LENGTH] = { 0 };
	char devPath[DEV_PATH_LENGTH+1] = { 0 };

	fd_set rfds;
	struct timeval tv = { 0 };

	int ret = SUCCESS;

	while(g_isHotPlugObserved)
	{

		FD_ZERO(&rfds);
		FD_SET(hotPlugEventSocketFd, &rfds);

		tv.tv_sec = 3;   ///< timeout for 3 seconds
		tv.tv_usec = 0;

		int selectRet = select(hotPlugEventSocketFd + 1, &rfds, NULL, NULL, &tv);

		if (selectRet == -1)
		{
			printf(" observeHotPlug select error \n");
			break;
		}
		
		if (selectRet == 0)
		{
			printf(" observeHotPlug select timeout then try again \n");
			continue;
		}

		///< there are some hot plug events to receive
		if (FD_ISSET(hotPlugEventSocketFd, &rfds))
		{
			n = recv(hotPlugEventSocketFd, msg, UEVENT_MSG_LEN, 0);

			printf(" observeHotPlug recv n=%d bytes data \n", n);

			if (n <= 0 || n >= UEVENT_MSG_LEN)
			{
				printf(" observeHotPlug recv n=%d bytes data , we cannot parse it \n", n);
				continue;
			}

			memset(&hotPlugEvent, 0, sizeof(hotPlugEvent));

			msg[n] = '\0';
			msg[n + 1] = '\0';

			parseHotPlugEvent(msg, &hotPlugEvent);

			printf("observeHotPlug parsed an hot plug event action=%s subsystem=%s devname=%s devtype=%s \n", hotPlugEvent.action, hotPlugEvent.subsystem, hotPlugEvent.devname, hotPlugEvent.devtype);

			///< 1, parse device action
			ret = parseDevAction(hotPlugEvent.action, &devAction);

			if (FAIL == ret)
			{
				continue;
			}

			///< 2, parse device type
			ret = parseDevType(hotPlugEvent.subsystem,&devType);

			if (FAIL == ret)
			{
				continue;
			}

			///< 3,parse device node
			memset(devNode, 0, sizeof(devNode));
			ret = parseDevNode(hotPlugEvent.devname, devNode);

			if (FAIL == ret)
			{
				continue;
			}

			///< 4, mount device node to an accessed directory or umount the directory mounted before for block device of which devType is partition
			if (devType == DevType_Block)
			{
				if (NULL == hotPlugEvent.devtype || strncmp(hotPlugEvent.devtype, "partition", 9) != 0)
				{
					printf("observeHotPlug unhandled block device with devtype=%s \n", hotPlugEvent.devtype);
					continue;
				}

				///< get device path to be mounted or umounted , format /mnt/devname , such as /mnt/sdb1
				memset(devPath, 0, sizeof(devPath));
				sprintf(devPath, "%s%s/", MOUNT_BASE_PATH, hotPlugEvent.devname);

				if (devAction == DevAction_Add)
				{
					///< mount devNode to devPath
					ret = mountDevNodeToDevPath(devNode,devPath);
				}
				else
				{
					///< umount devPath
					ret = umountDevPath(devPath);
				}

				if (FAIL == ret)
				{
					continue;
				}
			}
			else
			{
				///< devPath is also devNode for non_block device
				memcpy(devPath,devNode,sizeof(devNode));
			}

			///< 5, callback device type,action and path
			callbackDevEvent(devType,devAction,devPath);
		}
	}

       printf(" observe hot plug thread exit \n");

	close(hotPlugEventSocketFd);

	g_HotPlugInitMutex.lock();

	if (g_isHotPlugObserved)
	{
		g_isHotPlugObserved = false;
	}

	g_HotPlugInitMutex.unlock();
}

static int openHotPlugEventSocket()
{
	struct sockaddr_nl addr;

	memset(&addr, 0, sizeof(addr));

	addr.nl_family = AF_NETLINK;
	addr.nl_pid = getpid();
	addr.nl_groups = 0xffffffff;

	int ret = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);

	if (ret < 0)
	{
		return FAIL;
	}

	int sz = 64 * 1024;
	setsockopt(ret, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz));

	if (bind(ret, (struct sockaddr*) & addr, sizeof(addr)) < 0) 
	{
		close(ret);
		return FAIL;
	}

	return ret;
}

static void parseHotPlugEvent(const char* msg, struct HotPlugEvent* hotPlugEvent)
{
	while (*msg) {

		if (!strncmp(msg, "ACTION=", 7)) 
                {
			msg += 7;
			hotPlugEvent->action = msg;
		}
		else if (!strncmp(msg, "SUBSYSTEM=", 10)) 
                {
			msg += 10;
			hotPlugEvent->subsystem = msg;
		}
		else if (!strncmp(msg, "DEVNAME=", 8)) 
                {
			msg += 8;
			hotPlugEvent->devname = msg;
		}
		else if (!strncmp(msg, "DEVTYPE=", 8)) 
                {
			msg += 8;
			hotPlugEvent->devtype = msg;
		}

		while (*msg++);
	}

	printf("event { \n\t action=%s,\n\t subsystem=%s,\n\t devname=%s,\n\t devtype=%s\n}\n", hotPlugEvent->action, hotPlugEvent->subsystem, hotPlugEvent->devname, hotPlugEvent->devtype);
}



static int parseDevAction(const char * eventAction,DevAction * devAction)
{
	if (eventAction == NULL)
	{
		return FAIL;
	}

	if (strncmp(eventAction, "add", 3) == 0)         ///< found a device plug in
	{
		*devAction = DevAction_Add;
	}
	else if (strncmp(eventAction, "remove", 6) == 0)  ///< found a device remove
	{
		*devAction = DevAction_Remove;
	}
	else
	{
		printf(" parseDevAction unhandled devAction=%s \n", eventAction);
		return FAIL;
	}

	printf(" parseDevAction devAction=%s success \n", eventAction);

	return SUCCESS;
}

static int parseDevType(const char * devSubsystem, DevType * devType)
{
	if (devSubsystem == NULL)
	{
		return FAIL;
	}
	
	if (strncmp(devSubsystem, "block", 5) == 0)             ///< found a block device 
	{
		*devType = DevType_Block;
        }
	else if (strncmp(devSubsystem, "tty", 3) == 0)  	///< found a tty device
	{
		*devType = DevType_Tty;
	}
	else
	{
		printf(" observeHotPlug unhandled devType=%s \n", devSubsystem);
		return FAIL;
	}

	printf(" parseDevType devType=%s success \n", devSubsystem);

	return SUCCESS;
}

static int parseDevNode(const char * devname, char *devNode)
{
	if (devname == NULL)
	{
		return FAIL;
	}

	if (strlen(DEV_BASE_PATH) + strlen(devname) + 1 > DEV_PATH_LENGTH)
	{
		printf(" parseDevNode too long devNode=%s%s \n", DEV_BASE_PATH,devname);
		return FAIL;
	}

	///< get device node , format /dev/devname , such as /dev/sdb1
	sprintf(devNode, "%s%s", DEV_BASE_PATH, devname);

	printf(" parseDevNode devNode=%s success \n", devNode);

	return SUCCESS;
}

static int createDir(const char* sPathName);

static int mountDevNodeToDevPath(const char * devNode, const char * devPath)
{
	///< check devPath to be mounted exists or not , if not exist then we create it

	if (access(devPath, F_OK) != 0)
	{
		int createDevMountPathRet = createDir(devPath);

		if (createDevMountPathRet == FAIL)
		{
			printf(" mountDevNodeToDevPath create devPath %s fail , please check whether current process's owner's power to create dir under %s \n", devPath, MOUNT_BASE_PATH);
			return FAIL;
		}

		printf(" mountDevNodeToDevPath devPath %s create success \n", devPath);
	}
	else
	{
		printf(" mountDevNodeToDevPath devPath %s has exist \n", devPath);
	}

	///< mount devNode to devPath
	int mountDevPathRet = mount(devNode, devPath, "vfat", MS_SYNCHRONOUS, "iocharset=utf8");

	if (mountDevPathRet != 0)
	{
		printf("mountDevNodeToDevPath mount %s to %s fail mountDevPathRet=%d !\n", devNode, devPath, mountDevPathRet);
		return FAIL;
	}

	printf("mountDevNodeToDevPath mount %s to %s success !\n", devNode, devPath);

	return SUCCESS;
}

static int createDir(const char* sPathName)
{
	char   DirName[256] = {0};
	strcpy(DirName, sPathName);

	int i, len = strlen(DirName);

	if (DirName[len - 1] != '/')
	{
		strcat(DirName, "/");
	}

	len = strlen(DirName);

	for (i = 1; i < len; i++)
	{
		if (DirName[i] == '/')
		{
			DirName[i] = 0;

			if (access(DirName, F_OK) != 0)
			{
				if (mkdir(DirName, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1)
				{
					return   FAIL;
				}
			}

			DirName[i] = '/';
		}
	}

	return SUCCESS;
}

static int umountDevPath(const char * devPath)
{
	///< umount devMountPath
	int mountDevPathRet = umount(devPath);

	if (mountDevPathRet != 0)
	{
		printf("umountDevPath umount %s failed mountDevPathRet=%d !\n", devPath, mountDevPathRet);
		return FAIL;
	}

	printf("umountDevPath umount %s success !\n", devPath);

	return SUCCESS;
}

static void callbackDevEvent(DevType devType,DevAction devAction,const char * devPath)
{
	ObserveDeviceType observeDeviceType = ObserveDeviceType_Block;

	switch (devType)
	{
	case DevType_Block:
		observeDeviceType = ObserveDeviceType_Block;
		break;
	case DevType_Tty:
		observeDeviceType = ObserveDeviceType_Tty;
		break;
	default:
		printf("callbackDevEvent unhandled devType=%d \n", devType);
		return;
	}

	g_observeTypeToCallbackMapMutex.lock();

        ///< callback device event to assigned type which is observed
	auto observeCallbackPairs = g_observeTypeToCallbackMap.equal_range(observeDeviceType);

	for (auto p = observeCallbackPairs.first; p != observeCallbackPairs.second; ++p)
	{
		p->second(devType, devAction, devPath);
	}

	///< callback device event to all type which is observed
	observeCallbackPairs = g_observeTypeToCallbackMap.equal_range(ObserveDeviceType_All);

	for (auto p = observeCallbackPairs.first; p != observeCallbackPairs.second; ++p)
	{
		p->second(devType, devAction, devPath);
	}

	g_observeTypeToCallbackMapMutex.unlock();
}



测试demo.cpp如下:

#include "HotPlugObserver.h"

#include 
#include 

void observeBlockDeviceHotPlugEventCallback(const DevType devType,const DevAction devAction,const char * devPath);

int main(int argc , char * argv[])
{

    ///< init hot plug observer
    initHotPlugObserver();

    ///< register hot plug evet callback
    registerObserveCallback(ObserveDeviceType_Block,observeBlockDeviceHotPlugEventCallback);

    #if 0

    ///< unInit hot plug observer
    unInitHotPlugObserver();

    ///< unregister hot plug evet callback
    unregisterObserveCallback(ObserveDeviceType_Block,observeBlockDeviceHotPlugEventCallback);    

    #endif

    ///< suspend here to avoid main exit
    pause();

    return 0;
}

void observeBlockDeviceHotPlugEventCallback(const DevType devType,const DevAction devAction,const char * devPath)
{
    printf(" observeBlockDeviceHotPlugEventCallback devType=%d devAction=%d devPath=%s ",devType,devAction,devPath);

    if(devType == DevType_Block)
    {
        if(devAction == DevAction_Add)
        {
            ///< here we can use devPath to do our business , such as export some data from device , upgrade our device etc;
        }
    }
}

四,调试运行

编译过程如下:

HotPlugObserverComile.png

运行如下,为了确保挂载和目录创建成功,请使用有权限的用户来执行demo:

Linux下在用户空间实现设备热插拔检测_第2张图片

当我们插入U盘设备时,会打印很多信息,U盘检测和挂载过程如下

UDiskPlugin

此时我们便可以通过访问/mnt/sdb1/来访问U盘了,比如读取U盘中的升级程序包进行升级,导出日志等文件,甚至执行U盘中携带的程序来处理我们的业务。

当U盘拔出时,会执行卸载过程如下:

UDiskRemove

五、总结

    至此,linux下硬件设备的热插拔以及块设备的挂载卸载功能我们已经完成,在此基础上,我们可以拓展完成更多的硬件设备热插拔检测,进而完成更多的业务功能,希望可以帮到您!写得有问题的地方,还请路过的大神多多指点。

 

 

 

你可能感兴趣的:(C/C++随笔,linux,HotPlug)