嵌入式技术
笔者最近看到这样一篇文章 ,原作者让 ChatGPT 写一个内核模块,要求实现的功能是:每 5 秒向控制台打印一句 "Hello world",并且把编译需要的 Makefile 也一起写出来。
AI 最开始的实现方法是:创建一个内核线程,线程主体是一个 while 循环,每隔 1 毫秒检查一次,看时间是不是过去了 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) 是没有大毛病的,它确实可以通过睡眠暂时让出 CPU,避免 hogging,不像 busy loop 的 mdelay()。
但它这里实现的比较曲折,1 毫秒检查一次,5 秒内就要检查 5000 次,虽然没有「霸占」CPU,但是对 CPU 资源也是不小的浪费。
于是原作者让它改了一个更减少 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 秒一次。
把这个问题反馈给 AI 后,它立马做出了调整,加入了一句对 process 状态的设置后,就可以 work 了。
set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(HZ * 5);
查阅 Linux 源码可知,schedule_timeout() 最终会调用 __schedule() 函数,其对 process 切换的判断是这样的:
if (!preempt && prev->state) deactivate_task(rq, prev, ...);
再由于:
#define TASK_RUNNING 0x0000
所以如果之前的状态是 RUNNING,process 并不会真的离开 CPU 的 runqueue,岂不就是一直执行,一直框框地打印么。
在 https://livegrep.com/search/linux 上查了下 schedule_timeout() 在内核中的具体使用情况,好些对 set_current_state() 为 INTERRUPTIBLE 的设置没有和 schedule_timeout() 挨在一起,所以 AI“理解”不了这两者的关联,笔者觉得是可以接受的。
不过其实 Linux 是提供了一个“二合一”的封装函数的:
sched schedule_timeout_uninterruptible(signed long timeout) { set_current_state(TASK_UNINTERRUPTIBLE); return schedule_timeout(timeout); }
而它还有个更上层的封装 "msleep"(希望可被信号打断就用 "msleep_interruptible"):
void msleep(unsigned int msecs) { unsigned long timeout = msecs_to_jiffies(msecs) + 1; while (timeout) timeout = schedule_timeout_uninterruptible(timeout); }
咳,绕了这么大一圈,其实一开始直接用 msleep(5000) 最方便啦。
后来原作者又提了在「内核模块」开发中颇为常见的两点功能:
一是将 5 秒的间隔配置成 module parameter(以供动态调整),这个任务被顺利完成了。
二是在 "/proc" 文件系统中加入打印次数的统计功能(以便查询),这里出了点小岔子,AI 用的 "file_operations",而不是 "proc_ops",这在高于 5.7 的内核版本上是编译不过的(参看笔者亲身经历的这个案例)。
这也不能怪 AI,你没说内核版本不是。
小结
最后原作者写了下他的感受,大意就是 "half amazing and half terrifying",虽然 AI 中途犯了不少错,但总比自己现查资料来的快不是…
除此之外,笔者也有两点感受,一是 ChatGPT 即使有时会出错,但回答地总是非常自信(还好不是那么普通,却那么自信……),二是那个注释一条条地写的真是规范啊,连每个头文件为什么加,都有理有据,这一点就强过很多人。
#include// Needed for all kernel modules #include // Needed for KERN_INFO #include // Needed for the macros #include // Needed for jiffies #include // Needed for msleep
笔者自己也用这个题目,在 ChatGPT 上试了一把,得出了不太一样的结果,欲知后续,请看下文分解。
补充(为了避免影响主线剧情):
那 schedule_timeout() 返回的时候,也需要手动再将状态设置回 TASK_RUNNING 么?不需要,因为 timer 的 callback 在唤醒 process 后会将其状态(自动)设为 RUNNING(参考 Linux 中的等待队列机制 ):
void process_timeout(struct timer_list *t) { struct process_timer *timeout = from_timer(timeout, t, timer); wake_up_process(timeout->task); }
全部0条评论
快来发表一下你的评论吧 !