您的位置:首页 > 运维架构 > Linux

Linux 0.11 内核学习之main.c

2015-05-02 13:12 393 查看
1.之所以选择这么低的版本学习,答案是简单,高版本的代码量太大,对于我这样的初学者来说,就是瞎子摸象不会有什么感觉。开始吧!

2首先你需要在一个地方下载源码:OldLinux

3.分析:

/*
*  linux/init/main.c
*
*  (C) 1991  Linus Torvalds
*/

#define __LIBRARY__            //在unistd.h中,使用了#ifndef __LIBRARY__
#include <unistd.h>            //包含unitsd.h
#include <time.h>            // 结构体的定义,包含tm(结构体),时间函数定义

/*
* 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.
*/
/*--翻译--*/  //这个地方与boot目录下的bootsect.s,setup.s,head.s有关
/*
* 我们需要下面这些内嵌语句 - 从内核空间创建进程(forking)将导致没有
* 写时复制(COPY ON WRITE)!!! 直到一个执行execve 调用。这对堆栈可
* 能带来问题。处理的方法是在fork()调用之后不让main()使用任何堆栈。
* 因此就不能有函数调用 - 这意味着fork 也要使用内嵌的代码,否则我
* 们在从fork()退出时就要使用堆栈了。实际上只有pause 和fork 需要使用
* 内嵌方式,以保证从main()中不会弄乱堆栈,但是我们同时还定义了其它
* 一些函数
*/
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>    //tty头文件,定义了有关tty_io,串行通信方面的参数,常数
//所谓“串行通信”是指外设和计算机间使用一根数据信号线
//数据在一根数据信号线上按位进行传输,每一位都占据一个固定的时间长度
#include <linux/sched.h> //调度程序头文件,定义了任务结构和task_struct,第1一个初始任务的数据
//还有一些以宏的形式定义的有关描述参数设置和获取的嵌入式汇编函数程序
#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>//标准参数头文件,以宏的形式定义变量参数列表,主要说明一个类型(va_list)
// 和三个宏(va_list,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); //建立内核时间
extern long startup_time; //内核启动时间(开机时间)(秒)

/*
* This is set up by the setup-routine at boot-time
//一下数据是由setup.s程序在引导时间设置的
*/
#define EXT_MEM_K (*(unsigned short *)0x90002) //1m以后的拓展内存大小
#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) ({ \   //这段宏读取cmos实时时钟信息
outb_p(0x80|addr,0x70); \   //0x70是些端口号,0x80|addr是要读取CMOS内存地址
inb_p(0x71); \   //0x71是读端口号
})

#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10) //将BSD码转换数字

static void time_init(void) //读取cmos中的信息,初始化全局变量startup_time
{
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--;        //months since january - [0,11]
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; 高速缓存末端地址 -- buffer_memory_end
// 机器内存数 -- memory_end;主内存开始地址 -- main_memory_start
ROOT_DEV = ORIG_ROOT_DEV;
drive_info = DRIVE_INFO;
memory_end = (1<<20) + (EXT_MEM_K<<10); //内存大小=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;
else if (memory_end > 6*1024*1024) //否则如果内存 > 6Mb,则设置缓冲末端=2Mb
buffer_memory_end = 2*1024*1024;
else  //否则设置缓冲区末端=1Mb
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();//tty初始化
time_init();//设置开机启动时间,startup_time
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.
*/
/*
* 注意!! 对于任何其它的任务,'pause()'将意味着我们必须等待收到一个信号才会返
* 回就绪运行态,但任务0(task0)是唯一的意外情况(参见'schedule()'),因为任务0 在
* 任何空闲时间里都会被激活(当没有其它任务在运行时),因此对于任务0'pause()'仅意味着
* 我们返回来查看是否有其它任务可以运行,如果没有的话我们就回到这里,一直循环执行'pause()'。
*/
for(;;) pause();
}

static int printf(const char *fmt, ...)//使用变长参数,调用write系统调用
{
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);//用读写访问方式打开设备"/dev/tty0"
(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);
printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);
//下面的代码打开/etc/rc,然后执行/bin/sh,但是这里开辟了
//两个线程
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 */;
/*
* 如果执行到这里,说明刚创建的子进程的执行已停止或终止了。
* 下面循环中首先再创建一个子进程.如果出错,则显示“初始化
* 程序创建子进程失败”的信息并继续执行。对于所创建的子进
* 程关闭所有以前还遗留的句柄(stdin, stdout, stderr),新创
* 建一个会话并设置进程组号,然后重新打开/dev/tty0 作为stdin,
* 并复制成stdout 和stderr。再次执行系统解释程序/bin/sh。但
* 这次执行所选用的参数和环境数组另选了一套。然后父进程再次
* 运行wait()等待。如果子进程又停止了执行,则在标准输出上显
* 示出错信息“子进程pid 停止了运行,返回码是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);    /* NOTE! _exit, not exit() */
}


View Code
4.参考

参考一

参考二

参考三
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: