下一页 上一页 目录

1. 启动

1.1 构建 Linux 内核镜像

本节解释 Linux 内核编译过程中采取的步骤以及每个阶段产生的输出。构建过程取决于架构,因此我想强调我们只考虑构建 Linux/x86 内核。

当用户输入 'make zImage' 或 'make bzImage' 时,生成的引导内核镜像分别存储为 arch/i386/boot/zImagearch/i386/boot/bzImage。以下是镜像的构建方式

  1. C 和汇编源文件被编译成 ELF 可重定位目标文件格式 (.o),其中一些使用 ar(1) 逻辑分组到归档文件 (.a) 中。
  2. 使用 ld(1),上述 .o 和 .a 被链接到 vmlinux 中,这是一个静态链接的、未剥离的 ELF 32 位 LSB 80386 可执行文件。
  3. System.mapnm vmlinux 生成,不相关或不重要的符号被过滤掉。
  4. 进入目录 arch/i386/boot
  5. 引导扇区汇编代码 bootsect.S 在预处理时,根据目标是 bzImage 还是 zImage,可以选择是否使用 -D__BIG_KERNEL__,分别生成 bbootsect.sbootsect.s
  6. bbootsect.s 被汇编,然后转换为名为 bbootsect 的“原始二进制”形式(或 bootsect.s 被汇编并原始二进制转换为 bootsect 用于 zImage)。
  7. 设置代码 setup.S (setup.S 包括 video.S) 被预处理为用于 bzImage 的 bsetup.s 或用于 zImage 的 setup.s。与引导扇区代码的方式相同,区别在于 bzImage 存在 -D__BIG_KERNEL__。然后,结果被转换为名为 bsetup 的“原始二进制”形式。
  8. 进入目录 arch/i386/boot/compressed,并将 /usr/src/linux/vmlinux 转换为原始二进制格式的 $tmppiggy(临时文件名),删除 .note.comment ELF 节。
  9. gzip -9 < $tmppiggy > $tmppiggy.gz
  10. 将 $tmppiggy.gz 链接到 ELF 可重定位文件 (ld -r) piggy.o
  11. 将压缩例程 head.Smisc.c (仍在 arch/i386/boot/compressed 目录中)编译成 ELF 目标文件 head.omisc.o
  12. head.omisc.opiggy.o 链接到 bvmlinux 中(或 zImage 的 vmlinux,不要将其与 /usr/src/linux/vmlinux 混淆!)。请注意 vmlinux 使用的 -Ttext 0x1000bvmlinux 使用的 -Ttext 0x100000 之间的区别,即对于 bzImage,压缩加载器被加载到高地址。
  13. bvmlinux 转换为“原始二进制” bvmlinux.out,删除 .note.comment ELF 节。
  14. 返回 arch/i386/boot 目录,并使用程序 tools/build,将 bbootsectbsetupcompressed/bvmlinux.out 连接到 bzImage 中(对于 zImage,删除上面多余的 'b')。这会将重要的变量(如 setup_sectsroot_dev)写入引导扇区的末尾。
引导扇区的大小始终为 512 字节。设置的大小必须大于 4 个扇区,但上限约为 12K - 规则是

0x4000 字节 >= 512 + setup_sects * 512 + 引导扇区/设置运行时堆栈的空间

稍后我们将看到此限制的来源。

在此步骤生成的 bzImage 大小的上限约为 2.5M,用于使用 LILO 引导,对于从软盘或 CD-ROM(El-Torito 仿真模式)引导原始镜像,上限为 0xFFFF 段(0xFFFF0 = 1048560 字节)。

请注意,虽然 tools/build 验证了引导扇区、内核镜像和设置大小下限的大小,但它不检查所述设置大小的 *上限*。因此,只需在 setup.S 的末尾添加一些大的 ".space",就很容易构建一个损坏的内核。

1.2 启动:概述

启动过程的详细信息特定于架构,因此我们将重点关注 IBM PC/IA32 架构。由于旧的设计和向后兼容性,PC 固件以老式的方式引导操作系统。此过程可以分为以下六个逻辑阶段

  1. BIOS 选择启动设备。
  2. BIOS 从启动设备加载引导扇区。
  3. 引导扇区加载设置、解压缩例程和压缩内核镜像。
  4. 内核在保护模式下解压缩。
  5. 低级初始化由汇编代码执行。
  6. 高级 C 初始化。

