main.c 功能描述
之前 setup 在 0x90000 ~ 0x901FF 保存了一些重要的机器参数,其中包括主内存区的开始地址,内存大小和作为高速缓冲区内存的末端地址,如果 Makefile 中还定义了 RAMDISK 虚拟盘,则主内存区还会减小。
高速缓冲是用于供磁盘等块设备临时存放数据的地方,以 1KB 为一个数据块单位,其中包含了显存及其 BIOS 占用的区域。现在 main.c 将会用这些参数来划分内存区域
之后调用一堆初始化函数对内存、陷阱门、块设备、字符设备、tty、时间、进程调度、缓冲区、硬盘、软驱进行初始化,并完成进程 0 的创建,从内核态切换为用户态。
此时第一次调用 fork 函数创建用于运行 init 函数的子进程 1,init 函数主要作用为
- 安装根文件系统
- 显示系统信息
- 执行资源配置文件
- 执行登录 shell 程序
流程图如下:
代码分析
*.h 头文件没有指明路径默认在 include 目录下
1 2 3 4
| #define __LIBRARY__ #include <unistd.h> #include <time.h>
|
__always_inline 与 syscall 等在 include/unistd.h 中都有定义,_syscall 后面的数字表示定义的函数有几个参数,括号中的前两个参数为函数返回值类型及函数名,之后的每两个参数代表该函数参数类型及参数名
1 2 3 4 5
| __always_inline _syscall0(int,fork) __always_inline _syscall0(int,pause) __always_inline _syscall1(int,setup,void *,BIOS) __always_inline _syscall0(int,sync)
|
比如 _syscall1(int,setup,void *,BIOS) 表示定义了 int setup(void *BIOS) 这么一个函数,具体代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #define __always_inline inline __attribute__((always_inline))
#define __NR_fork 2 #define __NR_pause 29 #define __NR_setup 0 #define __NR_sync 36
#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; \ }
#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; \ }
|
对于 gcc AT&T 内嵌汇编不太熟悉的话,可以参考这篇 csdn 博文。以 _syscall0(int,fork) 为例,_syscall0(int,fork) 后产生了这么一个函数:
1 2 3 4 5 6 7 8
| int fork(){ long __res; __asm__ volatile ("int $0x80" : "=a" (__res) : "0" (__NR_fork)); if (__res >= 0) return (int) __res; errno = -__res; return -1; }
|
然后包含一堆头文件,引用一些初始化函数,定义一些常量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #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 <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;
#define EXT_MEM_K (*(unsigned short *)0x90002) #define DRIVE_INFO (*(struct drive_info *)0x90080) #define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)
|
接下来定义读取 CMOS 时钟信息的宏及 time_init 函数,从 CMOS 中读出的信息都是 BCD 码的形式,用 4 bits(半个字节)表示一个 10 进制数。于是定义一个 BCD_TO_BIN 的宏,val&15 取 10 进制个位,val>>4 取 10 进制十位,乘 10 与个位相加得到实际的十进制对应的二进制数值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #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); }
|
outb_p 与 inb_p 定义在 include/asm/io.h 中,分别表示向 io 端口输出信息及从 io 端口读取信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #define outb_p(value,port) \ __asm__ ("outb %%al,%%dx\n" \ "\tjmp 1f\n" \ "1:\tjmp 1f\n" \ "1:"::"a" (value),"d" (port))
#define inb_p(port) ({ \ unsigned char _v; \ __asm__ volatile ("inb %%dx,%%al\n" \ "\tjmp 1f\n" \ "1:\tjmp 1f\n" \ "1:":"=a" (_v):"d" (port)); \ _v; \ })
|
main 之前最后还定义了一些用于内存划分的变量
1 2 3 4 5 6
| 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;
|
main 函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| void main(void) { 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()) { init(); } for(;;) pause(); }
|
之前有说过 pause 的进程需等待一个信号才能被激活,进程 0 是个例外,当没有其他进程在运行时,会激活进程 0
接下来声明了一个 printf 函数,为 init 中输出信息做准备,并设置了一些配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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, NULL };
static char * argv[] = { "-/bin/sh",NULL }; static char * envp[] = { "HOME=/usr/root", NULL, NULL };
|
进程 1 执行的 init 函数,该函数首先对第一个将要执行的 shell 程序的环境进行初始化,然后以登录 shell 的方式加载并执行。如果运行登录 shell 的进程结束了,进程 1 又会重新新建一个进程,继续运行登录 shell。这里的登录 shell 指的就是开机完成用户可以键入用户名密码登录系统的 shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| 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)) ; 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); }
|