21

我正在学习Device Driver和编程。根据 Jonathan Corbet 的书,我们在设备驱动程序中Kernel没有功能。main()

#include <linux/init.h>
#include <linux/module.h>

static int my_init(void)
{
     return  0;
}

static void my_exit(void)
{
     return;
}

module_init(my_init);
module_exit(my_exit);

在这里我有两个问题:

  1. 为什么我们不需要main()设备驱动程序中的功能?
  2. 内核有main()功能吗?
4

6 回答 6

12

从根本上说,例程被命名并没有什么特别之处main()。如上所述,main()用作可执行加载模块的入口点。但是,您可以为加载模块定义不同的入口点。其实你可以定义多个入口点,比如引用你喜欢的dll。

从操作系统 (OS) 的角度来看,它真正需要的只是将用作设备驱动程序的代码入口点的地址。当需要设备驱动程序对设备执行 I/O 时,操作系统会将控制权传递给该入口点。

系统程序员定义(每个操作系统都有自己的方法)设备、充当设备驱动程序的加载模块以及加载模块中的入口点名称之间的连接。

每个操作系统都有自己的内核(显然),有些可能/可能开始,main()但我会惊讶地发现一个内核使用main()的不是简单的内核,比如 UNIX!当您编写内核代码时,您早已超越了将您编写的每个模块命名为main().

希望这可以帮助?

从 Unix 版本 6 的内核中找到此代码片段。正如您所见main(),这只是另一个程序,正在尝试开始!