1.3 启动:BIOS POST

  1. 电源启动时钟发生器并在总线上断言 #POWERGOOD 信号。
  2. CPU #RESET 行被断言(CPU 现在处于真实的 8086 模式)。
  3. %ds=%es=%fs=%gs=%ss=0, %cs=0xFFFF0000,%eip = 0x0000FFF0 (ROM BIOS POST 代码)。
  4. 所有 POST 检查都在禁用中断的情况下执行。
  5. IVT(中断向量表)在地址 0 处初始化。
  6. BIOS 引导加载程序功能通过 int 0x19 调用,其中 %dl 包含启动设备的“驱动器号”。这会将磁道 0、扇区 1 加载到物理地址 0x7C00 (0x07C0:0000)。

1.4 启动:引导扇区和设置

用于引导 Linux 内核的引导扇区可以是

我们在这里详细考虑 Linux 引导扇区。前几行初始化用于段值的便捷宏


29 SETUPSECS = 4                /* default nr of setup-sectors */
30 BOOTSEG   = 0x07C0           /* original address of boot-sector */
31 INITSEG   = DEF_INITSEG      /* we move boot here - out of the way */
32 SETUPSEG  = DEF_SETUPSEG     /* setup starts here */
33 SYSSEG    = DEF_SYSSEG       /* system loaded at 0x10000 (65536) */
34 SYSSIZE   = DEF_SYSSIZE      /* system size: # of 16-byte clicks */

(左侧的数字是 bootsect.S 文件的行号)DEF_INITSEGDEF_SETUPSEGDEF_SYSSEGDEF_SYSSIZE 的值取自 include/asm/boot.h


/* Don't touch these, unless you really know what you're doing. */
#define DEF_INITSEG     0x9000
#define DEF_SYSSEG      0x1000
#define DEF_SETUPSEG    0x9020
#define DEF_SYSSIZE     0x7F00

现在,让我们考虑 bootsect.S 的实际代码


    54          movw    $BOOTSEG, %ax
    55          movw    %ax, %ds
    56          movw    $INITSEG, %ax
    57          movw    %ax, %es
    58          movw    $256, %cx
    59          subw    %si, %si
    60          subw    %di, %di
    61          cld
    62          rep
    63          movsw
    64          ljmp    $INITSEG, $go
       
    65  # bde - changed 0xff00 to 0x4000 to use debugger at 0x6400 up (bde).  We
    66  # wouldn't have to worry about this if we checked the top of memory.  Also
    67  # my BIOS can be configured to put the wini drive tables in high memory
    68  # instead of in the vector table.  The old stack might have clobbered the
    69  # drive table.
       
    70  go:     movw    $0x4000-12, %di         # 0x4000 is an arbitrary value >=
    71                                          # length of bootsect + length of
    72                                          # setup + room for stack;
    73                                          # 12 is disk parm size.
    74          movw    %ax, %ds                # ax and es already contain INITSEG
    75          movw    %ax, %ss
    76          movw    %di, %sp                # put stack at INITSEG:0x4000-12.

第 54-63 行将引导扇区代码从地址 0x7C00 移动到 0x90000。这是通过以下方式实现的

  1. 将 %ds:%si 设置为 $BOOTSEG:0 (0x7C0:0 = 0x7C00)
  2. 将 %es:%di 设置为 $INITSEG:0 (0x9000:0 = 0x90000)
  3. 在 %cx 中设置 16 位字的数目(256 字 = 512 字节 = 1 扇区)
  4. 清除 EFLAGS 中的 DF(方向)标志以自动递增地址 (cld)
  5. 继续复制 512 字节 (rep movsw)

此代码不使用 rep movsd 是有意的(提示 - .code16)。

第 64 行跳转到引导扇区新副本中的标签 go:,即段 0x9000 中。接下来的三个指令(第 64-76 行)在 $INITSEG:0x4000-0xC 处准备堆栈,即 %ss = $INITSEG (0x9000) 和 %sp = 0x3FF4 (0x4000-0xC)。这就是我们之前提到的设置大小限制的来源(请参阅构建 Linux 内核镜像)。

