28 --> 详解 OpenWRT系统框架基础软件模块之libubox

一、简介

OpenWrt路由操作系统的框架基础软件有很多,大部分是通用的软件模块,如 dhcp 、dnsmasq、iproute、cmwp、vpn、ipsec等等;OpenWrt还集成部分具有专属特征软件模块,也是OpenWRT系统核心框架软件组件,从此篇开始分析 《OpenWrt系统框架基础软件模块》系列文章。
OpenWrt 核心软件:procd、uci、libubox、ubus、ubox、luci、netifd 软件组件内容,此部分软件模块是构成OpenWrt框架基础软件。
因为OpenWRT是小型软路由操作系统、网络协议栈知识内容十分丰富、望而生畏;我们先把庞大网络协议栈知识放一下,从易于入手的OpenWRT这个开源软路由开始入手,无论是网络协议栈还是OpenWRT的学习,总是要找到起点或入口;OpenWRT入口处就是 libubox ,因为其他软件是依托libubox库 api 接口构建起来的应用。

libubox是在2011年加入OpenWrt的代码库的。它是OpenWrt中的一个核心库,封装了一系列基础实用功能。
libubox主要提供以下三部分功能:

  • 提供多种基础通用功能接口,包含链表、平衡二叉树、二进制块处理、key-value链表、MD5等
  • 基于AF-UNIX 线程间通讯协议栈,封装sock接口
  • 提供一套基于事件驱动的机制及任务队列管理功能

它的目的是以动态链接库方式来提供可重用的通用功能,给其他模块提供便利和避免再造轮子。

二、源码功能解析

首先看一下libubox源码结构, CMakeList.txt 文件内容中,相关源码构成:

# 源码文件
SET(SOURCES avl.c avl-cmp.c blob.c blobmsg.c uloop.c usock.c ustream.c ustream-fd.c vlist.c utils.c safe_list.c runqueue.c md5.c kvlist.c ulog.c base64.c)
ADD_LIBRARY(ubox SHARED ${SOURCES}) ADD_LIBRARY(ubox-static STATIC ${SOURCES}) SET_TARGET_PROPERTIES(ubox-static PROPERTIES OUTPUT_NAME ubox)
SET(LIBS) CHECK_FUNCTION_EXISTS(clock_gettime HAVE_GETTIME) IF(NOT HAVE_GETTIME) CHECK_LIBRARY_EXISTS(rt clock_gettime "" NEED_GETTIME)
	IF(NEED_GETTIME) TARGET_LINK_LIBRARIES(ubox rt) ENDIF() ENDIF() 

本篇主要分析libubox的事件驱动机制和任务队列管理功能,OpenWRT系统管理框架就是依赖事件驱动型任务队列。

libubox基于AF-UNIX sock通讯机制和kqueue或epoll的IO框架封装实现了,事件驱动型任务队列。先回顾一下这两部分知识。

2.1 AF-UNIX

典型的本地IPC,类似于管道,依赖路径名标识发送方和接收方。即发送数据时,指定接收方绑定的路径名,操作系统根据该路径名可以直接找到对应的接收方,并将原始数据直接拷贝到接收方的内核缓冲区中,并上报给接收方进程进行处理。同样的接收方可以从收到的数据包中获取到发送方的路径名,并通过此路径名向其发送数据。
在这里插入图片描述
参考链接: [AF_INET域与AF_UNIX域socket通信原理对比]

2.2 kqueue和kevent

Kqueue为应用程序提供了一个标准的API,用于注册他们感兴趣的各种事件和条件,并以有效的方式通知他们。它的设计是可伸缩,灵活,可靠和准确的。

kevent 由一对<ident, filter>进行标识。标识符ident可以是一个描述符 (文件、套接字、流)、进程ID或信号量, 这取决于我们要监视的内容。筛选器filter标识用于处理相应事件的内核筛选器。有一些预先定义的系统筛选器 (如 EVFILT_READ 或 EVFILT_WRITE),当有可进行读取或写入操作的数据时,可分别被触发。
参考链接:[深入分析 Kqueue & Kevent 原理及应用]

2.3 uloop

libubox 的实现的事件驱动机制接口在uloop.c 文件,是基于epoll接口来实现的(可配置为Kqueue方式),uloop是一个I/O循环调度,将不同的文件描述符添加到轮询中。文件描述符fd的管理由 uloop_fd 结构来设置。仅需设置 fd 和事件发生时的回调函数,数据结构的其他部分供内部使用。超时管理 部分由 uloop_timeout 结构来管理,在定时时间到了之后调用回调函数,定时时间单位为 毫秒 ,常用接口:

