我正在学习Device Driver
和Kernel
编程。根据Jonathan Corbet的说法,我们在设备驱动程序中没有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);
这里有两个问题:
main()
功能?main()
功能?答案 0 :(得分:11)
从根本上说,名为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.
答案 1 :(得分:4)
<强> start_kernel
强>
在4.2上,start_kernel
from init/main.c
是一个相当大的初始化过程,可以与main
函数进行比较。
它是第一个运行的arch独立代码,并设置了很大一部分内核。与main
非常相似,start_kernel
前面有一些较低级别的设置代码(在userland crt*
中的main
个对象中完成),之后是“主”通用C代码运行。
如何在x86_64中调用start_kernel
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内核代码。它执行低级设置的 lot ,包括分段和分页。
那是第一件事,因为文件以:
开头.text
__HEAD
.code64
.globl startup_64
和include/linux/init.h
将__HEAD
定义为:
#define __HEAD .section ".head.text","ax"
与链接描述文件的第一个内容相同。
最后,它使用和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
。
答案 2 :(得分:3)
有几种方法可以看待它:
设备驱动程序不是程序。它们是加载到另一个程序(内核)的模块。因此,它们没有main()
功能。
所有程序必须具有main()
功能的事实才适用于用户空间应用程序。它不适用于内核,也不适用于设备驱动程序。
答案 3 :(得分:2)
使用main()
,你可以理解为main()
对程序的意义,即它的&#34;入口点&#34;。
对于init_module()
的模块。
来自Linux Device Driver's 2nd Edition:
虽然应用程序从头到尾执行单个任务,但模块会自行注册以便为将来的请求提供服务,而其主要的&#34;函数立即终止。换句话说,函数init_module(模块的入口点)的任务是为稍后调用模块的函数做准备;好像模块在说,&#34;我在这里,这就是我能做的。&#34;模块的第二个入口cleanup_module在模块卸载之前被调用。它应该告诉内核,&#34;我已经不在了;不要求我做任何其他事情。&#34;
答案 4 :(得分:1)
是的,Linux内核有一个main函数,它位于arch / x86 / boot / main.c文件中。但是内核执行从arch / x86 / boot / header.S开始,汇编文件和main()函数由“calll 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();
}
答案 5 :(得分:0)
虽然函数名main()只是一个常见的约定(没有真正的理由在内核模式下使用它),linux内核确实有许多架构的main()函数,当然usermode linux有一个主要的功能。
请注意,OS运行时加载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