第 77-103 行修补第一个磁盘的磁盘参数表,以允许多扇区读取


    77  # Many BIOS's default disk parameter tables will not recognise
    78  # multi-sector reads beyond the maximum sector number specified
    79  # in the default diskette parameter tables - this may mean 7
    80  # sectors in some cases.
    81  #
    82  # Since single sector reads are slow and out of the question,
    83  # we must take care of this by creating new parameter tables
    84  # (for the first disk) in RAM.  We will set the maximum sector
    85  # count to 36 - the most we will encounter on an ED 2.88.  
    86  #
    87  # High doesn't hurt.  Low does.
    88  #
    89  # Segments are as follows: ds = es = ss = cs - INITSEG, fs = 0,
    90  # and gs is unused.
       
    91          movw    %cx, %fs                # set fs to 0
    92          movw    $0x78, %bx              # fs:bx is parameter table address
    93          pushw   %ds
    94          ldsw    %fs:(%bx), %si          # ds:si is source
    95          movb    $6, %cl                 # copy 12 bytes
    96          pushw   %di                     # di = 0x4000-12.
    97          rep                             # don't need cld -> done on line 66
    98          movsw
    99          popw    %di
   100          popw    %ds
   101          movb    $36, 0x4(%di)           # patch sector count
   102          movw    %di, %fs:(%bx)
   103          movw    %es, %fs:2(%bx)

软盘控制器使用 BIOS 服务 int 0x13 功能 0(重置 FDC)重置,设置扇区紧跟在引导扇区之后加载,即物理地址 0x90200 ($INITSEG:0x200),再次使用 BIOS 服务 int 0x13,功能 2(读取扇区)。这发生在第 107-124 行期间


   107  load_setup:
   108          xorb    %ah, %ah                # reset FDC 
   109          xorb    %dl, %dl
   110          int     $0x13   
   111          xorw    %dx, %dx                # drive 0, head 0
   112          movb    $0x02, %cl              # sector 2, track 0
   113          movw    $0x0200, %bx            # address = 512, in INITSEG
   114          movb    $0x02, %ah              # service 2, "read sector(s)"
   115          movb    setup_sects, %al        # (assume all on head 0, track 0)
   116          int     $0x13                   # read it
   117          jnc     ok_load_setup           # ok - continue
       
   118          pushw   %ax                     # dump error code
   119          call    print_nl
   120          movw    %sp, %bp
   121          call    print_hex
   122          popw    %ax     
   123          jmp     load_setup
       
   124  ok_load_setup:

如果由于某种原因加载失败(坏软盘或有人在操作过程中拔出了软盘),我们会转储错误代码并在无限循环中重试。摆脱它的唯一方法是重启机器,除非重试成功,但通常不会(如果出现问题,只会变得更糟)。

如果成功加载 setup_sects 扇区的设置代码,我们跳转到标签 ok_load_setup:

然后我们继续在物理地址 0x10000 处加载压缩内核镜像。这样做是为了保留低内存 (0-64K) 中的固件数据区域。加载内核后,我们跳转到 $SETUPSEG:0 (arch/i386/boot/setup.S)。一旦不再需要数据(例如,不再调用 BIOS),它将被从 0x10000 移动到 0x1000(物理地址,当然)的整个(压缩)内核镜像覆盖。这是由 setup.S 完成的,它为保护模式设置并跳转到 0x1000,这是压缩内核的头部,即 arch/386/boot/compressed/{head.S,misc.c}。这设置了堆栈并调用 decompress_kernel(),它将内核解压缩到地址 0x100000 并跳转到它。

请注意,旧的引导加载程序(旧版本的 LILO)只能加载设置的前 4 个扇区,这就是为什么设置中有代码在需要时加载其余部分的原因。此外,设置中的代码必须处理加载程序类型/版本与 zImage/bzImage 的各种组合,因此非常复杂。

让我们检查一下引导扇区代码中的 kludge,它允许加载一个大的内核,也称为“bzImage”。设置扇区像往常一样加载在 0x90200 处,但内核一次加载 64K 的块,使用一个特殊的辅助例程,该例程调用 BIOS 将数据从低内存移动到高内存。此辅助例程在 bootsect.S 中由 bootsect_kludge 引用,并在 setup.S 中定义为 bootsect_helpersetup.S 中的 bootsect_kludge 标签包含设置段的值和 bootsect_helper 代码在其中的偏移量,以便引导扇区可以使用 lcall 指令跳转到它(段间跳转)。它在 setup.S 中的原因很简单,因为 bootsect.S 中没有更多空间了(这严格来说是不正确的 - 在 bootsect.S 中大约有 4 个备用字节和至少 1 个备用字节,但这显然是不够的)。此例程使用 BIOS 服务 int 0x15 (ax=0x8700) 移动到高内存,并将 %es 重置为始终指向 0x10000。这确保了 bootsect.S 中的代码在从磁盘复制数据时不会耗尽低内存。

