
/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" \
	: \
	: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
	"o" (*((char *) (gate_addr))), \
	"o" (*(4+(char *) (gate_addr))), \
	"d" ((char *) (addr)),"a" (0x00080000))
#define set_intr_gate(n,addr) \
	_set_gate(&idt[n],14,0,addr)
set_intr_gate 函数来设置一个中断服务程序(ISR)。0x2E 是硬盘设备的中断请求线(IRQ)编号,在这里被映射到处理器的中断向量表中。hd_interrupt 是实际的中断处理函数,当硬件检测到中断信号时,CPU会跳转到这个函数以处理中断事件,例如完成的数据传输或错误报告。
outb_p(inb_p(0x21) & 0xfb, 0x21);
这一行代码通过I/O端口 0x21 来访问并修改硬盘控制器的寄存器。inb_p 和 outb 分别是从I/O端口读取字节和向I/O端口写入字节的函数。首先,从端口 0x21 读取当前值,然后与 0xfb 进行按位与运算,目的是清除特定的比特位。这通常是为了禁用某个功能或设置控制器进入某种模式。
3.2 blk_dev_init分析
 
该函数功能是初始化块设备请求队列,NR_REQUEST为32,将其设备标识符设为-1,表示未指定设备,并将其下一个请求指针设为NULL表示队列末尾。这样,函数将块设备的请求队列初始化为空队列状态。
void blk_dev_init(void)
{
	int i;
	for (i=0 ; i<NR_REQUEST ; i++) {
		request[i].dev = -1;
		request[i].next = NULL;
	}
}
4. main 函数流程
 
- main函数是程序的入口点,它首先禁用中断,进行必要的初始化,然后重新启用中断。
- 执行一系列初始化函数:mem_init、trap_init、blk_dev_init、chr_dev_init、tty_init、time_init、sched_init、buffer_init,这些函数分别用于内存管理、陷阱处理、设备初始化、终端初始化、时间管理、调度初始化和缓冲区初始化。
- 创建一个子进程(init),这是Linux系统中的第一个用户级进程,负责后续的系统初始化和进程管理。
5. init 函数细节
 
- init函数用于创建一个守护进程,关闭标准输入、输出和错误流,然后执行- /bin/shshell。
- 如果fork失败或执行execve失败,会打印错误信息并退出。
- 监控子进程的运行状态,当子进程终止时,会输出相关信息并同步文件系统。
6. main.c源码
 
/*
 *  linux/init/main.c
 *
 *  (C) 1991  Linus Torvalds
 */
#define __LIBRARY__
#include <unistd.h>
#include <time.h>
/*
 * we need this inline - forking from kernel space will result
 * in NO COPY ON WRITE (!!!), until an execve is executed. This
 * is no problem, but for the stack. This is handled by not letting
 * main() use the stack at all after fork(). Thus, no function
 * calls - which means inline code for fork too, as otherwise we
 * would use the stack upon exit from 'fork()'.
 *
 * Actually only pause and fork are needed inline, so that there
 * won't be any messing with the stack from main(), but we define
 * some others too.
 */
static inline _syscall0(int,fork)
static inline _syscall0(int,pause)
static inline _syscall1(int,setup,void *,BIOS)
static inline _syscall0(int,sync)
#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/head.h>
#include <asm/system.h>
#include <asm/io.h>
#include <stddef.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <linux/fs.h>
static char printbuf[1024];
extern int vsprintf();
extern void init(void);
extern void blk_dev_init(void);
extern void chr_dev_init(void);
extern void hd_init(void);
extern void floppy_init(void);
extern void mem_init(long start, long end);
extern long rd_init(long mem_start, int length);
extern long kernel_mktime(struct tm * tm);
extern long startup_time;
/*
 * This is set up by the setup-routine at boot-time
 */
#define EXT_MEM_K (*(unsigned short *)0x90002)
#define DRIVE_INFO (*(struct drive_info *)0x90080)
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)
/*
 * Yeah, yeah, it's ugly, but I cannot find how to do this correctly
 * and this seems to work. I anybody has more info on the real-time
 * clock I'd be interested. Most of this was trial and error, and some
 * bios-listing reading. Urghh.
 */
