LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 2087|回复: 6

[原创]我的linux内核编程学习笔记(更新中)

[复制链接]
发表于 2006-11-18 14:18:55 | 显示全部楼层 |阅读模式
一 AT&T汇编语言

今天开始学习linux内核编程了,从没有内核编程基础开始学起。所以很多相关的知识都要了解。首先就是AT&T汇编语言。因为在linux内核源代码中,好像除了开始的bootsect.s和head.s是用intel的汇编外,别的汇编代码都是用的AT&T汇编语言,所以有必要把 AT&T汇编语言了解一下。
不过,由于AT&T汇编和intel汇编大同小异,所以,了解一下一些不同的地方就可以了。

   以下的内容都是AT&T汇编的特点:
1、寄存器前面要加“%”,如  mov %eax,%ebx
   这里要注意的一点是,AT&T汇编中,源寄存器和目的寄存器的顺序和intel汇编刚好相反,AT&T汇编中,左边的是源寄存器,右边的是目的寄存器,在上边那个例子中,%eax是源寄存器,%ebx是目的寄存器。

2、立即数/常数前面要加$,如  mov $4,%ebx 把4这个数装入ebx这个寄存器。
   符号常数直接用, 如  mov  value,%eax 即把value代表的那个值装入eax寄存器。
                     mov $value,%eax 即把value的值作为地址,而把对应那个地址中的值装入eax。

3、b(byte):8位, w(word):16位, l(long):32位
   如:  movb %ax,%bx     movw %eax,%ebx

4、jum/call的操作数前要加上“*"作为前缀, 远跳转ljmp,远调用lcall
   如  ljmp $section,$offset
       lcall $section,$offset
   这里$section和offset表示的就是,以section为段地址,offset为段内偏移地址。因此,ljmp $section,$offset即跳转到section: offset地址。

5、远返回lret
   如  lret $stack_adjust

6、寻址方式
   表示方式 section:disp(base,index,scale)
   计算方法 base+index*scale+disp
        即 section:[base+index*scale+disp]
   其中disp是表示偏移地址。
   如  movl -4(%ebp),%eax  把[%ebp-4]的内容装入eax

7、C语言中嵌入汇编
   格式: _asm_("asm statements": outputs:inputs:registers-modified)
   其中,"asm statements"是汇编语句表达式,outputs,inputs,register-modified都是可选参数,以冒号隔开,且一次以0~9 编号,如outputs的寄存器是0号,inputs寄存器是1号,往后依次类推。outputs是汇编语句执行完后输出到的寄存器,inputs是输入到某个寄存器。
   例1:_asm_("pushl %%eax\n\t" "movl $0,%%eax\n\t" "popl %%eax");
   在嵌入汇编中,寄存器前面要加两个%,因为gcc在编译是,会先去掉一个%再输出成汇编格式。
   例2:{ register char _res;\
         asm("push %%fs\n\t"
         "movw %%ax,%%fs\n\t"
         "movb %%fs:%2,%%al\n\t"
         "pop %%fs"
         :"=a"(_res):"0"(seg),"m"(*(addr)));\
         _res;}
    movb %%fs:%2,%%al\n\t一句中是把以fs为段地址,以后面的第二号寄存器即后面的seg中的值为偏移地址所对应的值装入al。"=a" (_res):"0"(seg),"m"(*(addr)))一句中,"=a"(_res)表示把a寄存器中的内容给_res,"0"(seg)表示把 seg中的内容给0所对应的寄存器,而0即表示使用和前一个寄存器相同的寄存器,这里即使用a寄存器,也就是说把seg中的内容个a寄存器。
   需要解释以下的是,a,b,c,d分别表示寄存器eax,ebx,ecx,edx
                  S,D分别表示寄存器esi,edi
                  r表示任意寄存器
                  0(数字0,不是o!)表示使用上一个寄存器
 楼主| 发表于 2006-11-18 14:23:28 | 显示全部楼层
二 保护模式

关于保护模式涉及到很多概念,比如分段、段描述符、分页、页表和任务状态段(TSS)等,内容比较繁多。要真正深入了解保护模式,就必须弄清这些概念。对于不熟悉保护模式的人来说,怎么能够一下子抓住保护模式的关键呢?什么是保护模式?保护模式的核心就是通过段页式寻址机制来实现多个任务之间的有效隔离!
   关于保护模式有两个要点:一是在段页式寻址机制下cpu能访问多达4GB的寻址空间,突破了实模式下的640KB的限制,使得同时装入执行多个任务成为可能;二是分段机制使得段具有访问权限和特权级,操作系统的代码和数据不允许被应用程序访问,使得操作系统与应用程序有效隔离。

