13-Openwrt libubox uloop

上一章节将libubox的一些简单组件介绍了一下,其实里面还有很多东西,只能等用到的时候再去学习,这边再介绍一个libubox里面比较经常用到的组件,就是uloop,uloop下面有太多东西了。

uloop是libubox下的一个模块,有三个功能:文件描述符触发事件的监控,timeout定时器处理, 当前进程的子进程的维护。

uloop只适合于单线程的任务,多线程会有bug???

Linux多线程调用ubus导致死锁问题:https://blog.csdn.net/TSZ0000/article/details/100575772

主框架接口

  • 初始化事件循环 int uloop_init(void)

创建一个epoll的句柄,最多监控32个文件描述符。

设置文件描述符属性,如FD_CLOEXEC。

  • 事件循环主处理入口 void uloop_run(void)

uloop_run轮询处理定时器、进程、描述符事件。

遍历定时器timeouts链表判断是否有定时器超时,如果有则进行相应的回调处理,没有跳过。
判断是否有子进程退出SIGCHLD信号,有就会遍历processes进程处理的链表,调勇相应的回调函数,没有跳过。

计算出距离下一个最近的定时器的时间,作为文件描述符事件epoll的超时时间。然后epoll进行事件监听,如果有文件描述符准备就绪(可读写时间)则调用相应的回调函数,或者有信号进行中断epoll返回停止监听,否则epoll阻塞直到超时时间完成。

  • 销毁事件循环 void uloop_done(void)

关闭epoll句柄。

清空定时器链表中的所有的定时器。

清空进程处理事件链表中删除所有的进程事件节点。

1 uloop_fd

epoll 的本质是什么:
https://mp.weixin.qq.com/s/MzrhaWMwrFxKT7YZvd68jw

挂载一个cb回掉函数即可,fd有改变时即触发

void read_std_callback(struct uloop_fd *u, unsigned int events)
{
    char buf[1024] = {0};
    if (ULOOP_READ) {
        if ( read(u->fd, buf, 1024) > 0) {
            printf("read_std: %s\n", buf);
        }
    }
}

void uloop_fd_test(void)
{
    struct uloop_fd fd_test = {
        .cb = read_std_callback,
        .fd = STDIN_FILENO,
    };
    uloop_init();
    /*添加uloop_fd*/
    uloop_fd_add(&fd_test, ULOOP_READ);
    uloop_run();
    uloop_fd_delete(&fd_test);
    uloop_done();
}

2 定时器time

如下,一个定时器的使用就是这么简单。

void timeout_callback(struct uloop_timeout *timeout)
{
    printf("timeout_callback\r\n");
   
    uloop_timeout_set(timeout, 5000);
}

void uloop_timeout_test(void)
{
    struct uloop_timeout fd_timeout = {
        .cb = timeout_callback,
    };
    uloop_init();
    uloop_timeout_set(&fd_timeout, 5000); //5 second
    uloop_run();
    uloop_timeout_cancel(&fd_timeout);
    uloop_done();
}

3 uloop_fd fd串口使用

平常一直在纠结linux的串口编程要怎么弄,又是read,又是select的太过麻烦了,赶快转成用libubox的epool方式,太方便了。

测试流程,开启一个定时器,5秒发一次数据,收到的数据会直接跑到回调函数里面,666

void uloop_fd_uart_test(void)
{
    int fd_uart = -1;

    fd_uart = UartInit();
    if(-1 == fd_uart)
    {
        printf("uart init error\r\n");
        return;
    }
    struct uloop_fd fd_uart_test = {
        .cb = read_uart_callback,
        .fd = fd_uart,
    };
    struct uloop_timeout fd_uart_timeout = {
        .cb = write_uart_timeout_callback,
    };
        
    uloop_init();
    /*添加uloop_fd*/
    uloop_fd_add(&fd_uart_test, ULOOP_READ);
    uloop_timeout_set(&fd_uart_timeout, 5000); //5 second
    uloop_run();
    uloop_fd_delete(&fd_uart_test);
    uloop_timeout_cancel(&fd_uart_timeout);
    uloop_done();
}