#define CMOS_READ(addr) ({ \
outb_p(0x80|addr,0x70); \
inb_p(0x71); \
})
#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
static void time_init(void)
{
	struct tm time;
	do {
		time.tm_sec = CMOS_READ(0);
		time.tm_min = CMOS_READ(2);
		time.tm_hour = CMOS_READ(4);
		time.tm_mday = CMOS_READ(7);
		time.tm_mon = CMOS_READ(8);
		time.tm_year = CMOS_READ(9);
	} while (time.tm_sec != CMOS_READ(0));
	BCD_TO_BIN(time.tm_sec);
	BCD_TO_BIN(time.tm_min);
	BCD_TO_BIN(time.tm_hour);
	BCD_TO_BIN(time.tm_mday);
	BCD_TO_BIN(time.tm_mon);
	BCD_TO_BIN(time.tm_year);
	time.tm_mon--;
	startup_time = kernel_mktime(&time);
}
static long memory_end = 0;
static long buffer_memory_end = 0;
static long main_memory_start = 0;
struct drive_info { char dummy[32]; } drive_info;
void main(void)		/* This really IS void, no error here. */
{			/* The startup routine assumes (well, ...) this */
/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
 	ROOT_DEV = ORIG_ROOT_DEV;
 	drive_info = DRIVE_INFO;
	memory_end = (1<<20) + (EXT_MEM_K<<10);
	memory_end &= 0xfffff000;
	if (memory_end > 16*1024*1024)
		memory_end = 16*1024*1024;
	if (memory_end > 12*1024*1024) 
		buffer_memory_end = 4*1024*1024;
	else if (memory_end > 6*1024*1024)
		buffer_memory_end = 2*1024*1024;
	else
		buffer_memory_end = 1*1024*1024;
	main_memory_start = buffer_memory_end;
#ifdef RAMDISK
	main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
	mem_init(main_memory_start,memory_end);
	trap_init();
	blk_dev_init();
	chr_dev_init();
	tty_init();
	time_init();
	sched_init();
	buffer_init(buffer_memory_end);
	hd_init();
	floppy_init();
	sti();
	move_to_user_mode();
	if (!fork()) {		/* we count on this going ok */
		init();
	}
/*
 *   NOTE!!   For any other task 'pause()' would mean we have to get a
 * signal to awaken, but task0 is the sole exception (see 'schedule()')
 * as task 0 gets activated at every idle moment (when no other tasks
 * can run). For task0 'pause()' just means we go check if some other
 * task can run, and if not we return here.
 */
	for(;;) pause();
}
static int printf(const char *fmt, ...)
{
	va_list args;
	int i;
	va_start(args, fmt);
	write(1,printbuf,i=vsprintf(printbuf, fmt, args));
	va_end(args);
	return i;
}
static char * argv_rc[] = { "/bin/sh", NULL };
static char * envp_rc[] = { "HOME=/", NULL };
static char * argv[] = { "-/bin/sh",NULL };
static char * envp[] = { "HOME=/usr/root", NULL };
void init(void)
{
	int pid,i;
	setup((void *) &drive_info);
	(void) open("/dev/tty0",O_RDWR,0);
	(void) dup(0);
	(void) dup(0);
	printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
		NR_BUFFERS*BLOCK_SIZE);
	printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);
	if (!(pid=fork())) {
		close(0);
		if (open("/etc/rc",O_RDONLY,0))
			_exit(1);
		execve("/bin/sh",argv_rc,envp_rc);
		_exit(2);
	}
	if (pid>0)
		while (pid != wait(&i))
			/* nothing */;
	while (1) {
		if ((pid=fork())<0) {
			printf("Fork failed in init\r\n");
			continue;
		}
		if (!pid) {
			close(0);close(1);close(2);
			setsid();
			(void) open("/dev/tty0",O_RDWR,0);
			(void) dup(0);
			(void) dup(0);
			_exit(execve("/bin/sh",argv,envp));
		}
		while (1)
			if (pid == wait(&i))
				break;
		printf("\n\rchild %d died with code %04x\n\r",pid,i);
		sync();
	}
	_exit(0);	/* NOTE! _exit, not exit() */
}



















