Linux Kernel Makefiles介绍

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux Kernel Makefiles介绍相关的知识,希望对你有一定的参考价值。

参考技术A

本文介绍 Linux 内核 Makefiles 的一些基础内容。

Makefiles 包括:

Linux 内核顶层的 Makefile 文件递归访问内核源代码的子目录。

每个子目录都有一个 kbuild Makefile 文件,根据 .config 文件内容构建内置或模块化目标。

arch/$(ARCH)/Makefile 文件向顶层 Makefile 提供特指定的体系结构信息。

scripts/Makefile.* 文件定义了 kbuild Makefile 构建内核的所有定义和规则等。

Linux 内核编译完成后,最终生成 vmlinux 和 modules 。

Linux 内核中的大多数 Makefile 都使用 kbuild 基础结构, kbuild 文件的首选名称是 Makefile 。如果 Makefile 和 kbuild 文件都存在,则使用 kbuild 文件。

目标定义是 kbuild Makefile 里的核心部分,定义了要构建的文件、特殊的编译选项和递归输入的任何子目录。

例:

kbuild Makefile 将编译所有 $(obj-y) 文件,然后调用 $(AR)rcSTP 将这些文件合并到 built-in.a 文件中。

built-in.a 中不包括符号表,稍后将通过 scripts/link-vmlinux.sh 脚本链接到 vmlinux 文件中。

在 Linux 内核引导期间,将按照链接顺序调用某些函数(例: module_init() 等)。

参考:

Documentation/kbuild/makefiles.txt

Linux kernel oops

本文以ARM64为例,介绍内核的Oops机制,我们使用grep搜索一下内核中可能会报Oops的地方:

./arch/arm64/kernel/sys_compat.c:142:	arm64_notify_die("Oops - bad compat syscall(2)", regs, &info, scno);
./arch/arm64/kernel/traps.c:771:	die("Oops - bad mode", regs, 0);
./arch/arm64/kernel/traps.c:929:		die("Oops - BUG", regs, 0);
./arch/arm64/mm/fault.c:270:	die("Oops", regs, esr);

搜索结果如上所示,一共有这几个地方定义为Oops,因此Oops可能包含如下一些场景:

  1. 64bit 系统调用发生了错误,报Oops
  2. CPU陷入了某种不正常的exception mode,在该exception对应的exception vector entry中直接报Oops
  3. traps中定义的BUG()函数被调用触发了Oops
  4. 内核空间中发生了内存地址相关的访问异常

本文着重从第4种情况来入手跟踪Oops的发生过程:

在代码文件 ./arch/arm64/mm/fault.c 中:

do_translation_fault --> do_bad_area --> __do_kernel_fault --> die_kernel_fault
do_alignment_fault --> do_bad_area --> __do_kernel_fault --> die_kernel_fault
do_page_fault --> __do_kernel_fault --> die_kernel_fault

调用路径如上所示,当内核访问一个内存地址发生错误时会分别调用 do_xxx_fault 该函数最终的目标是 die_kernel_fault:

static void die_kernel_fault(const char *msg, unsigned long addr,
                 unsigned int esr, struct pt_regs *regs)

    bust_spinlocks(1);

    pr_alert("Unable to handle kernel %s at virtual address %016lx\\n", msg,
         addr);

    mem_abort_decode(esr);

    show_pte(addr);
    die("Oops", regs, esr);
    bust_spinlocks(0);
    do_exit(SIGKILL);


这里最终会调用 die("Oops", regs, esr) 函数:

/*
 * This function is protected against re-entrancy.
 */
void die(const char *str, struct pt_regs *regs, int err)

    int ret;
    unsigned long flags;

    raw_spin_lock_irqsave(&die_lock, flags);

    oops_enter();

    console_verbose();
    bust_spinlocks(1);
    ret = __die(str, err, regs); // 其中会发送 notify_die 通知

    if (regs && kexec_should_crash(current))
        crash_kexec(regs);

    bust_spinlocks(0);
    add_taint(TAINT_DIE, LOCKDEP_NOW_UNRELIABLE);
    oops_exit();

    if (in_interrupt())
        panic("Fatal exception in interrupt");
    if (panic_on_oops)     // 判断是否要执行panic操作
        panic("Fatal exception");

    raw_spin_unlock_irqrestore(&die_lock, flags);

    if (ret != NOTIFY_STOP)
        do_exit(SIGSEGV);


在die中可以看到如果配置了panic_on_oops为1,那么才会直接触发panic操作,如果没有配置为1,并不会导致系统panic重启。Oops都会打印内核调用栈。

一种手动触发panic的机制

利用sysrq机制可以触发kernel crash:

echo c > /proc/sysrq-trigger

这种方式就是利用Oops机制来触发panic的:

static void sysrq_handle_crash(int key)

    char *killer = NULL;

    /* we need to release the RCU read lock here,
     * otherwise we get an annoying
     * 'BUG: sleeping function called from invalid context'
     * complaint from the kernel before the panic.
     */
    rcu_read_unlock();
    panic_on_oops = 1;  /* force panic */  //-------- (1)
    wmb();
    *killer = 1; //---------------------------(2)

  • 第(1)步先配置panic_on_oops为1,使得当内核oops时直接触发panic操作
  • 第(2)步访问一个内核NULL空地址,触发oops操作

到这里可能很多人会有一个疑惑,对一个内核空地址赋值,是如何产生了Oops呢?

查看异常arm64向量表:

 /*
  * EL1 mode handlers.
  */

 el1_da:
     /*
      * Data abort handling
      */
     mrs x3, far_el1
     inherit_daif    pstate=x23, tmp=x2
     clear_address_tag x0, x3
     mov x2, sp              // struct pt_regs
     bl  do_mem_abort

     kernel_exit 1
