# 2019q1 第 4 週測驗題 (下) :::info 目的: 檢驗學員對 C 程式設計的認知 ::: --- ### 測驗 `1` 考慮略為複雜的 reference counting 實作,如下: ```clike #include <limits.h> #include <stddef.h> #include <stdlib.h> /** * Destructor callback. * \param ptr Pointer to space to destroy and free */ typedef void (*rc_dtor)(void *ptr); typedef int volatile refcnt; #define REFCOUNTER_MAX (INT_MAX / 2) /* FIXME: reference counting should be robust against race conditions in * multi-threaded applications. Use C11 atomics. */ static inline refcnt refcnt_acquire(refcnt *dest) { return (*dest += 1) - 1; } static inline refcnt refcnt_release(refcnt *dest) { return (*dest -= 1) + 1; } struct refcnt_entry { refcnt n; rc_dtor dtor; void *ptr; }; /** * \brief Allocate some reference counted space * \param sz Size of space in bytes * \param dtor Destructor to use in case of free * \return the space on sucess, NULL otherwise */ void *rc_malloc(size_t sz, rc_dtor dtor) { size_t const static rc_offset = offsetof(struct refcnt_entry, ptr); size_t const static max_size = (WW) - offsetof(struct refcnt_entry, ptr); if (sz >= max_size) goto reject; void *out = malloc(sz + rc_offset); if (out) { struct refcnt_entry *const outref = (struct refcnt_entry *) out; outref->n = 1; outref->dtor = dtor; return XX; } reject: return NULL; } /** * \brief Acquire a reference to some space * \param ptr pointer, previously allocated with `rc_malloc` * \return the same pointer on sucess, NULL otherwise */ void *rc_acquire(void *ptr) { size_t const static rc_offset = offsetof(struct refcnt_entry, ptr); struct refcnt_entry *const ptrref = (struct refcnt_entry *) (void *) (((char *) ptr) - rc_offset); /* try to acquire a reference */ refcnt next = refcnt_acquire(&ptrref->n); if (YY) return ptr; /* artifically refuse the reference */ refcnt prev = refcnt_release(&ptrref->n); if (prev == 1) { if (ptrref->dtor) (*ptrref->dtor)(ptr); free(ptrref); } return NULL; } /** * \brief Release a reference to some space * \param ptr pointer, previously allocated with `rc_malloc` * \note If the reference count drops to zero, the space is freed. */ void rc_release(void *ptr) { size_t const static rc_offset = offsetof(struct refcnt_entry, ptr); if (!ptr) return; struct refcnt_entry *const ptrref = (struct refcnt_entry *) ZZ; /* release the reference */ refcnt prev = refcnt_release(&ptrref->n); if (prev == 1) { if (ptrref->dtor) (*ptrref->dtor)(ptr); free(ptrref); } } /* -- unit test -- */ #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> static void test_destructor(void *); static void *test_thread(void *); struct test_box *test_constructor(int j); struct test_box { int j; }; struct test_box *test_boxes[10] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; int test_repeat_count = 1000000; struct test_box *test_constructor(int j) { struct test_box *box = rc_malloc(sizeof(struct test_box), &test_destructor); assert(box != NULL); box->j = j; fprintf(stderr, "created item %d\n", box->j); return box; } void test_destructor(void *arg) { struct test_box *const box = (struct test_box *) arg; fprintf(stderr, "destroying item %d\n", box->j); box->j = -1; return; } void *test_thread(void *arg) { int const thread_id = *(int *) arg; int const repeat_count = test_repeat_count; int personal_refs[10]; int faults = 0; int overdrops = 0; int outstanding = 0; fprintf(stderr, "[Thread %i starting]\n", thread_id); for (int i = 0; i < 10; ++i) { personal_refs[i] = 0; } for (int i = 0; i < repeat_count; ++i) { int const step_rand = rand(); int command = step_rand % 2; int j = (step_rand / 2) % 10; if (command == 0) { /* acquire */ struct test_box *box; box = rc_acquire(test_boxes[j]); if (box == test_boxes[j]) { personal_refs[j] += 1; } else faults += 1; } else { /* release */ if (personal_refs[j] > 0) { rc_release(test_boxes[j]); personal_refs[j] -= 1; } else overdrops += 1; } } for (int i = 9; i >= 0; --i) { while (personal_refs[i] > 0) { rc_release(test_boxes[i]); outstanding += 1; personal_refs[i] -= 1; } } fprintf(stderr, "[Thread %i done: %i faults, %i overdrops, %i outstanding]\n", thread_id, faults, overdrops, outstanding); return NULL; } int main(int argc, char **argv) { int corrupt_count = 0; srand((int) (time(NULL))); /* make the boxes */ { for (int j = 0; j < 10; ++j) { test_boxes[j] = test_constructor(j); } } /* run thread start code */ { int thread_i = 0; test_thread(&thread_i); } /* inspect boxes */ { for (int j = 0; j < 10; ++j) { if (test_boxes[j]->j != j) { fprintf(stderr, "Box %i was corrupted.\n", j); corrupt_count += 1; } rc_release(test_boxes[j]); test_boxes[j] = NULL; } } return corrupt_count == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } ``` 預期的輸出如下: (其中一例) ``` created item 0 created item 1 created item 2 created item 3 created item 4 created item 5 created item 6 created item 7 created item 8 created item 9 [Thread 0 starting] [Thread 0 done: 0 faults, 2575 overdrops, 2721 outstanding] destroying item 0 destroying item 1 destroying item 2 destroying item 3 destroying item 4 destroying item 5 destroying item 6 destroying item 7 destroying item 8 destroying item 9 ``` 請補完程式碼。 ==作答區== WW = ? * `(a)` ptr * `(b)` ~((uint8_t) 0U) * `(c)` ~((size_t) 0U) XX = ? * `(a)` outref * `(b)` outref->ptr * `(c)` (*outref)->ptr * `(d)` &(outref->ptr) YY = ? * `(a)` next == REFCOUNTER_MAX * `(b)` next > REFCOUNTER_MAX * `(c)` next < REFCOUNTER_MAX * `(d)` next >= REFCOUNTER_MAX * `(e)` 0 ZZ = ? * `(a)` ptr * `(b)` ~((size_t) 0U) - rc_offset * `(c)` ptr - rc_offset * `(d)` (char *) ptr - rc_offset * `(e)` (void *) (((char *) ptr) - rc_offset) :::success 延伸問題: 1. 在 Linux 核心找出類似的 reference counting 程式碼並解說; 2. 用上述的 reference counting 解釋 `lsmod` 命令輸出的 `Used by` 運作原理,需要分析程式碼並設計實驗 :::