回调函数:

int m_uartFd = -1;

void read_uart_callback(struct uloop_fd *u, unsigned int events)
{
    unsigned char buffer[128] = {0};
    int len = 0;
    
    if (ULOOP_READ) {
        len = read(u->fd, buffer, 1024);
        if (len > 0) 
        {
#if 1
            {
                char PrintBuff[1024];
                int uiPrintLen = 0;

                uiPrintLen = sprintf((PrintBuff + uiPrintLen), "######## Uart Read(%02d)", len);

                for(int ii = 0; ii < len; ii++)
                {
                    uiPrintLen += sprintf((PrintBuff + uiPrintLen),"%02X ", buffer[ii]);
                }

                printf("%s\r\n", PrintBuff);
            }
#endif
        }
    }
}

void write_uart_timeout_callback(struct uloop_timeout *timeout)
{
    //char buffer[] = {0x55,0xAA,0x00,0x01,0x00,0x00,0x00};
    unsigned char buffer[] = {0xFF,0xFF,0xAA,0x02,0x11,0x22,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04,0x02,0x11,0x22,0x40,0x99,0x16};
    int len = 0;
    
    printf("write_uart_timeout_callback\r\n");
    
    len = write(m_uartFd, buffer, 21);
#if 1
    {
        char PrintBuff[1024];
        int uiPrintLen = 0;

        uiPrintLen = sprintf((PrintBuff + uiPrintLen), "######## Uart Write(%02d)", len);

        for(int ii = 0; ii < len; ii++)
        {
            uiPrintLen += sprintf((PrintBuff + uiPrintLen),"%02X ", buffer[ii]);
        }

        printf("%s\r\n", PrintBuff);
    }
#endif

    uloop_timeout_set(timeout, 5000);
}

串口初始化:

int UartInit(void)
{
    int baudrate = 115200;
    int databits = 8;
    int stopbits = 1;
    char parity = 'N';
    
    m_uartFd = open("/dev/ttyS2",O_RDWR|O_NOCTTY|O_NDELAY);
    if(m_uartFd <= 0)
    {
        perror("file open error");
        return -1;
    }

    //恢复串口为阻塞状态
    if(fcntl(m_uartFd, F_SETFL, 0) < 0)  
    {
        perror("fcntl failed!");
        return -1;
    }

    struct termios options;
    int   speed_arr[] = { B115200, B19200, B9600, B4800, B2400, B1200, B300};  
    int   name_arr[] = {115200,  19200,  9600,  4800,  2400,  1200,  300}; 
    int   i;
     
    /*tcgetattr(fd,&options)得到与fd指向对象的相关参数,并将它们保存于options,该函数还可以测试配置是否正确,该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为1. 
    */
    if( tcgetattr( m_uartFd,&options)  !=  0)  
    {  
        perror("SetupSerial 1"); 
        return -1;
    }  

    //设置串口输入波特率和输出波特率  
    for ( i= 0;  i < sizeof(speed_arr) / sizeof(int);  i++)  
    {  
        if(baudrate == name_arr[i])
        {
            cfsetispeed(&options, speed_arr[i]);   
            cfsetospeed(&options, speed_arr[i]);    
        }  
    }

    //修改控制模式,保证程序不会占用串口  
    options.c_cflag |= CLOCAL;  
    //修改控制模式,使得能够从串口中读取输入数据  
    options.c_cflag |= CREAD; 

    //不使用流控制  
    options.c_cflag &= ~CRTSCTS;

    //特殊字符不做转换
    options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);

    //设置数据位  
    //屏蔽其他标志位  
    options.c_cflag &= ~CSIZE;  
    switch (databits)  
    { 
        case 5:  
            options.c_cflag |= CS5;  
            break;  
        case 6:  
            options.c_cflag |= CS6;  
            break;  
        case 7: 
            options.c_cflag |= CS7;  
            break;  
        case 8: 
            options.c_cflag |= CS8;  
            break;    
        default:
            perror("Unsupported data size");  
            return -1;;   
    }  
    //设置校验位  
    switch (parity)  
    {
        case 'n':  
        case 'N': //无奇偶校验位。  
            options.c_cflag &= ~PARENB;   
            options.c_iflag &= ~INPCK;
            break;   
        case 'o':
        case 'O'://设置为奇校验      
            options.c_cflag |= (PARODD | PARENB);
            options.c_iflag |= INPCK; 
            break;   
        case 'e':
        case 'E'://设置为偶校验    
            options.c_cflag |= PARENB;
            options.c_cflag &= ~PARODD;
            options.c_iflag |= INPCK;
            break;  
        case 's':  
        case 'S': //设置为空格   
            options.c_cflag &= ~PARENB;  
            options.c_cflag &= ~CSTOPB;  
            break;   
        default:
            perror("Unsupported parity");
            return -1;
    }
    // 设置停止位   
    switch (stopbits)  
    {
        case 1:
            options.c_cflag &= ~CSTOPB; break;   
        case 2:
            options.c_cflag |= CSTOPB; break;
        default:
            perror("Unsupported stop bits");
        return -1;
    }  
     
    //修改输出模式,原始数据输出  
    options.c_oflag &= ~OPOST;

    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);  
    //options.c_lflag &= ~(ISIG | ICANON);
     
    //设置等待时间和最小接收字符  
    options.c_cc[VTIME] = 1; /* 读取一个字符等待1*(1/10)s */    
    options.c_cc[VMIN] = 1; /* 读取字符的最少个数为1 */  
     
    //如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读  
    tcflush(m_uartFd,TCIFLUSH);  
     
    //激活配置 (将修改后的termios数据设置到串口中)  
    if (tcsetattr(m_uartFd,TCSANOW,&options) != 0)
    {  
        perror("com set error!\n");    
        return -1;  
    }

    printf("Led Uart Init OK,fd(%d).\r\n",m_uartFd);

    return m_uartFd;
}

