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


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

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


主框架接口

  • 初始化事件循环 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 的本质是什么:

挂载一个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;
}