# 2021-04-06 Julian-Chu
contributed by < [Julian-Chu](https://github.com/Julian-Chu) >
###### tags: `linux2021`
> [GitHub](https://github.com/Julian-Chu/linux2021-quiz7)
## Part1: Unix like shell
```cpp=
/* Recursively run right-most part of the command line printing output to the
* file descriptor @t
*/
static void run(char *c, int t)
{
char *redir_stdin = NULL, *redir_stdout = NULL;
int pipefds[2] = {0, 0}, outfd = 0;
char *v[99] = {0};
char **u = &v[98]; /* end of words */
for (;;) {
// move to last non-null char
c--;
if (is_delim(*c)) /* if NULL (start of string) or pipe: break */
break;
if (!is_special(*c)) {
// move back to null termination
c++; /* Copy word of regular chars into previous u */
/* 在此提交你的程式碼 */
*c = '\0';
c--;
u--;
while(!is_special(*c)){
c--;
}
*u = (c+1);
}
if (is_redir(*c)) { /* If < or > */
if (*c == '<')
redir_stdin = *u;
else
redir_stdout = *u;
if ((u - v) != 98)
u++;
}
}
if ((u - v) == 98) /* empty input */
return;
```
#### 解說:
原本 c 可能會是 `"0echo hi\0"`, 經過處理後變成 `"0echo\0hi\0"`
然後將v[index] 指向 c 裡面每個命令開頭 char 的位置,
`v[98] = "hi\0"`
`v[97] = "echo\0"`
這樣 v 就可以符合 `execvp` 的參數簽章
#### ref
`man pipe`
`man execvp`: 傳入命令字串陣列
## Part2: httpd
```cpp=
static void dequeue(queue_t *q, int *fd)
{
node_t *old_head;
pthread_mutex_lock(q->head_lock);
/* Wait until signaled that queue is non_empty.
* Need while loop in case a new thread manages to steal the queue
* element after the waiting thread is signaled, but before it can
* re-acquire head_lock.
*/
/* 在此提交你的程式碼 */;
while(q->head == q->tail) pthread_cond_wait(q->non_empty, q->head_lock);
old_head = q->head->next;
q->head = q->head->next;
q->size--;
*fd = old_head->fd;
pthread_mutex_unlock(q->head_lock);
free(old_head);
}
```
問題1: 執行緒函式 greeter_routine 和 worker_routine 在上述網頁伺服器中,分別負責什麼功能?
* greeter: 負責取得可用的 socket connection, 將 connection 轉換成 file descriptor 後放入 queue
* worker: 從 queue 之中取得可用的 fd, 從 fd 中讀取資料, 做 request 的解析, 並回傳 response
問題2: enqueue 和 dequeue 是單向鏈結串列 (singly-linked list) 所實作的佇列 (queue) 裡頭的基本操作,你如何驗證自己的實作能在並行環境正確無誤?
答: 利用 [Thread Sanitizer](https://clang.llvm.org/docs/ThreadSanitizer.html) 看在 benchmark 的情況下, 是否有發生 data race 的情況
---
## Part3: ringbuffer
```cpp=
/* 提供你的實作 */
#define ALIGN_FLOOR(val, align) (((typeof(val))(val + ((typeof(val))align-1)))&(~((typeof(val))align-1)))
static inline int ringbuffer_sp_do_enqueue(ringbuf_t *r,
void *const *obj_table,
const unsigned n)
{
uint32_t mask = r->prod.mask;
uint32_t prod_head = r->prod.head, cons_tail = r->cons.tail;
/* The subtraction is done between two unsigned 32-bits value (the result
* is always modulo 32 bits even if we have prod_head > cons_tail). So
* @free_entries is always between 0 and size(ring) - 1.
*/
uint32_t free_entries = mask + cons_tail - prod_head;
/* check that we have enough room in ring */
if ((n > free_entries))
return -ENOBUFS;
uint32_t prod_next = prod_head + n;
r->prod.head = prod_next;
/* write entries in ring */
ENQUEUE_PTRS();
__compiler_barrier();
r->prod.tail = prod_next;
/* if we exceed the watermark */
/* 請提交你的實作 */
if((cons_tail - prod_head + 1 + n) > r->prod.watermark){
return -EDQUOT;
}
return 0;
}
```
:::warning
q1: 這邊有疑問的是`ALIGN_FLOOR`, 因為我的想法對齊應該是 round up/CEIL, 所以實作上是找到下一個 64byte 的倍數, 但就跟 macro 的命名不合?
:::
問題1: 上述程式碼的 watermark 有何作用?跟 lock-less 演算法有何關聯?
答:標記已處理資料的位置, 避免覆蓋掉尚未處理的資料。與 lock-less 演算法的關係, CAS的判斷條件(?)
問題2: 除了題目提到的 SPSC (single-producer and single-consumer),該如何擴展為 single producer + multiple consumer (SPMC) 和 single producer + multiple consumer (MPMC) 呢?請簡述你的手段,都針對 fixed-size queue。
答: 利用 lock 或是 atomic operation 避免 data race
## Part4: mbus
```cpp=
static bool execute_client_callback(bus_client_t *client, void *msg)
{
/* Load the client with which we are attempting to communicate. */
bus_client_t local_client;
__atomic_load(client, &local_client, __ATOMIC_SEQ_CST);
/* Loop until reference count isupdated or client becomes unregistered */
while (local_client.registered) {
/* The expected reference count is the current one + 1 */
bus_client_t new_client = local_client;
++(new_client.refcnt);
/* If CAS succeeds, the client had the expected reference count, and
* we updated it successfully. If CAS fails, the client was updated
* recently. The actual value is copied to local_client.
*/
/* 提交你的實作 */ ;
if(CAS(&client->refcnt, &new_client.refcnt, &local_client.refcnt)){
client->callback(client->ctx, msg);
return true;
}
__atomic_load(&client->refcnt, &local_client.refcnt, __ATOMIC_SEQ_CST);
}
/* Client was not registered or got unregistered while we attempted to send
* a message
*/
return false;
}
```
問題1: mbus.c 貌似彆扭的 callback function 設計,能夠解決什麼問題?
答:解耦執行緒之間的通訊跟資料傳送, e.g. thread 1不需要知道 thread 2 的具體函數即可以調用
問題2: 考慮到訊息處理的數量可能相當大,我們可事先建立 thread pool 並透過 Processor affinity 讓執行緒在執行 callback function 時,儘量不要相互干擾。請簡述你打算如何修改程式碼,以實現這目標,可斟酌列出關鍵程式碼。
答: 可考慮在 thread_func 引入`sched_setaffinity`指令, 並在 ctx 指定要綁定的 CPU ID