main()
{
     extern schar;
     register i, *p;
     /*
     * zero and free all of core
     */

     updlock = 0;
     i = *ka6 + USIZE;
     UISD->r[0] = 077406;
     for(;;) {
        if(fuibyte(0) < 0) break;
        clearsig(i);
        maxmem++;
        mfree(coremap, 1, i);
         i++;
     }
     if(cputype == 70) 
     for(i=0; i<62; i=+2) {
       UBMAP->r[i] = i<<12;
       UBMAP->r[i+1] = 0;
      }

    // etc. etc. etc.
于 2013-08-16T05:51:22.497 回答
11

start_kernel

在 4.2 上,start_kernelfrominit/main.c是一个相当大的初始化过程,可以比作一个main函数。

它是第一个运行的独立于架构的代码,并设置了内核的很大一部分。很像main,start_kernel之前是一些较低级别的设置代码(crt*在 userland 的对象中完成main),然后运行“主要”通用 C 代码。

如何start_kernel在 x86_64 中调用

arch/x86/kernel/vmlinux.lds.S,链接描述文件,设置:

ENTRY(phys_startup_64)

phys_startup_64 = startup_64 - LOAD_OFFSET;

和:

#define LOAD_OFFSET __START_KERNEL_map

arch/x86/include/asm/page_64_types.h定义__START_KERNEL_map为:

#define __START_KERNEL_map  _AC(0xffffffff80000000, UL)

这是内核入口地址。TODO 该地址是如何准确到达的?我必须了解 Linux 向引导加载程序公开的接口。

arch/x86/kernel/vmlinux.lds.S将第一个引导加载程序部分设置为:

.text :  AT(ADDR(.text) - LOAD_OFFSET) {
    _text = .;
    /* bootstrapping code */
    HEAD_TEXT

include/asm-generic/vmlinux.lds.h定义HEAD_TEXT

#define HEAD_TEXT  *(.head.text)

arch/x86/kernel/head_64.S定义startup_64. 这是第一个运行的 x86 内核代码。它做了很多低级设置,包括分段和分页。

那是运行的第一件事,因为文件以:

.text
__HEAD
.code64
.globl startup_64

include/linux/init.h定义__HEAD为:

#define __HEAD      .section    ".head.text","ax"

所以与链接描述文件的第一件事相同。

最后它用 and 调用x86_64_start_kernel有点尴尬lretq

movq    initial_code(%rip),%rax
pushq   $0      # fake return address to stop unwinder
pushq   $__KERNEL_CS    # set correct cs
pushq   %rax        # target address in negative space
lretq

和:

.balign 8
GLOBAL(initial_code)
.quad   x86_64_start_kernel

arch/x86/kernel/head64.c定义x86_64_start_kernel哪些调用x86_64_start_reservations哪些调用start_kernel

arm64 入口点

第一个在 v5.7 未压缩内核上运行的 arm64 定义在https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L72所以add x13, x18, #0x16要么b stextCONFIG_EFI

    __HEAD
_head:
    /*
     * DO NOT MODIFY. Image header expected by Linux boot-loaders.
     */
#ifdef CONFIG_EFI
    /*
     * This add instruction has no meaningful effect except that
     * its opcode forms the magic "MZ" signature required by UEFI.
     */
    add x13, x18, #0x16
    b   stext
#else
    b   stext               // branch to kernel start, magic
    .long   0               // reserved
#endif
    le64sym _kernel_offset_le       // Image load offset from start of RAM, little-endian
    le64sym _kernel_size_le         // Effective size of kernel image, little-endian
    le64sym _kernel_flags_le        // Informative flags, little-endian
    .quad   0               // reserved
    .quad   0               // reserved
    .quad   0               // reserved
    .ascii  ARM64_IMAGE_MAGIC       // Magic number
#ifdef CONFIG_EFI
    .long   pe_header - _head       // Offset to the PE header.

这也是未压缩内核映像的第一个字节。

这两种情况都会跳转到stext开始“真实”动作的地方。

如评论中所述,这两条指令是记录在案的标头的前 64 个字节,如下所述:https ://github.com/cirosantilli/linux/blob/v5.7/Documentation/arm64/booting.rst#4-call -内核映像

arm64第一个MMU启用指令:__primary_switched

我认为它__primary_switched在 head.S 中

/*
 * The following fragment of code is executed with the MMU enabled.
 *
 *   x0 = __PHYS_OFFSET
 */
__primary_switched:

此时,内核似乎创建了页表 + 可能会重新定位自己,以便 PC 地址与 vmlinux ELF 文件的符号匹配。因此,此时您应该能够在 GDB 中看到有意义的函数名称,而无需额外的魔法。

arm64 辅助 CPU 入口点

secondary_holding_pen定义于:https ://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L691

进入程序进一步描述在:https ://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L691

于 2015-10-29T18:52:00.243 回答
4

几种查看方式:

  1. 设备驱动程序不是程序。它们是加载到另一个程序(内核)中的模块。因此,它们没有main()功能。

  2. 所有程序都必须具有main()功能的事实仅适用于用户空间应用程序。它不适用于内核,也不适用于设备驱动程序。

于 2013-08-16T06:22:50.200 回答
2

main()你来说可能意味着什么main()是程序,即它的“入口点”。

对于一个模块,即init_module().

来自Linux 设备驱动程序的第 2 版

应用程序从头到尾执行单个任务,而模块注册自身以便为将来的请求提供服务,并且其“主”功能立即终止。换句话说,函数init_module(模块的入口点)的任务是为以后调用模块的函数做准备;就好像模块在说,“我在这里,这就是我能做的。” 模块的第二个入口点 cleanup_module 在模块卸载之前被调用。它应该告诉内核,“我不在那里了;不要让我做任何其他事情。”

于 2013-08-16T05:26:55.817 回答
2

是的,Linux 内核有一个 main 函数,它位于 arch/x86/boot/main.c 文件中。但是内核执行从 arch/x86/boot/header.S 程序集文件开始,并且 main() 函数通过“call main”指令从那里调用。这是主要功能:

void main(void)
{
    /* First, copy the boot header into the "zeropage" */
    copy_boot_params();

    /* Initialize the early-boot console */
    console_init();
    if (cmdline_find_option_bool("debug"))
        puts("early console in setup code.\n");

    /* End of heap check */
    init_heap();

    /* Make sure we have all the proper CPU support */
    if (validate_cpu()) {
        puts("Unable to boot - please use a kernel appropriate "
             "for your CPU.\n");
        die();
    }

    /* Tell the BIOS what CPU mode we intend to run in. */
    set_bios_mode();

    /* Detect memory layout */
    detect_memory();

    /* Set keyboard repeat rate (why?) and query the lock flags */
    keyboard_init();

    /* Query Intel SpeedStep (IST) information */
    query_ist();

    /* Query APM information */
#if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
    query_apm_bios();
#endif

    /* Query EDD information */
#if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
    query_edd();
#endif

    /* Set the video mode */
    set_video();

    /* Do the last things and invoke protected mode */
    go_to_protected_mode();
}
于 2017-01-23T14:33:12.380 回答
0

虽然函数名 main() 只是一个通用约定(没有真正的理由在内核模式下使用它),但 linux 内核对于许多体系结构确实有一个 main() 函数,当然用户模式 ​​linux 也有一个 main 函数。

请注意,操作系统运行时加载 main() 函数来启动应用程序,当操作系统启动时没有运行时,内核只是由引导加载程序加载到一个地址,该引导加载程序由硬件加载的 MBR 加载。因此,虽然内核可能包含一个名为 main 的函数,但它不一定是入口点。

也可以看看:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms633559%28v=vs.85%29.aspx

Linux内核源码:

x86:linux-3.10-rc6/arch/x86/boot/main.c

arm64:linux-3.10-rc6/arch/arm64/kernel/asm-offsets.c

于 2013-08-29T16:02:40.577 回答