内核hotplug事件---利用Netlink处理hotplug实现热插拔监控

先上monitor的demo:

sd_monitor.c

#include <stdio.h>
#include <stdlib.h> 
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <pthread.h>
#include <linux/netlink.h>
#include <linux/version.h>
#include <linux/input.h>
#include <syslog.h>
#include <stdarg.h>
//#include <execinfo.h>
#include <errno.h>
#include <sys/vfs.h>
/************** for tcp ********************/
#include <sys/types.h>			/* See NOTES */ 	 
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>

#define	RESTART_AK_IPC		"/mnt/mtd/start_ipc.sh"
#define SDP1_DEV_NAME       "/dev/mmcblk0p1"
#define SD_DEV_NAME         "/dev/mmcblk0"
#define SD_TOOL				"dsk_repair"
#define MOUNT_SDP1			"mount -rw /dev/mmcblk0p1 /mnt/sd_card" 
#define MOUNT_SD			"mount -rw /dev/mmcblk0 /mnt/sd_card" 
#define UMOUNT_SD			"umount /mnt/sd_card -l"

/**
 * *  @brief       	do_syscmd
 				执行系统命令
 * *  @author      	aj
 * *  @date       	2014-10-28
 * *  @param[in] 	char * cmd, 要执行的命令
 				char *result, 执行结果
 * *  @return       int
 * *  @retval		lengh of result buf --> success, less than zero --> failed
 * */

int do_syscmd(char *cmd, char *result)
{
	char buf[4096];	
	FILE *filp;
	
	filp = popen(cmd, "r");
	if (NULL == filp){
		ipc_print("[%s:%d] popen %s, cmd:%s!\n", 
			__func__, __LINE__, strerror(errno), cmd);
		return -2;
	}

	//fgets(buf, sizeof(buf)-1, filp);
	memset(buf, '\0', sizeof(buf));
	fread(buf, sizeof(char), sizeof(buf)-1, filp);
	
	sprintf(result, "%s", buf);
	
	pclose(filp);
	return strlen(result);	
}

/**
 * *  @brief       daemon_check_sd_prepare
 				write something to the TF card
 * *  @author      
 * *  @date        2015-5-18
 * *  @param[in]     void
 * *  @return      
 * */

void daemon_test_sd_ro(void)
{
	system("dd if=/dev/zero of=/mnt/sd_card/test_tf bs=1024 count=1");
	system("rm -rf /mnt/sd_card/test_tf");
}

/**
 * *  @brief       daemon_check_sd_ro
 				check the sd card 
 * *  @author      
 * *  @date        2015-5-18
 * *  @param[in]     int flag, 1->mmcblkp0,    0->mmcblk 
 * *  @return      
 * */

void daemon_check_sd_ro(int mmcblk0_p1)
{
	char cmd[128] = {0}, result[256], badfile[100] ={0}, dev_name[20] = {0};

	daemon_test_sd_ro();
	
	if(mmcblk0_p1)
		sprintf(dev_name, "%s", SDP1_DEV_NAME);		//"/dev/mmcblk0p1"
	else
		sprintf(dev_name, "%s", SD_DEV_NAME);		//"/dev/mmcblk0"

	sprintf(cmd, "mount | grep %s", dev_name); //do "mount | grep dev"

	do_syscmd(cmd, result);
	
	if(strlen(result) > 0){
		if(strstr(result, "rw,relatime") == NULL){			//if not read write	
			if(strstr(result, "ro,relatime") != NULL){			//find ro lable, the card was read only

				ipc_print("[%s:%d] The SD Card is Read Only, repair it\n", __func__, __LINE__);

			}
		}else{
			ipc_print("[%s:%d] The SD Card is OK\n", __func__, __LINE__);
			return;
		}
	}
}

/**
 * *  @brief    daemon_umount_sd
 			卸载SD 卡
 * *  @author   
 * *  @date        
 * *  @param[in]   void
 * *  @return      void
 * */

/* umount the sd card and delete the sd_test dir */
void daemon_umount_sd(void)
{
	char cmd[128] = {0};

	/** notice the anyka_ipc that  **/
	sprintf(cmd, "echo %d > %s; killall -12 aoni_ipc", 0, "/tmp/sd_status");
	system(cmd);
    system(UMOUNT_SD);
	ipc_print("[%s:%d] *** umount the sd ***\n", __func__, __LINE__);
	
}