1、分段机制和段表述符表
   保护模式下还是延续实模式下的“段:偏移”的地址格式,但是其中的“段”的含义完全不同。在保护模式下,CS,DS,ES,SS这些寄存器中的值是“段选择符”,需要查全局描述符表(GDT)或者局部描述符表(LDT)来获得段的基地址,在加上偏移,才能得到线性地址。简单的说,CS,DS,ES,SS寄存器里存的是段的索引而不是段本身!这个索引指向一个数据结构的一个表项,表项中详细定义了段的起始地址、界限、属性等内容,这个数据结构就是GDT或者 LDT。GDT中的表项也有一个专门的名字,叫做描述符(Descriptor)
   段描述符分为两大类:存储描述符(存储段是存放可由程序直接访问的代码和数据的段)和系统段描述符或门描述符(系统段是80386分段机制所使用的特别段,门与中断、异常及陷阱对应)。关于描述符的属性的详细内容大家可以找相关的书籍参考,这里就不多讲了。
   用来选择段描述符的是段选择符又叫选择子,它是一个16位的结构,15~3位用来选择段描述符,第2位用来表示是在GDT里选还是在LDT里选,1~0位是访问权限级别(RPL)。
2、虚拟存储器和分页机制
   在80386以上的cpu中,还提出了虚拟存储器(Virtual Memory)概念,这可使得应用程序能够使用远超过实际数量的内存。程序访问存储器时使用虚拟地址,虚拟地址在经过分段和分页两种机制才能得到物理地址,从而达到存储器保护的目的。
   这里涉及到存储器管理的一些知识。首先要区别逻辑地址,线性地址和物理地址。逻辑地址即段地址:偏移的方式,通过段地址和偏移的组合形成线性地址。如果启用了分页机制,则线性地址会通过分页机制转换成物理地址送到地址线上,如果没有启用分页机制,则得到的线性地址就是物理地址。
   这里再介绍一下分页机制方面的知识。存储器是有一个个的存储单元所组成的,但是,与需要占用存储空间的数据规模相比,以存储单元为分配单位则显得过于细微,一个直接的办法就是对整个存储空间进行分区。在计算机技术中,存储器中的一个分区叫做一页,在linux中一页通常是4KB大小。
   初始的主存就相当于一张方格纸,每一个方格相当于一个存储单元。而分页就是把整个主存分为大小相等的若干区,即每个区都有相同数目的存储单元,符合这个规定的分区就叫做页。然后对所有的页从0开始依次命名一个页号。分页就相当于把一张方格纸裁剪成大小相等若干张,然后把它们看成是一本方格纸,每张方格纸都有一个页码。分页后,就可以以页为单位为程序实体分配存储空间。
   我们知道,32位地址线其最大寻址空间是4G,但是一般计算机没有配备那么大的实际内存,因此提出了虚拟存储器的概念,把处理器所提供的地址空间叫做虚拟地址空间或者逻辑地址空间,而真正的实际配备的存储器所提供的空间叫做物理空间。对于程序员所能看到的是虚拟地址空间,所以在编程是,可以不用考虑实际的物理内存容量,只要不超过计算机处理器寻址空间就可以。
   更详细的关于存储器管理方面的知识大家可以参考《现代操作系统》一书。
   保护模式方面的知识就介绍到这里,只能给大家一个大概的认识,更多的细节需要大家自己去阅读相关书籍了。
回复 支持 反对

使用道具 举报

发表于 2006-11-18 22:40:10 | 显示全部楼层
ding      楼主坚持就是胜利@!!
回复 支持 反对

使用道具 举报

 楼主| 发表于 2006-11-20 16:57:22 | 显示全部楼层
三、linux内核体系结构(一)

1、linux内核模式和体系结构   
   一个完整可用的操作系统主要由4个部分组成:硬件、操作系统内核、操作系统服务和用户应用程序。最底层的使硬件,其上使操作系统内核,再上一层使操作系统服务,最上层使用户应用程序。
   linux内核的主要作用就是与计算机硬件进行交互,实现对硬件对编程控制和接口操作,调度对硬件资源对访问,并为计算机上的用户程序提供一个高级的执行环境和对硬件对虚拟接口。
   操作系统内核的结构模式主要可分为整体式的单内核模式和层次式的微内核模式。linux的内核在初期的版本采用了单内核模式。单内核模式单主要优点式内核代码结构紧凑、执行速度快,不足之处式层次结构性不强。
   在单内核模式的系统中,操作系统所提供的服务流程为:应用主程序使用指定的参数值执行系统调用指令(int x80),使CPU从用户态切换到核心态,完成功能以后再切换回用户态。
   linux内核主要有5个模块组成:进程调度模块、内存管理模块、文件系统模块、进程间通信模块和网络接口模块。所有到模块都与进程调度模块存在依存关系。

2、linux中断机制
   使用了两片8259A可编程中断控制芯片。每个芯片可以管理8个中断,可以多片级联。对linux内核来说,中断信号分为两类:硬件中断和软件中断(异常)。每个中断是有0~255之间对一个数字来标识。软件中断通常可以分为故障(fault)和陷阱(traps)两类。
   中断int0~int31是Intel公司保留对中断,int32~int255可以是用户自己定义的中断。

3、linux系统定时
   在linux0.11内核中,系统没隔10ms就发出一个时钟中断(IRQ0)信号,称为一个系统滴答。