1.5 使用 LILO 作为引导加载程序

与裸机 Linux 引导扇区相比,使用专用引导加载程序 (LILO) 有几个优点

  1. 能够在多个 Linux 内核甚至多个操作系统之间进行选择。
  2. 能够传递内核命令行参数(有一个名为 BCP 的补丁为裸机引导扇区+设置添加了此功能)。
  3. 能够加载更大的 bzImage 内核 - 高达 2.5M,而 zImage 为 1M。
旧版本的 LILO(v17 及更早版本)无法加载 bzImage 内核。较新版本(几年前或更早的版本)使用与 bootsect+setup 相同的技术,通过 BIOS 服务将数据从低内存移动到高内存。有些人(尤其是 Peter Anvin)认为应该删除 zImage 支持。主要原因(根据 Alan Cox 的说法)它仍然存在是因为显然有一些损坏的 BIOS,它们使得无法引导 bzImage 内核,但加载 zImage 内核却很好。

LILO 做的最后一件事是跳转到 setup.S,然后事情像往常一样进行。

1.6 高级初始化

通过“高级初始化”,我们考虑任何与引导程序不直接相关的东西,即使执行此操作的部分代码是用汇编编写的,即 arch/i386/kernel/head.S,它是未压缩内核的头部。执行以下步骤

  1. 初始化段值 (%ds = %es = %fs = %gs = __KERNEL_DS = 0x18)。
  2. 初始化页表。
  3. 通过设置 %cr0 中的 PG 位来启用分页。
  4. 零清理 BSS(在 SMP 上,只有第一个 CPU 执行此操作)。
  5. 复制启动参数的前 2k(内核命令行)。
  6. 使用 EFLAGS 检查 CPU 类型,并在可能的情况下使用 cpuid,能够检测 386 及更高版本。
  7. 第一个 CPU 调用 start_kernel(),所有其他 CPU 调用 arch/i386/kernel/smpboot.c:initialize_secondary(),如果 ready=1,它只是重新加载 esp/eip 并且不返回。

init/main.c:start_kernel() 是用 C 编写的,它执行以下操作

  1. 获取全局内核锁(需要它,以便只有一个 CPU 完成初始化)。
  2. 执行特定于架构的设置(内存布局分析、再次复制引导命令行等)。
  3. 打印 Linux 内核“标语”,其中包含版本、用于构建它的编译器等,到内核环形缓冲区以用于消息。这取自 init/version.c 中定义的变量 linux_banner,并且是与 cat /proc/version 显示的字符串相同的字符串。
  4. 初始化陷阱。
  5. 初始化 irq。
  6. 初始化调度程序所需的数据。
  7. 初始化计时数据。
  8. 初始化 softirq 子系统。
  9. 解析引导命令行选项。
  10. 初始化控制台。
  11. 如果模块支持已编译到内核中,则初始化动态模块加载工具。
  12. 如果提供了 "profile=" 命令行,则初始化性能分析缓冲区。
  13. kmem_cache_init(),初始化 slab 分配器的大部分。
  14. 启用中断。
  15. 计算此 CPU 的 BogoMips 值。
  16. 调用 mem_init(),它计算 max_mapnrtotalram_pageshigh_memory 并打印出 "Memory: ..." 行。
  17. kmem_cache_sizes_init(),完成 slab 分配器初始化。
  18. 初始化 procfs 使用的数据结构。
  19. fork_init(),创建 uid_cache,根据可用内存量初始化 max_threads,并将 init_taskRLIMIT_NPROC 配置为 max_threads/2
  20. 创建 VFS、VM、缓冲区缓存等所需的各种 slab 缓存。
  21. 如果编译了 System V IPC 支持,则初始化 IPC 子系统。请注意,对于 System V shm,这包括挂载 shmfs 文件系统的内部(内核中)实例。
  22. 如果编译了配额支持到内核中,则为其创建和初始化特殊的 slab 缓存。
  23. 执行特定于架构的“错误检查”,并在可能的情况下,激活处理器/总线/等错误的解决方法。比较各种架构表明“ia64 没有错误”,而“ia32 有相当多的错误”,一个很好的例子是“f00f 错误”,只有在为低于 686 编译内核时才检查它,并相应地解决它。
  24. 设置一个标志,指示应在“下一个机会”调用调度程序,并创建一个内核线程 init(),如果通过 "init=" 引导参数提供 execute_command,则执行它,否则尝试按顺序执行 /sbin/init/etc/init/bin/init/bin/sh;如果所有这些都失败,则以“建议”使用 "init=" 参数而崩溃。
  25. 进入空闲循环,这是一个 pid=0 的空闲线程。