/**
 * *  @brief       daemon_mount_sd
 * *  @author     
 * *  @date        
 * *  @param[in]   int flag, 1->mmcblkp0,    0->mmcblk 
 * *  @return      void
 * */

/* create a sd_test dir and mount the sd card in it */
void daemon_mount_sd(int flag)
{
	char cmd[128], status[20] = {0};
	int mmcblk0_p1;

    if(flag == 0)
    {
        if(access(SDP1_DEV_NAME, R_OK) >= 0)
        {
            ipc_print("**********we will skip mount /dev/mmcblk0*******\n");
            return;
        }
    }

	if (flag){
		mmcblk0_p1 = 1;
		sprintf(cmd, "%s", MOUNT_SDP1);
	}
	else{
		mmcblk0_p1 = 0;
		sprintf(cmd, "%s", MOUNT_SD);
	}
    system(cmd);

	//check sdcard readonly or not, and repair it.
	daemon_check_sd_ro(mmcblk0_p1);

	/*
	* when the sd card message is not come first time, 
	* it means hot plug and we will send sig to anyka_ipc 
	*/
    if(system_start_flag == 0)
    {
    	//send message to anyka_ipc that card is ready
		sprintf(status, "echo %d > %s", 1, "/tmp/sd_status");
		system(status);
        system("killall -12 aoni_ipc"); //send ipc_print to ipc
    }
	ipc_print("[%s:%d] *** mount the sd to /mnt ***\n", __func__, __LINE__);
	
}


/**
 * *  @brief    daemon_init_hotplug_sock
 			热插拔检测
 * *  @author   
 * *  @date        
 * *  @param[in]   void
 * *  @return      int, -1, 失败;else 成功
 * */

/* create the socket to recevie the uevent */
static int daemon_init_hotplug_sock(void)
{
	struct sockaddr_nl snl;
    const int buffersize = 2048;
    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)	{
        ipc_print("[%s:%d] socket: %s\n", __func__, __LINE__, strerror(errno));
        return -1;
    }

    /* set receive buffersize */
    setsockopt(hotplug_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));

    retval = bind(hotplug_sock, (struct sockaddr *)&snl, sizeof(struct sockaddr_nl));
    if (retval < 0) {
	    ipc_print("[%s:%d] bind failed: %s\n", __func__, __LINE__, strerror(errno));
        close(hotplug_sock);
        hotplug_sock = -1;
    }

    return hotplug_sock;
}


pid_t gettid()
{
	return syscall(SYS_gettid);
}

/* waitting the uevent and do something */
void *daemon_pth_func(void *data)
{	
    char buf[UEVENT_BUFFER_SIZE * 2] = {0};
    char temp_buf[20];	//find action
    char *p;	//find blk
    int i;
    int hotplug_sock;	//hotplug socket handle
    int p1_removed = 0;
	
	ipc_print("[%s:%d] This thread id: %ld\n", __func__, __LINE__, (long int)gettid());

	hotplug_sock = daemon_init_hotplug_sock();	
    if (hotplug_sock < 0)
		return NULL;
	
    system_start_flag = 1;
	sleep(1);
    if (access (SDP1_DEV_NAME, F_OK) == 0)
		daemon_mount_sd(1);
	else if (access (SD_DEV_NAME, F_OK) == 0)
		daemon_mount_sd(0);
    system_start_flag = 0;
	
    while(1) {
		//clear buf
        memset(buf, 0, sizeof(buf));

		//block here to wait ipc_print
        recv(hotplug_sock, &buf, sizeof(buf), 0);
        p = strrchr(buf, '/');	//get block name

		//get action
        for (i = 0; buf[i] != '@' && buf[i] != 0; i++)
            temp_buf[i] = buf[i];
        temp_buf[i] = 0;

		if (strcmp(temp_buf, "change"))
			ipc_print("%s\n", buf);

		//card insert
		if (!strcmp(temp_buf, "add")) {
			if (!strcmp(p, "/mmcblk0p1")) {
				//sleep(1);
				daemon_mount_sd(1);
			} else if (!strcmp(p, "/mmcblk0")) {
				//sleep(1);
				daemon_mount_sd(0);
			}
			p1_removed = 0;
			continue;
		}

		//card extract
		if (!strcmp(temp_buf, "remove")) {

			//if p1 removed, than we do need to umount k0
			if(!strcmp(p, "/mmcblk0p1")) {
				daemon_umount_sd();
				p1_removed = 1;
			} else if((!strcmp(p, "/mmcblk0")) && (!p1_removed))
				daemon_umount_sd();
		}
    }

	ipc_print("[%s:%d] Exit thread, id : %ld\n", __func__, __LINE__, (long int)gettid());
	return NULL;
}


