start_kernel()中主要执行了以下操作:
(1) 在屏幕上打印出当前的内核版本信息。
(2) 执行setup_arch(),对系统结构进行设置。
(3)执行sched_init(),对系统的调度机制进行初始化。先是对每个可用CPU上的runqueque进行初始化;然后初始化0号进程(其task struct和系统空M堆栈在startup_32()中己经被分配)为系统idle进程,即系统空闲时占据CPU的进程。
(4)执行parse_early_param()和parsees_args()解析系统启动参数。
(5)执行trap_in itQ,先设置了系统中断向量表。0-19号的陷阱门用于CPU异常处理;然后初始化系统调用向量;最后调用cpu_init()完善对CPU的初始化,用于支持进程调度机制,包括设定标志位寄存器、任务寄存器、初始化程序调试相关寄存器等等。
(6)执行rcu_init(),初始化系统中的Read-Copy Update互斥机制。
(7)执行init_IRQ()函数,初始化用于外设的中断,完成对IDT的最终初始化过程。
(8)执行init_timers(), softirq_init()和time_init()函数,分别初始系统的定时器机制,软中断机制以及系统日期和时间。
(9)执行mem_init()函数,初始化物理内存页面的page数据结构描述符,完成对物理内存管理机制的创建。
(10)执行kmem_cache_init(),完成对通用slab缓冲区管理机制的初始化工作。
(11)执行fork_init(),计算出当前系统的物理内存容量能够允许创建的进程(线程)数量。
(12)执行proc_caches_init() , bufer_init(), unnamed_dev_init() ,vfs_caches_init(), signals_init()等函数对各种管理机制建立起专用的slab缓冲区队列。
(13 )执行proc_root_init()Wl数,对虚拟文件系统/proc进行初始化。
Windows 是闭源开发的商业软件,受商业版权法保护,别说大家都没有,就算有也不能给你的(被微软起诉那可不是小事)。
Linux的是开源的,干嘛不去读Linux源码呢?
新版本的太长,写不开,给你最经典的0.11版的进程管理源码。其他的你自己去查。作为一个好的程序员,必须学会充分利用搜索引擎。
---------------------------------------。
linux0.11通过一个进程数组管理进程,最大允许64个进程存在。进程的状态有就绪态,运行态,暂停态,睡眠态和僵死态等。睡眠态又可分为可中断和不可中断两种。对于进程的管理,我觉得按照进程的状态来讲会清晰一些。
1.0号任务
0号比较特殊,是"纯手工制作",下面就是制作过程。
mem_init(main_memory_start,memory_end);。
trap_init();
blk_dev_init();。
char_dev_init();。
tty_init();
time_init();
sched_init();
buffer_init(buffer_memory_end);。
hd_init();
floppy_init();
sti();
move_to_user_mode();。
这么多init当然不全是为0任务准备的,他们是初始化系统。对任务0来说,重要的是sched_init()和move_to_user_mode()两个。sched_init()中手动设置了任务0的任务段描述符tss和局部段描述符ldt,并设置了tr和ldtr寄存器:
set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss)); //设置tss段描述符。
set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));//设置ldt描述符。
...
ltr(0); //加载tss描述符地址到tr。
lldt(0); //加载ldt描述符地址到ldtr。
我们来看一下任务0的tss和ldt是什么样子。
/*ldt*/ {0,0},\。
{0x9f,0xc0fa00},\。
{0x9f,0xc0f200},\。
/*tss*/{0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\。
0,0,0,0,0,0,0,0,\。
0,0,0x17,0x17,0x17,0x17,0x17,0x17,\。
_LDT(0),0X80000000,\。
{}\
},\
从ldt 可以看到,任务的代码和数据段均为640k,基址为0,DPL=3。这说明虽然任务0的代码还是处在内核段内,但是任务的级别已经是用户态了。从tss也可以看到这一点,代码和数据都被置为0x17,也就是局部段描述符表第2项。还可以看到任务0的内核太堆栈被设置在init_task之后的一页处,用户态堆栈就是move_to_user_mode之前的内核态堆栈。这和普通进程不一样,普通进程的用户态堆栈在其64Mb地址空间的末端。
move_to_user_mode是在堆栈中创建一个任务切换的假象,用iret跳转到外层3,这样cpu就会自动根据tr加载tss,并初始化各个寄存器运行任务0。所以,任务0其实就是内核空间中的用户态任务。
2.进程的创建
进程创建是由系统调用sys_fork完成的,主要使用了两个函数find_empty_process和copy_process。前者在进程在进程数组中找一个不用的进程号给子进程,后者完成子进程信息的创建,主要是复制父进程的信息。
我们来看一下copy_process的代码:
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,。
long ebx,long ecx,long edx,long fs,long es,long ds,。
long eip,long cs,long eflags,long esp,long ss)。
struct task_struct *p;。
int i;
struct file *f;。
//首先为子进程的进程描述符分配一块内存。
p=(struct task_struct *)get_free_page();。
if(!p)
return -EAGAIN;。
//将新任务结构指针加入数组中。
task[nr]=p;。
//复制当前用户的任务结构到子进程中。当然堆栈不复制。
*p=*current;。
//将子进程的状态设为不可中断等待,防止现在被调度。
p->state=TASK_UNINTERRUPTIBLE;。
P->pid=last_pid;。
p->father=current->pid;。
p->count=p->priority;。
p->signal=0;。
p->alarm=0;。
p->leader=0;。
p->utime=p->stime=0;。
p->cutime=p->cstime=0;。
p->start_time=jiffies;。
p->tss.back_link=0;。
//新进程的内核态堆栈在进程描述符页末端。
p->tss.esp0=PAGE_SIZE+(long)p;。
P->tss.ss0=0x10;。
//ip为父进程调用fork的下一条指令。
p->tss.eip=eip;。
//fork的返回值对子进程来说是0,对父进程来说是它的pid,通过这个区别,在fork调用返回后,父子进程的代码段便被分割来,
p->tss.eax=0;。
//虽然他们是在一个代码文件中写的。
p->tss.ecx=ecx;。
p->tss.edx=edx;。
p->tss.ebx=ebx;。
p->tss.esp=esp;。
p->tss.ebp=ebp;。
p->tss.esi=esi;。
p->tss.edi=edi;。
p->tss.es=es&0xffff;。
p->tss.cs=cs&0xffff;。
p->tss.ss=ss&0xffff;。
p->tss.ds=ds&0xffff;。
p->tss.fs=fs&0xffff;。
p->tss.gs=gs&0xffff;。
p->tss.ldt=_LDT(nr);。
p->tss.trace_bitmap=0x80000000;。
//如果父任务使用了协处理器,还要保存到tss中。
if(last_task_used_math==current)。
_asm("clts;fnsave %0"::"m"(p->tss.i387));。
//为新任务设置新的代码和数据段基地址。注意,因为linux0.11只支持64M的进程空间,所以进程的线性地址空间在64M的边界处。
//然后为新任务复制父进程的页表。通过copy_page_tales,父子任务此时共用一个只读的代码数据段。在写操作时,写时复制会为新进程申请新的物理页面。
if(copy_mem(nr,p)){。
task[nr]=NULL;。
free_page((long)p);。
return -EAGAIN;。
}
for(i=0;i<NR_OPEN;i++)。
if(f=p->filp)。
f->f_count++;。
if(current->pwd)。
current->pwd->i_count++;。
if(current->root)。
current->root->i_count++;。
if(current->executable)。
current->executable->i_count++;。
//设置新任务的tss和ldt描述符。
set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));。
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));。
//返回子进程的pid
return last_pid;。
参数都是堆栈中的值,nr是调用copy_process之前的find_empty_process的返回值,作为任务数组的序号。
3.进程的调度
进程创建之后并不是立即执行。系统会在适当的时刻调用系统调度函数schedule,它会选择适当的进程运行。调度函数可能在系统调用结束之后,进程暂停/ 睡眠/退出,时钟中断等多个地方调用。调度函数主要是通过进程的时间片来选择一个运行时间最短的进程运行。如果没有找到就运行空闲pause系统调用,在 Pause中,调度函数又会被调用。下面是主要代码。
while(1){
c=-1;
next=0;。
i=NR_TASKS;。
p=&task[NR_TASKS];。
//寻找就绪任务中运行时间最短的任务。
while(--i){。
if(!(*--p))。
continue;。
if((*p)->state==TASK_RUNNING&&(*p)->counter>c)。
c=(*p)->counter,next=i;。
}
//如果找到,就退出。否则重新计算任务的时间片。注意,如果没有任务,next始终为0,结果就被切换到任务0。
if(c)break;。
for(p=&LAST_TASK;p>&FIRST_TASK;--p)。
if(*p)。
(*p)->counter=((*p)->counter>>1)+(*)->priority;。
switch_to(next);//通过长跳转,转换任务到新任务。
时钟中断是执行调度的重要措施,正是由于不断的执行调度,系统才能在多个任务之间进行切换,完成多任务操作系统的目标。在调度器初始化时,除了手动设置了任务0,还对8253开启了定时器中断,对系统"点火".中断中调用了do_timer函数。现在这是个很短的函数:。
void do_timer(long cpl)。
...
//根据优先级调整进程运行时间。
if(cpl)
current->utime++;。
else
current->stime++;。
//处理定时器中断队列
if(next_timer){。
next_timer->jiffies--;。
while(next_timer&&next_timer->jiffies<=0){。
void(*fn)(void);。
fn=next_timer->fn;。
next_timer->fn=NULL;。
next_timer=next_timer->next;。
(fn)();。
}
..
//对进程时间片减1。如果大于0 ,表示时间片还没有用完,继续。
if((--current->counter>0)return;。
//注意,内核态任务是不可抢占的,在0.11中,除非内核任务主动放弃处理器,它将一直运行。
if(!cpl)return;。
//调度
schedule();
}
4.进程的终止
进程可以自己中止,也可以被中止信号中止。do_exit执行退出。如果它有子进程的话,子进程就会成为孤儿进程,这里会将它的孩子托付给进程1(即init进程)管理。在成为僵死进程之后,还要通知父进程,因为他的父进程可能在等它呢。
实模式,并开始执行位于地址0xFFFF0处。
的代码,也就是ROM-BIOS起始位置的代码。BIOS先进行一系列的系统自检,然后初始化位。
于地址0的中断向量表。最后BIOS将启动盘的第一个扇区装入到0x7C00,并开始执行此处。
的代码。这就是对内核初始化过程的一个最简单的描述。
最初,linux核心的最开始部分是用8086汇编语言编写的。当开始运行时,核心将自。
己装入到绝对地址0x90000,再将其后的2k字节装入到地址0x90200处,最后将核心的其余。
部分装入到0x10000。
当系统装入时,会显示Loading...信息。装入完成后,控制转向另一个实模式下的汇。
编语言代码boot/Setup.S。Setup部分首先设置一些系统的硬件设备,然后将核心从。
0x10000处移至0x1000处。这时系统转入保护模式,开始执行位于0x1000处的代码。
接下来是内核的解压缩。0x1000处的代码来自于文件Boot/head.S,它用来初始化寄。
存器和调用decompress_kernel( )程序。decompress_kernel( )程序由Boot/inflate.c,。
Boot/unzip.c和Boot../misc.c组成。解压缩后的数据被装入到了0x100000处,这也是。
linux不能在内存小于2M的环境下运行的主要原因。
解压后的代码在0x1010000处开始执行,紧接着所有的32位的设置都将完成: IDT、
GDT和LDT将被装入,处理器初始化完毕,设置好内存页面,最终调用start_kernel过程。
这大概是整个内核中最为复杂的部分。
[系统开始运行]
linux kernel 最早的C代码从汇编标记startup_32开始执行。
startup_32:
start_kernel
lock_kernel
trap_init
init_IRQ
sched_init
softirq_init
time_init
console_init
#ifdef CONFIG_MODULES。
init_modules
#endif
kmem_cache_init。
sti
calibrate_delay。
mem_init
kmem_cache_sizes_init。
pgtable_cache_init。
fork_init
proc_caches_init。
vfs_caches_init。
buffer_init
page_cache_init。
signals_init
#ifdef CONFIG_PROC_FS。
proc_root_init
#endif
#if defined(CONFIG_SYSVIPC)。
ipc_init
#endif
check_bugs
smp_init
rest_init
kernel_thread
unlock_kernel
cpu_idle
・startup_32 [arch/i386/kernel/head.S]。
・start_kernel [init/main.c]。
・lock_kernel [include/asm/smplock.h]。
・trap_init [arch/i386/kernel/traps.c]。
・init_IRQ [arch/i386/kernel/i8259.c]。
・sched_init [kernel/sched.c]。
・softirq_init [kernel/softirq.c]。
・time_init [arch/i386/kernel/time.c]。
・console_init [drivers/char/tty_io.c]。
・init_modules [kernel/module.c]。
・kmem_cache_init [mm/slab.c]。
・sti [include/asm/system.h]。
・calibrate_delay [init/main.c]。
・mem_init [arch/i386/mm/init.c]。
・kmem_cache_sizes_init [mm/slab.c]。
・pgtable_cache_init [arch/i386/mm/init.c]。
・fork_init [kernel/fork.c]。
・proc_caches_init。
・vfs_caches_init [fs/dcache.c]。
・buffer_init [fs/buffer.c]。
・page_cache_init [mm/filemap.c]。
・signals_init [kernel/signal.c]。
・proc_root_init [fs/proc/root.c]。
・ipc_init [ipc/util.c]。
・check_bugs [include/asm/bugs.h]。
・smp_init [init/main.c]。
・rest_init
・kernel_thread [arch/i386/kernel/process.c]。
・unlock_kernel [include/asm/smplock.h]。
・cpu_idle [arch/i386/kernel/process.c]。
start_kernel( )程序用于初始化系统内核的各个部分,包括:
*设置内存边界,调用paging_init( )初始化内存页面。
*初始化陷阱,中断通道和调度。
*对命令行进行语法分析。
*初始化设备驱动程序和磁盘缓冲区。
*校对延迟循环。
最后的function'rest_init' 作了以下工作:。
・开辟内核线程'init'
・调用unlock_kernel。
・建立内核运行的cpu_idle环, 如果没有调度,就一直死循环。
实际上start_kernel永远不能终止.它会无穷地循环执行cpu_idle.。
最后,系统核心转向move_to_user_mode( ),以便创建初始化进程(init)。此后,进程0开始进入无限循环。
初始化进程开始执行/etc/init、/bin/init 或/sbin /init中的一个之后,系统内核就不再对程序进行直接控制了。之后系统内核的作用主要是给进程提供系统调用,以及提供异步中断事件的处理。多任务机制已经建立起来,并开始处理多个用户的登录和fork( )创建的进程。
[init]
init是第一个进程,或者说内核线程。
init
lock_kernel
do_basic_setup
mtrr_init
sysctl_init
pci_init
sock_init
start_context_thread。
do_init_calls
(*call())-> kswapd_init。
prepare_namespace。
free_initmem
unlock_kernel
execve
[目录]
--------------------------------------------------------------------------------。
启动步骤
系统引导:
涉及的文件
./arch/$ARCH/boot/bootsect.s。
./arch/$ARCH/boot/setup.s。
bootsect.S
这个程序是linux kernel的第一个程序,包括了linux自己的bootstrap程序,。
但是在说明这个程序前,必须先说明一般IBM PC开机时的动作(此处的开机是指。
"打开PC的电源"):
一般PC在电源一开时,是由内存中地址FFFF:0000开始执行(这个地址一定。
在ROM BIOS中,ROM BIOS一般是在FEOOOh到FFFFFh中),而此处的内容则是一个。
jump指令,jump到另一个位於ROM BIOS中的位置,开始执行一系列的动作,包。
括了检查RAM,keyboard,显示器,软硬磁盘等等,这些动作是由系统测试代码。
(system test code)来执行的,随着制作BIOS厂商的不同而会有些许差异,但都。
是大同小异,读者可自行观察自家机器开机时,萤幕上所显示的检查讯息。
紧接着系统测试码之后,控制权会转移给ROM中的启动程序。
(ROM bootstrap routine),这个程序会将磁盘上的第零轨第零扇区读入。
内存中(这就是一般所谓的boot sector,如果你曾接触过电脑病。
毒,就大概听过它的大名),至於被读到内存的哪里呢? --绝对。
位置07C0:0000(即07C00h处),这是IBM系列PC的特性。而位在linux开机。
磁盘的boot sector上的正是linux的bootsect程序,也就是说,bootsect是。
第一个被读入内存中并执行的程序。现在,我们可以开始来。
看看到底bootsect做了什么。
第一步
首先,bootsect将它"自己"从被ROM BIOS载入的绝对地址0x7C00处搬到。
0x90000处,然后利用一个jmpi(jump indirectly)的指令,跳到新位置的。
jmpi的下一行去执行,
第二步
接着,将其他segment registers包括DS,ES,SS都指向0x9000这个位置,。
与CS看齐。另外将SP及DX指向一任意位移地址( offset ),这个地址等一下。
会用来存放磁盘参数表(disk para- meter table )。
第三步
接着利用BIOS中断服务int 13h的第0号功能,重置磁盘控制器,使得刚才。
的设定发挥功能。
第四步
完成重置磁盘控制器之后,bootsect就从磁盘上读入紧邻着bootsect的setup。
程序,也就是setup.S,此读入动作是利用BIOS中断服务int 13h的第2号功能。
setup的image将会读入至程序所指定的内存绝对地址0x90200处,也就是在内存。
中紧邻着bootsect 所在的位置。待setup的image读入内存后,利用BIOS中断服。
务int 13h的第8号功能读取目前磁盘的参数。
第五步
再来,就要读入真正linux的kernel了,也就是你可以在linux的根目录下看。
到的"vmlinuz" 。在读入前,将会先呼叫BIOS中断服务int 10h 的第3号功能,。
读取游标位置,之后再呼叫BIOS 中断服务int 10h的第13h号功能,在萤幕上输。
出字串"Loading",这个字串在boot linux时都会首先被看到,相信大家应该觉。
得很眼熟吧。
第六步
接下来做的事是检查root device,之后就仿照一开始的方法,利用indirect。
jump 跳至刚刚已读入的setup部份。
第七步
setup.S完成在实模式下版本检查,并将硬盘,鼠标,内存参数写入到 INITSEG。
中,并负责进入保护模式。
第八步
操作系统的初始化。
系统启动过程详解
首先让我们来了解一些基本概念。第一个是大家非常熟悉的BIOS(基本输入输出系统),BIOS是直接与硬件打交道的底层代码,它为操作系统提供了控制硬件设备的基本功能。BIOS包括有系统BIOS(即常说的主板BIOS)、显卡BIOS和其它设备(例如IDE控制器、SCSI卡或网卡等)的 BIOS,其中系统BIOS是本文要讨论的主角,因为计算机的启动过程正是在它的控制下进行的。BIOS一般被存放在ROM(只读存储芯片)之中,即使在关机或掉电以后,这些代码也不会消失。
第二个基本概念是内存的地址,我们的机器中一般安装有32MB、64MB或128MB内存,这些内存的每一个字节都被赋予了一个地址,以便CPU 访问内存。32MB的地址范围用十六进制数表示就是0~1FFFFFFH,其中0~FFFFFH的低端1MB内存非常特殊,因为最初的8086处理器能够访问的内存最大只有1MB,这1MB的低端640KB被称为基本内存,而A0000H~BFFFFH要保留给显示卡的显存使用, C0000H~FFFFFH则被保留给BIOS使用,其中系统BIOS一般占用了最后的64KB或更多一点的空间,显卡BIOS一般在 C0000H~C7FFFH处,IDE控制器的BIOS在C8000H~CBFFFH处。
好了,下面我们就来仔细看看计算机的启动过程吧。
1 windows 9x
第一步:当我们按下电源开关时,电源就开始向主板和其它设备供电,此时电压还不太稳定,主板上的控制芯片组会向CPU发出并保持一个RESET(重置)信号,让 CPU内部自动恢复到初始状态,但CPU在此刻不会马上执行指令。当芯片组检测到电源已经开始稳定供电了(当然从不稳定到稳定的过程只是一瞬间的事情),它便撤去RESET信号(如果是手工按下计算机面板上的Reset按钮来重启机器,那么松开该按钮时芯片组就会撤去RESET信号),CPU马上就从地址 FFFF0H处开始执行指令,从前面的介绍可知,这个地址实际上在系统BIOS的地址范围内,无论是Award BIOS还是AMI BIOS,放在这里的只是一条跳转指令,跳到系统BIOS中真正的启动代码处。
第二步: 系统BIOS的启动代码首先要做的事情就是进行POST(Power-On Self Test,加电后自检),POST的主要任务是检测系统中一些关键设备是否存在和能否正常工作,例如内存和显卡等设备。由于POST是最早进行的检测过程,此时显卡还没有初始化,如果系统BIOS在进行POST的过程中发现了一些致命错误,例如没有找到内存或者内存有问题(此时只会检查640K常规内存),那么系统BIOS就会直接控制喇叭发声来报告错误,声音的长短和次数代表了错误的类型。在正常情况下,POST过程进行得非常快,我们几乎无法感觉到它的存在,POST结束之后就会调用其它代码来进行更完整的硬件检测。
第三步:接下来系统BIOS将查找显卡的BIOS,前面说过,存放显卡BIOS的ROM芯片的起始地址通常设在C0000H处,系统BIOS在这个地方找到显卡 BIOS之后就调用它的初始化代码,由显卡BIOS来初始化显卡,此时多数显卡都会在屏幕上显示出一些初始化信息,介绍生产厂商、图形芯片类型等内容,不过这个画面几乎是一闪而过。系统BIOS接着会查找其它设备的BIOS程序,找到之后同样要调用这些BIOS内部的初始化代码来初始化相关的设备。
第四步: 查找完所有其它设备的BIOS之后,系统BIOS将显示出它自己的启动画面,其中包括有系统BIOS的类型、序列号和版本号等内容。
第五步: 接着系统BIOS将检测和显示CPU的类型和工作频率,然后开始测试所有的RAM,并同时在屏幕上显示内存测试的进度,我们可以在CMOS设置中自行决定使用简单耗时少或者详细耗时多的测试方式。
第六步: 内存测试通过之后,系统BIOS将开始检测系统中安装的一些标准硬件设备,包括硬盘、CD-ROM、串口、并口、软驱等设备,另外绝大多数较新版本的系统BIOS在这一过程中还要自动检测和设置内存的定时参数、硬盘参数和访问模式等。
第七步: 标准设备检测完毕后,系统BIOS内部的支持即插即用的代码将开始检测和配置系统中安装的即插即用设备,每找到一个设备之后,系统BIOS都会在屏幕上显示出设备的名称和型号等信息,同时为该设备分配中断、DMA通道和I/O端口等资源。
第八步: 到这一步为止,所有硬件都已经检测配置完毕了,多数系统BIOS会重新清屏并在屏幕上方显示出一个表格,其中概略地列出了系统中安装的各种标准硬件设备,以及它们使用的资源和一些相关工作参数。
第九步: 接下来系统BIOS将更新ESCD(Extended System Configuration Data,扩展系统配置数据)。ESCD是系统BIOS用来与操作系统交换硬件配置信息的一种手段,这些数据被存放在CMOS(一小块特殊的RAM,由主板上的电池来供电)之中。通常ESCD数据只在系统硬件配置发生改变后才会更新,所以不是每次启动机器时我们都能够看到“Update ESCD… Success”这样的信息,不过,某些主板的系统BIOS在保存ESCD数据时使用了与Windows 9x不相同的数据格式,于是Windows 9x在它自己的启动过程中会把ESCD数据修改成自己的格式,但在下一次启动机器时,即使硬件配置没有发生改变,系统BIOS也会把ESCD的数据格式改回来,如此循环,将会导致在每次启动机器时,系统BIOS都要更新一遍ESCD,这就是为什么有些机器在每次启动时都会显示出相关信息的原因。
第十步: ESCD更新完毕后,系统BIOS的启动代码将进行它的最后一项工作,即根据用户指定的启动顺序从软盘、硬盘或光驱启动。以从C盘启动为例,系统BIOS 将读取并执行硬盘上的主引导记录,主引导记录接着从分区表中找到第一个活动分区,然后读取并执行这个活动分区的分区引导记录,而分区引导记录将负责读取并执行IO.SYS,这是DOS和Windows 9x最基本的系统文件。Windows 9x的IO.SYS首先要初始化一些重要的系统数据,然后就显示出我们熟悉的蓝天白云,在这幅画面之下,Windows将继续进行DOS部分和GUI(图形用户界面)部分的引导和初始化工作。
如果系统之中安装有引导多种操作系统的工具软件,通常主引导记录将被替换成该软件的引导代码,这些代码将允许用户选择一种操作系统,然后读取并执行该操作系统的基本引导代码(DOS和Windows的基本引导代码就是分区引导记录)。
如果我们在DOS下按Ctrl+Alt+Del组合键(或从Windows中选择重新启动计算机)来进行热启动,那么POST过程将被跳过去,直接从第三步开始,另外第五步的检测CPU和内存测试也不会再进行。
2 windows nt
第一步: 首先计算机通电进行自检,并由 BIOS (即基本输入输出系统)完成基本硬件配置,然后读取硬盘的MBR(主引导记录)检查硬盘分区表以确定引导分区,并将引导分区上的操作系统引导扇区调入内存中执行,此处即执行NTLDR(操作系统加载器)文件。
Windows2000/XP支持多重启动。它在安装时会首先将已存在的其它操作系统引导扇区保存为BOOTSECT.DOS文件(位于活动分区根目录下),并修改系统引导扇区,以便系统启动时加载NTLDR文件,从而达到多重启动的目的。而Windows98则不具备这个功能,因此如果先装好 Windows2000/XP后再装Windows98会破坏掉Windows2000/XP的引导记录,导致2000/XP不能启动。
第二步: 进行出始化,NTLDR会把处理器从实模式转换为32位保护模式。
第三步:读取BOOT.INI文件。该文件位于活动分区根目录下,它的作用是使系统在启动过程中出现选择菜单,由用户选择希望启动的操作系统。如果选择启动 Windows2000/XP,NTLDR会继续引导进行以下过程;如果选择为非Windows2000/XP系统,NTLDR则会读取系统引导扇区副本 BOTSECT.DOS转入启动相应系统。
[BOOT LOADER]即操作系统加载器,指定系统选择菜单默认等待时间和默认引导的操作系统。可手工修改或在控制面板中修改,为了保险起见,建议在控制面板中修改。依次选择控制面板-〉系统-〉高级->启动和故障恢复,即可更改相关设置。(在WindowsXP中还有另一种方法,即运行msconfig (系统配置实用程序)。
[OPERATING SYSTEMS]段指定操作系统列表,由双引号括起来的部分就是列表所显示的内容,可任意修改,使其更加个性化。
形如MULTI(0)DISK(0)RDISK(0)PARTITION(1)格式的语句被称为ARC路径,它的格式为:MULTI()——指定磁盘控制器(若为SCSI控制器,则此处应替换为SCSI());DISK()——指定SCSI设备编号(对于MULTI该处值始终为0);RDISK ()——指定IDE设备编号(对于SCSI,此处被忽略);PARTITION()——指定分区编号。除分区编号由1开始外,其余编号均从0开始。
参数/FASTDETECT表示禁用串行鼠标检测,是系统默认值。还有几个常见参数:MAXMEM——指定Windows2000/XP可用内存容量;BASEVIDEO——使用标准VGA显示驱动程序;NOGUIBOOT——启动过程中不显示图形屏幕;SOS——加载设备驱动程序时显示其名称。
在操作系统选择菜单中的中文字体由位于活动分区根目录下的BOOTFONT.BIN文件提供。
第四步: 系统加载NTDETECT.COM文件。由它来检测机器硬件,如并行端口,显示适配器等等,并将收集到的硬件列表返回NTLDR用于以后在注册表中注册保存。
第五步:如果Windows2000/XP有多个硬件配置文件,此时会出现选择菜单,等待用户确定要使用的硬件配置文件,否则直接跳过此步,启用默认配置。硬件配置文件是指保存计算机特定硬件配置的系统文件。可以创建多个不同的硬件配置文件以满足计算机在不同场合的应用。可以依次选择控制面板-〉系统->硬件-〉硬件配置文件作出修改。
第六步: 引导过程开始装载Windows2000/XP内核NTOSKRNL.EXE。这个文件位于Windows2000/XP安装文件夹下的SYSTEM32文件夹中。随后,硬件抽象层(HAL)被引导进程加载,完成本步骤。
硬件抽象层(HAL):隐藏特定平台的硬件接口细节,为操作系统提供虚拟硬件平台,使其具有硬件无关性,可在多种平台上进行移植。
第七步: 内核完成初始化,NTLDR将控制权转交Windows2000/XP内核,后者开始装载并初始化设备驱动程序,以及启动WIN32子系统和WINDOWS2000/XP服务。
第八步: 开始登录进程。由WIN32子系统启动WINLOGON.EXE,并由它启动LOCAL SECURITY AUTHORITY(LSASS.EXE)显示登录对话框。用户登录后,WINDOWS2000/XP会继续配置网络设备和用户环境。最后,伴随着微软之声和我们熟悉的个性化桌面,WINDOWS2000/XP漫长的启动过程终于完成。
3 linux
第一步:硬件检测。当机器加电后它首先执行BIOS(基本输入输出系统)中的代码,BIOS首先执行加电自检程序(POST),当自检通过程便完成了硬件的启动。 POST程序通过对内存及其他硬件的设备的诊断检测确定硬件的存在并可正确操作。BIOS是固化在芯片里的程序,执行这一过程一般只需要几秒钟。当自检完成后BIOS按照系统COMS中设置的启动顺序搜寻有效的启动驱动器(这里我们以硬盘为例),并读入系统引导扇区,并将系统控制权交给引导程序。
第二步:加载和执行引导程序。系统引导程序主要是把系统内核装载到内存,启动盘必须在第一个逻辑磁道上包含引导记录。这512个字节的扇区又被称作是引导扇区,在系统完成加电自检后,BIOS从启动盘中将引导扇区读入到内存中。引导记录中包含了一些磁盘的物理特性的参数。在引导扇区被读入内存后,BIOS就能从这里读取到启动盘的物理参数。一旦引导记录加载完毕,BIOS就交出系统的执行控制权,跳转到引导程序的头部执行。引导记录开头是一条无条件转移指令,它将立即跳转到地址0x03e执行引导程序,在引导扇区中这个引导程序将从磁盘中读出其他几个更为复杂的程序并由它们加载系统内核。
Linux的引导程序由汇编代码文件arch/i386/boot/bootsect.S生成,它利用对BIOS功能的调用将 arch/i386/boot/下的setup.S文件和内核映象加载到内存。i386的体系结构的CPU分保护模式和实模式两种,在实模式下只能使用低端的640K内存。系统在加载引导程序时CPU是处在实模式下,而现在的内核映象文件一般都超过了640K的限制,即使是经过压缩过的内核映象,这个内核映象文件通常是bzImage,我们在编译内核时通常要用到这个文件。由于bzImage超出了640K这一限制,所以linux设计了一个 bootsect_helper子程序(定义在arch/i386/boot/setup.S中),引导程序通过循环调用bootsect_helper 将内核映象一块一块的装入内存,当内核加载完毕,系统跳转到setup.S的开始位置开始执行,setup.S仍在实模式下运行,主要功能是设置系统参数 (如:内存、磁盘等),并为进入保护模式做准备,最后进入到保护模式并跳转到内核映象文件的头部开始执行内核。这里提一下有关linux的引导程序 lilo和grub,lilo和grub可以引导多个系统,如果机器上要装多系统的话一般都会用到它们,这一引导程序也储存在引导扇区中或者存放在主引导记录中(MBR),lilo和grub都许允用户自己配置,它们在系统安装时建立了关于系统内核占用磁盘数据块的位置对照表。当用户选择启动linux系统后,同样也跳转到setup.S上运行。
第三步:内核初始化。当setup.S执行完后,CPU进行保护模式,并开始执行内核,如果内核是经过压缩的,那么首先执行 arch/i386/boot/compressed目录下的head.S建立堆栈并解压内核映象文件,然后再转入arch/i386/kernel下的 head.S。如果没有压缩则直接转到arch/i386/kernel下的head.S开始执行。arch/i386/kernel/head.S程序负责数据区(BBS)、中断描述表(IDT)、段描述表(GDT)、页表和寄存器的初始化。最后进入start_kernel()模块。此时系统运行在内核模式(0级别)下,转入到init/main.c中的start_kernel()。
start_kernel()继续其他方面的初始化工作,主要是初始化系统的核心数据结构,主要包括:
setup_arch():执行与体系结构相关的设置。
trap_init():设置各种入口地址。
init_IRQ():初始化IRQ中断处理机制。
sched_init():设置并启动第一个进程init_task()。
softirq_init():对软中断子系统进行初始化。
console_init():初始化控制台、显示器. 。
init_modules():初始化kernel_module。
fork_init():定义系统最大进程数. 。
最后进入rest_init()函数并调用kernel_thread()创建init内核线程,进行系统配置。
init内核线程占用进程描述表的第一项,由它来创建其他完成系统初始他的进程。
init内核线程首先要销定内核,然后调用do_basic_setup()来初始化外部设备及加载驱动程序。
主要的初始化工作包括:
PCI总线初始化。
网络初始化。
文件系统初始化。
加载文件系统。
在do_basic_setup()调用完成后,init()会释放初始化函数据占用的内存,并且打开/dev/console设备重新定向控制台,用系统调用execve来执行用户态程序/sbin/init。至此,linux的内核初始化工作完成。下面的工作就由用户态的/sbin/init 程序来完成。init程序程读取/etc/inittab文件来决定它具体的工作。在inittab中比较重要的几条是:
id:5:initdefault 决定操作系统启动时缺省的执行级别(这里说讲的是系统的运行级别,而不同于CPU的级别) 。
si:sysinit:/etc/rc.d/rc.sysinit 执行/etc/rc.d/rc.sysinit的脚本。rc.sysinit主要的工作是激活交换分区、检查磁盘、加载硬件模块。
1:2345:respawn:/sbin/mingetty tty1 显示登录界面 。
至此,整个系统的引导过程就完成了。
第1部分 嵌入式系统硬件开发
第1章 嵌入式系统概述 2
这一章对嵌入式系统的概念及其特点和应用作了概括介绍,笔者根据自己多年的经验阐述了对嵌入式系统的理解,并对一些常见的嵌入式处理器的硬件数据进行了比较。
1.1 嵌入式系统概念 2
1.2 嵌入式处理器 3
1.3 嵌入式系统应用 4
1.4 嵌入式系统发展 4
1.5 一些嵌入式处理器的硬件特性比较 5。
第2章 ARM处理器概述 16。
为了使本书内容完整,从第2章到第7章中的内容大部分是笔者阅读《ARM体系结构与编程》(详情参见附录中的参考文献)的笔记和心得,把与嵌入式系统开发和Linux内核密切相关的硬件知识进行了概括和整理,本章主要介绍了ARM处理器的特点、ARM处理器的体系架构版本和ARM处理器系列。
2.1 ARM发展历程 16
2.2 ARM处理器特点 17。
2.3 ARM处理器应用 17。
2.4 ARM体系架构 18
2.4.1 ARM体系架构版本 18。
2.4.2 ARM体系架构变种(Variant) 20。
2.4.3 ARM体系架构版本命名格式 22。
2.5 ARM处理器 22
2.5.1 ARM7系列处理器 23。
2.5.2 ARM9系列处理器 24。
2.5.3 ARM9E系列处理器 24。
2.5.4 ARM10E系列处理器 25。
2.5.5 SecurCore系列处理器 25。
2.5.6 StrongARM处理器 26。
2.5.7 Xscale处理器 26。
第3章 ARM指令及其寻址方式 27。
本章主要介绍了ARM处理器的指令和寻址方式以及ARM汇编伪指令,这是做ARM处理器应用系统底层软件开发必备的知识。
3.1 ARM处理器的程序状态寄存器(PSR) 27。
3.2 ARM指令的条件码 28。
3.3 ARM指令介绍 29
3.3.1 跳转指令 29
3.3.2 数据处理指令 30。
3.3.3 乘法指令 31
3.3.4 杂类算术指令 32。
3.3.5 状态寄存器访问指令 32。
3.3.6 Load/Store内存访问指令 33。
3.3.7 批量Load/Store内存访问指令 34。
3.3.8 LDREX和STREX指令 35。
3.3.9 信号量操作指令 37。
3.3.10 异常中断产生指令 37。
3.3.11 ARM协处理器指令 37。
3.4 ARM指令寻址方式 39。
3.4.1 数据处理指令的操作数的寻址方式 39。
3.4.2 字及无符号字节的Load/Store指令的寻址方式 43。
3.4.3 杂类Load/Store指令的寻址方式 47。
3.4.4 批量Load/Store指令的寻址方式 49。
3.4.5 协处理器Load/Store指令的寻址方式 51。
3.4.6 ARM指令的寻址方式总结 52。
3.5 ARM汇编伪操作(Directive) 53。
3.5.1 符号定义伪操作 54。
3.5.2 数据定义伪操作 54。
3.5.3 汇编控制伪操作 56。
3.5.4 栈中数据帧描述伪操作 57。
3.5.5 信息报告伪操作 57。
3.5.6 其他伪操作 58
3.6 ARM汇编伪指令 59。
3.7 Thumb指令介绍 60。
第4章 ARM处理器内存管理单元(MMU) 61。
本章主要介绍了ARM处理器内存管理单元(MMU)的工作原理,Linux内存管理功能是通过处理器硬件MMU实现的,在没有MMU的处理器系统中,Linux只能工作在物理地址模式,没有虚拟(线性)地址空间的概念。
4.1 ARM处理器中CP15协处理器的寄存器 61。
4.1.1 访问CP15寄存器的指令 61。
4.1.2 CP15寄存器介绍 62。
4.2 MMU简介 70
4.3 系统访问存储空间的过程 71。
4.3.1 使能MMU时的情况 71。
4.3.2 禁止MMU时的情况 71。
4.3.3 使能/禁止MMU时应注意的问题 72。
4.4 ARM处理器地址变换过程 72。
4.4.1 MMU的一级映射描述符 73。
4.4.2 MMU的二级映射描述符 74。
4.4.3 基于段的地址变换过程 75。
4.4.4 粗粒度大页地址变换过程 75。
4.4.5 粗粒度小页地址变换过程 76。
4.4.6 细粒度大页地址变换过程 76。
4.4.7 细粒度小页地址变换过程 77。
4.4.8 细粒度极小页地址变换过程 77。
4.5 ARM存储空间访问权限控制 78。
4.6 TLB操作 79
4.6.1 使TLB内容无效 79。
4.6.2 锁定TLB内容 79。
4.6.3 解除TLB中被锁定的地址变换条目 80。
4.7 存储访问失效 80
4.7.1 MMU失效(MMU Fault) 80。
4.7.2 外部存储访问失效(External Abort) 81。
第5章 ARM处理器的Cache和Write Buffer 82。
本章主要介绍了ARM处理器高速缓存(Cache)和写缓存(Write Buffer)的工作原理,使读者了解如何提高处理器的性能。
5.1 Cache和Write Buffer一般性介绍 82。
5.1.1 Cache工作原理 82。
5.1.2 地址映像方式 83。
5.1.3 Cache写入方式原理简介 84。
5.1.4 关于Write-through和Write-back 85。
5.1.5 Cache替换策略 86。
5.1.6 使用Cache的必要性 87。
5.1.7 使用Cache的可行性 87。
5.2 ARM处理器中的Cache和Write Buffer 88。
5.2.1 基本概念 88
5.2.2 Cache工作原理 88。
5.2.3 Cache地址映射和变换方法 89。
5.2.4 Cache分类 90。
5.2.5 Cache替换算法 91。
5.2.6 Cache内容锁定 91。
5.2.7 MMU映射描述符中B位和C位的含义 92。
5.2.8 Cache和Writer Buffer编程接口 93。
5.3 ARM处理器的快速上下文切换技术 94。
5.3.1 FCSE概述 94。
5.3.2 FCSE原理 94。
5.3.3 FCSE编程接口 95。
第6章 ARM处理器存储访问一致性问题 97。
本章介绍了在支持MMU、Cache和DMA的系统中可能出现的存储访问一致性问题,以及Linux中解决类似问题的方法。
6.1 存储访问一致性问题介绍 97。
6.1.1 地址映射关系变化造成的数据不一致性 97。
6.1.2 指令cache的数据不一致性问题 98。
6.1.3 DMA造成的数据不一致问题 99。
6.1.4 指令预取和自修改代码 99。
6.2 Linux中解决存储访问一致性问题的方法 99。
第7章 ARM处理器工作模式与异常中断处理 101。
本章主要介绍了ARM处理器的工作模式和异常中断处理过程,这是ARM处理器系统启动程序编写者或Bootloader开发人员的必备知识。
7.1 ARM处理器工作模式 101。
7.2 ARM处理器异常中断向量表和优先级 103。
7.3 ARM处理器异常中断处理 104。
7.3.1 进入异常中断处理 104。
7.3.2 退出异常中断处理 105。
7.4 ARM处理器的中断(IRQ或FIQ) 109。
第8章 ARM处理器启动过程 110。
本章根据笔者的开发经验介绍了ARM处理器系统的启动过程以及编写ARM处理器系统启动程序需要注意的事项。
8.1 ARM处理器上电/复位操作 110。
8.2 ARM处理器系统初始化过程 111。
8.3 ARM处理器系统初始化编程注意事项 111。
第9章 嵌入式系统设计与调试 113。
本章根据笔者10多年的开发经验介绍了嵌入式系统的设计流程和调试方法,列举了大量笔者工作中碰到的实际案例。本章内容对于嵌入式系统硬件开发和调试有较高的参考、指导价值。
9.1 嵌入式系统设计流程 113。
9.2 嵌入式系统硬件原理设计与审核 114。
9.3 硬件设计工具软件 117。
9.4 嵌入式系统调试仿真工具 117。
9.5 嵌入式系统调试诊断方法 118。
第10章 自制简易JTAG下载烧写工具 123。
本章根据笔者自己制作简易JTAG线缆的经验,介绍了简易JTAG线缆的硬件原理和软件流程,这是初学者必备的最廉价的工具,必须掌握。
10.1 JTAG简介 123。
10.1.1 一些基本概念 124。
10.1.2 JTAG接口信号 124。
10.1.3 TAP控制器的状态机 125。
10.1.4 JTAG接口指令集 129。
10.2 简易JTAG线缆原理 130。
10.2.1 PC并口定义 130。
10.2.2 PC并口的寄存器 131。
10.2.3 简易JTAG线缆原理图 133。
10.2.4 简易JTAG线缆烧写连接图(见图10-5) 134。
10.3 简易JTAG烧写代码分析 135。
10.3.1 简易JTAG烧写程序(flashp)使用说明 135。
10.3.2 flash与CPU连接及flash属性描述文件 136。
10.3.3 简易JTAG烧写程序的执行逻辑和流程 138。
第2部分 Linux内核开发初步。
第11章 Bootloader 142。
本章根据笔者的工作经验介绍了流行的几种Bootloader、Bootloader应该具备的基本功能以及Bootloader的裁剪与移植。
11.1 Bootloader的任务和作用 142。
11.2 各种各样的Bootloader 143。
11.3 Bootloader编译环境 144。
11.4 Bootloader的移植与裁减 145。
11.5 编译Bootloader 145。
11.6 烧写Bootloader 146。
11.7 Bootloader使用举例 148。
11.8 Bootloader修改举例 149。
第12章 创建嵌入式Linux开发环境 151。
本章介绍了如何创建嵌入式系统Linux内核交叉开发环境,本章和后续3章的内容是嵌入式系统Linux内核开发的基础,必须掌握。
12.1 安装Linux host 151。
12.2 在虚拟机中安装Linux host 152。
12.3 安装Linux交叉编译环境 157。
12.4 在主机上设置TFTP Server 160。
12.5 在主机上设置DHCP Server 161。
12.6 在主机上设置Telnet server 161。
12.7 在开发过程中使用NFS 162。
12.8 设置超级终端 163。
第13章 编译Linux内核 166。
本章介绍了Linux内核的配置和编译方法。
13.1 获取Linux内核源代码 166。
13.2 Linux内核目录结构 166。
13.3 配置Linux内核 167。
13.4 编译Linux内核 168。
第14章 创建Linux根文件系统 170。
本章介绍了Linux的根文件系统的结构以及创建根文件系统的方法。
14.1 根文件系统概述 170。
14.2 根文件系统目录结构 171。
14.3 获取根文件系统组件源代码 171。
14.4 编译根文件系统源代码 171。
14.5 创建一个32MB的RAMDISK根文件系统 173。
14.6 在根文件系统中添加驱动模块或者应用程序 173。
第15章 固化Linux内核和根文件系统 174。
本章介绍了固化(烧写)Linux内核和根文件系统的方法。
第16章 关于Clinux 176。
本章简要介绍了Clinux与标准Linux的区别。
16.1 Clinux简介 176。
16.2 Clinux源代码目录结构 177。
16.3 Clinux与标准Linux的区别 178。
16.4 编译Clinux 179。
第3部分 Linux 2.6内核原理。
第17章 Linux 2.6.10@ARM启动过程 182。
本章以start_kernel()和init()函数中调用到的函数说明的方式,介绍了从Linux汇编代码入口到init内核进程最后调用用户空间init命令的Linux整个启动过程。本章内容是笔者第一次阅读Linux内核源代码时对这些函数的注释,仅供读者了解start_kernel()和init()函数中调用到的每个函数的大致功能时使用。
17.1 Linux 2.6.10中与ARM处理器平台硬件相关的结构和全局变量 182。
17.1.1 相关数据结构 182。
17.1.2 相关全局变量 187。
17.2 Linux汇编代码入口 189。
17.3 Linux汇编入口处CPU的状态 189。
17.4 start_kernel()函数之前的汇编代码执行过程 190。
17.5 start_kernel()函数中调用的函数介绍 192。
17.5.1 lock_kernel()函数 192。
17.5.2 page_address_init()函数 192。
17.5.3 printk(linux_banner) 193。
17.5.4 setup_arch(&command_line)函数 193。
17.5.5 setup_per_cpu_areas()函数 198。
17.5.6 smp_prepare_boot_cpu()函数 199。
17.5.7 sched_init()函数 199。
17.5.8 build_all_zonelists()函数 200。
17.5.9 page_alloc_init()函数 200。
17.5.10 printk(Kernel command line: %s\n, saved_command_line) 201。
17.5.11 parse_early_param()函数 201。
17.5.12 parse_args()函数 201。
17.5.13 sort_main_extable()函数 202。
17.5.14 trap_init()函数 202。
17.5.15 rcu_init()函数 202。
17.5.16 init_IRQ()函数 203。
17.5.17 pidhash_init()函数 203。
17.5.18 init_timers()函数 203。
17.5.19 softirq_init()函数 204。
17.5.20 time_init()函数 204。
17.5.21 console_init()函数 205。
17.5.22 profile_init()函数 206。
17.5.23 local_irq_enable()函数 207。
17.5.24 vfs_caches_init_early()函数 207。
17.5.25 mem_init()函数 208。
17.5.26 kmem_cache_init()函数 210。
17.5.27 numa_policy_init()函数 225。
17.5.28 calibrate_delay()函数 227。
17.5.29 pidmap_init()函数 228。
17.5.30 pgtable_cache_init()函数 229。
17.5.31 prio_tree_init()函数 229。
17.5.32 anon_vma_init()函数 229。
17.5.33 fork_init(num_physpages)函数 229。
17.5.34 proc_caches_init()函数 230。
17.5.35 buffer_init()函数 231。
17.5.36 unnamed_dev_init()函数 231。
17.5.37 security_init()函数 231。
17.5.38 vfs_caches_init(num_physpages)函数 232。
17.5.39 radix_tree_init()函数 237。
17.5.40 signals_init()函数 237。
17.5.41 page_writeback_init()函数 237。
17.5.42 proc_root_init()函数 238。
17.5.43 check_bugs()函数 240。
17.5.44 acpi_early_init()函数 244。
17.5.45 rest_init()函数 244。
17.6 init()进程执行过程 265。
17.6.1 smp_prepare_cpus(max_cpus)函数 265。
17.6.2 do_pre_smp_initcalls()函数 265。
17.6.3 fixup_cpu_present_map()函数 267。
17.6.4 smp_init()函数 267。
17.6.5 sched_init_smp()函数 268。
17.6.6 populate_rootfs()函数 268。
17.6.7 do_basic_setup()函数 283。
17.6.8 sys_access()函数 292。
17.6.9 free_initmem()函数 301。
17.6.10 unlock_kernel()函数 301。
17.6.11 numa_default_policy()函数 302。
17.6.12 sys_dup()函数 302。
17.6.13 execve()函数 302。
第18章 Linux内存管理 305。
从本章开始,笔者将带领读者走进神秘的Linux内核世界。笔者在阅读内核源代码以及两本相关参考书(见参考文献)的基础上,以自己的理解和语言总结概括了Linux内核每个组件的原理。笔者对与每个内核组件相关的关键数据结构和全局变量作了尽量详尽的说明,并且对核心函数进行了详细注释,在向读者灌输理论知识的同时引导读者自己去阅读、分析Linux内核源代码。本章讲解了Linux内核第一大核心组件“内存管理”的原理和实现内幕。
18.1 Linux内存管理概述 305。
18.1.1 Linux内存管理的一些基本概念 305。
18.1.2 内存管理相关数据结构 309。
18.1.3 内存管理相关宏和全局变量 330。
18.1.4 Linux内存管理的任务 341。
18.1.5 Linux中的物理和虚拟存储空间布局 341。
18.2 为虚拟(线性地址)存储空间建立页表 345。
18.3 设置存储空间的访问控制属性 348。
18.4 Linux中的内存分配和释放 350。
18.4.1 在系统启动初期申请内存 350。
18.4.2 系统启动之后的内存分配与释放 360。
第19章 Linux进程管理 480。
本章讲解了Linux内核第二大核心组件“进程管理”的原理和实现内幕。
19.1 进程管理概述 480。
19.1.1 进程相关概念 480。
19.1.2 进程分类 481。
19.1.3 0号进程 481。
19.1.4 1号进程 481。
19.1.5 其他一些内核线程 482。
19.1.6 进程描述符(struct task_struct) 482。
19.1.7 进程状态 482。
19.1.8 进程标识符(PID) 483。
19.1.9 current宏定义 484。
19.1.10 进程链表 484。
19.1.11 PID hash表和链表 485。
19.1.12 硬件上下文(Hardware Context) 485。
19.1.13 进程资源限制 485。
19.1.14 进程管理相关数据结构 486。
19.1.15 进程管理相关宏定义 502。
19.1.16 进程管理相关全局变量 514。
19.2 进程管理相关初始化 520。
19.3 进程创建与删除 529。
19.4 进程调度 551
19.4.1 进程类型 553。
19.4.2 进程调度类型 554。
19.4.3 基本时间片计算方法 555。
19.4.4 动态优先级算法 556。
19.4.5 交互式进程 556。
19.4.6 普通进程调度 557。
19.4.7 实时进程调度 557。
19.4.8 进程调度函数分析 558。
19.5 进程切换 576
19.6 用户态进程间通信 581。
19.6.1 信号(Signal) 581。
19.6.2 管道(pipe)和FIFO(命名管道) 627。
19.6.3 进程间通信原语(System V IPC) 641。
第20章 Linux文件管理 651。
本章讲解了Linux内核第三大核心组件“文件系统”的原理和实现内幕。
20.1 文件系统概述 651。
20.1.1 Linux文件管理相关概念 652。
20.1.2 Linux文件管理相关数据结构 657。
20.1.3 Linux文件管理相关宏定义 682。
20.1.4 Linux文件管理相关全局变量 691。
20.2 文件管理相关初始化 699。
20.3 文件系统类型注册 711。
20.4 挂接文件系统 712。
20.5 文件系统类型超级块读取 730。
20.5.1 get_sb_single()通用超级块读取函数 731。
20.5.2 get_sb_nodev()通用超级块读取函数 737。
20.5.3 get_sb_bdev()通用超级块读取函数 738。
20.5.4 get_sb_pseudo()通用超级块读取函数 740。
20.6 路径名查找 747
20.7 访问文件操作 759。
20.7.1 打开文件 759。
20.7.2 关闭文件 766。
20.7.3 读文件 768
20.7.4 写文件 785
20.8 异步I/O系统调用 792。
20.9 Linux特殊文件系统 792。
20.9.1 rootfs文件系统 793。
20.9.2 sysfs文件系统 797。
20.9.3 devfs设备文件系统 800。
20.9.4 bdev块设备文件系统 803。
20.9.5 ramfs文件系统 804。
20.9.6 proc文件系统 804。
20.10 磁盘文件系统 813。
20.10.1 ext2文件系统相关数据结构 813。
20.10.2 ext2文件系统磁盘分区格式 819。
20.10.3 ext2文件系统的各种文件 820。
20.10.4 创建ext2文件系统 821。
20.10.5 ext2文件系统的操作方法 822。
20.11 关于initramfs 824。
20.11.1 initramfs概述 824。
20.11.2 initramfs与initrd的区别 824。
20.11.3 initramfs相关全局变量 825。
20.11.4 initramfs被编译链接的位置 825。
20.11.5 initramfs文件的生成过程 825。
20.11.6 initramfs二进制文件格式说明(cpio格式) 828。
20.11.7 initramfs二进制文件和列表文件对照示例 829。
20.11.8 initramfs利弊 830。
20.12 关于initrd 830。
20.12.1 initrd概述 830。
20.12.2 initrd相关全局变量 831。
20.13 关于gzip压缩文件 832。
第21章 Linux模块设计 834。
本章讲解了Linux内核模块程序与应用程序的区别以及如何编写和加载Linux内核模块程序。
21.1 Linux模块设计概述 834。
21.2 Linux的内核空间和用户空间 834。
21.3 内核模块与应用程序的区别 835。
21.4 编译模块 837
21.5 装载和卸载模块 837。
21.6 模块层叠 838
21.7 模块版本依赖 839。
21.8 模块编程示例 839。
第22章 Linux系统异常中断管理 841。
本章讲解了Linux内核如何管理系统异常中断以及Linux系统调用的实现内幕。
22.1 Linux异常中断处理 841。
22.2 指令预取和数据访问中止异常中断处理 849。
22.2.1 指令预取中止异常中断处理 850。
22.2.2 数据访问中止异常中断处理 858。
22.3 Linux中断处理 863。
22.3.1 内核模式下的中断处理 863。
22.3.2 用户模式下的中断处理 867。
22.4 从中断返回 868
22.5 Linux中断管理 869。
22.5.1 Linux中断管理相关数据结构与全局变量 870。
22.5.2 Linux中断管理初始化 872。
22.5.3 安装和卸载中断处理程序 874。
22.5.4 使能和禁止中断 878。
22.6 Linux系统调用 880。
22.6.1 Linux系统调用内核实现过程 880。
22.6.2 从系统调用返回 889。
22.6.3 Linux系统调用用户程序接口函数 890。
22.6.4 Linux系统调用用户接口函数与内核实现函数之间参数传递 899。
第23章 Linux软中断和工作队列 901。
本章讲解了Linux内核中的两种延迟处理机制“软中断”和“工作队列”的原理和实现。
23.1 概述 901
23.2 Linux软中断 902。
23.2.1 软中断相关数据结构和全局变量 903。
23.2.2 软中断初始化 904。
23.2.3 软中断的核心操作函数do_softirq() 908。
23.2.4 软中断看护进程执行函数ksoftirqd() 912。
23.2.5 如何使用软中断 913。
23.3 Linux工作队列 918。
23.3.1 Linux工作队列相关数据结构和全局变量 918。
23.3.2 Linux工作队列初始化 921。
23.3.3 将工作加入到工作队列中 924。
23.3.4 工作者进程执行函数worker_thread() 928。
23.3.5 使用Linux工作队列 931。
第24章 Linux并发与竞态 933。
本章讲解了Linux内核同步机制,包括几种锁定技术以及免锁算法。
24.1 并发与竞态概述 933。
24.1.1 Linux中的并发源 934。
24.1.2 竞态可能导致的后果 934。
24.1.3 避免竞态的规则 934。
24.2 消除竞态的“锁定”技术 935。
24.2.1 信号量(semphore)和互斥体(mutual exclusion) 935。
24.2.2 读写信号量(rw_semaphore) 938。
24.2.3 完成量(completion) 941。
24.2.4 自旋锁(spinlock_t) 942。
24.2.5 读写自旋锁(rwlock_t) 946。
24.2.6 使用“锁定”技术的注意事项 949。
24.3 消除竞态的非“锁定”方法 949。
24.3.1 免锁算法 949。
24.3.2 原子操作 950。
24.3.3 位操作 951
24.3.4 顺序锁 952
24.3.5 读-复制-更新(Read-Copy-Update,RCU) 954。
第25章 Linux设备驱动程序 958。
本章讲解了Linux内核第四大核心组件“设备驱动”的原理和实现内幕。同时还总结归纳了编写各种设备驱动程序的方法和步骤。
25.1 设备驱动程序概述 958。
25.1.1 设备驱动程序组成部分 959。
25.1.2 设备号 959
25.1.3 设备文件 960。
25.1.4 编写设备驱动程序的关键 961。
25.2 字符设备驱动程序 961。
25.2.1 字符设备相关数据结构 961。
25.2.2 字符设备相关全局变量 963。
25.2.3 字符设备驱动程序全局初始化 963。
25.2.4 为字符设备分配设备号 964。
25.2.5 注册字符设备驱动程序 968。
25.2.6 字符设备的操作方法 971。
25.2.7 用户对字符设备驱动程序的调用过程 972。
25.2.8 如何编写字符设备驱动程序 974。
25.2.9 关于TTY设备驱动程序 974。
25.2.10 控制台设备驱动程序 975。
25.3 块设备驱动程序 986。
25.3.1 块设备相关数据结构 986。
25.3.2 块设备相关宏定义 997。
25.3.3 块设备相关全局变量 999。
25.3.4 块设备驱动程序全局初始化 1004。
25.3.5 为块设备分配主设备号 1006。
25.3.6 注册块设备驱动程序 1009。
25.3.7 块设备驱动程序的操作方法 1017。
25.3.8 调用块设备驱动程序过程 1017。
25.3.9 I/O调度 1031。
25.3.10 如何编写块设备驱动程序 1032。
25.4 网络设备驱动程序 1033。
25.4.1 网络设备驱动程序概述 1033。
25.4.2 网络设备相关数据结构 1034。
25.4.3 网络设备相关宏定义 1044。
25.4.4 网络设备相关全局变量 1045。
25.4.5 创建net_device结构 1046。
25.4.6 注册网络设备 1048。
25.4.7 网络设备的操作方法 1050。
25.4.8 网络设备中断服务程序 1051。
25.4.9 如何编写网络设备驱动程序 1051。
25.5 PCI设备驱动程序 1052。
25.5.1 PCI接口定义 1053。
25.5.2 PCI设备的三个地址空间 1057。
25.5.3 PCI总线仲裁 1058。
25.5.4 PCI设备编号 1059。
25.5.5 如何访问PCI配置空间 1059。
25.5.6 如何配置PCI设备 1061。
25.5.7 PCI驱动程序相关数据结构 1062。
25.5.8 PCI驱动程序相关宏定义 1068。
25.5.9 PCI驱动程序相关全局变量 1068。
25.5.10 Bootloader和内核做的事 1069。
25.5.11 PCI驱动程序注册 1069。
25.5.12 PCI驱动程序接口函数 1071。
25.5.13 如何编写PCI驱动程序 1072。
第4部分 Linux内核开发高级指南。
第26章 Linux系统参数设置 1076。
从本章开始的后续章节主要讲解了比较高级或者平时较少关注的Linux内核方面的知识,本章讲解了Linux中的4种系统参数格式和设置方法。
26.1 旗语系统参数(tag) 1076。
26.1.1 与旗语系统参数相关数据结构和全局变量 1076。
26.1.2 旗语系统参数说明 1082。
26.1.3 旗语系统参数设置方法 1084。
26.2 前期命令行设置的系统参数 1084。
26.2.1 与前期命令行系统参数相关数据结构和全局变量 1084。
26.2.2 前期命令行设置的系统参数说明 1085。
26.2.3 前期命令行系统参数设置方法 1086。
26.2.4 如何添加自己的前期命令行设置的系统参数 1087。
26.3 老式命令行系统参数 1087。
26.3.1 与老式命令行系统参数相关数据结构和全局变量 1087。
26.3.2 老式命令行设置的系统参数说明 1088。
26.3.3 老式命令行设置的系统参数设置方法 1089。
26.3.4 如何添加自己的老式命令行设置的系统参数 1089。
26.4 命令行系统参数 1089。
26.4.1 与命令行系统参数相关数据结构和全局变量 1089。
26.4.2 命令行设置的系统参数说明 1090。
26.4.3 命令行设置的系统参数设置方法 1090。
第27章 Linux内核调试 1091。
本章介绍了Linux内核的调试方法。
27.1 打开Linux内核及其各模块自带的调试开关 1091。
27.2 内核剖析(Profiling) 1093。
27.3 通过打印调试(printk) 1095。
27.3.1 关于printk() 1095。
27.3.2 内核信息级别 1096。
27.3.3 打印速度限制 1097。
27.3.4 控制台重定向 1098。
27.4 使用proc文件系统调试 1098。
27.5 oops消息 1098。
27.6 通过跟踪命令strace调试 1099。
27.7 使用gdb、kdb、kgdb调试 1099。
第28章 Linux内核移植 1101。
本章介绍了Linux内核的移植方法。
第29章 Linux内核优化 1104。
本章介绍了Linux内核的优化方法。
29.1 编译优化 1104
29.2 根据CPU特性进行优化 1105。
29.3 对内核进行裁减 1105。
29.4 优化系统内存配置 1106。
29.5 优化系统启动过程以缩减系统启动时间 1106。
29.6 内存映射优化 1107。
29.7 工具软件辅助优化 1107。
第30章 Linux定时器 1109。
本章介绍了Linux内核的软件定时器。
30.1 定时器相关数据结构 1109。
30.2 定时器相关宏定义 1111。
30.3 定时器相关全局变量 1112。
30.4 定时器和时钟初始化 1113。
30.5 获取系统时间 1114。
30.6 延迟函数 1115
30.7 与定时器相关系统调用 1115。
30.8 使用定时器方法 1116。
第31章 杂项 1117
本章介绍了PER_CPU变量以及Linux中的数据类型定义。
31.1 per_cpu变量 1117。
31.2 Linux中的数据类型定义 1118。
第32章 编译链接文件说明 1119。
本章注释了ARM处理器系统中Linux内核的链接文件,以帮助读者了解编译出来的Linux内核各区段在内存中的存放位置。
参考文献 1125