【linux V0.11】init/main.c

发布于:2025-07-16 ⋅ 阅读:(19) ⋅ 点赞:(0)

void main(void)

#define EXT_MEM_K(∗(unsignedshort∗)0x90002//1MB以后的扩展内存大小(KB)。
#define DRIVE_NFO(∗(structdrive info∗)0x90080//硬盘参数表基址。
#define ORIG_ROOT_DEV(∗(unsignedshort∗)0x901FC//根文件系统所在设备号。

struct drive_info { char dummy[32]; } drive_info;//用于存放硬盘参数表信息
void main(void)
{
 	ROOT_DEV = ORIG_ROOT_DEV;				//根设备号 →ROOT DEV
 	drive_info = DRIVE_INFO;
	memory_end = (1<<20) + (EXT_MEM_K<<10);	//内存大小=1MB+扩展内存(KB)∗1024。
	memory_end &= 0xfffff000;				//内存大小页对齐,忽略不到4KB(1页)的内存数
	if (memory_end > 16*1024*1024)			//如果内存超过 16MB,则限制最大可用内存为 16MB
		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()) {
		init();	//创建第一个用户态进程 init,它是所有后续进程的祖先进程。
	}
	//task0(空闲进程),是唯一一个可以在没有收到信号的情况下被唤醒的任务。
	for(;;) pause();	//进入 idle 循环 pause()
}

move_to_user_mode

内核在初始化结束时“切换”到初始任务0。
模拟中断调用返回过程,即利用指令iret运行初始任务0。

首先设置堆栈,模拟刚 进入中断调用过程时(具有特权层切换的) 堆栈的内容布置情况。
move_to_user_mode

movl %esp,%eax	//将当前堆栈指针 esp 保存到 eax,这是用户态堆栈的地址(即内核栈当前的位置)
pushl $0x17		//首先将Task0堆栈段选择符(SS)入栈
pushl %eax		//将 eax(即用户栈指针)压入栈中
pushfl			//将当前标志寄存器压栈,保存状态
pushl $0x0f		//将Task0代码段选择符(cs)入栈。
pushl $1f		//将下面标号1的偏移地址(eip)入栈。
iret
1:	movl $0x17,%eax	//设置用户态的段寄存器,都设置为 0x17,即用户数据段选择符
	movw %ax,%ds
	movw %ax, %es
	movw %ax,%fs
	movw %ax,%gs

_syscall

这些宏的作用是让用户程序通过 int $0x80 指令触发中断,进入内核态,从而调用相应的内核函数。
_syscall0处理无参数,_syscall1处理一个参数,以此类推。
用户程序不能直接访问硬件或执行特权指令 ,必须通过“系统调用”来请求内核完成任务。

//内核实现的系统调用符号常数,用作系统调用函数表中的索引值
#define __NR_setup	0	/* used only by init, to get system going */
#define __NR_exit	1
#define __NR_fork	2
#define __NR_read	3
...

#define _syscall0(type,name)
type name(void)
{
    long __res;
    __asm__ volatile ("int $0x80"	//调用系统中断0x80
        : "=a" (__res)				//返回值==>eax( res)。
        : "0" (__NR_##name));		//输入为系统中断调用号 NR name
    if (__res >= 0)
        return (type) __res;		//如果返回值>=0,则直接返回该值
    errno = -__res;					//否则置出错号,并返回-1
    return -1;
}

当用户态执行 int $0x80 后,CPU 会跳转到内核中的中断处理入口(_system_call)。Linux 内核根据sys_call_table和传入的系统调用号(索引)找到对应的处理函数。

fork()

static inline _syscall0(int,fork)
fork
首先调用C函数find_empty_process(),取得一个进程号pid。若返回负数则说明目前任务数组已
#满。然后调用copy_process()复制进程。

_sys_fork:
	call _find_empty_process	//查找空进程槽
	testl %eax,%eax
	js 1f
	push %gs
	pushl %esi
	pushl %edi
	pushl %ebp
	pushl %eax
	call _copy_process			//调用copy_process()复制进程
	addl $20,%esp
1:	ret

具体内容以后再看。

void init(void)

init()函数运行在任务0创建的子进程(任务1)中。它首先对第一个要执行的程序(shell)的环境进行初始化,然后加载该程序并执行之。

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;
	//sys_setup,读取硬盘参数包括分区表信息并建立虚拟盘和安装根文件系统设备。
	setup((void *) &drive_info);
	//用读写访问方式打开设备“/dev/tty0”,这里对应终端控制台。返回的句柄0号———stdin标准输入设备
	(void) open("/dev/tty0",O_RDWR,0);
	(void) dup(0);	//复制句柄,产生句柄1号———stdout标准输出设备。
	(void) dup(0);	//复制句柄,产生句柄2号———stderr标准出错输出设备。
	printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
		NR_BUFFERS*BLOCK_SIZE);//打印缓冲区块数和总字节数,每块1024字节
	printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);////空闲内存字节数
	//fork()用于创建一个子进程(子任务)。
	//对于被创建的子进程,fork()将返回0值,对于原(父进程)将返回子进程的进程号。
	if (!(pid=fork())) {
		//子进程执行的内容
		close(0);//该子进程关闭了句柄0(stdin)
		if (open("/etc/rc",O_RDONLY,0))//以只读方式打开/etc/rc文件
			_exit(1);
		execve("/bin/sh",argv_rc,envp_rc);//执行/bin/sh程序
		_exit(2);
	}
	//wait()是等待子进程停止或终止,其返回值应是子进程的进程号(pid)。
	//父进程等待子进程的结束。
	//&i是存放返回状态信息的位置。如果wait()返回值不等于子进程号,则继续等待。
	if (pid>0)
		while (pid != wait(&i));
	//如果执行到这里,说明刚创建的子进程的执行已停止或终止了。
	while (1) {
		//先再创建一个子进程,如果出错,打印信息后继续fork()。
		if ((pid=fork())<0) {
			printf("Fork failed in init\r\n");
			continue;
		}
		if (!pid) {
			close(0);close(1);close(2);//关闭所有以前还遗留的句柄(stdin,stdout,stderr)
			//新创建一个会话并设置进程组号,然后重新打开/dev/tty0作为stdin,并复制成stdout和stderr。
			setsid();
			(void) open("/dev/tty0",O_RDWR,0);
			(void) dup(0);
			(void) dup(0);
			_exit(execve("/bin/sh",argv,envp));//再次执行系统解释程序,但是参数环境与上次不一样
		}
		//父进程再次运行wait()等待
		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() */
}

其他

main.c还有关于时间的初始化一些内容,ez


网站公告

今日签到

点亮在社区的每一天
去签到