4、linux内核进程控制
   程序是一个可执行文件,而进程(process)是一个执行中的程序实例。对linux0.11内核来讲,系统最多可以有64个进程同时存在。除了第一个进程是“手工”建立对以外,其余进程都是进程使用fork函数来创建对。被创建的进程叫做子进程(child process),创建者称为父进程(parent process)。每个进程有一个惟一的ID。
   每个进程只能执行自己的代码和访问自己的数据和堆栈区。进程之间的通信需要通过系统调用来进行。
   linux系统中,一个进程可以在内核态或用户态运行,因此linux内核堆栈和用户堆栈是分开的。
   内核程序通过进程表(又叫做进程控制块或者进程描述符)来管理进程,其中包含了进程当前运行的状态信息、信号、进程号、父进程号、运行时间累计值、正在使用的文件和本任务的局部描述符等信息。
   进程在其生存期可有以下几种状态:
   (1)运行状态,即正在被CPU执行的状态或者准备运行状态。
   (2)可中断睡眠状态,即进程在处于睡眠状态时,可以被系统的中断,它正在等待的资源或某个信号等方式唤醒。
   (3)不可中断睡眠状态,即睡眠中的进程只能被wake_up()函数明确唤醒时才能转换到可运行到就绪状态。
   (4)暂停状态,即当进程收到SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时进会进入暂停状态。
   (5)僵死状态,即进程已经停止运行了,但其父进程还没有查询其状态信息时,进程就处于僵死状态。
   
   进程的抢占发生在进程处于用户态执行阶段,在内核态执行阶段时不能被抢占的。linux0.11采用了基于优先级排队的调度策略对进程进行调度。调度函数为schedule()。
回复 支持 反对

使用道具 举报

发表于 2006-11-26 17:09:38 | 显示全部楼层
不错,顶一下。老兄,我现在也在学内核,以后能交流一下吗?
回复 支持 反对

使用道具 举报

 楼主| 发表于 2006-11-28 14:21:57 | 显示全部楼层
5、linux内核对内存对管理
   在linux0.11内核中,内存被划分为几个功能区:首先内存从起始位置开始,划分出一个内核模块区,接着是用于提供硬盘或软盘等块设备使用的高速缓冲区,最后是主内存区,如果含有RAM虚拟盘的系统,主内存区头部还要划分出一部分供虚拟盘存贮数据。
   linux同时采用了intel cpu的内存分段和分页管理机制,因此我们首先要分清3种地址以及它们之间的关系:
   (1)逻辑地址:有程序产生的与段相关的偏移地址部分,在intel保护模式下就是指程序执行代码段限长内的偏移地址。
   (2)线性地址:是逻辑地址到物理地址变换的中间层。段中的偏移地址加上相应段段基地址就生成了一个线性地址。
   (3)物理地址:指的是出现在cpu外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。
   除此之外,我们还需要知道什么是虚拟内存。虚拟内存是指计算机呈现出要比实际拥有的内存大得多的内存量。一个比喻可以让我们很好的把握这个概念:假如说我们把一个大型的程序或者说整个计算机上的程序比喻成一个大型图书馆,cpu就是读者,而实际内存呢就是一个阅览用的书桌,那么假设我们有足够长的时间去看完图书馆所有的书,可是即使是这样,我们也不必一次把图书馆的所有书借来放到你看书的桌子上,当然桌子也放不下。因此,我们只把目前要看的书放到书桌上,等看完了在换另一本书。那么好,在计算机中也是这样的,一个程序的运行一般都是有一定顺序的,我们不用把整个程序全部加载到内存中,只是加载当前运行需要到部分,由cpu来执行,等这部分执行完了,在把另外需要的部分加载到内存中来。这样,虽然每次运行的程序只是内存中的部分,但是对于cpu来说,它面对的其实还是整个“图书馆”。这里的这个“图书馆‘就是虚拟内存!

在linux0.11内核中,给每个程序(进程)都划分了总量为64M的虚拟内存空间,因此程序的逻辑地址范围是0x00000000到0x40000000。

6、linux内核源代码的目录结构
   由于linux内核是单内核模式,所有内核中所有程序几乎都有着紧密的联系。在linux0.11内核源代码中一共有14个子目录,总共包括102个代码文件。
  linux     
    |--- boot                          系统引导汇编程序
    |--- fs                            文件系统
    |--- include                    头文件(*h)
    |    |--- asm                     与cpu体系结构相关的部分
    |    |--- linux                   linux内核专用部分
    |    |--- sys                    系统数据结构部分
    |--- init                          内核初始化程序
    |--- kernel                     内核进程调度、信号处理、系统调用等程序
    |    |--- blk_drv              块设备驱动程序
    |    |--- chr_drv             字符设备驱动程序
    |    |--- math                 数学协处理器仿真处理程序
    |--- lib                          内核库函数
    |--- mm                        内存管理程序
    |--- tools                      生成内核Image文件的工具程序
回复 支持 反对

使用道具 举报

发表于 2006-12-5 14:19:46 | 显示全部楼层
期待楼主的更新
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表