# [2021q1](http://wiki.csie.ncku.edu.tw/linux/schedule) 第 13 週測驗題
###### tags: `linux2021`
:::info
目的: 檢驗學員對 ==[RCU 同步機制](https://hackmd.io/@sysprog/linux-rcu)== 的認知
:::
==[作答表單](https://docs.google.com/forms/d/e/1FAIpQLSeU8kGCycQ9kNK8X9PHSBquv9x75QXB4q8UQ7hRETlXnovfbg/viewform)==
### 測驗 `1`
以下程式碼嘗試將 [RCU 同步機制](https://hackmd.io/@sysprog/linux-rcu) 在使用者層級重新實作,類似 [userspace RCU](https://liburcu.org/),但更精簡,著重 linked list + RCU:
```cpp
/* A concurrent linked list utilizing the simplified RCU algorithm */
#include <stdbool.h>
typedef struct rcu_list rcu_list_t;
typedef struct {
struct list_node *entry;
} iterator_t;
typedef struct {
struct rcu_list *list;
struct zombie_node *zombie;
} rcu_handle_t;
typedef rcu_handle_t read_handle_t;
typedef rcu_handle_t write_handle_t;
typedef void (*deleter_func_t)(void *);
typedef bool (*finder_func_t)(void *, void *);
#define _GNU_SOURCE
#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h>
typedef struct list_node {
bool deleted;
struct list_node *next, *prev;
void *data;
} list_node_t;
typedef struct zombie_node {
struct zombie_node *next;
struct list_node *zombie;
rcu_handle_t *owner;
} zombie_node_t;
struct rcu_list {
pthread_mutex_t write_lock; /* exclusive lock acquired by writers */
list_node_t *head, *tail; /* head and tail of the "live" list */
zombie_node_t *zombie_head; /* head of the zombie list */
deleter_func_t deleter;
};
static list_node_t *make_node(void *data);
static zombie_node_t *make_zombie_node(void);
static void lock_for_write(rcu_list_t *list);
static void unlock_for_write(rcu_list_t *list);
static rcu_list_t *list_new_with_deleter(deleter_func_t deleter)
{
if (!deleter)
return NULL;
rcu_list_t *list = malloc(sizeof(rcu_list_t));
if (!list)
return NULL;
if (pthread_mutex_init(&list->write_lock, NULL) != 0) {
free(list);
return NULL;
}
list->head = list->tail = NULL;
list->zombie_head = NULL;
list->deleter = deleter;
return list;
}
static void list_free(void *arg)
{
rcu_list_t *list = arg;
for (list_node_t *iter = list->head; iter;) {
list_node_t *tmp = iter->next;
free(iter->data);
free(iter);
iter = tmp;
}
free(list);
}
rcu_list_t *list_new(void)
{
return list_new_with_deleter(list_free);
}
void list_delete(rcu_list_t *list)
{
if (!list || !list->deleter)
return;
list->deleter(list);
}
void list_push_front(rcu_list_t *list, void *data, write_handle_t *handle)
{
if (!list)
return;
list_node_t *node = make_node(data);
if (!node)
return;
lock_for_write(list);
list_node_t *old_head;
__atomic_load(&list->head, &old_head, __ATOMIC_RELAXED);
if (!old_head) {
/* list is currently empty */
__atomic_store(&list->head, &node, __ATOMIC_SEQ_CST);
__atomic_store(&list->tail, &node, __ATOMIC_SEQ_CST);
} else {
/* general case */
__atomic_store(&node->next, &old_head, __ATOMIC_SEQ_CST);
__atomic_store(&old_head->prev, &node, __ATOMIC_SEQ_CST);
__atomic_store(&list->head, &node, __ATOMIC_SEQ_CST);
}
unlock_for_write(list);
}
iterator_t list_find(rcu_list_t *list,
void *data,
finder_func_t finder,
read_handle_t *handle)
{
iterator_t iter = {.entry = NULL}; /* initialize an invalid iterator */
if (!list)
return iter;
list_node_t *current;
__atomic_load(&list->head, ¤t, __ATOMIC_SEQ_CST);
while (current) {
if (finder(current->data, data)) {
iter.entry = current;
break;
}
__atomic_load(¤t->next, ¤t, __ATOMIC_SEQ_CST);
}
return iter;
}
iterator_t list_begin(rcu_list_t *list, read_handle_t *handle)
{
iterator_t iter = {.entry = NULL};
if (!list)
return iter;
list_node_t *head;
__atomic_load(&list->head, &head, __ATOMIC_SEQ_CST);
iter.entry = head;
return iter;
}
void *iterator_get(iterator_t *iter)
{
return (iter && iter->entry) ? iter->entry->data : NULL;
}
read_handle_t list_register_reader(rcu_list_t *list)
{
read_handle_t handle = {.list = list, .zombie = NULL};
return handle;
}
write_handle_t list_register_writer(rcu_list_t *list)
{
write_handle_t handle = {.list = list, .zombie = NULL};
return handle;
}
void rcu_read_lock(read_handle_t *handle)
{
zombie_node_t *z_node = make_zombie_node();
z_node->owner = handle;
handle->zombie = z_node;
rcu_list_t *list = handle->list;
zombie_node_t *old_head;
__atomic_load(&list->zombie_head, &old_head, __ATOMIC_SEQ_CST);
do {
__atomic_store(&z_node->next, &old_head, __ATOMIC_SEQ_CST);
} while (!__atomic_compare_exchange(&list->zombie_head, &old_head, &z_node,
true, __ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST));
}
void rcu_read_unlock(read_handle_t *handle)
{
zombie_node_t *z_node = handle->zombie;
zombie_node_t *cached_next;
__atomic_load(&z_node->next, &cached_next, __ATOMIC_SEQ_CST);
bool last = true;
/* walk through the zombie list to determine if this is the last active
* reader in the list.
*/
zombie_node_t *n = cached_next;
while (n) {
list_node_t *owner;
__atomic_load(&n->owner, &owner, __ATOMIC_SEQ_CST);
if (owner) {
last = false; /* this is not the last active reader */
break;
}
__atomic_load(XXX, &n, __ATOMIC_SEQ_CST);
}
n = cached_next;
if (last) {
while (n) {
list_node_t *dead_node = n->zombie;
free(dead_node);
zombie_node_t *old_node = n;
__atomic_load(&n->next, &n, __ATOMIC_SEQ_CST);
free(old_node);
}
__atomic_store(YYY, &n, __ATOMIC_SEQ_CST);
}
void *null = NULL;
__atomic_store(&z_node->owner, &null, __ATOMIC_SEQ_CST);
}
void rcu_write_lock(write_handle_t *handle)
{
rcu_read_lock(handle);
}
void rcu_write_unlock(write_handle_t *handle)
{
rcu_read_unlock(handle);
}
static list_node_t *make_node(void *data)
{
list_node_t *node = malloc(sizeof(list_node_t));
if (!node)
return NULL;
node->data = data;
node->next = node->prev = NULL;
node->deleted = false;
return node;
}
static zombie_node_t *make_zombie_node(void)
{
zombie_node_t *z_node = malloc(sizeof(zombie_node_t));
if (!z_node)
return NULL;
z_node->zombie = NULL;
z_node->owner = NULL;
z_node->next = NULL;
return z_node;
}
static void lock_for_write(rcu_list_t *list)
{
pthread_mutex_lock(&list->write_lock);
}
static void unlock_for_write(rcu_list_t *list)
{
pthread_mutex_unlock(&list->write_lock);
}
/* test program starts here */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct dummy {
int a, b;
} dummy_t;
static dummy_t *make_dummy(int a, int b)
{
dummy_t *dummy = malloc(sizeof(dummy_t));
dummy->a = a, dummy->b = b;
return dummy;
}
static bool finder(void *x, void *y)
{
dummy_t *dx = x, *dy = y;
return (dx->a == dy->a) && (dx->b == dy->b);
}
static void *reader_thread(void *arg)
{
rcu_list_t *list = arg;
read_handle_t reader = list_register_reader(list);
rcu_read_lock(&reader);
/* read from list here */
iterator_t iter = list_find(list, &(dummy_t){1, 1}, finder, &reader);
void *data = iterator_get(&iter);
assert(data);
iter = list_find(list, &(dummy_t){2, 2}, finder, &reader);
data = iterator_get(&iter);
assert(data);
iterator_t first = list_begin(list, &reader);
data = iterator_get(&first);
assert(data);
dummy_t *as_d2 = data;
assert(as_d2->a == 2);
assert(as_d2->b == 2);
assert(iter.entry == first.entry);
rcu_read_unlock(&reader);
return NULL;
}
static void *writer_thread(void *arg)
{
dummy_t *d1 = make_dummy(1, 1);
dummy_t *d2 = make_dummy(2, 2);
rcu_list_t *list = arg;
write_handle_t writer = list_register_writer(list);
rcu_write_lock(&writer);
/* write to list here */
list_push_front(list, d1, &writer);
list_push_front(list, d2, &writer);
rcu_write_unlock(&writer);
return NULL;
}
#define N_READERS 10
int main(void)
{
rcu_list_t *list = list_new();
dummy_t *d0 = make_dummy(0, 0);
list_push_front(list, d0, NULL);
pthread_t t0, t_r[N_READERS];
pthread_create(&t0, NULL, writer_thread, list);
for (int i = 0; i < N_READERS; i++)
pthread_create(&t_r[i], NULL, reader_thread, list);
for (int i = 0; i < N_READERS; i++)
pthread_join(t_r[i], NULL);
pthread_join(t0, NULL);
list_delete(list);
return EXIT_SUCCESS;
}
```
預期程式執行過程不會遇到 assert 失敗。
請補完原始程式碼,使其運作符合預期。
參考資訊:
* [Using RCU to Protect Read-Mostly Linked Lists](https://www.kernel.org/doc/html/latest/RCU/listRCU.html)
==作答區==
XXX = ?
YYY = ?
:::success
延伸問題:
1. 解釋上述程式碼運作原理,指出設計和實作的缺陷,並予以改進
2. 從 [userspace RCU](https://liburcu.org/) 抽離出獨立可執行的程式碼 (一如本測驗題內附的程式碼所為),儘量保持 Linux 核心原始程式碼中 RCU 的風格
3. 解釋 RCU 在 Linux 核心的應用案例,至少要涵蓋 [include/linux/hashtable.h](https://github.com/torvalds/linux/blob/master/include/linux/hashtable.h)
:::