4 uloop_fd usock

uloop_fd的时候不止这些,sock也可以监听,只要时流设备都可以监听,如下例子:

服务器:

void recv_sock_callback(struct uloop_fd *u, unsigned int events)
{    
    char buf[1024] = {0};
    int connect_fd;
    struct sockaddr_in cli_addr;
    socklen_t len = sizeof(struct sockaddr);    

    connect_fd = accept(u->fd, (struct sockaddr *)(&cli_addr), &len);
    if (connect_fd < 0) {
        perror("accept");
        return;
    }
    if (recv(connect_fd, buf, 1024, 0) > 0) {
        printf("recv_buf: %s\n", buf);
    }
    close(connect_fd);
}

int usock_and_uloop_fd_test(void)
{
    int type = USOCK_TCP | USOCK_SERVER  | USOCK_NOCLOEXEC | USOCK_IPV4ONLY;
    const char *host = "127.0.0.1";
    const char *service = "1212";
    int u_fd = usock(type, host, service);    
    if (u_fd < 0) {
        perror("usock");
        return -1;
    }
    
    struct uloop_fd fd_sock = {
        .cb = recv_sock_callback,
        .fd = u_fd,
    };
    uloop_init();
    /*添加uloop_fd*/
    uloop_fd_add(&fd_sock, ULOOP_READ);
    uloop_run();
    uloop_fd_delete(&fd_sock);
    uloop_done();

    return 0;
}

客户端:

#include "ztest.h"

void log_init(void)
{
    ulog_open(ULOG_SYSLOG, LOG_USER, NULL);
    ulog_threshold(LOG_INFO);
}

int main(int argc, char **argv){
    int type = USOCK_TCP | USOCK_NOCLOEXEC | USOCK_IPV4ONLY;
    const char *host = "127.0.0.1";
    const char *service = "1212";
    
    log_init();
    ULOG_INFO("--------zclient--------\n");

    int c_fd = usock(type, host, service);
    if(c_fd < 0) {
        perror("usock");
        return -1;
    }

    send(c_fd, "helloworld", 10, 0);

    close(c_fd);
    
    return 1;
}

