contributed by < BrotherHong
>
$ gcc --version
gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 39 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 16
On-line CPU(s) list: 0-15
Vendor ID: GenuineIntel
Model name: 13th Gen Intel(R) Core(TM) i7-13620H
CPU family: 6
Model: 186
Thread(s) per core: 2
Core(s) per socket: 10
Socket(s): 1
Stepping: 2
CPU(s) scaling MHz: 10%
CPU max MHz: 4900.0000
CPU min MHz: 400.0000
BogoMIPS: 5836.80
Virtualization features:
Virtualization: VT-x
Caches (sum of all):
L1d: 416 KiB (10 instances)
L1i: 448 KiB (10 instances)
L2: 9.5 MiB (7 instances)
L3: 24 MiB (1 instance)
NUMA:
NUMA node(s): 1
NUMA node0 CPU(s): 0-15
commit 726f122
head
的記憶體空間時有可能會因為失敗而回傳 NULL
,這是我以前時常沒注意到的地方if (head) {
INIT_LIST_HEAD(head);
}
commit 4962106
queue
使用到的記憶體
head
本身為 NULL
直接結束list.h
中的 list_for_each_entry_safe
將所有 element_t
用到的記憶體釋放掉head
本身使用的記憶體插入一個 element
到 queue
的頭/尾
head
本身為 NULL
直接回傳 false
element
用,並初始化 list
false
value
用
element
的記憶體,再回傳 false
value
中list
放入 queue
完成插入操作程式碼片段:配置記憶體給字串,並將字串複製進 value
element
用到的空間先釋放掉再回傳 size_t len = strlen(s);
if (!(e->value = malloc(sizeof(char) * (len + 1))))
return false;
strncpy(e->value, s, len);
e->value[len] = '\0';
insert
操作重寫並簡化了字串複製操作
e->value = strdup(s);
if (!e->value) {
free(e);
return false;
}
queue
的頭/尾移除一個 element
,並回傳被 remove 的 element
queue
是否為 NULL
或 empty
sp
為 non-NULL
,將 value
複製至 sp
queue
中 remove 該 element
並回傳commit 60c86c8
queue
中有多少個 element
存在
len
來記錄數量commit 32977aa
刪除 queue
中位於中間的 element
queue
的長度為 n
則刪除第 ⌊n / 2⌋
個 element
(0-based indexing)queue
要刪除的點就是中間那個;偶數數量則是切半後右半邊最靠近中間的點
0 1 2 3 4
刪除的是 2
0 1 2 3 4 5
刪除的是 3
front
和 back
指標分別由前和後同時往中間移動,直到兩者指向同個元素或相鄰為止queue
中移除並釋放與其相關的記憶體程式碼片段:尋找中間的元素
while
迴圈結束後,back
會指向我們要找的點 struct list_head *front = head->next, *back = head->prev;
while (front != back && front->next != back) {
front = front->next;
back = back->prev;
}
commit 576aa75
queue
中所有連續且 value
相同的元素全部刪除
1 2 2 4 3 4 4 4 5
會變成 1 4 3 5
list
tmp_head
,用來記錄要保留的元素queue
的前端往後找 value
相同的元素直到不相同為止node
指向第一個元素,並使用 last
指標來記錄連續相同元素的尾端的下一個元素
1 1 1 2(<last) 3
node->next == last
代表沒有重複發生
node
放入 tmp_head
node->next != last
代表有連續重複的元素
last
head
為空時表示已經將所有連續重複的元素都移除並存在 tmp_head
裡面,最後利用 list_splice
把所有元素移至 head
完成操作commit 03e540e
queue
中由前往後兩兩相鄰的元素不重疊地互換位置,多餘的則維持原樣
1 2 3 4 5 6 7
經過操作後會變成 2 1 4 3 6 5 7
node
來指向指向兩兩相鄰的第一個節點
node1
和 node2
分別表示相鄰的左和右節點node1
用 list_del
把它從 queue
中移除,再把它插入在 node2
後面node1
的下一個節點q_swap
的要求commit 6853cdb
queue
中的元素順序反轉
head
)的 next
和 prev
分別指向 prev
和 next
指到的節點就能達成list.h
中的 list_for_each_safe
會保存下一個節點的特性,直接對每個節點做上述的操作head
也做一樣的操作,因為迴圈不包含它commit b0d2f49
queue
中的元素由前往後每 K
個一組並反轉其順序,不足 K
個則維持原樣
reverse
的想法:由後往前依序將每個元素插入尾端即能完成反轉queue
的前端開始找 K
個元素,並依照由後往前的順序移除並插入 queue
的尾端K
個元素,則依照原順序移除並插入尾端last
紀錄尾端的元素last
則代表已到尾端commit 18b65b8
queue
的元素
merge sort
來實現fast-slow pointer
找到中間點,把左半邊放到 list_left
,右半邊繼續放在 head
q_merge_two
將兩個 list
合併commit 0314025
queue
中的元素呈現單調遞增/遞減的樣子
queue
為一個 stack,令前端為 stack 的底部,node
指向 stack 的 topnode
從第一個元素持續往尾端前進,每次移動時就像是往 stack push 一個元素commit bbbce6f
k
個 sorted list
合併成一個 sorted list
head
找出該鏈結串列所有節點所在的 queue_context
q_merge_two
一個一個合併到第一個 list
中qtest
提供新的命令 shuffle
commit 7b9debb
void q_shuffle(struct list_head *head)
{
int len = q_size(head);
struct list_head *new = head->prev;
while (len) {
int random = rand() % len;
struct list_head *old = head->next;
while (random--) {
old = old->next;
}
/* Swap value */
element_t *old_entry = list_entry(old, element_t, list);
element_t *new_entry = list_entry(new, element_t, list);
char *tmp = old_entry->value;
old_entry->value = new_entry->value;
new_entry->value = tmp;
len--;
new = new->prev;
}
}
qtest
ADD_COMMAND(shuffle, "Shuffle the nodes of the queue", "");
shuffle
執行測試程式得到以下結果
Expectation: 41666
Observation: {'1234': 41553, '1243': 41674, '1324': 41688, '1342': 41988, '1423': 41841, '1432': 41575, '2134': 41667, '2143': 41670, '2314': 41819, '2341': 41410, '2413': 41431, '2431': 41861, '3124': 41531, '3142': 41824, '3214': 41577, '3241': 41697, '3412': 41595, '3421': 41622, '4123': 41437, '4132': 41476, '4213': 41477, '4231': 41821, '4312': 42022, '4321': 41744}
chi square sum: 16.27883646138338
使用 matplotlib
繪製成圖表如下
想要知道這個 shuffle 的結果是否是 Uniform distribution,所以進行以下假設檢定:
論文提出了一種方法可以檢測一支程式是否以常數時間 (constant time) 執行,並且此種方法是基於洩漏檢測 (leakage detection) 的方式,有別於以往許多方法需要以模擬硬體的方式來實現 (如:ctgrind
,ctverif
)。
TIMING LEAKAGE DETECTION
Our approach in a nutshell is to perform a leakage detection test on the execution time.
簡單來說,論文使用的方法就是對程式的執行時間 (execution time) 做洩漏檢測。
問題
為何對執行時間做洩漏檢測就能判斷是否是以常數時間來執行?
怎樣的實驗結果才算是時間洩漏 (timing leakage)?
時間洩漏 (timing leakage) 的定義是什麼?
實現方法可以分為三個步驟:
TODO…
qtest
的 simulation 實作主要執行測試的起點在 fixture.c
中的 test_const
方法
TEST_TRIES = 10
ENOUGH_MEASURE = 10000
N_MEASURES = 150
DROP_SIZE = 20
static bool test_const(char *text, int mode)
{
bool result = false;
t = malloc(sizeof(t_context_t));
for (int cnt = 0; cnt < TEST_TRIES; ++cnt) {
printf("Testing %s...(%d/%d)\n\n", text, cnt, TEST_TRIES);
init_once();
for (int i = 0; i < ENOUGH_MEASURE / (N_MEASURES - DROP_SIZE * 2) + 1;
++i)
result = doit(mode);
printf("\033[A\033[2K\033[A\033[2K");
if (result)
break;
}
free(t);
return result;
}
各個變數用途
mode
: 分辨測試的操作是哪一個 (insert/remove head/tail)t
: 用來儲存 t-test 所需要的資料
init_once
被初始化result
: 紀錄測試結果是否為 constant time運作流程
test_const
提供了 TEST_TRIES
次機會來檢測該指定操作的執行時間是否是 constant time
每一次機會都會在 doit
這個方法中測量(measure)並檢驗結果數次,將最終(?)結果存在 result
這邊的數次是寫成
ENOUGH_MEASURE / (N_MEASURES - DROP_SIZE * 2) + 1
次,直接展開的話會是10000 - (150 / 20 * 2) + 1 = 9986
次,目前還不知到為何會這樣設定
最終結果?
result = doit(mode);
這行程式碼表示只要迴圈最後一圈的結果為 true
就好了?
只要有一次檢驗結果為 true
即代表此操作程式可能是常數執行時間
在這邊只要至少有一次過,
make test
就會讓你拿到分
fixture.c
: doit 方法
cpu cycles
分別存在 before_ticks
和 after_ticks
before_ticks
和 after_ticks
的差來得到執行時間 exec_times
execc_times
更新統計分析所需的相關參數static bool doit(int mode)
{
/* Allocate memory */
// ...
prepare_inputs(input_data, classes);
bool ret = measure(before_ticks, after_ticks, input_data, mode);
differentiate(exec_times, before_ticks, after_ticks);
update_statistics(exec_times, classes);
ret &= report();
/* Deallocate memory */
// ...
return ret;
}
constant.c
: measure
方法for (size_t i = DROP_SIZE; i < N_MEASURES - DROP_SIZE; i++) {
char *s = get_random_string();
dut_new();
dut_insert_head(
get_random_string(),
*(uint16_t *) (input_data + i * CHUNK_SIZE) % 10000);
int before_size = q_size(l);
before_ticks[i] = cpucycles();
dut_insert_head(s, 1);
after_ticks[i] = cpucycles();
int after_size = q_size(l);
dut_free();
if (before_size != after_size - 1)
return false;
}
lib/list_sort.c
到 lab0-c
A Unix file is a sequence of
bytes:
所有 I/O 裝置,像是網路、硬碟、終端機都被當程式一個 file,所有對裝置的 input/output,都是透過對檔案做 reading/writing 來達成的。
open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(char *filename, int flags, mode_t mode);
close
#include <unistd.h>
int close(int fd);
read
write
size_t 被定義為 unsigned int
ssize_t 是 signed size ,被定義為 int
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t n);
ssize_t write(int fd, const void *buf, size_t n);
如果想要建造一個可依賴的網路應用程式 (如:Web servers),就必須處理好每次 read
write
遇到的 short counts 。
可以自動化解決 short counts 的問題
RIO 提供兩種 functions
rio_readn
、rio_writen