主講人: jserv / 課程討論區: 2025 年系統軟體課程
返回「Linux 核心設計」課程進度表Image Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
CMU (位於美國賓州匹茲堡的卡內基美隆大學) 電腦科學系的 Introduction to Computer Systems (ICS) 課程準備 C Programming Lab 作為檢閱學生 C 語言程式設計的認知:
NULL
指標;
在給定的程式碼中,用鏈結串列 (linked list) 來實作佇列 (queue),請詳閱「你所不知道的 C 語言: linked list 和非連續記憶體」,以得知 Linux 核心原始程式碼風格的鏈結串列實作的考量,並研讀 The Linux Kernel API - List Management Functions。
這過程可用下圖來表示:佇列的上層建構於鏈結串列為基礎的物件。一開始,此資料結構僅包含單一成員 head
,之後陸續以鏈結串列增添給定的字串,如 "cab"
, "bead"
, "cab"
等等。佇列內的每個節點由單向的鏈結串列構成,每個節點由 list_ele_t
類型的物件來表示,後者包含真正保存字串的 value
和指向下個鏈結串列節點開頭地址的 next
。
圖 1: 透過鏈結串列實作的佇列。每個鏈結串列節點都有個 value
指標,指向一段連續的記憶體 (即 C-style string,以 NULL 字元結尾的字串),字元依據 ASCII 編碼 (以十六進位顯示)。
C 語言中,字串以連續的char
型態物件的排列來表示,對多數的硬體來說,char
型態表示為 1 個 byte。而為了儲存長度 l
的字串,需要至少 l + 1
個 char
節點,並在最後設定為 \0
字元。上圖的 [cab, bead, cab]
,其中字元 a
和 e
以十六進位表示為 ASCII 編碼的 61
和 65
。觀察上圖,字串 "cab"
和 "bead"
在執行時期可能對應到兩段不相鄰的記憶體。
在給定的 C Programming Lab 程式碼中,佇列透過 queue_t *
指標型態來操作,程式碼需要考慮以下二種特別狀況,作出對應的處置:
NULL
佇列:是指標值為 NULL
;head
) 的值為 NULL
;另一種解讀方式:
若你有空的紅包袋,裡面的東西是 empty;
若你連紅包袋都沒有,就可以說 NULL;
queue.c 僅提供介面 (interface) 但尚未有完整程式實作,需要由學員補完並逐步精進,包含以下:
q_new
: 建立新的「空」佇列;q_free
: 釋放佇列所佔用的記憶體;q_insert_head
: 在佇列開頭 (head) 插入 (insert) 給定的新節點 (以 LIFO 準則);q_insert_tail
: 在佇列尾端 (tail) 插入 (insert) 給定的新節點 (以 FIFO 準則);q_remove_head
: 在佇列開頭 (head) 移去 (remove) 給定的節點;q_remove_tail
: 在佇列尾端 (tail) 移去 (remove) 給定的節點;q_size
: 計算目前佇列的節點總量;q_delete_mid
: 移走佇列中間的節點,詳見 LeetCode 2095. Delete the Middle Node of a Linked Listq_delete_dup
: 在已經排序的狀況,移走佇列中具備重複內容的節點,詳見 LeetCode 82. Remove Duplicates from Sorted List IIq_swap
: 交換佇列中鄰近的節點,詳見 LeetCode 24. Swap Nodes in Pairsq_reverse
: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列節點,換言之,它只能重排已經存在的節點;q_reverseK
: 類似 q_reverse
,但指定每 k 個節點為一組,進行反向順序排列,詳見 LeetCode 25. Reverse Nodes in k-Groupq_sort
: 以遞增順序來排序鏈結串列的節點,可參閱 Linked List Sort 得知實作手法;q_ascend
及 q_descend
: 參閱 LeetCode 2487. Remove Nodes From Linked List,注意節點間的順序q_merge
: 合併所有已排序的佇列,並合而為一個新的已排序佇列,詳見 LeetCode 23. Merge k Sorted Listsremove(B)
操作完成後,就會變成 A 在檔案 queue.c 的程式碼註解可見到更詳盡的資訊,包括如何處理無效的操作 (如,從「空」佇列或 NULL
佇列中刪除),及應具有的副作用和返回值。你的程式碼將透過 1,000,000
個以上節點的佇列進行測試。
「Linux 核心設計」課程延伸 C Programming Lab 並追加以下機制:
It should operate in O(1) time
卻未有自動評分,本課程參照 dudect 工具及對應的論文 Dude, is my code constant time?,提出以實驗而非理論驗證時間複雜度的方法上述的素養對於接觸有一定規模的開放原始碼專案 (特別是 Linux 核心),是非常關鍵的前期訓練。
給定的程式碼在編譯後,會得到名為 qtest
的執行檔,後者提供許多測試這次作業可用的工具,執行 qtest
後,輸入 help
即有各命令的解說。參考執行過程: (以 $
開頭的字串表示應當在命令列執行的命令)
$ ./qtest
cmd> help
Commands:
# ... | Display comment
dedup | Delete all nodes that have duplicate string
descend | Remove every node which has a node with a strictly greater value anywhere to the right side of it
dm | Delete middle node in queue
free | Delete queue
help | Show summary
ih str [n] | Insert string str at head of queue n times. Generate random string(s) if str equals RAND. (default: n == 1)
it str [n] | Insert string str at tail of queue n times. Generate random string(s) if str equals RAND. (default: n == 1)
log file | Copy output to file
merge | Merge all the queues into one sorted queue
new | Create new queue
next | Switch to next queue
option [name val] | Display or set options. See 'Options' section for details
prev | Switch to previous queue
quit | Exit program
reverse | Reverse queue
reverseK [K] | Reverse the nodes of the queue 'K' at a time
rh [str] | Remove from head of queue. Optionally compare to expected value str
rt [str] | Remove from tail of queue. Optionally compare to expected value str
show | Show queue contents
size [n] | Compute queue size n times (default: n == 1)
sort | Sort queue in ascending order
source | Read commands from source file
swap | Swap every two adjacent nodes in queue
time cmd arg ... | Time command execution
web [port] | Read commands from builtin web server
Options:
echo 1 | Do/don't echo commands
entropy 0 | Show/Hide Shannon entropy
error 5 | Number of errors until exit
fail 30 | Number of times allow queue operations to return false
length 1024 | Maximum length of displayed string
malloc 0 | Malloc failure probability percent
simulation 0 | Start/Stop simulation mode
verbose 4 | Verbosity level
本程式內建自動評分程式,以下是參考執行輸出。一開始什麼都沒做,當然是零分:
Test performance of insert_tail, size, and reverse
ERROR: Need to allocate separate string for each list element
ERROR: Insertion of gerbil failed (1 failures total)
ERROR: Computed queue size as 0, but correct value is 2
ERROR: Computed queue size as 0, but correct value is 2
--- trace-17-complexity 0/5
--- TOTAL 0/100
你註冊 GitHub 帳號所用的電子郵件信箱應該是自己常用的,之後自動評分程式、授課教師,甚至是其他學員進行 code review 時,會發電子郵件到註冊帳號時指定的信箱Image Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
虛擬機器的選擇: 針對 macOS,可安裝 lima;針對 Microsoft Windows 11,可安裝 WSL
$ sudo apt install build-essential git-core valgrind
$ sudo apt install cppcheck clang-format wamerican aspell colordiff
v1.90
,可用 $ cppcheck --version
檢查版本,若小於 1.90
則需要額外準備:
$ sudo apt remove cppcheck
,再依據 Cppcheck Developer Info 網頁指示,編譯並安裝 Cppcheck;"If I had eight hours to chop down a tree, I’d spend six hours sharpening my axe." – Abraham Lincoln
「如果我有 8 小時可以砍 1 棵樹,我會花 6 小時把斧頭磨利。」 – 亞伯拉罕.林肯
(類似漢語「工欲善其事,必先利其器」)
lab0-c
,請確保名稱符合這規範,否則後續操作可能會失敗
處置既有的 repository
https://github.com/<username>/lab0-c_20XX.git
,git remote set-url origin https://github.com/<username>/lab0-c_20XX
git push
方法二: GitHub 網頁
利用 GitHub 網站提供的 import repository
同步 lab0-c
使用 GitHub 提供的 sync fork
如果有與 upstream 不一致的 commit ,會出現 discard commit 的提示
首先,在 Ubuntu Linux 環境建立適合開發的目錄 (directory),例如 /home/ubuntu/linux2025
:
$ cd ~
$ mkdir -p linux2025
在剛才建立的新目錄,自 GitHub 取得 lab0-c 程式碼:
$ git clone https://github.com/sysprog21/lab0-c
一旦你 fork 後,可將上面 sysprog21
換為你的 GitHub 帳號,對應的命令會是:
$ git clone git@github.com:你的帳號名稱/lab0-c
或
$ git clone https://github.com/你的帳號名稱/lab0-c
切換到 lab0-c
目錄並嘗試編譯程式碼:
$ cd lab0-c
$ make
預期會看到以下輸出:
CC qtest.o
CC report.o
CC console.o
CC harness.o
CC queue.o
CC random.o
CC dudect/constant.o
CC dudect/fixture.o
CC dudect/ttest.o
CC shannon_entropy.o
CC linenoise.o
CC web.o
LD qtest
也可清除編譯輸出的檔案:
$ make clean
若你想知道編譯過程的細節,可執行以下命令:
$ make VERBOSE=1
預期輸出:
gcc -o qtest.o -O1 -g -Wall -Werror -c -MMD -MF .qtest.o.d qtest.c
gcc -o report.o -O1 -g -Wall -Werror -c -MMD -MF .report.o.d report.c
gcc -o console.o -O1 -g -Wall -Werror -c -MMD -MF .console.o.d console.c
gcc -o harness.o -O1 -g -Wall -Werror -c -MMD -MF .harness.o.d harness.c
gcc -o queue.o -O1 -g -Wall -Werror -c -MMD -MF .queue.o.d queue.c
gcc -o qtest qtest.o report.o console.o harness.o queue.o
不難發現,名為 qtest
的執行檔正是編譯過程最終的輸出。請不要被上述一堆看似嚇人的參數給迷惑。UNIX 風格 (當然 GNU/Linux 也繼承這優良傳統) 是儘量揭露系統資訊給使用者,這也是「Linux 核心設計」課程特別想讓學員體會到的精神。
-o
一類的參數使用,可參見 你所不知道的 C 語言:編譯器和最佳化原理篇
參閱 lab0-c 的文件 (即 README.md
檔案) 後,我們得知能用以下命令測試 qtest
:
$ make check
對應的程式輸出如下:
./qtest -v 3 -f traces/trace-eg.cmd
cmd>
cmd> # Demonstration of queue testing framework
cmd> # Use help command to see list of commands and options
cmd> # Initial queue is NULL.
cmd> show
q = NULL
cmd> # Create empty queue
cmd> new
q = []
cmd> # Fill it with some values. First at the head
cmd> ih dolphin
參閱 qtest
的 help
命令輸出,我們可得知以下:
qtest 命令 | 作用 |
---|---|
new | Create new queue |
free | Delete queue |
ih str [n] | Insert string str at head of queue n times (default: n == 1) |
it str [n] | Insert string str at tail of queue n times (default: n == 1) |
也就是說,$ make check
將 traces/trace-eg.cmd 檔案內容指派給 qtest
,進行以下操作: (僅列出部分)
trace-eg
嗎?顯然這是個示範的檔案,但為何叫作 eg
呢?為何不用 "example" 開頭兩個字母來簡稱 "ex" 呢?這可有學問!"e.g." 是拉丁文 "exempli gratia" 的縮寫,意思是「例如」、「舉例來說」,即英文的 "for example" 或 "for instance"。ex.
放到句子裡當「例如」的用途!
qtest
的 new
命令可建立新的佇列,而 prev
和 next
命令可在這些佇列間切換 (內部是另一層環狀佇列),示意如下:
w1, w2, w3 皆為藉由
new
建立的佇列
由於 lab0-c 已整合 linenoise,我們可善用其中的自動補齊 (auto-complete) 功能,假如目前已輸入 he
:
cmd> he
|___ 游標在此
按下 tab 鍵,則完整的命令 help
會自動補齊,同樣地,若我們已輸入 op
再按下 tab 鍵,則會得到 option
。此外,在 cmd>
命令提示列中,我們可按「上」和「下」來切換歷史紀錄的命令。
初步理解 qtest
運作機制後,就可前進到 lab0-c 的自動評分系統。操作命令如下:
$ make test
實際就是執行 scripts/driver.py 這個 Python 程式。同樣地,請不要因為自己沒學過 Python 程式語言就退堂 (或「退選」),你可以快速瀏覽,應能發現,這個自動評分程式就是將 traces/trace-XX-CAT.cmd 這樣形式的命令序列提供給 qtest
內建的命令直譯器,然後依據給定的檢驗標準,逐一判定該給測試者多少分數。
或許你會糾結於 driver.py
這個檔名,在你的印象中,"driver" 的翻譯是「驅動程式」,不就是該用低階程式語言撰寫嗎?怎麼會跟 Python 和自動評分系統有關?
查閱 dictionary.com 的 "driver" 詞目,我們會發現:
Computers. software or hardware that controls the interface between a computer and a peripheral device.
Electronics. a circuit whose output provides the input of another circuit
在電子電機電腦科學 (即 EECS) 領域中,"driver" 可以是軟體,也能是硬體,顯然上述的 "driver.py" 是軟體,其作用是控制電腦程式 (這裡指給定的命令序列,如 ih
及 it
) 和特定的單元,後者就是由 qtest
所提供的命令直譯器。平常我們看到「驅動程式」的翻譯方式,其實是 device driver 的簡稱,在程式設計中,我們可見到類似的 "driver" 案例,如:
另外,lab0-c 專案支援透過 $ make SANITIZER=1
開啟 Address Sanitizer 從而強化執行時期的記憶體檢測,隨後 $ make test
執行過程中會見到類似以下輸出:
==8522==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000008 (pc 0x55ea517092cb bp 0x7ffe778b4900 sp 0x7ffe778b4900 T0)
==8522==The signal is caused by a READ memory access.
==8522==Hint: address points to the zero page.
#0 0x55ea517092ca in q_remove_head lab0-c/queue.c:74
#1 0x55ea51704880 in do_remove_head lab0-c/qtest.c:311
#2 0x55ea51707054 in interpret_cmda lab0-c/console.c:217
#3 0x55ea51707a58 in interpret_cmd lab0-c/console.c:240
#4 0x55ea51708725 in cmd_select lab0-c/console.c:568
#5 0x55ea51708b42 in run_console lab0-c/console.c:627
#6 0x55ea51705c7d in main lab0-c/qtest.c:700
#7 0x7facce0d8b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
#8 0x55ea51703819 in _start (lab0-c/qtest+0x5819)
這是符合預期的結果,請不要慌張。你可依循上述訊息,追查程式碼檔名及行號,推測為何會觸發 SIGSEGV (在 UNIX 及其相容系統上,意味著 Segmentation fault)。請搭配閱讀以下簡報:
lab0-c 採用的程式碼開發風格可參見 CONTRIBUTING.md,後者與 Linux 核心的規範相近。
使用一致的 programming style 很重要,我們可透過 clang-format 這個工具來調整作業程式要求的風格,使用方式如下:
$ clang-format -i *.[ch]
課程要求的 C 程式撰寫風格簡述:
switch (c) {
case 'h':
usage(argv[0]);
break;
{ }
英語: curly brackets, 又稱花括號) 與空格
{
放在行末,把右大括號 }
放在行首,如:if (x == true) {
do_y();
} else {
do_z();
}
do {
/* body of do-loop */
} while (condition);
int function(int x)
{
body of function
}
sizeof
, typeof
, alignof
, __attribute__
(註: 這是 gcc 延展的功能),在 Linux 核心中,這些關鍵字的使用都會帶上一對括號,儘管在 C 語言的使用上並不需帶上括號
sizeof
不是函式,而是 operator!if
, switch
, case
, for
, do
, while
sizeof
, typeof
, alignof
, __attribute__
之後s = sizeof(struct file);
s = sizeof( struct file );
*
符號) 的位置應該緊靠著變數名稱或函式名稱,而非型別名稱,例如:
char *linux_banner;
unsigned long long memparse(char *ptr, char **retptr);
char *match_strdup(substring_t *s);
= + - < > * / % | & ^ <= >= == != ? :
&*+-~!
sizeof
typeof
alignof
__attribute__
defined
++
--
運算子之前新增空格 (即 i++
)++
--
運算子之後新增空格 (即 ++i
).
和 ->
) 周圍新增空格ThisVariableIsATemporaryCounter
這種「可愛」的名字,相反地,一個 C 程式設計師會把這種變數命名為 tmp
,如此簡潔易寫。static
宣告的函式) 需要使用描述性的名稱。若你有個計算活躍使用者數量的函式,你應該用 count_active_users() 一類的名稱,避免用 cntusr()
這樣不易望文生義的名稱。i
。反過來說,命名為 loop_counter
是生產力很低的行為。同樣地,tmp
可以是任何型別的區域變數。首次執行 make
後,Git pre-commit / pre-push hook 將自動安裝到現行的工作區 (workspace),之後每次執行 git commit
時,Git hook 會檢查 C/C++ 原始程式碼的風格是否一致,並透過 Cppcheck 進行靜態程式碼檢查。
下圖展示 Git pre-commit hook 偵測到開發者的修改並未遵守一致的 coding style,主動回報並提醒開發者:
紅色標注的二行程式碼 (即 int member1;
和 int member2;
) 不符合指定的 4 個空白縮排方式,在 git pre-commit 階段就成功阻擋這樣風格不一致的程式碼變更。
Git commit message 是什麼呢?在取得 lab0-c 程式碼後,執行 $ git log
的輸出就是了。你或許會納悶,commit message 又不是程式碼,充其量只能算是「程式開發的軌跡」,為何要特別探討?
tig
套件,更便利地瀏覽 git repository 資訊。$ sudo apt install tig
Peter Hutterer 在 On commit messages 說得很精闢:
「重新了解一段程式碼更動的脈絡很浪費腦力。雖然這件事情沒辦法完全避免,但是我們可以盡量降低這件事情的複雜度。Commit messages 正可以做到這點,而我們可以從 commit message 看出一個開發者是否為一位好的合作對象。」
一個專案是否能長期且成功地運作 (撇除其他影響的因素),取決於它的可維護性,而在這件事上沒有其他工具比專案本身的開發軌跡更為強大。因此花時間學習如何撰寫與維護專案的開發紀錄,是件值得投資的事。一開始你可能會覺得麻煩,但它很快就會變成一種習慣,甚至能成為你之所以感到自豪及具備生產力的因素。
Chris Beams 在 How to Write a Git Commit Message 一文 (繁體中文翻譯: 如何寫一個 Git Commit Message) 提出,好的 Git commit message 應符合七條規則:
lab0-c 內建 Git pre-commit hook 就包含依循上述七條規則的自動檢查,可阻擋不符合規則的 git commit message,此外還透過 GNU Aspell 對 commit message 的標題進行拼字檢查。本專案除了貫徹上述的七條規則,還額外規範以下,詳閱 CONTRIBUTING.md:
Update queue.c
這樣的內容,每則 commit message 都有充分描述()
,這對溝通沒有實際益處make
命令時,建構腳本會自動安裝 Git hooks,後續執行 git 命令時,務必確認在同一目錄及狀態下運作以下實際操作 Git。
事先編輯檔案 queue.h
後,執行 $ git commit
會發現:
$ git commit
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
modified: queue.h
no changes added to commit
原來要指定變更的檔案,用命令 $ git add
:
$ git add queue.h
$ git commit -a
這道命令,讓 Git 自動追蹤變更的檔案並提交
重新 git commit
,這時可發現安裝的 Git hooks 發揮作用:
$ git commit -m "add fields to point to the tail"
add fields to point to the tail [line 1]
- Capitalize the subject line
Proceed with commit? [e/n/?] e
add fields to point to the tail [line 1]
- Capitalize the subject line
Proceed with commit? [e/n/?] y
How to Write a Git Commit Message: https://chris.beams.io/posts/git-commit/
e - edit commit message
n - abort commit
? - print help
Proceed with commit? [e/n/?] ?
How to Write a Git Commit Message: https://chris.beams.io/posts/git-commit/
e - edit commit message
n - abort commit
? - print help
Proceed with commit? [e/n/?] e
[master 23f7113] Add fields to point to the tail
Git pre-commit hook 提示我們:
e
,可編輯 git commit message;y
,因為 y 不是有效選項, 所以出現 help;e
, 再次編輯訊息,因訊息有限制標題開頭要大寫;注意:請避免用 $ git commit -m
,而是透過編輯器調整 git commit message。許多網路教學為了行文便利,用 $ git commit -m
示範,但這樣很容易讓人留下語焉不詳的訊息,未能提升為 好的 Git Commit Message。因此,從今以後,不要用 git commit -m
, 改用 git commit -a
(或其他參數) 並詳細查驗變更的檔案。
在 Ubuntu Linux 預設組態中,執行 $ git commit -a
會呼叫 GNU nano 來編輯訊息,可透過變更 EDITOR
環境變數,讓 git 找到你偏好的編輯器,例如指定 vim 就可以這樣做:
export EDITOR=vim
你也可把上述變更加到 bash 設定檔案中,例如:
$ echo "export EDITOR=vim" >> ~/.bashrc
或藉由 git config
命令,將偏好的編輯器寫入 git 組態,例如:
$ git config --global core.editor vim
一番折騰後,終於順利提交 (commit) 我們的變更:
$ git commit -a
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
本專案的 Git hook 相較於其他專案,做了一系列加工,包含附上 Change-Ids,這對於 Android Open Source Project (AOSP) 一類採用 Gerrit Code Review 系統的專案來說,尤為重要,本專案強制所有在 2025 年 2 月 21 日以後的 commit message 都該有 Change-Id
字樣,示範如下:
commit d93a2036e2defc978910ff449753f6ee48388f6c
Author: Jim Huang <jserv.tw@gmail.com>
Date: Thu Feb 20 04:45:12 2025 +0800
Enforce the use of Change-Id in each commit
To prepare for code review systems like Gerrit, every git commit message
must include a Change‑Id.
Gerrit groups commits into a single review using a Change-Id in the
commit message footer. e.g., if a change needs updating, a follow-up
commit can address issues, and Gerrit will link the new version to the
original review—even across cherry-picks and rebases.
Reference:
https://gerrit-review.googlesource.com/Documentation/user-changeid.html
Change-Id: Ib344622a3887387f9ac954dcd599c4ba45836419
需要注意的是,目前提交的變更僅存在於這台電腦中,還未發佈到 GitHub 上,於是我們需要執行 git push
下方的
jserv
是示範帳號,請換為你自己的 GitHub 帳號名稱
$ git push
Username for 'https://github.com': jserv
Password for 'https://jserv@github.com':
Hint: You might want to know why Git is always asking for my password.
https://help.github.com/en/github/using-git/why-is-git-always-asking-for-my-password
Running pre push to master check...
Trying to build tests project...
Pre-push check passed!
Counting objects: 4, done.
Delta compression using up to 64 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 709 bytes | 709.00 KiB/s, done.
Total 4 (delta 3), reused 0 (delta 0)
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To https://github.com/sysprog21/lab0-c
d2d7711..b42797a master -> master
不難發現上面訊息提到 Why is Git always asking for my password? 這個超連結,如果你厭倦每次 $ git push
都需要輸入帳號及密碼,可參考超連結內容去變更設定。
又因 pre-push hook 使然,每次發布變更到 GitHub 服務時,只要是 master
branch (主要分支) 就會觸發編譯要求,只有通過編譯的程式碼才能發布,但 master
以外的 branch 不受此限,換言之,我們鼓勵學員運用 git branch
進行各項實驗,請參照線上中文教材 《為你自己學 Git》。
在你 fork 後得到的 lab0-c
repository,務必保持 master
分支作為預設,否則自動測試系統會出錯。
GitHub Actions 是 GitHub 提供的 CI/CD 服務,CI/CD 代表的是 Continuous Integration 持續整合與 Continuous Deployment 持續部署,簡單來說就是將程式流程自動化。lab0-c
提供幾項自動化測試,包含:檢查排版、編譯結果和自動評分等等。這裡需要注意的是 fork 完成後,預設情況下 GitHub Action 不會被啟動,所以需要手動開啟 GitHub Actions,在你所 fork 出的 repository 的 Actions 內點選 I understand my workflows, go ahead and enable them
,如下圖:
開啟 GitHub Actions 後,當每次 push 到遠端時,GitHub 就會自動測試作業設計的檢查項目,當有錯誤時會收到 CI failed 的 email 通知。
在現有的 GitHub Actions 對應的測試項目中,一旦收到 git push
的事件,系統就會自動執行 make test
,並在失敗時發信件通知學員。如下圖:
點擊信件中的 View workflow run
即可在 GitHub 網站中得知 GitHub Actions 的測試狀況。
若你很有耐心地讀到這裡,恭喜你,終於可著手修改 lab0-c 程式碼。
這裡我們嘗試實作函式 q_size
,首先找出 queue.c
檔案中對應的註解:
Return number of elements in queue.
Return 0 if q is NULL or empty
修改 queue.c
檔案,嘗試撰寫以下程式碼: (list_for_each
是 Linux 核心風格的鏈結串列所提供的巨集,詳見 The Linux Kernel API - List Management Functions)
int q_size(struct list_head *head)
{
if (!head) return 0;
int len = 0;
struct list_head *li;
list_for_each (li, head)
len++;
return len;
}
執行 $ make check
,儘管無法通過指定的測試,但 q_size
的確正確執行,訊息如下:
cmd> # Create empty queue
cmd> new
l = NULL
cmd> # See how long it is
cmd> size
Warning: Calling size on null queue
Queue size = 0
l = NULL
看到 Queue size = 0
字樣,就表示上面的 q_size
已被呼叫。
如果你的開發環境裡頭沒有設定 git,請依據 初次設定 Git 進行設定:
$ git config --global user.name 你偏好的英文或拼音名稱
$ git config --global user.email 你的電子郵件信箱
我們可用 git commit
命令來提交修改。不過當我們執行 $ git commit -a
時,會遇到程式碼排版和風格檢查的錯誤:
--- modified queue.c
+++ expected coding style
@@ -91,7 +91,8 @@ void q_release_element(element_t *e)
*/
int q_size(struct list_head *head)
{
- if (!head) return 0;
+ if (!head)
+ return 0;
int len = 0;
struct list_head *li;
[!] queue.c does not follow the consistent coding style.
Make sure you indent as the following:
clang-format -i queue.c
請依據指示,先執行 clang-format -i queue.c
再執行 git commit
。
git commit
(或者自作聰明,使用 GitHub 網頁直接修改程式碼),則會因產生的 commit message 格式不合,在執行 make test
時遇到類似下方畫面的錯誤訊息:之所以會有 does not end with a valid Change-Id
的錯誤訊息,就是因為沒有正確安裝 Git hooks。務必詳閱說明並確實依循。