接 口 名 称含 义
uloop_fd_add将一个新文件描述符增加到事件处理循环中
uloop_fd_delete从事件处理循环中删除指定的文件描述符
uloop_init初始化 uloop.内部将调用 epoll_create 函数来创建 epoll 对象
uloop_run进入事件处理循环中
uloop_done反初始化 uloop,即释放内部 epoll 对象,删除内部的超时和 process 对象
uloop_end设置 uloop 内部结束循环标志
uloop_timeout_set设置定时器超时时间,并增加到链表中

三、 应用实例

#include <sys/socket.h>
#include <netinet/in.h>

#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "ustream.h"
#include "uloop.h"
#include "usock.h"

static struct uloop_fd server;
static const char *port = "10000";
struct client *next_client = NULL;

struct client {
	struct sockaddr_in sin;

	struct ustream_fd s;
	int ctr;
};

static void client_read_cb(struct ustream *s, int bytes)
{
	struct client *cl = container_of(s, struct client, s.stream);
	struct ustream_buf *buf = s->r.head;
	char *newline, *str;

	do {
		str = ustream_get_read_buf(s, NULL);
		if (!str)
			break;

		newline = strchr(buf->data, '\n');
		if (!newline)
			break;

		*newline = 0;
		ustream_printf(s, "%s\n", str);
		ustream_consume(s, newline + 1 - str);
		cl->ctr += newline + 1 - str;
	} while(1);

	if (s->w.data_bytes > 256 && !ustream_read_blocked(s)) {
		fprintf(stderr, "Block read, bytes: %d\n", s->w.data_bytes);
		ustream_set_read_blocked(s, true);
	}
}

static void client_close(struct ustream *s)
{
	struct client *cl = container_of(s, struct client, s.stream);

	fprintf(stderr, "Connection closed\n");
	ustream_free(s);
	close(cl->s.fd.fd);
	free(cl);
}

static void client_notify_write(struct ustream *s, int bytes)
{
	fprintf(stderr, "Wrote %d bytes, pending: %d\n", bytes, s->w.data_bytes);

	if (s->w.data_bytes < 128 && ustream_read_blocked(s)) {
		fprintf(stderr, "Unblock read\n");
		ustream_set_read_blocked(s, false);
	}
}

static void client_notify_state(struct ustream *s)
{
	struct client *cl = container_of(s, struct client, s.stream);

	if (!s->eof)
		return;

	fprintf(stderr, "eof!, pending: %d, total: %d\n", s->w.data_bytes, cl->ctr);
	if (!s->w.data_bytes)
		return client_close(s);

}

static void server_cb(struct uloop_fd *fd, unsigned int events)
{
	struct client *cl;
	unsigned int sl = sizeof(struct sockaddr_in);
	int sfd;

	if (!next_client)
		next_client = calloc(1, sizeof(*next_client));

	cl = next_client;
	sfd = accept(server.fd, (struct sockaddr *) &cl->sin, &sl);
	if (sfd < 0) {
		fprintf(stderr, "Accept failed\n");
		return;
	}

	cl->s.stream.string_data = true;
	cl->s.stream.notify_read = client_read_cb;
	cl->s.stream.notify_state = client_notify_state;
	cl->s.stream.notify_write = client_notify_write;
	ustream_fd_init(&cl->s, sfd);
	next_client = NULL;
	fprintf(stderr, "New connection\n");
}

static int run_server(void)
{

	server.cb = server_cb;
	server.fd = usock(USOCK_TCP | USOCK_SERVER | USOCK_IPV4ONLY | USOCK_NUMERIC, "127.0.0.1", port);
	if (server.fd < 0) {
		perror("usock");
		return 1;
	}

	uloop_init();
	uloop_fd_add(&server, ULOOP_READ);
	uloop_run();

	return 0;
}

static int usage(const char *name)
{
	fprintf(stderr, "Usage: %s -p <port>\n", name);
	return 1;
}

int main(int argc, char **argv)
{
	int ch;

	while ((ch = getopt(argc, argv, "p:")) != -1) {
		switch(ch) {
		case 'p':
			port = optarg;
			break;
		default:
			return usage(argv[0]);
		}
	}

	return run_server();
}

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值