我们知道,用户空间的程序与设备通信的方法,主要有以下几种方式:
1. 通过ioperm获取操作IO端口的权限,然后用inb/inw/ inl/ outb/outw/outl等函数,避开设备驱动程序,直接去操作IO端口。(没有用过)
2. 用ioctl函数去操作/dev目录下对应的设备,这是设备驱动程序提供的接口。像键盘、鼠标和触摸屏等输入设备一般都是这样做的。
3. 用write/read/mmap去操作/dev目录下对应的设备,这也是设备驱动程序提供的接口。像framebuffer等都是这样做的。

上面的方法在大多数情况下,都可以正常工作,但是对于热插拨(hotplug)的设备,比如像U盘,就有点困难了,因为你不知道:什么时候设备插上了,什么时候设备拔掉了。这就是所谓的hotplug问题了。

处理hotplug传统的方法是,在内核中执行一个称为hotplug的程序,相关参数通过环境变量传递过来,再由hotplug通知其它关注hotplug事件的应用程序。这样做不但效率低下,而且感觉也不那么优雅。新的方法是采用NETLINK实现的,这是一种特殊类型的socket,专门用于内核空间与用户空间的异步通信。上面的这个简单的例子,可以监听来自内核hotplug的事件。
udev的主体部分在udevd.c文件中,它主要监控来自4个文件描述符的事件/消息,并做出处理:

1. 来自客户端的控制消息。这通常由udevcontrol命令通过地址为/org/kernel/udev/udevd的本地socket,向udevd发送的控制消息。其中消息类型有:
l UDEVD_CTRL_STOP_EXEC_QUEUE 停止处理消息队列。
l UDEVD_CTRL_START_EXEC_QUEUE 开始处理消息队列。
l UDEVD_CTRL_SET_LOG_LEVEL 设置LOG的级别。
l UDEVD_CTRL_SET_MAX_CHILDS 设置最大子进程数限制。好像没有用。
l UDEVD_CTRL_SET_MAX_CHILDS_RUNNING 设置最大运行子进程数限制(遍历proc目录下所有进程,根据session的值判断)。
l UDEVD_CTRL_RELOAD_RULES 重新加载配置文件。

2. 来自内核的hotplug事件。如果有事件来源于hotplug,它读取该事件,创建一个udevd_uevent_msg对象,记录当前的消息序列号,设置消息的状态为EVENT_QUEUED,然后并放入running_list和exec_list两个队列中,稍后再进行处理。

3. 来自signal handler中的事件。signal handler是异步执行的,即使有signal产生,主进程的select并不会唤醒,为了唤醒主进程的select,它建立了一个管道,在signal handler中,向该管道写入长度为1个子节的数据,这样就可以唤醒主进程的select了。

4. 来自配置文件变化的事件。udev通过文件系统inotify功能,监控其配置文件目录/etc/udev/rules.d,一旦该目录中文件有变化,它就重新加载配置文件。

其中最主要的事件,当然是来自内核的hotplug事件,如何处理这些事件是udev的关键。udev本身并不知道如何处理这些事件,也没有必要知道,因为它只实现机制,而不实现策略。事件的处理是由配置文件决定的,这些配置文件即所谓的rule。

关于rule的编写方法可以参考《writing_udev_rules》,udev_rules.c实现了对规则的解析。

在规则中,可以让外部应用程序处理某个事件,这有两种方式,一种是直接执行命令,通常是让modprobe去加载驱动程序,或者让mount去加载分区。另外一种是通过本地socket发送消息给某个应用程序。
在udevd.c:udev_event_process函数中,我们可以看到,如果RUN参数以”socket:”开头则认为是发到socket,否则认为是执行指定的程序。

下面的规则是执行指定程序:
60-pcmcia.rules: RUN+="/sbin/modprobe pcmcia"

下面的规则是通过socket发送消息:
90-hal.rules:RUN+="socket:/org/freedesktop/hal/udev_event"

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值