1 .. include:: ../disclaimer-zh_CN.rst 2 3 :Original: Documentation/kernel-hacking/hackin 4 5 :译者: 6 7 吴想成 Wu XiangCheng <bobwxc@email.cn> 8 9 ============== 10 内核骇客指北 11 ============== 12 13 :作者: Rusty Russell 14 15 引言 16 ===== 17 18 欢迎咱优雅的读者们来阅读Rusty的 19 描述了内核代码的常见例程和一般 20 开发。我回避了实现细节:这是代 21 22 在你读这篇文章之前,请理解我从 23 但我一直想读这样的文章,自己写 24 通用起点和其他信息的汇编。 25 26 玩家 27 ======= 28 29 在任何时候,系统中的每个CPU都可 30 31 - 与任何进程无关,服务于硬件中 32 33 - 与任何进程无关,服务于软件中 34 35 - 运行于内核空间中,与进程(用 36 37 - 在用户空间中运行进程。 38 39 它们之间有优先级顺序。最下面的 40 每个层级只能被上方的抢占。例如 41 会抢占它,但是硬件中断可以抢占 42 43 我们将会看到许多方法,用户上下 44 45 用户上下文 46 ------------ 47 48 用户上下文是指当您从系统调用或 49 重要的任务和中断抢占。您可以通 50 51 .. note:: 52 53 在模块加载和卸载以及块设备层 54 55 在用户上下文中,当前 ``current`` 指 56 且 :c:func:`in_interrupt()` ( ``include/lin 57 58 .. warning:: 59 60 请注意,如果您禁用了抢占或软 61 返回假阳性。 62 63 硬件中断(Hard IRQs) 64 ---------------------- 65 66 像定时器、网卡和键盘等都是可能 67 处理程序,为硬件提供服务。内核 68 它将被排队(或丢弃)。因为它会 69 确认中断,标记一个“软件中断” 70 71 您可以通过 in_hardirq() 返回真来判 72 73 .. warning:: 74 75 请注意,如果中断被禁用,这将 76 77 软件中断上下文:软中断(Softirqs 78 ---------------------------------------------- 79 80 当系统调用即将返回用户空间或硬 81 过硬件中断)的“软件中断”将运 82 83 此处完成了许多真正的中断处理工 84 部”(BHs)机制,无法利用多个CPU 85 我们放弃了这个限制,转而使用“ 86 87 ``include/linux/interrupt.h`` 列出了不同 88 的软中断( ``include/linux/timer.h`` ) 89 函数。 90 91 软中断通常是一个很难处理的问题 92 子任务( ``include/linux/interrupt.h`` ) 93 您可以拥有任意数量),并且它们 94 子任务也可以同时运行。 95 96 .. warning:: 97 98 “tasklet”这个名字是误导性的 99 100 你可以使用 :c:func:`in_softirq()` 宏( 101 是否处于软中断(或子任务)中。 102 103 .. warning:: 104 105 注意,如果持有 :ref:`bottom half lo 106 假阳性。 107 108 一些基本规则 109 ================ 110 111 缺少内存保护 112 如果你损坏了内存,无论是在用 113 你确定你不能在用户空间里做你 114 115 缺少浮点或MMX 116 FPU上下文不会被保存;即使在用 117 您会弄乱某些用户进程的FPU状态 118 完整的FPU状态(并避免上下文切 119 120 严格的堆栈限制 121 对于大多数32位体系结构,根据 122 多数64位机器,内核堆栈大约为1 123 应避免深度递归和栈上的巨型本 124 125 Linux内核是可移植的 126 就这样吧。您的代码应该是纯64 127 尽量减少CPU特定的东西,例如内 128 最小化以便于移植。一般来说, 129 130 输入输出控制(ioctls):避免编写 131 ============================================== 132 133 系统调用(system call)通常看起来 134 135 asmlinkage long sys_mycall(int arg) 136 { 137 return 0; 138 } 139 140 141 首先,在大多数情况下,您无需创 142 的输入输出控制(ioctls)。这比系 143 ``include/asm/unistd.h`` 和 ``arch/kernel/ent 144 接受。 145 146 如果您的程序所做的只是读取或写 147 148 在输入输出控制中,您处于进程的 149 (errno,请参阅 ``include/uapi/asm-generi 150 ``include/uapi/asm-generic/errno.h`` 和 ``inc 151 回0。 152 153 在睡眠之后,您应该检查是否出现 154 调用,并返回 ``-ERESTARTSYS`` 错误。 155 信号处理程序,然后系统调用将重 156 备好处理重新启动,例如若您处理 157 158 :: 159 160 if (signal_pending(current)) 161 return -ERESTARTSYS; 162 163 164 如果你要做更长时间的计算:优先 165 应该定期检查你是否需要让出CPU( 166 习惯用法:: 167 168 cond_resched(); /* Will sleep */ 169 170 171 接口设计的小注释:UNIX系统调用的 172 Provide mechanism not policy”。 173 174 死锁的“配方” 175 ==================== 176 177 您不能调用任何可能睡眠的程序, 178 179 - 您处于用户上下文中。 180 181 - 你未拥有任何自旋锁。 182 183 - 您已经启用中断(实际上,Andi Kle 184 您想要的)。 185 186 注意,有些函数可能隐式地睡眠: 187 ``GFP_ATOMIC`` 的内存分配函数。 188 189 您应该始终打开 ``CONFIG_DEBUG_ATOMIC_S 190 规则,它将警告您。如果你 **真的* 191 192 真的会这样。 193 194 195 常用函数/程序 196 =============== 197 198 :c:func:`printk()` 199 ------------------ 200 201 定义于 ``include/linux/printk.h`` 202 203 :c:func:`printk()` 将内核消息提供给控 204 试和报告错误很有用,并且可以在 205 的控制台中充斥着printk消息则会无 206 字符串,并通过C字符串串联为其提 207 208 printk(KERN_INFO "i = %u\n", i); 209 210 211 参见 ``include/linux/kern_levels.h`` ;了 212 解释为级别。特殊用法:打印IP地 213 214 __be32 ipaddress; 215 printk(KERN_INFO "my ip: %pI4\n", &ipaddre 216 217 218 :c:func:`printk()` 内部使用的1K缓冲区 219 220 .. note:: 221 222 当您开始在用户程序中将printf打 223 :) 224 225 .. note:: 226 227 另一个注释:最初的unix第六版 228 不应该用于叽叽喳喳”。你也应 229 230 :c:func:`copy_to_user()` / :c:func:`copy_from_ 231 ---------------------------------------------- 232 233 定义于 ``include/linux/uaccess.h`` / ``asm/ 234 235 **[睡眠]** 236 237 :c:func:`put_user()` 和 :c:func:`get_user()` 238 间中传出单个值(如int、char或long 239 引用:应该使用这些程序复制数据 240 241 :c:func:`copy_to_user()` 和 :c:func:`copy_fro 242 空间复制任意数量的数据。 243 244 .. warning:: 245 246 与 :c:func:`put_user()` 和 :c:func:`get_ 247 数据量(即0仍然意味着成功) 248 249 【是的,这个讨厌的接口真心让我 250 —— Rusty Russell】 251 252 这些函数可以隐式睡眠。它不应该 253 或获得自旋锁。 254 255 :c:func:`kmalloc()`/:c:func:`kfree()` 256 ------------------------------------- 257 258 定义于 ``include/linux/slab.h`` 259 260 **[可能睡眠:见下]** 261 262 这些函数用于动态请求指针对齐的 263 :c:func:`kmalloc()` 需要额外的标志词 264 265 ``GFP_KERNEL`` 266 可以睡眠和交换以释放内存。只 267 的方法。 268 269 ``GFP_ATOMIC`` 270 不会睡眠。较 ``GFP_KERNEL`` 更不 271 有一个很好的内存不足错误处理 272 273 ``GFP_DMA`` 274 分配低于16MB的ISA DMA。如果你不 275 276 如果您看到一个从无效上下文警告 277 ``GFP_ATOMIC`` 的情况下从中断上下文 278 快点! 279 280 如果你要分配至少 ``PAGE_SIZE`` ( ``a 281 字节,请考虑使用 :c:func:`__get_free_p 282 它采用顺序参数(0表示页面大小, 283 优先级标志字。 284 285 如果分配的字节数超过一页,可以 286 配虚拟内存。此块在物理内存中不 287 是为您准备好的连续空间(因此它 288 如果您真的需要为一些奇怪的设备 289 Linux对此支持很差,因为正在运行 290 方法是在引导过程的早期通过 :c:fun 291 292 在创建自己的常用对象缓存之前, 293 缓存。 294 295 :c:macro:`current` 296 ------------------ 297 298 定义于 ``include/asm/current.h`` 299 300 此全局变量(其实是宏)包含指向 301 用户上下文中有效。例如,当进程 302 在中断上下文中不为空(**not NULL** 303 304 :c:func:`mdelay()`/:c:func:`udelay()` 305 ------------------------------------- 306 307 定义于 ``include/asm/delay.h`` / ``include/ 308 309 :c:func:`udelay()` 和 :c:func:`ndelay()` 函 310 大的值,因为这样会导致溢出—— 311 考虑 :c:func:`msleep()`。 312 313 :c:func:`cpu_to_be32()`/:c:func:`be32_to_cpu() 314 ---------------------------------------------- 315 316 定义于 ``include/asm/byteorder.h`` 317 318 :c:func:`cpu_to_be32()` 系列函数(其中 319 “le”)是在内核中进行字节序转 320 提供反向转换函数: 321 :c:func:`be32_to_cpu()` 等。 322 323 这些函数有两个主要的变体:指针 324 指向给定类型的指针,并返回转换 325 :c:func:`cpu_to_be32s()` ,它转换指针引 326 327 :c:func:`local_irq_save()`/:c:func:`local_irq_ 328 ---------------------------------------------- 329 330 定义于 ``include/linux/irqflags.h`` 331 332 333 这些程序禁用本地CPU上的硬中断, 334 ``unsigned long flags`` 参数中保存以前 335 直接使用 :c:func:`local_irq_disable()` 和 336 337 .. _local_bh_disable_zh: 338 339 :c:func:`local_bh_disable()`/:c:func:`local_bh 340 ---------------------------------------------- 341 342 定义于 ``include/linux/bottom_half.h`` 343 344 345 这些程序禁用本地CPU上的软中断, 346 软中断,那么在调用这对函数之后 347 CPU上运行。 348 349 :c:func:`smp_processor_id()` 350 ---------------------------- 351 352 定义于 ``include/linux/smp.h`` 353 354 :c:func:`get_cpu()` 禁用抢占(这样您 355 处理器号,介于0和 ``NR_CPUS`` 之间 356 使用 :c:func:`put_cpu()` 再次返回。 357 358 如果您知道您不能被另一个任务抢 359 可以使用 :c:func:`smp_processor_id()`。 360 361 ``__init``/``__exit``/``__initdata`` 362 ------------------------------------ 363 364 定义于 ``include/linux/init.h`` 365 366 引导之后,内核释放一个特殊的部 367 标记的数据结构在引导完成后被丢 368 ``__exit`` 用于声明只在退出时需要 369 被删除。请参阅头文件以使用。请 370 :c:func:`EXPORT_SYMBOL_GPL()` 将标记为 ``_ 371 的——这将出问题。 372 373 374 :c:func:`__initcall()`/:c:func:`module_init()` 375 ---------------------------------------------- 376 377 定义于 ``include/linux/init.h`` / ``includ 378 379 内核的许多部分都作为模块(内核 380 :c:func:`module_init()` 和 :c:func:`module_ex 381 ``#ifdef`` ,即可以作为模块运行或 382 383 :c:func:`module_init()` 宏定义在模块插 384 调用哪个函数:如果文件未编译为 385 :c:func:`__initcall()` ,它通过链接器 386 387 该函数可以返回一个错误值,以导 388 中,则此操作无效)。此函数在启 389 390 :c:func:`module_exit()` 391 ----------------------- 392 393 394 定义于 ``include/linux/module.h`` 395 396 这个宏定义了在模块删除时要调用 397 只有在模块使用计数到零时才会调 398 时,所有的东西都必须清理干净。 399 400 注意,这个宏是可选的:如果它不 401 402 :c:func:`try_module_get()`/:c:func:`module_put 403 ---------------------------------------------- 404 405 定义于 ``include/linux/module.h`` 406 407 这些函数会操作模块使用计数,以 408 则无法删除模块,参见下文)。在 409 :c:func:`try_module_get()` :若失败,那 410 若成功,您就可以安全地进入模块 411 412 大多数可注册结构体都有所有者字 413 :c:type:`struct file_operations <file_operatio 414 宏 ``THIS_MODULE`` 。 415 416 等待队列 ``include/linux/wait.h`` 417 ==================================== 418 419 **[睡眠]** 420 421 等待队列用于等待某程序在条件为 422 条件。先声明一个 :c:type:`wait_queue_h 423 一个关于它们自己的 :c:type:`wait_queu 424 425 声明 426 ----- 427 428 使用 :c:func:`DECLARE_WAIT_QUEUE_HEAD()` 宏 429 或者在初始化代码中使用 :c:func:`ini 430 431 排队 432 ----- 433 434 将自己放在等待队列中相当复杂, 435 个宏可以来执行此操作: :c:func:`wai 436 ( ``include/linux/wait.h`` )第一个参 437 式;当该表达式为true时宏返回0, 438 :c:func:`wait_event()` 版本会忽略信号 439 440 唤醒排队任务 441 ------------- 442 443 调用 :c:func:`wake_up()` ( ``include/linux 444 进程。例外情况:如果有一个进程 445 会被唤醒。这个基本函数的其他变 446 447 原子操作 448 ========= 449 450 某些操作在所有平台上都有保证。 451 ( ``include/asm/atomic.h`` )的函数; 452 您必须使用这些函数来操作或读取 453 :c:func:`atomic_read()` 和 :c:func:`atomic_se 454 :c:func:`atomic_add()` ,:c:func:`atomic_sub( 455 :c:func:`atomic_dec()` 和 :c:func:`atomic_dec 456 则返回true)。 457 458 是的。它在原子变量为零时返回true 459 460 请注意,这些函数比普通的算术运 461 462 第二类原子操作是在 ``unsigned long`` 463 原子位操作。这些操作通常采用指 464 位。:c:func:`set_bit()`,:c:func:`clear_bit 465 清除和更改给定位。:c:func:`test_and_s 466 和 :c:func:`test_and_change_bit()` 执行相 467 true;这些对于原子设置标志特别有 468 469 可以使用大于 ``BITS_PER_LONG`` 位的位 470 不太正常,所以最好不要这样做。 471 472 符号 473 ===== 474 475 在内核内部,正常的链接规则仍然 476 否则它可以在内核中的任何位置使 477 该表将入口点限制为内核内部。模 478 479 :c:func:`EXPORT_SYMBOL()` 480 ------------------------- 481 482 定义于 ``include/linux/export.h`` 483 484 这是导出符号的经典方法:动态加 485 486 :c:func:`EXPORT_SYMBOL_GPL()` 487 ----------------------------- 488 489 定义于 ``include/linux/export.h`` 490 491 492 类似于 :c:func:`EXPORT_SYMBOL()`,只是 : 493 符号只能由具有由 :c:func:`MODULE_LICEN 494 意味着此函数被认为是一个内部实 495 开发人员在添加一些新的API或功能 496 497 :c:func:`EXPORT_SYMBOL_NS()` 498 ---------------------------- 499 500 定义于 ``include/linux/export.h`` 501 502 这是 ``EXPORT_SYMBOL()`` 的变体,允许 503 Documentation/core-api/symbol-namespaces.rst 504 505 :c:func:`EXPORT_SYMBOL_NS_GPL()` 506 -------------------------------- 507 508 定义于 ``include/linux/export.h`` 509 510 这是 ``EXPORT_SYMBOL_GPL()`` 的变体,允 511 Documentation/core-api/symbol-namespaces.rst 512 513 程序与惯例 514 =========== 515 516 双向链表 ``include/linux/list.h`` 517 ----------------------------------- 518 519 内核头文件中曾经有三组链表程序 520 需求,那么这是一个不错的选择。 521 522 通常 :c:func:`list_for_each_entry()` 很有 523 524 返回值惯例 525 ------------ 526 527 对于在用户上下文中调用的代码, 528 负错误值(例如 ``-EFAULT`` )表示失 529 相当普遍。 530 531 使用 :c:func:`ERR_PTR()` ( ``include/linux 532 然后使用 :c:func:`IS_ERR()` 和 :c:func:`P 533 使用单独的指针参数。挺讨厌的, 534 535 破坏编译 536 ---------- 537 538 Linus和其他开发人员有时会更改开 539 让每个人都保持警惕,还反映了一 540 调用,或者执行额外的检查,或者 541 相当全面的注释到相应的内核邮件 542 替换通常只会让事情变得 **更糟** 543 544 初始化结构体成员 545 ------------------ 546 547 初始化结构体的首选方法是使用指 548 例如:: 549 550 static struct block_device_operations opt_ 551 .open = opt_open, 552 .release = opt_release, 553 .ioctl = opt_ioctl, 554 .check_media_change = opt_media_ch 555 }; 556 557 558 这使得很容易查找(grep),并且可 559 因为它看起来很酷。 560 561 GNU 扩展 562 ---------- 563 564 Linux内核中明确允许GNU扩展。请注 565 得到很好的支持,但以下内容被认 566 的“C 扩展”部分——是的,实际 567 568 - 内联函数 569 570 - 语句表达式(Statement expressions) 571 572 573 - 声明函数/变量/类型的属性(__attr 574 575 - typeof 576 577 - 零长度数组 578 579 - 宏变量 580 581 - 空指针运算 582 583 - 非常量(Non-Constant)初始化程序 584 585 - 汇编程序指令(在 arch/ 和 include/a 586 587 - 字符串函数名(__func__)。 588 589 - __builtin_constant_p() 590 591 在内核中使用long long时要小心,gcc 592 不能工作,因为内核环境中缺少用 593 594 C++ 595 --- 596 597 在内核中使用C++通常是个坏主意, 598 测试包含文件。不过这仍然是可能 599 异常处理(exceptions)。 600 601 #if 602 --- 603 604 通常认为,在头文件(或.c文件顶 605 处理器语句更干净。 606 607 把你的东西放进内核里 608 ====================== 609 610 为了让你的东西更正式、补丁更整 611 612 - 搞清楚你修改的代码属于谁。查 613 ``CREDITS`` 文件的最后一部分。你 614 或者尝试一些已经被拒绝的东西 615 616 确保你把你的名字和电子邮件地 617 现一个缺陷,或者想要做出修改 618 619 - 通常你需要一个配置选项来支持 620 配置语言很容易通过剪切和粘贴 621 Documentation/kbuild/kconfig-language.rst 622 623 在您对选项的描述中,请确保同 624 在此说明任何不兼容和问题。结 625 这是针对那些看不懂你在说什么 626 627 - 编辑 ``Makefile`` :配置变量在这 628 “obj-$(CONFIG_xxx) += xxx.o”。语法 629 Documentation/kbuild/makefiles.rst 。 630 631 - 如果你认为自己做了一些有意义 632 止一个文件(无论如何你的名字 633 意味着您希望在对子系统进行更 634 代码做出更多承诺。 635 636 - 最后,别忘记去阅读 Documentation/p 637 638 Kernel 仙女棒 639 =============== 640 641 浏览源代码时的一些收藏。请随意 642 643 ``arch/x86/include/asm/delay.h``:: 644 645 #define ndelay(n) (__builtin_constant_p(n) 646 ((n) > 20000 ? __bad_ndelay() : __ 647 __ndelay(n)) 648 649 650 ``include/linux/fs.h``:: 651 652 /* 653 * Kernel pointers have redundant informat 654 * scheme where we can return either an er 655 * pointer with the same return value. 656 * 657 * This should be a per-architecture thing 658 * error and pointer decisions. 659 */ 660 #define ERR_PTR(err) ((void *)((long)( 661 #define PTR_ERR(ptr) ((long)(ptr)) 662 #define IS_ERR(ptr) ((unsigned long)( 663 664 ``arch/x86/include/asm/uaccess_32.h:``:: 665 666 #define copy_to_user(to,from,n) 667 (__builtin_constant_p(n) ? 668 __constant_copy_to_user((to),(fro 669 __generic_copy_to_user((to),(from 670 671 672 ``arch/sparc/kernel/head.S:``:: 673 674 /* 675 * Sun people can't spell worth damn. "com 676 * At least we *know* we can't spell, and 677 */ 678 679 /* Uh, actually Linus it is I who cannot s 680 * Sparc assembly will do this to ya. 681 */ 682 C_LABEL(cputypvar): 683 .asciz "compatibility" 684 685 /* Tested on SS-5, SS-10. Probably someone 686 .align 4 687 C_LABEL(cputypvar_sun4m): 688 .asciz "compatible" 689 690 691 ``arch/sparc/lib/checksum.S:``:: 692 693 /* Sun, you just can't beat me, yo 694 * give up. I'm serious, I am goi 695 * out of you, game over, lights o 696 */ 697 698 699 致谢 700 ===== 701 702 感谢Andi Kleen提出点子,回答我的问 703 感谢Philipp Rumpf做了许多拼写和清晰 704 感谢Werner Almesberger对 :c:func:`disable_i 705 Jes Sorensen和Andrea Arcangeli补充了一些 706 感谢Michael Elizabeth Chastain检查并补 707 感谢Telsa Gwynne教我DocBook。
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.