1. 引入
所谓热插拔(hot-pluggin)即带电插拔,热插拔功能是允许用户在不关闭系统,不切断电源的情况下插入或拨出设备,例如:USB设备(U盘、USB鼠标等)SD卡设备。这个对用户看似简单的操作,在系统层面却有着复杂的处理。
2. SylixOS 热插拔系统简介
很早之前的计算机系统,程序员知道它们只需在启动时扫描所有的设备,并且他们从来不必关心设备消失直到整个机器被关闭。现在,随着USB设备、CardBus、PCI热插拔控制器的出现,SylixOS系统需要能够安全可靠的将硬件从系统中添加或者删除,这对驱动设计者无疑产生了额外的负担,因为现在他们必须处理一个突然冒出的或者突然消失的设备。
针对上面出现的情况好多操作系统增加了对热插拔的支持,如:Linux、Windows等,当然SylixOS也增加了自己的一套热插拔机制。
分两种情况来看待SylixOS热插拔,一种是内核看待热插拔是硬件,也即内核和设备驱动之间的交互;另一种是用户看待热插拔是“/dev/hotplug”设备文件,热插拔设备文件由内核创建。
3. SylixOS 热插拔系统原理分析
3.1 SylixOS 检测热插拔设备方法
- 热插拔事件,例如: USB设备插入和拔出时可以通过API_HotplugEvent函数来异步的处理这两个事件.在事件处理函数中, 驱动程序可以使用oemDiskMount或其他挂载函数来挂载大容量设备.或者自行创建其他设备;
- 循环检测,当有些热插拔设备不能产生事件时,(例如:没有插拔中断的设备)需要轮询检测某些事件标志,来获取设备的状态, 那么驱动程序并不需要自己创建线程或使用定时器来检查,只需要调用API_HotplugPollAdd函数将检测函数和参数插入hotplug循环检测队列即可,t_hotplug线程会定时调用检测函数.;
- 热插拔消息,当设备热插拔操作结束时,可以通过API_HotplugEventMessage将热插拔的结果通知给应用程序,应用程序通过读取“/dev/hotplug”文件,即可获得驱动通知的热插拔消息.。
3.2 SylixOS热插拔系统分析
我们上面说过热插拔分两种情况来看,一是系统与驱动,二是系统与用户层。
3.2.1 系统与驱动层
系统与驱动层的交互,我们这里又分为热插拔事件和循环检查。热插拔事件是针对设备能够产生热插拔中断通知,这样SylixOS可将这个通知以异步的方式来处理;而循环检查是设备并不能产生一个热插拔中断通知,需要轮询检测某些事件标志,来获取设备的插入拔出状态。
1. 热插拔事件:
能够产生热插拔事件的设备,在驱动层只要调用函数API_HotplugEvent即可,SylixOS热插拔系统中对热插拔事件处理是通过我们之前博文中讲的JobQueue来实现的,这里我们不再做更多的解释。
2. 循环检测:
针对不能产生一个热插拔中断事件的设备,在驱动层可调用函数API_HotplugPollAdd来将驱动中的轮询检查函数来添加到系统中,这样系统会在一个确定的时间间隔轮询检查设备状态,这里我们介绍一下这种方法的原理。
typedef struct {
LW_LIST_LINE HPPN_lineManage; /* 节点链表 */
VOIDFUNCPTR HPPN_pfunc; /* 函数指针 */
PVOID HPPN_pvArg; /* 函数参数 */
LW_RESOURCE_RAW HPPN_resraw; /* 资源管理节点 */
} LW_HOTPLUG_POLLNODE;
LW_LIST_LINE_HEADER _G_plineHotplugPoll = LW_NULL;/* 热插拔循环检测链 */
上面的结构是轮询检测节点,函数指针和函数参数两个成员是驱动设计者所要关心的,也就是驱动的轮询检测函数,从下面的代码片段我们可以证实这一点,全局链表头_G_plineHotplugPoll是链接了所有的热插拔节点。
LW_API
INT API_HotplugPollAdd (VOIDFUNCPTR pfunc, PVOID pvArg)
{
...
phppn->HPPN_pfunc = pfunc;
phppn->HPPN_pvArg = pvArg;
...
return (ERROR_NONE);
}
在SylixOS系统层会通过“t_hotplug”线程循环检查热插拔节点链,从下面的代码片段我们可以证实这一点。
static VOID _hotplugThread (VOID)
{
ULONG ulError;
for (;;) {
...
for (plineTemp = _G_plineHotplugPoll;
plineTemp != LW_NULL;
plineTemp = _list_line_get_next(plineTemp)) {
phppn = _LIST_ENTRY(plineTemp, LW_HOTPLUG_POLLNODE, HPPN_lineManage);
if (phppn->HPPN_pfunc) {
/*
* 调用先前安装的循环检测函数
*/
phppn->HPPN_pfunc(phppn->HPPN_pvArg);
}
}
...
}
}
}
3.2.2 系统与用户层
系统与用户层的交互是系统将热插拔消息通知给用户层,这样用户层可以感知到设备的插入和拔出,通过调用函数API_HotplugEventMessage即可将热插拔消息通知出去,然后用户层通过读设备“/dev/hotplug”即可获得热插拔消息。
这里的实现原理分两部分,一是热插拔设备部分,二是消息传递部分:
1. 热插拔设备部分:
热插拔设备的创建涉及到了SylixOS设备驱动的知识,这里不再详述,在之后的文章中将做详细介绍。
2. 消息传递部分:
消息传递部分这里用到了之前博文中的块消息,从下面的代码片段可看出。
LW_API
INT API_HotplugEventMessage (INT iMsg,
BOOL bInsert,
CPCHAR pcPath,
UINT32 uiArg0,
UINT32 uiArg1,
UINT32 uiArg2,
UINT32 uiArg3)
{
...
_hotplugDevPutMsg(iMsg, ucBuffer, i);
return (ERROR_NONE);
}
VOID _hotplugDevPutMsg (INT iMsg, CPVOID pvMsg, size_t stSize)
{
...
for (plineTemp = _G_hotplugdev.HOTPDEV_plineFile;
plineTemp != LW_NULL;
plineTemp = _list_line_get_next(plineTemp)) {
photplugfil = _LIST_ENTRY(plineTemp, LW_HOTPLUG_FILE, HOTPFIL_lineManage);
if ((photplugfil->HOTPFIL_iMsg == iMsg) ||
(photplugfil->HOTPFIL_iMsg == LW_HOTPLUG_MSG_ALL)) {
_bmsgPut(photplugfil->HOTPFIL_pbmsg, pvMsg, stSize);
API_SemaphoreBPost(photplugfil->HOTPFIL_ulReadSync);
bWakeup = LW_TRUE;
}
}
...
}
4. 应用实例
下面这段程序通过信号模仿硬件的中断以实现热插拔事件机制,程序中用信号SIGALRM模仿设备插入,用信号SIGUSR1模仿设备拔出,此例子仅供显示热插拔函数的使用。
#define __SYLIXOS_KERNEL
#include <SylixOS.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
static char *msg = "Dev";
static pthread_t tid;
static int stop = 0;
void *process_thread (void *arg)
{
int *fd = (int *)arg;
char buf[64];
while (1) {
memset(buf, 0, 64);
read(*fd, buf, 64);
if (buf[4] == 1) {
fprintf(stdout, "%s insert.\n", buf + 5);
} else {
fprintf(stdout, "%s pullout.\n", buf + 5);
}
if (stop) {
pthread_exit(NULL);
}
}
return NULL;
}
void send_event (void *arg)
{
int signum = (int)arg;
if (signum == SIGALRM) {
API_HotplugEventMessage(LW_HOTPLUG_MSG_ALL, 1, msg, 0, 0, 0, 0);
} else if (signum == SIGUSR1){
API_HotplugEventMessage(LW_HOTPLUG_MSG_ALL, 0, msg, 0, 0, 0, 0);
}
}
void pullout_handler (int signum)
{
API_HotplugEvent((VOIDFUNCPTR)send_event, (void *)signum, 0, 0, 0, 0, 0);
}
void insert_handler (int signum)
{
API_HotplugEvent((VOIDFUNCPTR)send_event, (void *)signum, 0, 0, 0, 0, 0);
}
int main (int argc, char *argv[])
{
int fd;
int ret, i;
fd = open("/dev/hotplug", O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Open hotplug dev failed.\n");
return -1;
}
ret = pthread_create(&tid, NULL, process_thread, (void *)&fd);
if (ret < 0) {
fprintf(stderr, "Create thread failed.\n");
return -1;
}
if (signal(SIGALRM, insert_handler) == SIG_ERR) {
fprintf(stderr, "Install signal handler failed.\n");
return -1;
}
if (signal(SIGUSR1, pullout_handler) == SIG_ERR) {
fprintf(stderr, "Install signal handler failed.\n");
return -1;
}
for (i = 0; i < 16; ++i) {
alarm(2);
pause();
pthread_kill(getpid(), SIGUSR1);
}
stop = 1;
pthread_join(tid, NULL);
return 0;
}