# [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(&timespec, 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 *) &timespec1, (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 *) &timespec2, (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),進行效能分析 :::