diff --git a/articles/20230731-riscv-klibc-linux-kernel-libc-intro.md b/articles/20230731-riscv-klibc-linux-kernel-libc-intro.md
new file mode 100644
index 0000000000000000000000000000000000000000..fe754a3c7e0036f530cbe301c86005894c9db774
--- /dev/null
+++ b/articles/20230731-riscv-klibc-linux-kernel-libc-intro.md
@@ -0,0 +1,429 @@
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc1 - [spaces pangu autocorrect]
+> Author: Jingqing3948 <2351290287@qq.com>
+> Date: 2023/07/31
+> Revisor: Falcon
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+> Sponsor: PLCT Lab, ISCAS
+
+# Linux 内核库函数以及 kernel libc 简介
+
+## 简介
+
+本文主要介绍了实验盘中 Linux 内核源码下的 lib 库,包括其架构、基本功能、测试方法。后续第二篇文章还会引入 klibc 在 Linux 官网上单独下载的内核库的用法和介绍。
+
+## kernel libc 是什么
+
+创立目的:为嵌入式,启动准备的 Linux 内核精简小型 c 库。
+
+他具有:轻量,高效,可靠,尽可能减少对 os 和硬件的依赖,启动快等特点。
+
+在此先声明一下我们指的 kernel libc 库,这个是 Linux kernel 中的 libc 库,而不是直接下载 klibc 库得到的,笔者和吴老师讨论后决定:**用 kernel libc 指代 Linux kernel 中的 c 库,用 klibc 指代单独下载的 klibc 库。**
+
+获取 kernel libc 库的两种方法:
+
+1. 实验盘文件:进入 linux-lab 后,可以在 `src/linux-stable/lib` 文件夹下找到。s
+2. https://www.kernel.org/ 官网下载解压后,得到 `linux[-v]` 文件夹,其下的 `lib` 文件夹中可以找到。
+
+## kernel libc vs glibc
+
+glibc 通用库相较 kernel libc 体积较大且功能齐全。kernel libc 在其轻量的特点上也付出了一些代价,貌似在优化方面做的并不如 glibc 好。但是能精简代码这点在嵌入式领域比较重要。
+
+## kernel libc 目录结构
+
+
+
+通过 chatgpt 总结分类:
+
+1. 内核功能相关文件
+
+- 内核自旋锁、互斥锁、读写锁等锁相关的自测文件
+
+ - locking-selftest-wlock-hardirq.h
+
+ - locking-selftest-wlock-softirq.h
+
+ - locking-selftest-mutex.h
+
+ - locking-selftest-rlock.h
+
+ - locking-selftest-rlock-hardirq.h
+
+ - locking-selftest-rlock-softirq.h
+
+ - locking-selftest-rsem.h
+
+ - locking-selftest-rtmutex.h
+
+ - locking-selftest-softirq.h
+
+ - locking-selftest-spin.h
+
+ - locking-selftest-spin-hardirq.h
+
+ - locking-selftest-spin-softirq.h
+
+ - locking-selftest.c
+
+- 早期 cpio 文件支持
+
+ - earlycpio.c
+
+- 故障注入(及其拷贝)用于模拟故障的发生
+
+ - fault-inject.c
+
+ - fault-inject-usercopy.c
+
+- Undefined Behavior Sanitizer 相关的文件
+
+ - ubsan.c
+
+ - Kconfig.ubsan
+
+ - ubsan.h
+
+- 用于 Kernel Address SANitizer 的文件
+
+ - kasan 相关文件
+
+- 内核 Fence 机制相关文件
+
+ - kfence
+
+- 内核 Concurrency Sanitizer 相关文件
+
+ - kcsan
+
+2. 文件系统和配置相关文件:
+
+- 用于文件系统或其他模块的文件。
+ - 842
+- 用于异常处理的外部表相关文件
+ - extable.c
+- 用于内核启动配置的文件
+ - bootconfig.c
+- 用于处理命令行参数的文件
+ - cmdline.c
+ - cmdline_kunit.c
+- 生成 CRC 表的文件
+ - gen_crc*.c
+- KUnit 测试框架相关文件
+ - Kconfig
+ - kunit
+- 用于内核后期初始化的文件
+ - late_init.c
+- 用于内核热补丁的相关文件
+ - livepatch
+- OID 注册表相关文件
+ - oid_registry.c
+ - build_OID_registry
+
+3. 硬件和驱动相关文件
+
+- Flattened Device Tree (FDT) 相关文件
+ - fdt_addresses.c
+ - fdt.c
+ - fdt_empty_tree.c
+ - fdt_ro.c
+ - fdt_rw.c
+- 用于逻辑设备的文件
+ - logic_*.c
+- PCI I/O 映射相关文件
+ - pci_iomap.c
+- 用于硬件或数据结构的文件
+ - bch.c
+ - bsearch.c
+ - btree.c
+
+4. 网络和协议相关文件:
+
+- 用于审计相关功能的文件
+ - audit.c
+- 非屏蔽中断回溯相关文件
+ - nmi_backtrace.c
+- 用于文本搜索的文件
+ - textsearch.c
+- Virtual Dynamic Shared Object 相关文件
+ - vdso
+
+5. 数据结构和算法相关文件
+
+- 关联数组相关文件
+ - assoc_array.c
+- 基数树相关文件
+ - radix-tree.c
+- 位域测试文件
+ - bitfield_kunit.c
+- 用于内核功能测试的文件
+ - 一些以 `test_` 开头的文件
+
+6. 其他文件:
+
+- 密码学相关文件
+ - crypto
+- 用于构建 ID 的文件
+ - buildid.c
+- 数学库相关文件
+ - math
+- 内存相关文件
+ - memcat_p.c
+- UUID 相关文件
+ - uuid.c
+- zlib 压缩库相关文件
+ - zlib_*
+- 用于输出函数的文件
+ - vsprintf.c
+
+## 新库函数的测试
+
+首先熟悉一下测试流程。
+
+在添加完一个库函数后,编写相应的测试文件,并不是通过直接编译运行此 test 文件来执行的,而是通过编辑 Makefile 文件,Kconfig 文件。以及在 kernel-menuconfig 里面,启动相应的测试文件,这样在 `make boot` 阶段即可执行。因此整体的测试流程如下:
+
+- 在 lib 库里编写相应的 test_xxx.c 文件。
+- 在 lib/makefile 里添加编译时的配置。
+- 在 lib/Kconfig 里添加配置。
+- 在 menuconfig 里启动这个 test 测试用例。
+
+下面我们以 printf 函数的测试为例展开分析。
+
+首先查看 vsprintf.c, vsprintf 函数可以把输出存到字符缓冲中:
+
+```c
+// src/linux-stable/lib/vsprintf.c:2707
+
+/**
+ * vsnprintf - Format a string and place it in a buffer
+ * @buf: The buffer to place the result into
+ * @size: The size of the buffer, including the trailing null space
+ * @fmt: The format string to use
+ * @args: Arguments for the format string
+ *
+ * This function generally follows C99 vsnprintf, but has some
+ * extensions and a few limitations:
+ *
+ * - ``%n`` is unsupported
+ * - ``%p*`` is handled by pointer()
+ *
+ * See pointer() or Documentation/core-api/printk-formats.rst for more
+ * extensive description.
+ *
+ * **Please update the documentation in both places when making changes**
+ *
+ * The return value is the number of characters which would
+ * be generated for the given input, excluding the trailing
+ * '\0', as per ISO C99. If you want to have the exact
+ * number of characters written into @buf as return value
+ * (not including the trailing '\0'), use vscnprintf(). If the
+ * return is greater than or equal to @size, the resulting
+ * string is truncated.
+ *
+ * If you're not already dealing with a va_list consider using snprintf().
+ */
+int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
+EXPORT_SYMBOL(vsnprintf);
+```
+
+然后摘取 test_printf.c 中的一部分:test string 部分分析。首先封装了 do_test 和 test 函数:
+
+```c
+// src/linux-stable/lib/test_printf.c:41
+
+do_test(int bufsize, const char *expect, int elen,
+ const char *fmt, va_list ap)
+{
+ va_list aq;
+ int ret, written;
+
+ total_tests++;
+
+ memset(alloced_buffer, FILL_CHAR, BUF_SIZE + 2*PAD_SIZE);
+ va_copy(aq, ap);
+ ret = vsnprintf(test_buffer, bufsize, fmt, aq);
+ va_end(aq);
+
+ if (ret != elen) {// 输入长度是否和预期长度匹配
+ pr_warn("vsnprintf(buf, %d, \"%s\", ...) returned %d, expected %d\n",
+ bufsize, fmt, ret, elen);
+ return 1;
+ }
+
+ if (memchr_inv(alloced_buffer, FILL_CHAR, PAD_SIZE)) {// 写入范围是否超出了 PAD_SIZE
+ pr_warn("vsnprintf(buf, %d, \"%s\", ...) wrote before buffer\n", bufsize, fmt);
+ return 1;
+ }
+
+ if (!bufsize) {// bufsize==0 但是有输入信息
+ if (memchr_inv(test_buffer, FILL_CHAR, BUF_SIZE + PAD_SIZE)) {
+ pr_warn("vsnprintf(buf, 0, \"%s\", ...) wrote to buffer\n",
+ fmt);
+ return 1;
+ }
+ return 0;
+ }
+
+ written = min(bufsize-1, elen);
+ if (test_buffer[written]) {// 校验结尾是否是、0
+ pr_warn("vsnprintf(buf, %d, \"%s\", ...) did not nul-terminate buffer\n",
+ bufsize, fmt);
+ return 1;
+ }
+
+ if (memchr_inv(test_buffer + written + 1, FILL_CHAR, BUF_SIZE + PAD_SIZE - (written + 1))) {// 是否是在 PAD_SIZE 后面终止的
+ pr_warn("vsnprintf(buf, %d, \"%s\", ...) wrote beyond the nul-terminator\n",
+ bufsize, fmt);
+ return 1;
+ }
+
+ if (memcmp(test_buffer, expect, written)) {// 比较一下写入缓冲区的数据和预期是否一样,assert
+ pr_warn("vsnprintf(buf, %d, \"%s\", ...) wrote '%s', expected '%.*s'\n",
+ bufsize, fmt, test_buffer, written, expect);
+ return 1;
+ }
+ return 0;// 以上问题都没有碰到,成功
+}
+```
+
+```c
+// src/linux-stable/lib/test_printf.c:95
+
+static void __printf(3, 4) __init
+__test(const char *expect, int elen, const char *fmt, ...)
+{
+ va_list ap;
+ int rand;
+ char *p;
+
+ if (elen >= BUF_SIZE) {// 长度超出缓冲区,直接不存了
+ pr_err("error in test suite: expected output length %d too long. Format was '%s'.\n",
+ elen, fmt);
+ failed_tests++;
+ return;
+ }
+
+ va_start(ap, fmt);
+
+ /*
+ * Every fmt+args is subjected to four tests: Three where we
+ * tell vsnprintf varying buffer sizes (plenty, not quite
+ * enough and 0), and then we also test that kvasprintf would
+ * be able to print it as expected.
+ */
+ failed_tests += do_test(BUF_SIZE, expect, elen, fmt, ap);// 统计多少用例没有通过
+ rand = 1 + prandom_u32_max(elen+1);
+ /* Since elen < BUF_SIZE, we have 1 <= rand <= BUF_SIZE. */
+ failed_tests += do_test(rand, expect, elen, fmt, ap);// 随机缓冲区长度
+ failed_tests += do_test(0, expect, elen, fmt, ap);// 0 缓冲区长度
+
+ p = kvasprintf(GFP_KERNEL, fmt, ap);
+ if (p) {
+ total_tests++;
+ if (memcmp(p, expect, elen+1)) {
+ pr_warn("kvasprintf(..., \"%s\", ...) returned '%s', expected '%s'\n",
+ fmt, p, expect);
+ failed_tests++;
+ }
+ kfree(p);
+ }
+ va_end(ap);
+}
+```
+
+然后在具体测试用例中传入参数交给 test。比如下例是测试打印字符串。
+
+```c
+// src/linux-stable/lib/test_printf.c:183
+
+static void __init
+test_string(void)
+{
+ test("", "%s%.0s", "", "123");// 参数 1 是预期输出,参数 2 是占位符组合,后面变长参数是传入占位符的数据
+ test("ABCD|abc|123", "%s|%.3s|%.*s", "ABCD", "abcdef", 3, "123456");
+ test("1 | 2|3 | 4|5 ", "%-3s|%3s|%-*s|%*s|%*s", "1", "2", 3, "3", 3, "4", -3, "5");
+ test("1234 ", "%-10.4s", "123456");
+ test(" 1234", "%10.4s", "123456");
+ /*
+ * POSIX and C99 say that a negative precision (which is only
+ * possible to pass via a * argument) should be treated as if
+ * the precision wasn't present, and that if the precision is
+ * omitted (as in %.s), the precision should be taken to be
+ * 0. However, the kernel's printf behave exactly opposite,
+ * treating a negative precision as 0 and treating an omitted
+ * precision specifier as if no precision was given.
+ *
+ * These test cases document the current behaviour; should
+ * anyone ever feel the need to follow the standards more
+ * closely, this can be revisited.
+ */
+ test(" ", "%4.*s", -5, "123456");
+ test("123456", "%.s", "123456");
+ test("a||", "%.s|%.0s|%.*s", "a", "b", 0, "c");
+ test("a | | ", "%-3.s|%-3.0s|%-3.*s", "a", "b", 0, "c");
+}
+```
+
+然后在 Makefile 里有添加这么一行:
+
+```makefile
+// src/linux-stable/lib/Makefile:83
+
+obj-$(CONFIG_TEST_PRINTF) += test_printf.o
+```
+
+根据 CONFIG_TEST_PRINTF 变量来判断是否编译出 test_printf 的目标文件。
+
+然后在 lib/KConfig.debug 里可以看到 test 文件设定在 menuconfig 的什么位置下。
+
+```shell
+menu "Kernel hacking"
+config TEST_PRINTF
+ tristate "Test printf() family of functions at runtime"
+```
+
+然后在 linux-lab 目录下执行 `make kernel-menuconfig`,可以看到 printf 对应的测试项:
+
+ kernel-hacking
+
+ -> Kernel Testing and Coverage
+
+ -> Run time Testing
+
+ -> Test printf() family of functions at runtime
+
+我们将其启用:
+
+
+
+也就是说我们在 menuconfig 里启用了 "Test printf() family of functions at runtime" 这一项,则相当于启用了 TEST_PRINTF 这个变量,则 makefile 中 CONFIG_TEST_PRINTF 这个变量也会被启用,然后编译链接的时候就会添加 test_printf.c 生成的目标文件,并在运行内核的时候一并运行。
+
+完成配置后,`make kernel` 重新编译内核,并 `make boot` 运行,可以看到打印的调试信息中出现了:
+
+```shell
+test_printf: loaded.
+crng possibly not yet initialized. plain 'p' buffer contains "(____ptrval____)"
+test_printf: crng possibly not yet initialized. plain 'p' buffer contains "(____ptrval____)"
+test_printf: crng possibly not yet initialized. plain 'p' buffer contains "(____ptrval____)"
+test_printf: crng possibly not yet initialized. plain 'p' buffer contains "(____ptrval____)"
+test_printf: all 416 tests passed
+```
+
+
+
+这一段说明测试模块顺利启动并通过测试。
+
+## 总结
+
+本文主要是对 kernel libc 库的目录结构,编译、运行,以及添加、运行测试文件做了一些分析,并且也分析了在测试过程中发现的 Linux kernel 内核库如何编译、运行、测试。
+
+同样,我们也可以借由这个过程反推出:如果我们要新添加一个库函数,添加流程以及如何对其展开测试。
+
+- 首先,在 lib 库文件夹里编写相应文件;
+- 在 lib/Makefile 里以大致为 `obj-$(CONFIG_NAME) += XXX.o` 的形式把这个模块定义为 CONFIG_NAME,让 Kconfig 可见;
+- 在 lib/Kconfig 里规定 CONFIG_NAME 的数据类型(如可选的 bool 型),所属菜单使得开发者使用时知道从 menuconfig 的什么位置去启用等;
+- 在 menuconfig 中勾选启用对应模块,保存 .config 文件后 `make kernel` 重新编译,这样下次启动后该模块就会同时被启用;
+- 测试文件的编写:基本流程同上,编写相应文件,在 lib/Kconfig.debug 里添加测试文件的信息,通常以 test_xxx 格式命名,添加于 `make kernel-menuconfig` -> kernel-hacking -> Kernel Testing and Coverage -> Run time Testing 目录下;
+- 下次编译后 `make boot` 启动时就会自动运行 test 文件完成启动时的测试。
+
+对于 Linux kernel 库的使用,本文只做了一个简单尝试。后续可能展开尝试 kernel libc 库的具体用法,以及继续对 kernel libc 库进行分析。
+
+## 参考资料
diff --git a/articles/images/riscv-klibc/20230731-kernel-libc-file-structure.jpg b/articles/images/riscv-klibc/20230731-kernel-libc-file-structure.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..03537663d4926422a538475b21d1ef3269d417a8
Binary files /dev/null and b/articles/images/riscv-klibc/20230731-kernel-libc-file-structure.jpg differ
diff --git a/articles/images/riscv-klibc/20230731-kernel-libc-menuconfig.jpg b/articles/images/riscv-klibc/20230731-kernel-libc-menuconfig.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..90a8d9144932c180ebfb8a62eee5d62071fa988c
Binary files /dev/null and b/articles/images/riscv-klibc/20230731-kernel-libc-menuconfig.jpg differ
diff --git a/articles/images/riscv-klibc/20230731-kernel-libc-test-vsprint-example.jpg b/articles/images/riscv-klibc/20230731-kernel-libc-test-vsprint-example.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b0b43c64607dc56d45927e8421d26d13477e58c4
Binary files /dev/null and b/articles/images/riscv-klibc/20230731-kernel-libc-test-vsprint-example.jpg differ