/init/main.c
是Linux操作系统启动过程的核心部分,它负责初始化硬件、设备、内存和系统服务,以及启动第一个用户进程,为后续的系统运行奠定基础。
详细解析
1. 内联函数定义
fork
,pause
,setup
,sync
: 这些函数被声明为内联,意味着它们在编译时会被直接嵌入到调用点,而不是通过常规函数调用的方式执行。这在内核上下文中特别重要,因为避免了不必要的栈操作和上下文切换,提高了效率。尤其是fork
和pause
,由于它们在main
函数中的关键作用,需要确保不会破坏栈的完整性。
1.1 fork
内联函数分析
系统调用宏定义
#define __NR_setup 0 /* used only by init, to get system going */
#define __NR_exit 1
#define __NR_fork 2
#define _syscall0(type,name) \
type name(void) \
{
\
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
此宏定义 _syscall0
的功能是简化创建无参数系统调用函数的过程,其具体作用如下:
- 参数解析:
type
: 指定系统调用函数的返回值类型。name
: 系统调用函数的名称。
- 函数体详解:
- 定义一个长整型变量
__res
用于存储系统调用的结果。 - 使用内联汇编
__asm__
插入一条int $0x80
指令,这是 Linux 中触发系统调用的标准方式。 - 汇编指令使用寄存器传递参数和接收结果,其中
"=a" (__res)
表示将结果存储到eax
寄存器(即__res
),而"0" (__NR_##name)
表示从eax
寄存器读取系统调用编号,这里__NR_##name
是根据name
动态生成的系统调用编号。
- 定义一个长整型变量
- 错误处理:
- 如果系统调用成功,即
__res >= 0
,则将__res
转换为type
类型并返回。 - 如果系统调用失败,即
__res < 0
,则将-__res
的值赋给全局变量errno
,表示错误代码,并返回-1
。
- 如果系统调用成功,即
1.2 setup
内联函数分析
这个宏提供了一种便捷方式来封装系统调用,避免了每次手动编写相似的汇编代码和错误处理逻辑。例如,可以这样使用:_syscall1(int, read, int, fd)
, 生成一个 read
函数,用于读取文件描述符 fd
的数据。
#define _syscall1(type,name,atype,a) \
type name(atype a) \
{
\
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(a))); \
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
-
参数解析:
type
: 指定系统调用返回值的数据类型。name
: 系统调用函数的名称。atype
: 系统调用单个参数的数据类型。a
: 系统调用的参数。
-
功能实现:
- 定义了一个函数
name
接受参数a
类型为atype
。 - 使用内嵌汇编指令
"int $0x80"
触发 Linux 系统调用机制。 __NR_##name
是系统调用编号,它根据name
动态生成,如__NR_open
对应 open 系统调用的编号。- 参数
a
被传递到寄存器b
中,供系统调用使用。 - 调用结果存储在
__res
变量中。 - 如果
__res
大于等于 0,表示系统调用成功,将__res
强制转换为type
类型并返回。 - 如果
__res
小于 0,表示系统调用失败,将-__res
的值赋给全局变量errno
表示错误码,函数返回-1
。
- 定义了一个函数
2. 系统初始化与配置
-
硬件与内存初始化:
EXT_MEM_K
和DRIVE_INFO
用于读取系统BIOS提供的扩展内存信息和磁盘驱动器信息。memory_end
,buffer_memory_end
,main_memory_start
: 这些变量用于确定系统可用的内存范围,以及分配给缓冲区的内存大小。根据系统总内存的不同,分配给缓冲区的内存也会相应调整。
-
实时钟初始化 (
time_init
)- 通过访问CMOS寄存器读取系统时间,然后将其从二进制编码的十进制(BCD)格式转换为二进制格式,最后计算出系统启动的时间戳。
3. 设备与资源初始化
hd_init
,floppy_init
,blk_dev_init
,chr_dev_init
: 这些函数分别用于初始化硬盘、软盘、块设备和字符设备,确保系统可以访问和管理这些硬件资源。
3.1 hd_init
分析
此函数主要负责硬盘设备的初始化工作,确保系统可以正确地与硬盘交互。
void hd_init(void)
{
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
set_intr_gate(0x2E,&hd_interrupt);
outb_p(inb_p(0x21)&0xfb,0x21);
outb(inb_p(0xA1)&0xbf,0xA1);
}
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
这一行代码将硬盘设备的请求处理函数设置为 DEVICE_REQUEST
。在Linux设备驱动模型中,blk_dev 是一个数组,存储了所有注册的块设备信息。MAJOR_NR
表示硬盘设备的主设备号,request_fn
是该设备结构体中的一个成员,用于指定当有I/O
请求到达时应该调用哪个函数来处理这些请求。这里的 DEVICE_REQUEST
就是这个处理函数,它负责调度和执行具体的读写操作。
set_intr_gate(0x2E, &hd_interrupt);
#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
"movw %0,%%dx\n\t" \
"movl %%eax,%1\n\t" \
"movl %%edx,%2"