uloop出来fd,timeout还有一个process,不过我自己没有用过,别人与用过的可以留下链接改成用的时候参考。

typedef void (*uloop_fd_handler)(struct uloop_fd *u, unsigned int events) // 描述符
typedef void (*uloop_timeout_handler)(struct uloop_timeout *t) // 定时器
typedef void (*uloop_process_handler)(struct uloop_process *c, int ret)  // 进程

4 uloop_process

uloop_process一般越来监控的进程ID,比如监听到某个进程死掉,这将进程所用到的内存释放,做一些善后处理。

#include <libubox/list.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>

struct uapi_process {
	struct list_head list;
	struct uloop_process uloop;

	struct ustream_fd log;
	bool log_overflow;
	char process_name[64];
	char *mqtt_topic;
};


static void uapi_process_cb(struct uloop_process *proc, int ret)
{
	struct uapi_process *lp;
	lp = container_of(proc, struct uapi_process, uloop);

	if(WIFEXITED(ret)) {
		printf("%s (%d): exit with %d\n",
			lp->process_name, lp->uloop.pid, WEXITSTATUS(ret));
	} else if (WIFSIGNALED(ret)) {
		printf("%s (%d): exit with signal %d\n",
			lp->process_name, lp->uloop.pid, WTERMSIG(ret));
	} else if (WIFSTOPPED(ret)) {
		printf("%s (%d): stop with signal %d\n",
			lp->process_name, lp->uloop.pid, WSTOPSIG(ret));
	}

	uapi_delete_process(&lp->uloop);
}

int uapi_process_data(const char *process_argv)
{
	int pfds[2];
	pid_t pid;
	struct uapi_process *l_proc = NULL;

	if (pipe(pfds) < 0) {
		fprintf(stderr, "pipe failed for (%d)", errno);
		return -1;
	}

	/*
	 * free after process stop
	 */
	l_proc = malloc(sizeof(struct uapi_process));

	memset(l_proc, 0, sizeof(struct uapi_process));

	if (!l_proc) {
		fprintf(stderr, "malloc uapi_process failed");
		goto error;
	}

	if ((pid = fork()) < 0) {
		fprintf(stderr, "fork uapi_process failed for");
		goto error;
	}

	if (!pid) {
		/* child */
		int i;
		char app_path[PATH_MAX];

		/*
		 * disable pipe read
		 */
		close(pfds[0]);

		/*
		 * stdin/sdtout/stderr to pipe write
		 */
		for (i = 0; i <= 2; i++) {
			if (pfds[1] == i)
			continue;

			dup2(pfds[1], i);
		}

		if (pfds[1] > 2)
			close(pfds[1]);

		memset(app_path, 0, sizeof(app_path));
		snprintf(app_path, sizeof(app_path), "%s%s", UAPI_PATH, UAPI_PROCESS_NAME);
		execl(app_path, UAPI_PROCESS_NAME, UAPI_MQTT_ARGV, process_argv, (char *)NULL);
		fprintf(stderr, "failed execl %s", app_path);
		exit(127);
	}
	
	/*
	 * parent disable pipe write
	 */
	close(pfds[1]);
	memcpy(l_proc->process_name, UAPI_PROCESS_NAME, sizeof(l_proc->process_name));
	l_proc->uloop.cb = uapi_process_cb;
	l_proc->uloop.pid = pid;
	uloop_process_add(&l_proc->uloop);
	list_add_tail(&l_proc->list, &uapi_process_list);

	return 0;

error:
	if (l_proc) {
		free(l_proc);
	}

	close(pfds[0]);
	close(pfds[1]);
	return -1;
}

代码位于github:https://github.com/creatorly/TestCode/tree/master/openwrt/ztest

发布了106 篇原创文章 · 获赞 76 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/Creator_Ly/article/details/95798248