筆者最近看到這樣一篇文章? ,原作者讓 ChatGPT 寫一個(gè)內(nèi)核模塊,要求實(shí)現(xiàn)的功能是:每 5 秒向控制臺打印一句 "Hello world",并且把編譯需要的 Makefile 也一起寫出來。
AI 最開始的實(shí)現(xiàn)方法是:創(chuàng)建一個(gè)內(nèi)核線程,線程主體是一個(gè) while 循環(huán),每隔 1 毫秒檢查一次,看時(shí)間是不是過去了 5 秒:
?
while (!kthread_should_stop()) { unsigned long time_since_load = jiffies - jiffies_at_load; unsigned long time_since_load_sec = time_since_load / HZ; if (time_since_load_sec >= 5) { printk(KERN_INFO "Hello world! "); jiffies_at_load = jiffies; } // Sleep for 1 ms to avoid hogging the CPU msleep(1); }
?
此處用 msleep(1) 是沒有大毛病的,它確實(shí)可以通過睡眠暫時(shí)讓出 CPU,避免 hogging,不像?busy loop?的 mdelay()。
但它這里實(shí)現(xiàn)的比較曲折,1 毫秒檢查一次,5 秒內(nèi)就要檢查 5000 次,雖然沒有「霸占」CPU,但是對 CPU 資源也是不小的浪費(fèi)。
于是原作者讓它改了一個(gè)更減少 CPU 消耗的版本出來:
?
while (!kthread_should_stop()) { printk(KERN_INFO "Hello world! "); // Sleep for 5 seconds to avoid hogging the CPU schedule_timeout(HZ * 5); }
?
看起來是解決了原來存在的問題,但編譯出來一試,好家伙,"Hello world" 是突突地往控制臺上打啊,根本不是間隔5 秒一次。
把這個(gè)問題反饋給 AI 后,它立馬做出了調(diào)整,加入了一句對 process 狀態(tài)的設(shè)置后,就可以 work 了。
?
set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(HZ * 5);
?
查閱 Linux 源碼可知,schedule_timeout() 最終會調(diào)用 __schedule() 函數(shù),其對 process 切換的判斷是這樣的:
?
if (!preempt && prev->state) deactivate_task(rq, prev, ...);
?
再由于:
?
#define TASK_RUNNING 0x0000
?
所以如果之前的狀態(tài)是 RUNNING,process 并不會真的離開 CPU 的 runqueue,豈不就是一直執(zhí)行,一直框框地打印么。
在 https://livegrep.com/search/linux 上查了下 schedule_timeout() 在內(nèi)核中的具體使用情況,好些對 set_current_state() 為 INTERRUPTIBLE 的設(shè)置沒有和 schedule_timeout() 挨在一起,所以 AI“理解”不了這兩者的關(guān)聯(lián),筆者覺得是可以接受的。
不過其實(shí) Linux 是提供了一個(gè)“二合一”的封裝函數(shù)的:
?
sched schedule_timeout_uninterruptible(signed long timeout) { set_current_state(TASK_UNINTERRUPTIBLE); return schedule_timeout(timeout); }
?
而它還有個(gè)更上層的封裝 "msleep"(希望可被信號打斷就用 "msleep_interruptible"):
?
void msleep(unsigned int msecs) { unsigned long timeout = msecs_to_jiffies(msecs) + 1; while (timeout) timeout = schedule_timeout_uninterruptible(timeout); }
?
咳,繞了這么大一圈,其實(shí)一開始直接用 msleep(5000) 最方便啦。
后來原作者又提了在「內(nèi)核模塊」開發(fā)中頗為常見的兩點(diǎn)功能:
一是將 5 秒的間隔配置成 module parameter(以供動(dòng)態(tài)調(diào)整),這個(gè)任務(wù)被順利完成了。
二是在 "/proc" 文件系統(tǒng)中加入打印次數(shù)的統(tǒng)計(jì)功能(以便查詢),這里出了點(diǎn)小岔子,AI 用的 "file_operations",而不是 "proc_ops",這在高于 5.7 的內(nèi)核版本上是編譯不過的(參看筆者親身經(jīng)歷的這個(gè)案例)。
這也不能怪 AI,你沒說內(nèi)核版本不是。
小結(jié)
最后原作者寫了下他的感受,大意就是 "half amazing and half terrifying",雖然 AI 中途犯了不少錯(cuò),但總比自己現(xiàn)查資料來的快不是…
除此之外,筆者也有兩點(diǎn)感受,一是 ChatGPT 即使有時(shí)會出錯(cuò),但回答地總是非常自信(還好不是那么普通,卻那么自信……),二是那個(gè)注釋一條條地寫的真是規(guī)范啊,連每個(gè)頭文件為什么加,都有理有據(jù),這一點(diǎn)就強(qiáng)過很多人。
?
#include// Needed for all kernel modules #include // Needed for KERN_INFO #include // Needed for the macros #include // Needed for jiffies #include // Needed for msleep
?
筆者自己也用這個(gè)題目,在 ChatGPT 上試了一把,得出了不太一樣的結(jié)果,欲知后續(xù),請看下文分解。
補(bǔ)充(為了避免影響主線劇情):
那 schedule_timeout() 返回的時(shí)候,也需要手動(dòng)再將狀態(tài)設(shè)置回 TASK_RUNNING 么?不需要,因?yàn)?timer 的 callback 在喚醒 process 后會將其狀態(tài)(自動(dòng))設(shè)為 RUNNING(參考 Linux 中的等待隊(duì)列機(jī)制 ):
?
void process_timeout(struct timer_list *t) { struct process_timer *timeout = from_timer(timeout, t, timer); wake_up_process(timeout->task); }
?
評論
查看更多