1、 10.1 Linux内核简介10.1.1 Linux内核的地位Linux操作系统由4个主要的子系统所组成:1)用户应用程序:在某个特定的Linux系统上运行的应用程序集合,它将随着该计算机系统的用途不同而有所变化,但一般会包括文字处理应用程序和Web浏览器。2)O/S服务:这些服务一般认为是操作系统的一部分(命令外壳程序等)。3)Linux内核:包括内核抽象和对硬件资源(如CPU)的间接访问。4)硬件控制器:10.1.2 系统初始化及运行 当PC启动时,Intel系列的CPU首先进入的是实模式,并开始执行位于地址0 xFFFF0处的代码,也就是ROM-BIOS起始位置的代码。BIOS先进行一
2、系列的系统自检,然后初始化位于地址0的中断向量表。最后BIOS将启动盘的第一个扇区装入到0 x 7C00,并开始执行此处的代码。10.1.3 内核提供的各种系统调用1系统的基本数据结构 进程控制表中的每一项都是一个task _ struct 结构,而task _ struct结构本身是在include/linux/sched.h中定义的。进程控制表既是一个数组,又是一个双向链表,同时又是一个树。其物理实现是一个包括多个指针的静态数组。此数组的长度保存在include/linux/tasks.h 定义的常量NR_ TASKS中,其缺省值为128,数组中的结构则保存在系统预留的内存页中。链表是由n
3、ext_task 和prev _ task两个指针实现的,而树的实现则比较复杂。系统启动后,内核通常作为某一个进程的代表。一个指向task _ struct的全局指针变量current用来记录正在运行的进程。某一个进程只能运行在用户方式(user mode)或内核方式(kernel mode)下。用户程序运行在用户方式下,而系统调用运行在内核方式下。2创建和撤消进程1)申请一个空闲的页面来保存task _ struct。2)查找一个空的进程槽(find _ empty _ process())。3)为kernel _ stack _ page申请另一个空闲的内存页作为堆栈。4)将父进程的LDT
4、表拷贝给子进程。5)复制父进程的内存映射信息。6)管理文件描述符和链接点。10.1.4 存取文件系统 Linux在系统内核和文件系统之间提供了一种叫做VFS(virtual file system)的标准接口。这样,文件系统的代码就分成了两部分:上层用于处理系统内核的各种表格和数据结构;而下层用来实现文件系统本身的函数,并通过VFS来调用。10.2 Linux内核源代码10.2.1 了解Linux内核源码lLinux内核源码的版本号 Linux内核源码的版本号表示方法非常简单:所有偶数版(如2.0.30)都是已发行的稳定版;所有奇数版(如2.1.42)都是测试版。l显示内核版本号显示内核版本号
5、的方法如下:#ls/boot l对内核源码的修改 10.2.2 内核机制1Bottom Half处理 系统内核中可以有多达32个不同的bottom half 处理程序。bh _ base中保存着指向每一个bottom half处理程序的指针。2任务队列 任务队列是系统内核将任务推迟到以后再做的方法。Linux系统有一个机制可以把任务放入到队列中等待以后处理。3计时器Linux有两种系统定时器,在某一系统时间同时被调用,但它们在实现上略有不同。第一种,即老的定时器机制,有一个包含32个指针的静态数据组和一个活跃定时器屏蔽码(timer _ active),这些指针指向timer _ struct
6、数据结构,定时器程序与定时器表的连接是静态定义的,大多数定时器程序入口是在系统初始化时加入到定时器表中的;第二种,即新的定时器机制,使用了一个链表,表中的timer _ list数据结构以递增的超时数排序。4等待队列 很多情况下处理器因等待某种系统资源而无法继续运行,例如:处理器需要一个描述目录的VFS索引节点,但该索引节点当前不在内存缓冲区中,这样处理器就必须先等到索引节点从磁盘中读到内存之后,才能继续运行。对于这种等待的处理,Linux内核使用了一种简单的数据结构等待队列,其中包括一个指向task _ struct的指针和一个指向队列中下一元素的指针。10.2.3 内核模块的装入与卸载 对
7、于Linux的内核模块,可以用insmod或rmmod命令显式地载入或卸载,或是由内核在需要时调用内核守护程序(kerneld)进行载入和卸载。10.3 Linux内核分析10.3.1 相关源代码的分析1系统的引导和初始化 Linux 系统的引导有好几种方式:常见的有 Lilo,Loadin引导和Linux的自举引导(bootsect-loader),而后者所对应源程序为arch/i386/boot/bootsect.S,它为实模式的汇编程序;无论是哪种引导方式,最后都要跳转到 arch/i386/Kernel/setup.S,setup.S主要是进行时模式下的初始化,为系统进入保护模式做准备
8、;2系统初始化后运行的第一个内核程序 asmlinkage void _init start_kernel(void)是系统初始化后运行的第一个内核程序,它定义在/usr/src/linux/init/main.c中,通过调用usr/src/linux/arch/i386/kernel/traps.c 中的一个函数 void _init trap_init(void)把各自陷和中断服务程序的入口地址设置到 idt 表中,其中系统调用总控程序system_cal就是中断服务程序之一;3中断总控程序 中断总控程序主要负责保存处理机执行系统调用前的状态,检验当前调用是否合法,并根据系统调用向量,使处
9、理机跳转到保存在 sys_call_table 表中的相应系统服务例程的入口;从系统服务例程返回后恢复处理机状态退回用户程序;而系统调用向量则定义在/usr/src/linux/include/asm-386/unistd.h 中;sys_call_table 表定义在/usr/src/linux/arch/i386/kernel/entry.S 中;同时在/usr/src/linux/include/asm-386/unistd.h 中也定义了系统调用的用户编程接口。10.3.2 对内核源码的修改1在kernel/sys.c中增加系统服务asmlinkage int sys_addtotal
10、(int numdata)int i=0,enddata=0;while(i=numdata)enddata+=i+;return enddata;2把 asmlinkage int sys_addtotal(int)的入口地址加到sys_call_table表中arch/i386/kernel/entry.S 中的最后几行源代码修改前为:.long SYMBOL_NAME(sys_sendfile).long SYMBOL_NAME(sys_ni_syscall)/*streams1*/.long SYMBOL_NAME(sys_ni_syscall)/*streams2*/.long SY
11、MBOL_NAME(sys_vfork)/*190*/.rept NR_syscalls-190.long SYMBOL_NAME(sys_ni_syscall).endr 修改后为:.long SYMBOL_NAME(sys_sendfile).long SYMBOL_NAME(sys_ni_syscall)/*streams1*/.long SYMBOL_NAME(sys_ni_syscall)/*streams2*/.long SYMBOL_NAME(sys_vfork)/*190*/*add by I*/.long SYMBOL_NAME(sys_addtotal).rept NR_s
12、yscalls-191.long SYMBOL_NAME(sys_ni_syscall).endr 3把增加的 sys_call_table 表项所对应的向量,在include/asm-386/unistd.h 中进行必要声明,以供用户进程和其他系统进程查询或调用:增加后的部分/usr/src/linux/include/asm-386/unistd.h 文件如下:.#define _NR_sendfile 187#define _NR_getpmsg 188#define _NR_putpmsg 189#define _NR_vfork 190/*add by I*/#define _NR_addtotal 191 4测试程序(test.c)如下:include include _syscall1(int,addtotal,int,num)main()int i,j;do printf(Please input a numbern);while(scanf(%d,&i)=EOF);if(j=addtotal(i)=-1)printf(Error occurred in syscall-addtotal();n);printf(Total from 0 to%d is%d n,i,j);