# [2021q1](http://wiki.csie.ncku.edu.tw/linux/schedule) 第 11 週測驗題
###### tags: `linux2021`
:::info
目的: 檢驗學員對 ==[高效 Web 伺服器開發](https://hackmd.io/@sysprog/fast-web-server)==, ==[透過 timerfd 處理週期性任務](https://hackmd.io/@sysprog/linux-timerfd)== 的認知
:::
==[作答表單](https://docs.google.com/forms/d/e/1FAIpQLSdc2dH_4i_jBnJpLMhugIyHtQfJglJ1ka_ufOAXAsIjaCW3EA/viewform)==
### 測驗 `1`
考慮以下是向 [libev](http://software.schmorp.de/pkg/libev.html) 致敬的 event loop 實作,預期執行輸出如下:
```
Test: oneshot timer
callback called
Test: periodic timer
callback timer periodic called 5
callback timer periodic called 4
callback timer periodic called 3
callback timer periodic called 2
callback timer periodic called 1
run timer cancel test ... passed
Test: raw events
```
原始程式碼 (`ev.c`) 包含註解和單元測試程式。
> 編譯選項: `-O2 -std=gnu99`
> 參考資訊: [signalfd](https://man7.org/linux/man-pages/man2/signalfd4.2.html)
```cpp
#include <inttypes.h>
#include <sys/time.h>
enum {
EV_READ = (1 << 0),
EV_WRITE = (1 << 1),
EV_TIMEOUT_ONESHOT = (1 << 2),
EV_TIMEOUT_PERIODIC = (1 << 3),
EV_SIGNAL = (1 << 4),
EV_CLOEXEC = (1 << 0),
};
/*
* Forward declaration - ev objects are fully opaque to callers. Access is
* always done via access functions. Just keep in mind: ev is the object you
* usualy needs one, the main object. For each registered event like timer or
* file descriptor you corresponding ev_entry object is required.
*/
struct ev;
struct ev_entry;
/**
* ev_new - initialize a new event object, eve main data structure
*
* It return the new ev object or NULL in the case of an error.
*/
struct ev *ev_new(int flags);
/**
* Add ev_event to the main ev event structure
*
* This registers all events (fd, signals, timer) at the main structure and
* must be called before ev_loop.
*
* In the case of an error an negative errno value is returned. It is up to
* the caller to free ev_entry data structure.
*/
int ev_add(struct ev *, struct ev_entry *);
/**
* Main event loop start function
*
* This function will call epoll_wait and will block until event is triggered.
* Please call this at the end after every ev_event's are registered.
*/
int ev_loop(struct ev *, int);
/* To end the processing loop
*
* Keep in mind: this will not free any memory, nor does this function call
* ev_del to deregister. It just break out after an event it triggered.
*
* This function is probably not what you want to use
*/
int ev_run_out(struct ev *);
/**
* ev_destroy - deallocate ev structure
* @ev: pointer instance of ev object
*
* Shutdown, close and free all associated resourches of ev. This function
* it the counterpart to ev_new() and should at least be called at
* program shutdown or restart.
*
* Keep in mind that the caller is responsible to deallocate all registered
* ev_event data structures, close file descriptors, etc. This cannot be done
* by ev_destroy().
*
* This function cannot fail and thus return no return status.
*/
void ev_destroy(struct ev *);
/**
* ev_entry new provides api to register a raw filedescriptor (e.g. socket)
* for later use in epoll set. The arguments:
*
* 1) the filedescriptor
* 2) EV_READ or EV_WRITE
* 3) a callback, called if fd is ready for read or write
* 4) a private data hand over to the caller within the callback
*
* The callback protoype is similar:
*
* 1) the filedescriptor
* 2) EV_READ or EV_WRITE
* 3) the private data pointer, registered at ev_entry_new time
*
* Warning: do not throw exceptions or call longjmp from a callback.
*
* The next steps is to register this entry at the main loop wia
* ev_add()
*
* This function return NULL in the case of an error or a pointer
* to a newly allocated struct.
*/
struct ev_entry *ev_entry_new_raw(int,
uint32_t,
void (*cb)(int, uint32_t, void *),
void *);
/**
* Deregister event from main event loop
*
* Please make sure to call ev_entry_free() to remove all allocated
* resourches to free memory.
*
* Return 0 in the case of sucess, otherwise a negative error code.
*/
int ev_del(struct ev *, struct ev_entry *);
/**
* Deallocate resourcheso of ev_eventy
*
* This is the counterpart of ev_entry_new(), ev_timer_oneshot_new(),
* and ev_timer_periodic_new() must be called to free associated memory.
*/
void ev_entry_free(struct ev_entry *);
/**
* Create new oneshot timer
*
* Just arm a timer for one shot, after the callback the timer is not re-added
* to the main loop automatically. The caller is responsible to free
* resourches afterwards with ev_entry_free()
*
* Warning: do not throw exceptions or call longjmp from a callback.
*/
struct ev_entry *ev_timer_oneshot_new(struct timespec *,
void (*cb)(void *),
void *);
/**
* Start periodic timer
*
* Ater timespec time the user provided callack cb is called. To end the timer
* ev_timer_cancel() must be called. Normally followed by ev_entry_free()
*
* NOTE: the first callback argument tell the number of missed events. This
* can happen if too much work is scheduled and the even machinery cannot
* execute fast enough. Normally you should only see 1.
*
* Warning: do not throw exceptions or call longjmp from a callback.
*
* Returns NULL in case the case of an error
*/
struct ev_entry *ev_timer_periodic_new(struct timespec *,
void (*cb)(unsigned long long missed,
void *),
void *);
/*
* struct ev_event * is freed by ev_timer_cancel - user provided callbacks
* and data not - sure. So do not dereference ev_entry afterwards
*
* Make sure that ever you cancel the timer you call ev_entry_free()
*
*/
int ev_timer_cancel(struct ev *, struct ev_entry *);
/**
* set filedescriptor in non-blocking mode
*
* All descriptors registered at epoll must be operate in non-blocking
* way. Often you can get a non-blocking descriptor with the right options
* for some syscalls. If not this function can be used.
*
* This function return the 0 in the case of success or negative value
* if something went wront.
*/
int ev_set_non_blocking(int fd);
/* Implementation starts here */
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <sys/timerfd.h>
#define EVE_EPOLL_ARRAY_SIZE 64
struct ev {
int fd;
int break_loop;
unsigned long long entries;
/* implementation specific data, e.g. select timer handling
* will use this to store the rbtree */
void *priv_data;
};
struct ev_entry {
/* monitored FD if type is EV_READ or EV_WRITE */
int fd;
/* EV_* if raw is 0 -> type is used. E.g for
* EV_READ, EV_WRITE or EV_TIMEOUT_ONESHOT.\
* EV_RAW_* if raw is 1 -> type_raw is used then
*/
union {
int type;
uint32_t type_raw;
};
/* 0 for "old" mode, if 1 type is interpreted identical
* as epoll_ctl flags */
int raw;
/* timeout val if type is EV_TIMEOUT_ONESHOT */
struct timespec timespec;
union {
void (*fd_cb)(int, int, void *);
void (*fd_cb_raw)(int, uint32_t, void *);
void (*timer_cb_oneshot)(void *);
void (*timer_cb_periodic)(unsigned long long, void *);
void (*signal_cb)(uint32_t, uint32_t, void *);
};
/* user provided pointer to data */
void *data;
/* implementation specific data (e.g. for epoll, select) */
void *priv_data;
};
static struct ev *struct_ev_new_internal(void)
{
struct ev *ev = malloc(sizeof(*ev));
if (!ev) return NULL;
memset(ev, 0, sizeof(*ev));
return ev;
}
int ev_run_out(struct ev *ev)
{
ev->break_loop = 1;
return 0;
}
/* similar for all implementations, at least
* under Linux. Solaris, AIX, etc. differs and need
* a separate implementation */
int ev_set_non_blocking(int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) return -EINVAL;
flags = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
if (flags < 0) return -EINVAL;
return 0;
}
struct ev_entry_data_epoll {
/* std fd handling data */
uint32_t flags;
union {
sigset_t signal_mask;
};
};
void ev_destroy(struct ev *ev)
{
/* close epoll descriptor */
close(ev->fd);
/* clear potential secure data */
memset(ev, 0, sizeof(struct ev));
free(ev);
}
static inline int ev_new_flags_convert(int flags)
{
if (flags == 0) return 0;
if (flags == EV_CLOEXEC) return EPOLL_CLOEXEC;
return -EINVAL;
}
struct ev *ev_new(int flags)
{
int flags_epoll = ev_new_flags_convert(flags);
if (flags_epoll < 0) return NULL;
struct ev *ev = struct_ev_new_internal();
if (!ev) return NULL;
ev->fd = epoll_create1(flags_epoll);
if (ev->fd < 0) {
free(ev);
return NULL;
}
ev->entries = 0;
ev->break_loop = 0;
return ev;
}
struct ev_entry *ev_entry_new_epoll_internal(void)
{
struct ev_entry *ev_entry = malloc(sizeof(struct ev_entry));
if (!ev_entry) return NULL;
memset(ev_entry, 0, sizeof(struct ev_entry));
ev_entry->priv_data = malloc(sizeof(struct ev_entry_data_epoll));
if (!ev_entry->priv_data) {
free(ev_entry);
return NULL;
}
memset(ev_entry->priv_data, 0, sizeof(struct ev_entry_data_epoll));
return ev_entry;
}
struct ev_entry *ev_entry_new_raw(int fd,
uint32_t events,
void (*cb)(int, uint32_t, void *),
void *data)
{
struct ev_entry *ev_entry = ev_entry_new_epoll_internal();
if (!ev_entry) return NULL;
ev_entry->fd = fd;
ev_entry->type_raw = events;
ev_entry->fd_cb_raw = cb;
ev_entry->raw = CCC;
ev_entry->data = data;
struct ev_entry_data_epoll *ev_entry_data_epoll = ev_entry->priv_data;
ev_entry_data_epoll->flags = events;
return ev_entry;
}
struct ev_entry *ev_timer_oneshot_new(struct timespec *timespec,
void (*cb)(void *),
void *data)
{
struct ev_entry *ev_entry = ev_entry_new_epoll_internal();
if (!ev_entry) return NULL;
ev_entry->type = EV_TIMEOUT_ONESHOT;
ev_entry->data = data;
ev_entry->timer_cb_oneshot = cb;
ev_entry->raw = DDD;
memcpy(&ev_entry->timespec, timespec, sizeof(struct timespec));
return ev_entry;
}
struct ev_entry *ev_timer_periodic_new(struct timespec *timespec,
void (*cb)(unsigned long long, void *),
void *data)
{
struct ev_entry *ev_entry = ev_entry_new_epoll_internal();
if (!ev_entry) return NULL;
ev_entry->type = EV_TIMEOUT_PERIODIC;
ev_entry->data = data;
ev_entry->timer_cb_periodic = cb;
ev_entry->raw = EEE;
memcpy(&ev_entry->timespec, timespec, sizeof(struct timespec));
return ev_entry;
}
static void ev_entry_timer_free(struct ev_entry *ev_entry)
{
close(ev_entry->fd);
}
static void ev_entry_signal_free(struct ev_entry *ev_entry)
{
close(ev_entry->fd);
}
void ev_entry_free(struct ev_entry *ev_entry)
{
if (ev_entry->raw) goto out;
switch (ev_entry->type) {
case EV_TIMEOUT_ONESHOT:
case EV_TIMEOUT_PERIODIC:
ev_entry_timer_free(ev_entry);
break;
case EV_SIGNAL:
ev_entry_signal_free(ev_entry);
break;
default:
// other events have no special cleaning
// functions. do nothing
break;
}
out:
free(ev_entry->priv_data);
memset(ev_entry, 0, sizeof(struct ev_entry));
free(ev_entry);
}
static int ev_arm_timerfd_oneshot(struct ev_entry *ev_entry)
{
struct timespec now;
struct itimerspec new_value;
struct ev_entry_data_epoll *ev_entry_data_epoll = ev_entry->priv_data;
memset(&new_value, 0, sizeof(struct itimerspec));
int ret = clock_gettime(CLOCK_MONOTONIC, &now);
if (ret < 0) return -EINVAL;
new_value.it_value.tv_sec = now.tv_sec + ev_entry->timespec.tv_sec;
new_value.it_value.tv_nsec = now.tv_nsec + ev_entry->timespec.tv_nsec;
/* timerfd_settime() cannot handle larger nsecs - catch overflow */
if (new_value.it_value.tv_nsec >= 1000000000) {
new_value.it_value.tv_sec++;
new_value.it_value.tv_nsec -= 1000000000;
}
new_value.it_interval.tv_sec = 0;
new_value.it_interval.tv_nsec = 0;
int fd = timerfd_create(CLOCK_MONOTONIC, 0);
if (fd < 0) return -EINVAL;
ret = timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL);
if (ret < 0) {
close(fd);
return -EINVAL;
}
ret = ev_set_non_blocking(fd);
if (ret < 0) {
close(fd);
return -EINVAL;
}
ev_entry_data_epoll->flags = EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP;
ev_entry->fd = fd;
return 0;
}
static int ev_arm_timerfd_periodic(struct ev_entry *ev_entry)
{
struct ev_entry_data_epoll *ev_entry_data_epoll = ev_entry->priv_data;
struct itimerspec new_value = {
.it_value.tv_sec = ev_entry->timespec.tv_sec,
.it_value.tv_nsec = ev_entry->timespec.tv_nsec,
.it_interval.tv_sec = ev_entry->timespec.tv_sec,
.it_interval.tv_nsec = ev_entry->timespec.tv_nsec,
};
int fd = timerfd_create(CLOCK_MONOTONIC, 0);
if (fd < 0) return -EINVAL;
int ret = timerfd_settime(fd, 0, &new_value, NULL);
if (ret < 0) {
close(fd);
return -EINVAL;
}
ret = ev_set_non_blocking(fd);
if (ret < 0) {
close(fd);
return -EINVAL;
}
ev_entry_data_epoll->flags = EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP;
ev_entry->fd = fd;
return 0;
}
static int ev_arm_signal(struct ev_entry *ev_entry)
{
struct ev_entry_data_epoll *ev_entry_data_epoll = ev_entry->priv_data;
int ret = sigprocmask(SIG_BLOCK, &ev_entry_data_epoll->signal_mask, NULL);
if (ret < 0) return -EINVAL;
int fd = signalfd(-1, &ev_entry_data_epoll->signal_mask, SFD_CLOEXEC);
if (fd < 0) return -EINVAL;
ret = ev_set_non_blocking(fd);
if (ret < 0) {
close(fd);
return -EINVAL;
}
ev_entry_data_epoll->flags = EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP;
ev_entry->fd = fd;
return 0;
}
int ev_add(struct ev *ev, struct ev_entry *ev_entry)
{
int ret;
struct epoll_event epoll_ev;
struct ev_entry_data_epoll *ev_entry_data_epoll = ev_entry->priv_data;
memset(&epoll_ev, 0, sizeof(struct epoll_event));
if (ev_entry->raw) {
/* type is interpreted as raw epoll_ctl event, not special
* internal event, no special treatment required */
goto out;
}
switch (ev_entry->type) {
case EV_TIMEOUT_ONESHOT:
ret = ev_arm_timerfd_oneshot(ev_entry);
if (ret != 0) return -EINVAL;
break;
case EV_TIMEOUT_PERIODIC:
ret = ev_arm_timerfd_periodic(ev_entry);
if (ret != 0) return -EINVAL;
break;
case EV_SIGNAL:
ret = ev_arm_signal(ev_entry);
if (ret != 0) return -EINVAL;
break;
default:
// no special treatment of other entries
break;
}
out:
/* FIXME: the mapping must be a one to one mapping */
epoll_ev.events = ev_entry_data_epoll->flags;
epoll_ev.data.ptr = ev_entry;
ret = epoll_ctl(ev->fd, EPOLL_CTL_ADD, ev_entry->fd, &epoll_ev);
if (ret < 0) return -EINVAL;
ev->entries++;
return 0;
}
int ev_del(struct ev *ev, struct ev_entry *ev_entry)
{
struct epoll_event epoll_ev;
memset(&epoll_ev, 0, sizeof(struct epoll_event));
int ret = epoll_ctl(ev->fd, EPOLL_CTL_DEL, ev_entry->fd, &epoll_ev);
if (ret < 0) return -EINVAL;
ev->entries--;
return 0;
}
int ev_timer_cancel(struct ev *ev, struct ev_entry *ev_entry)
{
int ret = ev_del(ev, ev_entry);
if (ret != 0) return -EINVAL;
return 0;
}
static inline void ev_process_timer_oneshot(struct ev *ev,
struct ev_entry *ev_entry)
{
unsigned long long missed;
/* and now: cleanup timer specific data and
* finally all event specific data */
ssize_t ret = read(ev_entry->fd, &missed, sizeof(missed));
if (ret < 0) assert(0);
ev_del(ev, ev_entry);
/* first of all - call user callback */
ev_entry->timer_cb_oneshot(ev_entry->data);
}
static inline void ev_process_timer_periodic(struct ev_entry *ev_entry)
{
unsigned long long missed;
/* and now: cleanup timer specific data and
* finally all event specific data */
ssize_t ret = read(ev_entry->fd, &missed, sizeof(missed));
if (ret < 0) assert(0);
/* first of all - call user callback */
ev_entry->timer_cb_periodic(missed, ev_entry->data);
}
static inline void ev_process_signal(struct ev_entry *ev_entry)
{
struct signalfd_siginfo sigsiginfo;
/* and now: cleanup timer specific data and
* finally all event specific data */
ssize_t ret = read(ev_entry->fd, &sigsiginfo, sizeof(sigsiginfo));
if (ret < 0) {
assert(0);
return;
}
if (ret != sizeof(sigsiginfo)) return;
ev_entry->signal_cb(sigsiginfo.ssi_signo, sigsiginfo.ssi_pid,
ev_entry->data);
}
static inline void ev_process_call_internal(struct ev *ev,
struct ev_entry *ev_entry)
{
(void) ev;
if (ev_entry->raw) {
ev_entry->fd_cb_raw(ev_entry->fd, ev_entry->type_raw, ev_entry->data);
return;
}
switch (ev_entry->type) {
case EV_READ:
case EV_WRITE:
ev_entry->fd_cb(ev_entry->fd, ev_entry->type, ev_entry->data);
return;
break;
case EV_TIMEOUT_ONESHOT:
ev_process_timer_oneshot(ev, ev_entry);
break;
case EV_TIMEOUT_PERIODIC:
ev_process_timer_periodic(ev_entry);
break;
case EV_SIGNAL:
ev_process_signal(ev_entry);
break;
default:
return;
break;
}
return;
}
int ev_loop(struct ev *ev, int flags)
{
(void) flags;
struct epoll_event events[EVE_EPOLL_ARRAY_SIZE];
while (ev->entries > 0) {
int nfds = epoll_wait(ev->fd, events, EVE_EPOLL_ARRAY_SIZE, -1);
if (nfds < 0) return -EINVAL;
/* multiplex and call the registerd callback handler */
for (int i = 0; i < nfds; i++) {
struct ev_entry *ev_entry = events[i].data.ptr;
ev_process_call_internal(ev, ev_entry);
}
if (ev->break_loop) break;
}
return 0;
}
/* Unit test starts here */
#define SLEEP_SECONDS 1
#define ITERATIO_MAX 2
int i = 0;
void timer_cd(void *data)
{
struct ev *ev = data;
struct ev_entry *ev_e;
struct timespec timespec = {SLEEP_SECONDS, 0};
fprintf(stderr, "timer_cd() called %d time\n", i);
if (i++ >= ITERATIO_MAX) return;
ev_e = ev_timer_oneshot_new(×pec, timer_cd, ev);
if (!ev_e) {
fprintf(stderr, "failed to create a ev_entry object\n");
exit(666);
}
int ret = ev_add(ev, ev_e);
if (ret != 0)
fprintf(stderr, "Cannot add entry to event handler (%d)\n", i);
return;
}
struct ev_wrapper {
struct ev *ev;
struct ev_entry *ev_entry;
};
static void cancel_timer_cb(void *data)
{
struct ev_wrapper *ev_wrapper = data;
int ret = ev_timer_cancel(ev_wrapper->ev, ev_wrapper->ev_entry);
if (ret != 0) {
fprintf(stderr, "failed to cancel timer\n");
exit(EXIT_FAILURE);
}
ev_entry_free(ev_wrapper->ev_entry);
return;
}
/* idea, test that timers are called in strict order and that the
* first timer (fired after 1 seconds) cancel the 5 second timout timer */
static int do_cancel_test(struct ev *ev)
{
int flags = 0;
struct ev_entry *eve1, *eve2;
struct timespec timespec1 = {.tv_sec = 5, .tv_nsec = 0};
struct timespec timespec2 = {.tv_sec = 1, .tv_nsec = 0};
struct ev_wrapper *ev_wrapper;
fprintf(stderr, "run timer cancel test ...");
eve1 = ev_timer_oneshot_new((void *) ×pec1, (void *) timer_cd,
(void *) ev);
if (!eve1) {
fprintf(stderr, "Failed to create a ev_entry object\n");
exit(EXIT_FAILURE);
}
int ret = ev_add(ev, eve1);
if (ret != 0) {
fprintf(stderr, "Cannot add entry to event handler\n");
exit(EXIT_FAILURE);
}
ev_wrapper = malloc(sizeof(struct ev_wrapper));
if (!ev_wrapper) {
perror("malloc");
exit(EXIT_FAILURE);
}
ev_wrapper->ev = ev;
ev_wrapper->ev_entry = eve1;
eve2 = ev_timer_oneshot_new((void *) ×pec2, (void *) cancel_timer_cb,
(void *) ev_wrapper);
if (!eve2) {
fprintf(stderr, "Failed to create a ev_entry object\n");
exit(EXIT_FAILURE);
}
ret = ev_add(ev, eve2);
if (ret != 0) {
fprintf(stderr, "Cannot add entry to event handler\n");
exit(EXIT_FAILURE);
}
ev_loop(ev, flags);
free(ev_wrapper);
fprintf(stderr, " passed\n");
return 1;
}
static void test_timer(void)
{
struct ev *ev = ev_new(0);
if (!ev) {
fprintf(stderr, "Cannot create event handler\n");
return;
}
do_cancel_test(ev);
ev_destroy(ev);
}
struct ctx_timer {
struct ev_entry *eve;
struct ev *ev;
unsigned periodic_runs;
};
struct ctx_timer *ctx_timer_new(void)
{
struct ctx_timer *ctxo = malloc(sizeof(*ctxo));
if (!ctxo) abort();
memset(ctxo, 0, sizeof(*ctxo));
return ctxo;
}
void callback_oneshot(void *data)
{
struct ctx_timer *ctxo = data;
ev_entry_free(ctxo->eve);
free(ctxo);
fprintf(stderr, "callback called\n");
}
static void test_timer_oneshot(void)
{
int flags = 0, ret;
struct ev_entry *eve;
struct ctx_timer *ctxo;
struct timespec ts = {.tv_sec = 1, .tv_nsec = 0};
fprintf(stderr, "Test: oneshot timer\n");
struct ev *ev = ev_new(0);
if (!ev) {
fprintf(stderr, "Cannot create event handler\n");
return;
}
ctxo = ctx_timer_new();
ctxo->ev = ev;
eve = ev_timer_oneshot_new(&ts, callback_oneshot, ctxo);
if (!eve) {
fprintf(stderr, "Failed to create a ev_entry object\n");
exit(EXIT_FAILURE);
}
ctxo->eve = eve;
ret = ev_add(ev, eve);
if (ret != 0) {
fprintf(stderr, "Cannot add entry to event handler\n");
exit(EXIT_FAILURE);
}
// ev_loop will run until the timeout is fired. Which
// in turn is the last event, which will end the ev loop
ev_loop(ev, flags);
ev_destroy(ev);
}
void callback_timer_periodic(unsigned long long missed, void *data)
{
struct ctx_timer *ctxo = data;
fprintf(stderr, "callback timer periodic called %d\n", ctxo->periodic_runs);
if (missed > 1) fprintf(stderr, "missed timer events: %llu\n", missed - 1);
ctxo->periodic_runs--;
if (ctxo->periodic_runs == 0) {
// finish, enough testing
int ret = ev_timer_cancel(ctxo->ev, ctxo->eve);
if (ret < 0) {
fprintf(stderr, "failed to cancel timer\n");
exit(EXIT_FAILURE);
}
ev_entry_free(ctxo->eve);
free(ctxo);
}
}
static void test_timer_periodic(void)
{
int flags = 0;
struct timespec ts = {.tv_sec = 1, .tv_nsec = 0};
fprintf(stderr, "Test: periodic timer\n");
struct ev *ev = ev_new(0);
if (!ev) {
fprintf(stderr, "Cannot create event handler\n");
return;
}
struct ctx_timer *ctxo = ctx_timer_new();
ctxo->ev = ev;
ctxo->periodic_runs = 5;
struct ev_entry *eve =
ev_timer_periodic_new(&ts, callback_timer_periodic, ctxo);
if (!eve) {
fprintf(stderr, "Failed to create a ev_entry object\n");
exit(EXIT_FAILURE);
}
ctxo->eve = eve;
int ret = ev_add(ev, eve);
if (ret != 0) {
fprintf(stderr, "Cannot add entry to event handler\n");
exit(EXIT_FAILURE);
}
// ev_loop will run until the timeout is fired. Which
// in turn is the last event, which will end the ev loop
ev_loop(ev, flags);
ev_destroy(ev);
}
static uint32_t events = EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP;
void fd_cb_raw(int fd, uint32_t events_ret, void *priv_data)
{
struct ev *ev = priv_data;
(void) fd;
assert(events == events_ret);
ev_run_out(ev);
}
void test_events_raw(void)
{
struct ev *ev;
int flags = 0, ret;
struct ev_entry *eve;
int pipefd[2];
fprintf(stderr, "Test: raw events\n");
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
ev = ev_new(0);
if (!ev) {
fprintf(stderr, "Cannot create event handler\n");
return;
}
eve = ev_entry_new_raw(pipefd[0], events, fd_cb_raw, ev);
if (!eve) {
fprintf(stderr, "Failed to create a ev_entry object\n");
exit(EXIT_FAILURE);
}
ret = ev_add(ev, eve);
if (ret != 0) {
fprintf(stderr, "Cannot add entry to event handler\n");
exit(EXIT_FAILURE);
}
write(pipefd[1], "1", 1);
// ev_loop will run until the timeout is fired. Which
// in turn is the last event, which will end the ev loop
ev_loop(ev, flags);
ev_entry_free(eve);
ev_destroy(ev);
}
int main(void)
{
test_timer_oneshot();
test_timer_periodic();
test_timer();
test_events_raw();
return EXIT_SUCCESS;
}
```
請依據預期執行輸出,補完程式碼。
==作答區== (單選)
CCC = ?
* `(a)` 0
* `(b)` 1
DDD = ?
* `(a)` 0
* `(b)` 1
EEE = ?
* `(a)` 0
* `(b)` 1
:::success
延伸問題:
1. 解釋上述程式碼運作的原理
2. 移除 [signalfd](https://man7.org/linux/man-pages/man2/signalfd4.2.html) 的使用,改寫程式碼,使得能夠比照 [Benchmarking libevent against libev](http://libev.schmorp.de/bench.html),進行效能分析
:::