......

el0_da:
    /*
     * Data abort handling
     */
    mrs x26, far_el1
    enable_daif
    ct_user_exit
    clear_address_tag x0, x26
    mov x1, x25
    mov x2, sp
    bl  do_mem_abort
    b   ret_to_user


其中el1_da和el1_da中会调用到do_mem_abort,这个向量函数是在CPU运行时发生了data abort异常时进入的一种模式,并且会执行到向量表中对应的函数。

asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr,
                     struct pt_regs *regs)

    const struct fault_info *inf = esr_to_fault_info(esr);
    struct siginfo info;

    if (!inf->fn(addr, esr, regs))
        return;

    if (!user_mode(regs)) 
        pr_alert("Unhandled fault at 0x%016lx\\n", addr);
        mem_abort_decode(esr);
        show_pte(addr);
    

    clear_siginfo(&info);
    info.si_signo = inf->sig;
    info.si_errno = 0;
    info.si_code  = inf->code;
    info.si_addr  = (void __user *)addr;
    arm64_notify_die(inf->name, regs, &info, esr);


其中对应一个系统错误处理列表:

static inline const struct fault_info *esr_to_fault_info(unsigned int esr)

    return fault_info + (esr & 63);


static const struct fault_info fault_info[] = 
     do_bad,       SIGKILL, SI_KERNEL, "ttbr address size fault"   ,
     do_bad,       SIGKILL, SI_KERNEL, "level 1 address size fault"    ,
     do_bad,       SIGKILL, SI_KERNEL, "level 2 address size fault"    ,
     do_bad,       SIGKILL, SI_KERNEL, "level 3 address size fault"    ,
     do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 0 translation fault" ,
     do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 1 translation fault" ,
     do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 2 translation fault" ,
     do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 3 translation fault" ,
     do_bad,       SIGKILL, SI_KERNEL, "unknown 8"         ,
     do_page_fault,    SIGSEGV, SEGV_ACCERR,   "level 1 access flag fault" ,
     do_page_fault,    SIGSEGV, SEGV_ACCERR,   "level 2 access flag fault" ,
     do_page_fault,    SIGSEGV, SEGV_ACCERR,   "level 3 access flag fault" ,
     do_bad,       SIGKILL, SI_KERNEL, "unknown 12"            ,
     do_page_fault,    SIGSEGV, SEGV_ACCERR,   "level 1 permission fault"  ,
     do_page_fault,    SIGSEGV, SEGV_ACCERR,   "level 2 permission fault"  ,
     do_page_fault,    SIGSEGV, SEGV_ACCERR,   "level 3 permission fault"  ,
     do_sea,       SIGBUS,  BUS_OBJERR,    "synchronous external abort"    ,
     do_bad,       SIGKILL, SI_KERNEL, "unknown 17"            ,
     do_bad,       SIGKILL, SI_KERNEL, "unknown 18"            ,
     do_bad,       SIGKILL, SI_KERNEL, "unknown 19"            ,
     do_sea,       SIGKILL, SI_KERNEL, "level 0 (translation table walk)"  ,
     do_sea,       SIGKILL, SI_KERNEL, "level 1 (translation table walk)"  ,
     do_sea,       SIGKILL, SI_KERNEL, "level 2 (translation table walk)"  ,
     do_sea,       SIGKILL, SI_KERNEL, "level 3 (translation table walk)"  ,
     do_sea,       SIGBUS,  BUS_OBJERR,    "synchronous parity or ECC error" ,    // Reserved when RAS is implemented
     do_bad,       SIGKILL, SI_KERNEL, "unknown 25"            ,
     do_bad,       SIGKILL, SI_KERNEL, "unknown 26"            ,
     do_bad,       SIGKILL, SI_KERNEL, "unknown 27"            ,
     do_sea,       SIGKILL, SI_KERNEL, "level 0 synchronous parity error (translation table walk)" ,  // Reserved when RAS is implemented
     do_sea,       SIGKILL, SI_KERNEL, "level 1 synchronous parity error (translation table walk)" ,  // Reserved when RAS is implemented
     do_sea,       SIGKILL, SI_KERNEL, "level 2 synchronous parity error (translation table walk)" ,  // Reserved when RAS is implemented
     do_sea,       SIGKILL, SI_KERNEL, "level 3 synchronous parity error (translation table walk)" ,  // Reserved when RAS is implemented
     do_bad,       SIGKILL, SI_KERNEL, "unknown 32"            ,
     do_alignment_fault,   SIGBUS,  BUS_ADRALN,    "alignment fault"       ,
     do_bad,       SIGKILL, SI_KERNEL, "unknown 34"            ,
     do_bad,       SIGKILL, SI_KERNEL, "unknown 35"            ,
     do_bad,       SIGKILL, SI_KERNEL, "unknown 36"            ,
     do_bad,       SIGKILL, SI_KERNEL, "unknown 37"            ,
     do_bad,       SIGKILL, SI_KERNEL, "unknown 38"            ,
     do_bad,       SIGKILL, SI_KERNEL, "unknown 39"            ,
     do_bad,       SIGKILL, SI_KERNEL, "unknown 40"            ,
......

经过这一系列的调用,最终内核会运行对应的错误处理函数。

以上是关于Linux Kernel Makefiles介绍的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核Makefile文件(翻译自内核手册)

linux中make makefiles这个命令是啥意思

multiple makefiles in one directory

linux 时钟源初步分析linux kernel 时钟框架详细介绍

13.3linux kernel介绍

Linux kernel的中断子系统之:IRQ Domain介绍