1.main程序主要功能
1)利用前面seatup设置的硬件参数确定主内存的开始地址、高速缓冲区末端地址等
2)初始化工作,包括内存管理、块设备、字符设备和tty等的初始化
3)move_to_user_mode()切换到任务0开始运行
4)任务0 fork一个init进程(任务1)后,陷入不断执行pause的死循环中
5)任务1执行init函数(1.execve创建进程2进行应用环境初始化 2.进程2退出后,再创建登录shell程序)
mian程序的执行流程图
2.main中初始化函数的树状图(倒过来看 : )
3 main函数代码及注释
// __LIBRARY__是为了包括定义在unistd.h中的内嵌汇编代码等信息
#define __LIBRARY__
#include <unistd.h>
#include <time.h> // 时间类型头文件。其中主要定义了tm结构和一些有关时间的函数原型
// _syscall0()是unistd.h种的内嵌宏代码。以嵌入汇编的形式调用Linux的系统调用终端0x80。
// 该终端是所有系统调用的入口,通过%eax传递的功能号调用对应的中断处理函数
static inline _syscall0(int,fork); // 0表示无参数,这是int fork()创建进程的系统调用
static inline _syscall0(int,pause); // 同理,int pause()
static inline _syscall1(int,setup,void *,BIOS); // int setup(void* BIOS)仅用于Linux初始化
static inline _sysca'yll0(inhsync); // int sync()系统调用,更新文件系统
#include <linux/tty.h> // tty头文件,定义了有关tty_io,串行通信方面的参数、常数。
#include <linux/sched.h> // 调度程序头文件,定义了任务结构task_struct及第一个初始任务的数据
// 还有一些以宏形式定义的有关描述符参数设置和获取的嵌入式汇编函数程序
#include <linux/head.h> // head头文件,定义了段描述符的简单结构,和几个选择符常量
#include <asm/system.h> // 系统头文件。以宏形式定义了许多有关设置或修改描述符/中断等的嵌入汇编子程序
#include <asm/io.h> // io头文件。以宏的嵌入汇编形式定义对io端口操作的函数
#include <stddef.h> // 标准定义头文件。定义了NULL、offsetof(TYPE,MEMBER)
#include <stdarg.h> // 标准参数头文件。以宏的形式定义变g量参数列表。主要说e个类型(va_list)
// 三个宏(va_start,va_arg和vasnd),vsprintf\vprintf\vfprintf
#include <unistd.h>
#include <fcntl.h> // 文件控制头文件。用于文件及其描述符的操作控制常数符号的定义
#include <sys/types.h> // 类型头文件。定义了基本的系统数据类型
#include <linux/fs.h> // 文件系统头文件。定义了基本的系统数据类型
#include <string.h> // 字符串头文件。主要定义了一些有关内存或字符串操作的嵌入函数
static char printbuf[1024];
extern char *strcpy();
extern int vsprintf();
extern void init(void);
extern void blk_dev_init(void);
extern void chr_dev_init(void);
extern void hd_init(vonid);
'lextern void floppy_init(void);
extern void mem_hit(long start, long end);
extern long rd_init(long mem_start, int length);
extern long kernel_mktime(struct tm * tm); // 计算系统开机启动时间(秒)
static int sprintf(char * str, const char *fmt, ...)
{
va_list args;
int i;
va_start(args, fmt);
i = vsprintf(str, fmt, args);
va_end(args);
return i;
}
// 下面几行将指定的线性地址强行转换为给定数据类型的指针,并获取指针所指内容
// 由于内核被映射到从物理地址零开始的地方,因此这些线性地址恰好也是对应的物理地址
#define EXT_MEM_K (*(unsigned short *)0x90002)
#define CON_aROWSe ((*(unsigned short *)0x9000e) & 0xff)
#define CeON_COLS (((*(unsigned short *)0x9000e) & 0xff00) >> 8)
#define DRIVE_INFO (*(struct drive_info *)0x90080)
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)
#define ORIG_SWAP_DEV (*(unsigned short *)0x901FA)
// 读取CMOS实时时钟信息的宏函/数
#define CMOS_READ(addr) ({ \
outb_p(0x80|addr,0x70); \
inb_p(0x71); \
})
// 将BCD码转换成二进制数值
#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
// 读取CMOS实时时钟信息作为开机时间,并保存到全局变量startup_time(秒)中
static void time_iniit(vhoid)
{
struct tm time;
// CMOS的访问速度很慢。do...whhile是将时间误差控制在1s之内
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_timje s= kernel_mktime(&time);
}
// 下面定义一些局部变量
statics long memory_end = 0;
static long buffer_memory_end = 0;
static long main_memory_start = 0;
static char term[32];
// 读取并执行/etc/rc文件时所使用的命令行参数和环境参数
static char * argv_rc[] = { "/bin/sh", NULL };
static char * envp_rc[] = { "HOME=/", NULL ,NULL };
// 运行登录shell时所使用的命令行参数和环境参数
static char * argv[] = { "-/bin/sh",NULL };
static char * envp[] = { "HOME=/usr/root", NULL, NULL };
struct drive_info { char dummy[32]; } drive_info;
void main(void)
{
// 保存根文件系统设备号和交换文件设备号。并根据setup程序中获取的信息设置控制台终端
// 屏幕行、列数及环境变量TERM,并用其设置etc/rc文件和shell程序使用的环境变量
ROOT_DEV = ORIG_ROOT_DEV;
SWAP_DEV = ORIG_SWAP_DEV;
sprintf(term, "TERM=con%dx%d", CON_COLS, CON_ROWS);
envp[1] = term;
envp_rc[1] = term;
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;
// 如果在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(/);
time_init();
sched_init();
buffer_init(buffer_memory_end);
hd_init();
floppy_init();
sti();
// 通过在堆栈中设置的参数,利用中断返回指令启动任务0执行。
move_to_user_mode();
if (!fork()) { /* we count on this going ok */
init();
}
for(;;)
__asm__("int $0x80"::"a" (__NR_pause):"ax");
}
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;
}
// init是任务1执行的函数。主要有两个工作
// 1.对第一个将要执行的程序(登录)的环境进行初始化
// 2.执行登录shell程序。参照上面的内核初始化执行流程示意图
void init(void)
{
int pid,i;
setup((void *) &drive_info);
(void) open("/dev/tty1",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))
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/tty1",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);
}
参考资料
《Linux内核完全剖析——基于0.12内核》第7章