本节主要是记录一下Linux经过引导启动后的初始化工作,即/linux/init/main.c文件完成的工作。
这里主要记录一些初始化的流程,对于细节函数不会深究,然后重点说几个在学习中遇到的问题。
先来总览main.c函数,有几个突出的特点:
1)包含了非常多的头文件,如:<linux/tty.h><linux/sched.h><unistd.h>等等。
2)通过extern声明了很多外部函数,如:extern int vsprintf(); extern void init(); 等等。
3)采用了大量的宏定义,如:#define EXT_MEM_K (*(unsigned short *)0x90002) 等等。
以上这几个特点也从侧面说明了main函数是linux内核中的一个核心函数,其初始化过程与很多文件和函数息息相关。
main.c主要分为以下几部分:
一、初始化设备
这些函数基本都是用extern声明的外部函数,其具体定义大多存在于内核中的其他文件中,所以对此不予深究。
1)init() 函数原型,初始化
2)blk_dev_init() 块设备初始化(块设备:比如硬盘、软盘等随机读取设备)
3)chr_dev_init() 字符设备初始化(字符设备:比如键盘、串口等顺序读取设备)
4)hd_init() 硬盘初始化
5)floppy_init() 软驱初始化
6)mem_init() 内存管理初始化
7)rd_init() 内存管理初始化
(注:
(1)字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/字符来读写数据。举例来说,键盘、串口、调制解调器都是典型的字符设备。
(2)块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。硬盘、软盘、CD-ROM驱动器和闪存都是典型的块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块(通常是512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址。
(2)块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。硬盘、软盘、CD-ROM驱动器和闪存都是典型的块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块(通常是512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址。
)
二、初始化时间
开机时的系统时间是如何保存的?又是如何被系统读取的呢?
答案就是CMOS!CMOS是一块由电池独立供电的内存区域,所以即使电脑关机了,它内部的时间也是会不断更新的,这样就保证了开机时能够顺利读取其中的实时时间。
1)CMOS内存中的有效区域地址为 0x00 ~ 0x33(一共是14字节),其中包含了当前年、月、日、时、分、秒等信息。
2)初始化过程通过调用例如:time.tm_sec = CMOS_READ(0) 这样的函数返回各种时间信息,其中 0 代表CMOS中的信息地址。
time.tm_sec 等变量是
BCD码
, 需要转换成十进制进行显示,所以定义了宏
#define BCD_TO_BIN(val) ((val) = ((val)&15) +((val)>>4)*10)
进行转换。
(val)&15 为取其个位数上的值,(val)>>4)*10为取其十位数上的值并加权。
3)最后一步:startup_time = kernel_mktime(&time); 将取出来的时间”time“送入kernel_mktime进行处理,返回值即为系统的启动时间了。
(注:
关于CMOS内存的问题还有一个小插曲~ 众所周知,Linus是初代linux内核版本的编写者,当时由于他手里并没有CMOS内存的相关资料,也就是说”他并不知道CMOS中某个字节代表什么?哪个字节代表年,哪个字节代表分?“,他在注释里解释说”我并不知道CMOS内存的地址分别表示什么信息,我都是一个个试出来的……“。我是服了。。。
)
三、初始化内存
首先,介绍内存初始化之前,我们需要先了解内存的分布情况。为此从书上截了一下内存分布图,便于直观理解(其实是为了偷懒没自己画。。。)
可以看到,内存主要由四部分组成:内核区、高速缓冲区、虚拟盘、主内存区。
内核区:之前第一篇介绍的引导模块,system模块等内核部分的数据都是存放在这里的。
高速缓冲区:主要是为硬盘、软盘等块设备进行读写数据准备的。
虚拟盘区:对于含有RAM虚拟盘的系统,会从主内存区的前端取出一部分作为虚拟盘区。
主内存区:所有应用程序都可以访问的数据区域,有MM内存管理模块负责分配和释放,进行分页管理。
了解了内存结构,那我们就要按照这个结构对内存进行初始化了~
1)利用memory_end 设定内存大小
2)根据内存大小范围,利用buffer_memory_end 设置缓冲区末端的位置
3)main_memory_start = buffer_memory_end ;即主内存区域起始位置 = 缓冲区末端位置
4)根据RAMDISK大小 , 将main_memory_start 位置向后移动,预留出 虚拟盘区 的空间。
四、初始化进程
自从程序启动到现在,虽然经过了很多的引导和初始化过程,但是此时仍然是一个单进程程序!那怎么实现多进程的任务呢?那就需要初始化进程了。
当前进程习惯的称为进程0,我们需要创造新的进程去执行其他应用,所以运行了如下程序:
if( ! fork() ){
init(); //子进程(称为进程1)去执行了 init() 函数, 而父进程则直接跳过了 if语句执行下一步,为什么?请参见下注。
}
for(;;) pause(); //父进程执行到了这里,并且停住了,所以说父进程执行完初始化后就什么也不做了!
(注:
这里介绍一下fork()函数,这是一个创建新进程(我们一般称为子进程)的函数。它有如下特点,请一定牢记!
1)如果当前是子进程,fork()返回值 = 0;
2)如果当前是父进程,fork()返回值 = 子进程的ID;
)
以上是main.c的简介,请注意是简!介!所以如果想要仔细研究各个函数的功能和实现细节的话,需要进一步去学习了,这里只是做一下思路的整理~