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