# 2019q1 Homework1 (lab0)
###### tags: `sysprog2019` `dev_record`
contributed by < `HTYISABUG` >
## Environment
```shell
$ uname -a
Linux GL552VW 4.15.0-45-generic #48-Ubuntu SMP Tue Jan 29 16:28:13 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 94
Model name: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
Stepping: 3
CPU MHz: 3091.593
CPU max MHz: 3500.0000
CPU min MHz: 800.0000
BogoMIPS: 5184.00
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 6144K
NUMA node0 CPU(s): 0-7
```
## Implementation
### queue_t
為了滿足 `q_insert_tail` & `q_size` 時間複雜度 $O(1)$ 的要求
在 structure 內補上 tail & size 以取得 queue 的尾端與長度
```clike=
typedef struct {
list_ele_t *head;
list_ele_t *tail;
size_t size;
} queue_t;
```
### q_new
依據註解補上 allocation error 的判斷式
以及 member 的初始化
### q_free
先確認 queue 不為 `NULL`
再以迴圈將各個 element 釋放
最後才釋放 queue
```clike=
void q_free(queue_t *q)
{
if (!q) {
return;
}
list_ele_t *indic = q->head;
while (indic) {
q->head = indic->next;
free(indic->value);
free(indic);
indic = q->head;
}
free(q);
}
```
### q_insert_head
依序判斷 queue 不為 `NULL` 、malloc 成功與否
如果字串複製失敗,就將 element 釋放並回傳錯誤
最後更新 queue 的內容
```clike=
bool q_insert_head(queue_t *q, char *s)
{
list_ele_t *newh;
// if (!q || !(newh = malloc(sizeof(list_ele_t)))) {
if ((!q) || !(newh = malloc(sizeof(list_ele_t)))) {
return false;
}
if (!(newh->value = strdup(s))) {
free(newh);
return false;
}
if (!q->tail) {
q->tail = newh;
}
newh->next = q->head;
q->head = newh;
q->size++;
return true;
}
```
第四行的寫法在 commit 時會報錯 memory leak
檢查後發現是括號導致的執行順序錯誤
OR operator 兩側都以括號包裝即可解決
### q_insert_tail
以 tail 直接取得尾端
其餘操作與 `q_insert_tail` 相同
```clike=
bool q_insert_tail(queue_t *q, char *s)
{
list_ele_t *newt;
// if (!q || !(newt = malloc(sizeof(list_ele_t)))) {
if ((!q) || !(newt = malloc(sizeof(list_ele_t)))) {
return false;
}
if (!(newt->value = strdup(s))) {
free(newt);
return false;
}
newt->next = NULL;
q->tail->next = newt;
q->tail = newt;
q->size++;
return true;
}
```
第四行的問題同 `q_insert_head`
以括號包裝可以解決
### q_remove_head
補上 queue 不為 `NULL` 或空的判斷
更新 queue 的 member data
新增字串複製以及記憶體釋放的部份
```clike=
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if (!q || !q->head) {
return false;
}
list_ele_t *head = q->head;
q->head = q->head->next;
q->size--;
if (sp) {
strncpy(sp, head->value, bufsize - 1);
sp[bufsize-1] = '\0';
}
free(head->value);
free(head);
return true;
}
```
### q_size
以 queue 的 member `q_size` 直接取得長度
以達成時間複雜度 $O(1)$ 的要求
### q_reverse
同樣補上 queue 不為 `NULL` 或空的判斷
用兩個新指標來儲存新的 head & tail
再循序將 elements 重新指向
```clike=
void q_reverse(queue_t *q)
{
if (!q || !q->head) {
return;
}
list_ele_t *newh = NULL, *newt = q->head;
list_ele_t *indic = q->head;
while (indic) {
indic = indic->next;
q->head->next = newh;
newh = q->head;
q->head = indic;
}
q->head = newh;
q->tail = newt;
}
```
實作方法示意圖如下

## Auto Judge Program
使用 `cscope` 幫助追蹤程式流程
### Procedure Analysis
- `queue_init`
- 初始化 queue
- 設定 signal handler
- `init_cmd`
- 初始化 shell 指令
- `console_init`
- 增加 queue API 到 shell 指令
- `run_console`
- shell 開始實際執行
### Detail
#### Signal Handler
皆為 `trigger_exception` 的 wrapper
`trigger_exception` 使用 `siglongjmp` 返回至 main loop
返回點則是在設定 shell 指令時使用 `sigsetjmp`
紀錄當下的 stack pointer 、 instruction pointer 等資訊
之所以不使用 `setjmp` 、 `longjmp` 是因為這裡牽涉到 signal 遮罩
如果使用以上兩個 function 會導致 signal handler 失效
從這幾個 function 的行為來看
我猜想它們在 kernel 內的定位是用在 context switch 的部份
#### Commands & Parameters
設定中以 singly linked list 儲存新增的項目
#### Quit Helper
為一方便增加程式結束時的處理的方法
用這方法能避免增加處理時需要修改多個檔案的問題
#### Waiting for input
相比一般等待使用者輸入會用 `scanf` 等來自 standard I/O 的 function
`qtest` 中使用了 `select` 作為替代方案
`select` 的用途是監視 file descriptor 是否可用
包含 input, output, except 皆可**同時**監視
雖然在 `qtest` 中只用於監視單一 input 來源
但這種監視多個 file descriptor 的功能
我猜想或許在任務排程時會有作用
### Memory Leak When Input File Not Exist
在了解程式運作流程的時候
因為打錯檔名而意外發現檔案不存在時會產生 memory leak
Trace 程式碼發現是這種情況下會因為 `run_console` 回傳 false
而使 `ok` 變為 false
進而導致下一行的 `finish_cmd` 不被執行
修改 `finish_cmd` 與 `ok` 的順序來修復這個問題
:::warning
嘗試提交 pull request
:notes: jserv
:::
```clike=
// qtest.c
int main(int argc, char *argv[])
{
...
bool ok = true;
ok = ok && run_console(infile_name);
// ok = ok && finish_cmd();
ok = finish_cmd() && ok;
return ok ? 0 : 1;
}
```
### Invalid Data Type
以下節錄自 `ISOIEC 9899:201x` 6.7.6.2 節 陣列宣告
> If the expression is a constant expression,
> it shall have a value greater than zero.
而 `harness.c` 中這個 struct 違反這段的規定
不知道是不是我搞錯什麼了
```clike=
typedef struct BELE {
struct BELE *next;
struct BELE *prev;
size_t payload_size;
/* Marker to see if block seems legitimate */
size_t magic_header;
/* Invalid ? */
unsigned char payload[0];
/* Also place magic number at tail of every block */
} block_ele_t;
```
追加:
看來這個寫法來自 [GCC extension](https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html)
作為動態指定 struct member 大小的方法使用
這樣同時還能確保記憶體的連續
對應 C99 的 flexible array member
操作方法基本相同,需要預留足夠的記憶體
但如果沒有預留任何記憶體給 flexible array member
這樣會導致 undefined behavior
> If this array would have no elements,
> it behaves as if it had one element but the behavior is undefined
> if any attempt is made to access that element
> or to generate a pointer one past it.