# Reading [The Linux Kernel Module Programming Guide](https://sysprog21.github.io/lkmpg/) (7) ## Blocking Processes and threads ### sleep (但我被搞到沒法sleep) 編譯完sleep.c和cat_nonblock.c後,利用tail -f持續read /proc/sleep 使得cat_nonblock被 ``` if (file->f_flags & O_NONBLOCK) return -EAGAIN; ``` 抓住,因此拋出異常。 然而,使用`tail -f /proc/sleep &`,tail會一直read /proc/sleep造成占用,因此會看到一直print出來`Last input:`,並且這個時候使用`cat`或`echo "something" > /proc/sleep`就會被卡住,直到`tail -f /proc/sleep &`的process被kill,一切乎也沒什麼,但是我再跑範例的時候出了大trouble。`tail -f /proc/sleep &`這一條指令並沒有直接show `Last input:`出來,反而是一直在後台run,困擾了我好久,測幾次之後發現如果開了兩個`tail -f /proc/sleep &`,接著將執行中(running time比較長)的那個process kill之後,另一個process才會正常print `Last input:`,起初還以為是我的電腦中邪了,仔細去觀察busybox 1.36.1版本的coreutils/tail.c,做了一些實驗後才發現問題。 沒做改動的結果: ``` ~ # insmod ./workspace/sleep.ko [ 2.534513] sleep: loading out-of-tree module taints kernel. [ 2.540987] /proc/sleep created ~ # tail -f /proc/sleep & ~ # /workspace/cat_nonblock /proc/sleep Open would block ~ # /workspace/cat_nonblock /proc/sleep Open would block ~ # /workspace/cat_nonblock /proc/sleep Open would block ~ # /workspace/cat_nonblock /proc/sleep Open would block ~ # ``` 並沒有正確顯示 `Last input:` #### 結論: [tail.c](https://elixir.bootlin.com/busybox/1.37.0/source/coreutils/tail.c#L91)這邊使用的full_read會一次將module_output的message一次吃完,使[這一行](https://elixir.bootlin.com/busybox/1.37.0/source/coreutils/tail.c#L266)的while loop收到的nread一直大於0。然而這邊的loop是一次吃完所有的message才print buffer。所以loop裡面也不會print東西出來,導致這個怪怪的情況。 #### 解法: 將full_read直接換成safe_read就可以解決了 流程如下: 1. 第一次進while loop : safe_read get message "Last input:\n" from module_output, nread > 0 is true. 2. 第二次進while loop : safe_read get message "" from module_output, nread == 0. <---- module_output return 0 3. 繼續執行後面的loop 改完後tail.c的tail_read會長這樣(其他都不動) ``` static ssize_t tail_read(int fd, char *buf, size_t count) { ssize_t r; r = safe_read(fd, buf, count); if (r < 0) { bb_simple_perror_msg(bb_msg_read_error); G.exitcode = EXIT_FAILURE; } return r; } ``` 輸出會長這樣,這邊的tailv2是我改寫busybox的tail.c成tailv2.c後重新編譯的結果,詳細的過程之後再更新 ``` [ 13.763443] sleep: loading out-of-tree module taints kernel. [ 13.768161] /proc/sleep created ~ # /workspace/cat_nonblock /proc/sleep Last input: ~ # tailv2 -f /proc/sleep & ~ # Last input: Last input: Last input: Last input: Last input: Last input: Last input: Last input: /workspace/cat_nonblock /proc/sleep Open would block ~ # Last input: Last input: Last input: QEMU: Terminated ``` #### 再一個結論 這邊是為了要補充前面提到`為何執行兩次然後kill正在跑的process之後,第二個就會變得正常`,我們已經知道infinit loop的原因了,那麼執行兩次然後kill正在跑的process之後,有解決inifint loop嗎? 顯然是沒有,但為何他可以逃過那個whill loop? 答案是它高機率性的逃過。因為我們sleep.c的module_output的finished變數如果process被kill的當下是1,那麼下次執行時mdolue_output就會直接return 0,誤打誤撞得停下那個while loop,反之就一樣會被卡在那裏QQ,但是finished是1的時候要複製字串會比較花時間,在這個過程被中斷的機會比較大。 ## 更新一些修改busybox測試時遇到的大小trouble 首先,如果你問chatgpt或上網google怎麼新增applet到busybox裡面會出現很多舊的方法,但其實只要[參考](https://elixir.bootlin.com/busybox/1.37.0/source/docs/new-applet-HOWTO.txt)在你寫的C Program前面加上適當的註解就可以了,makefile 會自動去抓所有.c檔然後生成config和kbuild。下面是我改寫tail.c的註解 ``` /* vi: set sw=4 ts=4: */ /* * Mini tailv2 implementation for busybox * * Copyright (C) 2001 by Matt Kraai <kraai@alumni.carnegiemellon.edu> * * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ /* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) * * Pretty much rewritten to fix numerous bugs and reduce realloc() calls. * Bugs fixed (although I may have forgotten one or two... it was pretty bad) * 1) mixing printf/write without fflush()ing stdout * 2) no check that any open files are present * 3) optstring had -q taking an arg * 4) no error checking on write in some cases, and a warning even then * 5) q and s interaction bug * 6) no check for lseek error * 7) lseek attempted when count==0 even if arg was +0 (from top) */ //config:config TAILV2 //config: bool "tailv2 (6.8 kb)" //config: default y //config: help //config: tailv2 is used to print the last specified number of lines //config: from files. //config: //config:config FEATURE_FANCY_TAILV2 //config: bool "Enable -q, -s, -v, and -F options" //config: default y //config: depends on TAILV2 //config: help //config: These options are provided by GNU tailv2, but //config: are not specified in the SUSv3 standard: //config: -q Never output headers giving file names //config: -s SEC Wait SEC seconds between reads with -f //config: -v Always output headers giving file names //config: -F Same as -f, but keep retrying //applet:IF_TAILV2(APPLET(tailv2, BB_DIR_USR_BIN, BB_SUID_DROP)) //kbuild:lib-$(CONFIG_TAILV2) += tailv2.o /* BB_AUDIT SUSv3 compliant (need fancy for -c) */ /* BB_AUDIT GNU compatible -c, -q, and -v options in 'fancy' configuration. */ /* http://www.opengroup.org/onlinepubs/007904975/utilities/tailv2.html */ //usage:#define tailv2_trivial_usage //usage: "[OPTIONS] [FILE]..." //usage:#define tailv2_full_usage "\n\n" //usage: "Print last 10 lines of FILEs (or stdin) to.\n" //usage: "With more than one FILE, precede each with a filename header.\n" //usage: "\n -c [+]N[bkm] Print last N bytes" //usage: "\n -n N[bkm] Print last N lines" //usage: "\n -n +N[bkm] Start on Nth line and print the rest" //usage: "\n (b:*512 k:*1024 m:*1024^2)" //usage: IF_FEATURE_FANCY_TAILV2( //usage: "\n -q Never print headers" //usage: "\n -v Always print headers" //usage: ) //usage: "\n -f Print data as file grows" //usage: IF_FEATURE_FANCY_TAILV2( //usage: "\n -F Same as -f, but keep retrying" //usage: "\n -s SECONDS Wait SECONDS between reads with -f" //usage: ) //usage: //usage:#define tailv2_example_usage //usage: "$ tailv2 -n 1 /etc/resolv.conf\n" //usage: "nameserver 10.0.0.1\n" ``` main function要定義成 : `yourapplet_main` 最後要加`MAIN_EXTERNALLY_VISIBLE` ``` int tailv2_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; ``` # 這些註解很重要要要要要要要要要要 # //config:config TAILV2 這樣是對的 # // config:config TAILV2 不要亂加空白,會讀不到 # auto format 會發生悲劇