这里需要注意的重要一点是,init() 内核线程调用 do_basic_setup(),而 do_basic_setup() 又调用 do_initcalls(),后者遍历通过 __initcallmodule_init() 宏注册的函数列表并调用它们。这些函数要么彼此不依赖,要么它们的依赖关系已通过 Makefiles 中的链接顺序手动修复。这意味着,根据目录在树中的位置和 Makefiles 的结构,调用初始化函数的顺序可能会更改。有时,这很重要,因为您可以想象两个子系统 A 和 B,其中 B 依赖于 A 完成的一些初始化。如果 A 是静态编译的,而 B 是一个模块,那么保证在 A 准备好所有必要的环境后调用 B 的入口点。如果 A 是一个模块,那么 B 也必然是一个模块,因此没有问题。但是,如果 A 和 B 都静态链接到内核中怎么办?调用它们的顺序取决于内核镜像的 .initcall.init ELF 节中的相对入口点偏移量。Rogier Wolff 建议引入一个分层“优先级”基础设施,模块可以通过该基础设施让链接器知道它们应该以什么(相对)顺序链接,但到目前为止,还没有可用的补丁以足够优雅的方式实现这一点以被内核接受。因此,请确保您的链接顺序正确。如果在上面的示例中,A 和 B 在静态编译一次时工作正常,那么它们将始终工作,前提是它们在同一个 Makefile 中按顺序列出。如果它们不起作用,请更改其目标文件的列出顺序。

另一个值得注意的事情是 Linux 通过传递 "init=" 引导命令行来执行“替代 init 程序”的能力。这对于从意外覆盖的 /sbin/init 中恢复或手动调试初始化 (rc) 脚本和 /etc/inittab 非常有用,一次执行一个脚本。

1.7 x86 上的 SMP 启动

在 SMP 上,BP 经历引导扇区、设置等的正常序列,直到到达 start_kernel(),然后到达 smp_init(),特别是 src/i386/kernel/smpboot.c:smp_boot_cpus()smp_boot_cpus() 循环遍历每个 apicid(直到 NR_CPUS),并在其上调用 do_boot_cpu()do_boot_cpu() 所做的是为目标 CPU 创建(即 fork_by_hand)一个空闲任务,并在 Intel MP 规范(0x467/0x469)定义的众所周知的位置写入 trampoline.S 中找到的 trampoline 代码的 EIP。然后,它向目标 CPU 生成 STARTUP IPI,这使得该 AP 执行 trampoline.S 中的代码。

引导 CPU 在低内存中为每个 CPU 创建 trampoline 代码的副本。AP 代码在其自己的代码中写入一个魔数,BP 验证该魔数以确保 AP 正在执行 trampoline 代码。Intel MP 规范强制要求 trampoline 代码必须在低内存中。

trampoline 代码只是将 %bx 寄存器设置为 1,进入保护模式并跳转到 startup_32,这是 arch/i386/kernel/head.S 的主要入口点。

现在,AP 开始执行 head.S 并发现它不是 BP,它跳过清除 BSS 的代码,然后进入 initialize_secondary(),后者只是为该 CPU 进入空闲任务 - 回想一下 init_tasks[cpu] 已经由 BP 执行 do_boot_cpu(cpu) 初始化。

请注意,init_task 可以共享,但每个空闲线程都必须有自己的 TSS。这就是为什么 init_tss[NR_CPUS] 是一个数组。

1.8 释放初始化数据和代码

当操作系统初始化自身时,大多数代码和数据结构永远不再需要。大多数操作系统(BSD、FreeBSD 等)无法处理这些不需要的信息,从而浪费了宝贵的物理内核内存。他们使用的借口(参见 McKusick 的 4.4BSD 书)是“相关代码分散在各个子系统中,因此释放它不可行”。当然,Linux 不能使用这样的借口,因为在 Linux 下“如果某件事在原则上是可能的,那么它已经实现或有人正在努力实现它”。

所以,正如我之前所说,Linux 内核只能编译为 ELF 二进制文件,现在我们找到了原因(或原因之一)。与丢弃初始化代码/数据相关的原因是 Linux 提供了两个要使用的宏

这些评估为 gcc 属性说明符(也称为“gcc 魔术”),如 include/linux/init.h 中定义的那样


