一、源码分析
系统初始化程序init/main.c主要功能是对系统进行初始化,并切换到用户模式下执行登录程序
(1)库文件介绍
// 宏定义__LIBRARY__为了包括在unistd.h中内嵌汇编代码
#define __LIBRARY__
// *.h所在头文件默认目录在include/
// unistd.h是标准符号常数与类型文件,定义了各种符号常数和类型,并声明了各种函数
// 若定义了_LIBRARY_,则还会包含系统调用号和内嵌汇编代码如syscall0
#include <unistd.h>
// 时间类型头文件,定义了tm结构和关于时间的函数原型
#include <time.h>
#include <linux/tty.h> //定义了tty_io,串口通信方面的参数,常数
#include <linux/sched.h> //调度,定义了任务结构体task_struct,第一个初始任务的数据
#include <linux/head.h> //定义了段描述符的简单结构和几个选择符常量
#include <asm/system.h> //以宏的形式定义了有关描述符参数设置或修改描述符,中断门等汇编程序
#include <asm/io.h> //以嵌入式汇编程序形式定义了对io端操作的函数
#include <stddef.h> //标准定义头文件,定义了NULL,TYPE,MEMBER
#include <stdarg.h> //标志参数头文件,以宏的形式定义了变量参数列表
//一个类型:va_list,三个宏:va_start,va_arg,va_end
//vsprintf,vprintf,vfprintf
#include <unistd.h> //标准符号常数与类型文件,定义了各种符号常数和类型,并声明了各种函数
#include <fcntl.h> //文件控制头文件,用于文件及其描述符的操作控制常数符号的定义
#include <sys/types.h> //类型头文件,定义了基本的系统数据类型
#include <linux/fs.h> //文件系统头文件,定义了文件表结构(file,buffer_head,m_inode)
(2)CMOS读取时间
//读取CMOS实时时钟信息
//outb_p端口输出宏定义,inb_p端口输入宏定义
//0x70是写地址端口号 0x80|addr是要读取CMOS内存地址,0x71是度数据端口号
#define CMOS_READ(addr) ({
\
outb_p(0x80|addr,0x70); \
inb_p(0x71); \
})
//将BCD码转化为二进制数值
//(val)&15取BCD码第四位也就是个位,val)>>4右移四位只剩高四位也就是十位
#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
//该函数取CMOS实时中信息作为开机时间,并保存到全局变量startup_time
static void time_init(void)
{
struct tm time; //时间结构体
do {
//从CMOS内存列表中读取时间
time.tm_sec = CMOS_READ(0); //秒值(BCD码形式)
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); //转换位2进制数值
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); //计算开机时间
}
(3)内核所有初始化
//内核进行所有方面的初始化
mem_init(main_memory_start,memory_end); //主内存区初始
trap_init(); //陷阱门(硬件中断向量)初始化
blk_dev_init(); //块设备初始化
chr_dev_init(); //字符设备初始化
tty_init(); //tty初始化
time_init(); //设置开机启动时间
sched_init(); //调度程序初始化(加载任务0的tr,ldtr)
buffer_init(buffer_memory_end); //缓冲管理初始化,建内存链表
hd_init(); //硬盘初始化
floppy_init(); //软盘初始化
sti(); //所有初始化完成,开启中断
//下面过程通过在堆栈中设置的参数,利用中断返回指令启动任务0执行
move_to_user_mode(); //切换到用户模式
(4)切换到用户模式
//下面过程通过在堆栈中设置的参数,利用中断返回指令启动任务0执行
move_to_user_mode(); //切换到用户模式
if (!fork()) {
/* we count on this going ok */
init(); //在新建的子进程中运行
}
二、完整源码
/*
* linux/init/main.c
*
* (C) 1991 Linus Torvalds
*/
// 宏定义__LIBRARY__为了包括在unistd.h中内嵌汇编代码
#define __LIBRARY__
// *.h所在头文件默认目录在include/
// unistd.h是标准符号常数与类型文件,定义了各种符号常数和类型,并声明了各种函数
// 若定义了_LIBRARY_,则还会包含系统调用号和内嵌汇编代码如syscall0
#include <unistd.h>
// 时间类型头文件,定义了tm结构和关于时间的函数原型
#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.
*/
/*
在内核空间创建进程将会导致没有写时复制,
为了保证不使用任务0的用户栈:
main在移动到用户模式(到任务0)后执行内嵌方式的fork()和pause()
在执行moveto_user_mode()后,main就以任务0的方式运行
任务0是所有子进程的父进程
当它创建子进程时,由于任务1属于内核空间,无写时复制
任务0的用户空栈就是任务1的用户栈,即他们共用一个栈空间
因此在运行任务0时不要对堆栈有任何的操作
但在再次执行fork()并执行过execve()后,被加载的程序也不属于内核空间
*/
// 定义内联函数
static inline _syscall0(int,fork) // 实际上是 int fork()创建进程系统调用
static inline _syscall0(int,pause) //int pause()系统调用 :暂停进程的执行,直到收到一个信息
static inline _syscall1(int,setup,void *,BIOS) // int setup(void *,BIOS)系统调用 用于linux初始化
static inline _syscall0(int,sync) // int sync()系统调用:更新文件系统
#include <linux/tty.h> //定义了tty_io,串口通信方面的参数,常数
#include <linux/sched.h> //调度,定义了任务结构体task_struct,第一个初始任务的数据
#include <linux/head.h> //定义了段描述符的简单结构和几个选择符常量
#include <asm/system.h> //以宏的形式定义了有关描述符参数设置或修改描述符,中断门等汇编程序
#include <asm/io.h> //以嵌入式汇编程序形式定义了对io端操作的函数
#include <stddef.h> //标准定义头文件,定义了NULL,TYPE,MEMBER
#include <stdarg.h> //标志参数头文件,以宏的形式定义了变量参数列表
//一个类型:va_list,三个宏:va_start,va_arg,va_end
//vsprintf,vprintf,vfprintf
#include <unistd.h> //标准符号常数与类型文件,定义了各种符号常数和类型,并声明了各种函数
#include <fcntl.h> //文件控制头文件,用于文件及其描述符的操作控制常数符号的定义
#include <sys/types.h> //类型头文件,定义了基本的系统数据类型
#include <linux/fs.h> //文件系统头文件,定义了文件表结构(file,buffer_head,m_inode)
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); //计算系统开机启动时间(s)
extern long startup_time; //内核启动时间(s)
/*
* This is set up by the setup-routine at boot-time
*/
//这些数据在内核引导期间由setup设置
//将指定的线性地址强行转换为给的数据类型的指针,并获取指针所指的内容
#define EXT_MEM_K (*(unsigned short *)0x90002) //1MB以后的扩展内容大小
#define DRIVE_INFO (*(struct drive_info *)0x90080) //硬盘参数表的32字节内容
#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.
*/
//读取CMOS实时时钟信息
//outb_p端口输出宏定义,inb_p端口输入宏定义
//0x70是写地址端口号 0x80|addr是要读取CMOS内存地址,0x71是度数据端口号
#define CMOS_READ(addr) ({
\
outb_p(0x80|addr,0x70); \
inb_p(0x71); \
})
//将BCD码转化为二进制数值
//(val)&15取BCD码第四位也就是个位,val)>>4右移四位只剩高四位也就是十位
#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
//该函数取CMOS实时中信息作为开机时间,并保存到全局变量startup_time
static void time_init(void)
{
struct tm time; //时间结构体
do {
//从CMOS内存列表中读取时间
time.tm_sec = CMOS_READ(0); //秒值(BCD码形式)
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); //转换位2进制数值
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; //用于存放硬盘参数表信息
//内核初始化主程序,初始化结束以后将以任务0(idle任务位空闲任务)的身份运行
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; //保存根设备号 -->ROOT_DEV
drive_info = DRIVE_INFO; //保存0x90080磁盘参数表内容
memory_end = (1<<20) + (EXT_MEM_K<<10); //机器内存数--->memory_end ,内存大小=1MB+扩展内存(k)*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) //内存>12MB,高速缓冲末端=4mb
buffer_memory_end = 4*1024*1024; //高速缓冲末端地址--->buffer_memory_end
else if (memory_end > 6*1024*1024) //内存>6MB,高速缓冲末端=4mb
buffer_memory_end = 2*1024*1024;
else //否则高速缓冲末端=1mb
buffer_memory_end = 1*1024*1024;
main_memory_start = buffer_memory_end; //主内存开始地址=高速缓冲末端
//如果在Makefile中定义了内存虚拟盘符号RAMDISK,则初始化虚拟盘(主内存减少)
#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(); //tty初始化
time_init(); //设置开机启动时间
sched_init(); //调度程序初始化(加载任务0的tr,ldtr)
buffer_init(buffer_memory_end); //缓冲管理初始化,建内存链表
hd_init(); //硬盘初始化
floppy_init(); //软盘初始化
sti(); //所有初始化完成,开启中断
//下面过程通过在堆栈中设置的参数,利用中断返回指令启动任务0执行
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.
*/
//运行任务0
//pause()系统调用会把任务0转换成可中断等待状态,再执行调度函数
//若调度函数发现系统中没有没有其他任务可以运行就会切换到任务0,而不依赖任务0的状态
for(;;) pause();
}
//printf()产生格式化信息输出到标准设备stdout(1),在屏幕上显示
// 参数fmt:指定输出将采用的格式
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;
}
//读取并执行/etc/rc文件所使用的命令行参数和环境参数
static char * argv_rc[] = {
"/bin/sh", NULL };
static char * envp_rc[] = {
"HOME=/", NULL };
//运行登录shell时所使用的命令行参数和环境参数
static char * argv[] = {
"-/bin/sh",NULL };
static char * envp[] = {
"HOME=/usr/root", NULL };
//在main()中进行了系统初始化,包括内存管理,各种硬件设备和驱动设备
//而init()运行在任务0第一次创建的子进程,对第一个要执行的shell程序的环境进行初始化
//然后以登录shell方式加载该程序并执行
void init(void)
{
int pid,i;
//setup()用于读取硬盘参数包含分区表信息并加载虚拟信息和安装根文件系统设备
setup((void *) &drive_info);
(void) open("/dev/tty0",O_RDWR,0); //终端控制台
(void) dup(0);
(void) dup(0);
//打印缓冲区块数和总字节数,每块1024字节,以及主内存区空闲内存字节数
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);
//创建一个子进程(任务2) -->返回值为 =0,父进程-->返回值 = 子进程pid
//创建失败
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)) //等待子进程结束。&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() */
//_exit() 终止一个函数属于sys_exit系统调用
//exit() 终止一个函数,属于库函数,它会先执行一些清除操作,然后调用_exit()
}