ILD

openwrt uloop 学习
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2022-8-16 站点:Inside Linux Development

uloop是openwrt的一个事件库,由libubox包提供,使用时需要链接libubox.so。libubox除了提供uloop还提供avl,blob等功能。


uloop头文件:libubox/uloop.h

uloop源码:uloop.c uloop-epoll.c


uloop的运行数据都存储在共享库的静态变量中,因此不可以运行两个uloop实例。在linux上,uloop使用epoll。


1 全局变量

#define ULOOP_MAX_EVENTS 10


static struct list_head timeouts = LIST_HEAD_INIT(timeouts);

static struct list_head processes = LIST_HEAD_INIT(processes);


static int poll_fd = -1;

bool uloop_cancelled = false;

bool uloop_handle_sigchld = true;

static int uloop_status = 0;

static bool do_sigchld = false;


static struct uloop_fd_event cur_fds[ULOOP_MAX_EVENTS];

static int cur_fd, cur_nfds;

static int uloop_run_depth = 0;


导出的全局变量

extern bool uloop_cancelled;

extern bool uloop_handle_sigchld;


uloop_cancelled是否取消uloop的运行。

uloop_handle_sigchld是否注册sigchld信号处理函数,等等待进程事件时,必须设置为1。


2 初始化

接口:int uloop_init(void)

初始化uloop,成功返回0,失败返回-1。使用者必须调用这个接口来初始化uloop。

1 调用uloop_init_pollfd()创建poll_fd,将被将其设置为FD_CLOEXEC

2 调用waker_init()

    waker是用来在信号到来的时候唤醒uloop的。

    使用pipe()创建管道,第一个fd使用uloop_fd_add()添加到uloop中,其read回调函数是 waker_consume(),其读完fd中的数据。

    第二个fd,保存到waker_pipe变量。

3 uloop_setup_signals(true); 设置信号处理函数,

   设置SIGINT/SIGTERM的信号处理函数为uloop_handle_sigint。

   如果uloop_handle_sigchld为true,设置SIGCHLD的信号处理函数为uloop_sigchld。

   忽略SIGPIPE信号。


uloop_handle_sigint

设置 uloop_status 为 signo。

设置 uloop_cancelled 为 true。

调用 uloop_signal_wake() 往 waker_pipe写入一个w字符。


uloop_sigchld()

设置 do_sigchld 为true。

调用 uloop_signal_wake() 往 waker_pipe写入一个w字符。


3 fd事件

结构体,直接定义在uloop.h,使用者可以看到这个结构体。

typedef void (*uloop_fd_handler)(struct uloop_fd *u, unsigned int events);

struct uloop_fd

{

        uloop_fd_handler cb;

        int fd;

        bool eof;

        bool error;

        bool registered;

        uint8_t flags;

};


flags:

#define ULOOP_READ              (1 << 0)

#define ULOOP_WRITE             (1 << 1)

#define ULOOP_EDGE_TRIGGER      (1 << 2)

#define ULOOP_BLOCKING          (1 << 3)


接口:int uloop_fd_add(struct uloop_fd *sock, unsigned int flags);

添加一个fd。

如果没有ULOOP_BLOCKING,就将fd设置成非阻塞。

调用register_poll() -> epoll_ctl() 注册到epoll。

设置 registered 为 true。

设置 eof 为false。

设置 error为false。


返回epoll_ctl的返回值。


可以看到,uloop本身并不缓存添加的uloop_fd,而是通过epoll_ctl直接下发到内核。


4 定时事件

定时器是添加到双向链表中的。


struct uloop_timeout

{

        struct list_head list;

        bool pending;


        uloop_timeout_handler cb;

        struct timeval time;

};


注意上面的time是一个CLOCK_MONOTONIC的绝对时间


接口:int uloop_timeout_add(struct uloop_timeout *timeout)

如果pending为true,则返回-1.

将其插入到timeouts这个双向链表中,插入的时候,按时间顺序插入。

设置pending为true。


接口:int uloop_timeout_cancel(struct uloop_timeout *timeout)

如果pending为false,返回-1

将timeout从链表中删除

将pending设置为false。


接口:int uloop_timeout_set(struct uloop_timeout *timeout, int msecs);

如果已经添加,先删除。

计算timeout为当前时间之后msecs。

然后将timeout添加到双向链表。


5 进程事件

进程也是添加到双向链表中的,当sigchild信号来之后,设置do_sigchld为true,然后uloop就去wait这些进程。


struct uloop_process

{

        struct list_head list;

        bool pending;


        uloop_process_handler cb;

        pid_t pid;

};


接口:int uloop_process_add(struct uloop_process *p)

如果pending为true,则返回-1

将p按pid的顺序插入到双向链表中

将pending设置为true。


接口:int uloop_process_delete(struct uloop_process *p)

将process从双向链表中删除。


6 uloop_run

实际上是调用 uloop_run_timeout(-1);


接口:int uloop_run_timeout(int timeout)


int uloop_run_timeout(int timeout)

{

        int next_time = 0;

        struct timeval tv;


        uloop_run_depth++;


        uloop_status = 0;

        uloop_cancelled = false;

        while (!uloop_cancelled)

        {

                uloop_gettime(&tv);

                uloop_process_timeouts(&tv);


                if (do_sigchld)

                        uloop_handle_processes();


                if (uloop_cancelled)

                        break;


                uloop_gettime(&tv);


                next_time = uloop_get_next_timeout(&tv);

                if (timeout >= 0 && timeout < next_time)

                        next_time = timeout;

                uloop_run_events(next_time);

        }


        --uloop_run_depth;


        return uloop_status;

}


在一个uloop_cancelled的while循环中


1 先处理定时器

uloop_gettime(&tv);

uloop_process_timeouts(&tv);

    由于定时器是按时间顺序存储的,所以它遍历的方式是,每次遍历第一个,如果这个定时器超时,就先cancel,

    然后执行这个定时器的cb。cancel之后,下一个变成第一个,又遍历第一个,直到没有超时,就break。


2 处理进程事件

如果do_sigchld为true,表明收到了SIGCHLD信号,那就调用 uloop_handle_processes()

uloop_handle_processes处理逻辑如下:

    while执行wait直到没有子进程,wait到一个pid后,就去遍历双向链表,

    遍历到后,就先删除uloop_process_delete(p); 然后调用cb。


3 fd事件处理

先根据定时器双向链表的第一个定时器,计算出poll最多等待的时间,这样做是为了定时器能够得到及时的处理。

另外如果传入了timeout参数,且timeout参数小于等待时间,则将等待时间设置为timeout。

然后调用uloop_run_events(next_time);


uloop_run_events

    先调用uloop_fetch_events(timeout)读取事件,然后一一处理。


注意,如果没有定时器,那么timeout永远是-1。


7 uloop_done

由于uloop内核创建了poll fd,还是设置了信号处理函数,因此在结束uloop的时候需要做一些清理工作,结束uloop通常也意味着进程即将终结。


接口:void uloop_done(void)

1 关闭poll fd

2 关闭 waker pipe

3 清除timeouts

4 清除processes



Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.