#ifndef MODULE
#define __init        __attribute__ ((__section__ (".text.init")))
#define __initdata    __attribute__ ((__section__ (".data.init")))
#else
#define __init
#define __initdata
#endif

这意味着如果代码静态编译到内核中(即未定义 MODULE),则它被放置在特殊的 ELF 节 .text.init 中,该节在 arch/i386/vmlinux.lds 的链接器映射中声明。否则(即,如果它是一个模块),宏评估为无。

在引导期间发生的情况是,“init”内核线程(函数 init/main.c:init())调用特定于架构的函数 free_initmem(),后者释放地址 __init_begin__init_end 之间的所有页面。

在典型的系统(我的工作站)上,这导致释放大约 260K 的内存。

通过 module_init() 注册的函数放置在 .initcall.init 中,在静态情况下也会释放它。Linux 的当前趋势是在设计子系统(不一定是模块)时,从设计的早期阶段就提供 init/exit 入口点,以便将来可以在需要时将所讨论的子系统模块化。pipefs 就是一个例子,请参阅 fs/pipe.c。即使给定的子系统永远不会成为模块,例如 bdflush(请参阅 fs/buffer.c),对它的初始化函数使用 module_init() 宏仍然很好且整洁,前提是函数被调用的确切时间无关紧要。

还有两个以类似方式工作的宏,称为 __exit__exitdata,但它们与模块支持的联系更直接,因此将在后面的章节中进行解释。

1.9 处理内核命令行

让我们回顾一下在引导期间传递给内核的命令行发生了什么

  1. LILO(或 BCP)使用 BIOS 键盘服务接受命令行,并将其存储在物理内存中的众所周知的位置,以及一个签名,表明那里有一个有效的命令行。
  2. arch/i386/kernel/head.S 将其前 2k 复制到零页。
  3. arch/i386/kernel/setup.c:parse_mem_cmdline()(由 setup_arch() 调用,而 setup_arch() 本身由 start_kernel() 调用)从零页复制 256 字节到 saved_command_line,后者由 /proc/cmdline 显示。如果存在 "mem=" 选项,则此例程也会处理 "mem=" 选项,并对 VM 参数进行适当的调整。
  4. 我们在 parse_options()(由 start_kernel() 调用)中返回到命令行,它处理一些“内核内”参数(当前为 "init=" 以及 init 的环境/参数)并将每个单词传递给 checksetup()
  5. checksetup() 遍历 ELF 节 .setup.init 中的代码并调用每个函数,如果单词匹配,则将单词传递给它。请注意,使用通过 __setup() 注册的函数返回的 0 值,可以将相同的 "variable=value" 传递给多个函数,其中 "value" 对一个函数无效,而对另一个函数有效。Jeff Garzik 评论说:“这样做的人会被打屁股 :)” 为什么?因为这显然是 ld-order 特定的,即以一种顺序链接的内核将在函数 B 之前调用函数 A,而另一种顺序链接的内核将以相反的顺序调用,结果取决于顺序。

那么,我们如何编写处理引导命令行的代码呢?我们使用 include/linux/init.h 中定义的 __setup()


/*
 * Used for kernel command line parameter setup
 */
struct kernel_param {
        const char *str;
        int (*setup_func)(char *);
};

extern struct kernel_param __setup_start, __setup_end;

#ifndef MODULE
#define __setup(str, fn) \
   static char __setup_str_##fn[] __initdata = str; \
   static struct kernel_param __setup_##fn __initsetup = \
   { __setup_str_##fn, fn }

#else
#define __setup(str,func) /* nothing */
endif

因此,您通常会在您的代码中像这样使用它(取自真实驱动程序 BusLogic HBA drivers/scsi/BusLogic.c 的代码)


static int __init
BusLogic_Setup(char *str)
{
        int ints[3];

        (void)get_options(str, ARRAY_SIZE(ints), ints);

        if (ints[0] != 0) {
                BusLogic_Error("BusLogic: Obsolete Command Line Entry "
                                "Format Ignored\n", NULL);
                return 0;
        }
        if (str == NULL || *str == '\0')
                return 0;
        return BusLogic_ParseDriverOptions(str);
}

__setup("BusLogic=", BusLogic_Setup);

请注意,__setup() 对模块没有任何作用,因此希望处理引导命令行并且可以是模块或静态链接的代码必须在其模块初始化例程中手动调用其解析函数。这也意味着可以编写在编译为模块时处理参数的代码,但在静态编译时则不处理参数,反之亦然。


下一页 上一页 目录