linux内核学习(1)

来源:互联网 发布:淘宝气垫床 编辑:程序博客网 时间:2024/05/17 03:24

我的学习主要根据《 Linux内核完全注释 》这本书开始的。
第一章    概述
      这一章中让我感受颇深的是Linus编制Linux的过程,1990年,那是才20岁的Linus还是芬兰一所大学二年级的学生,购买了一台386兼容电脑学习计算机知识,并从美国定购了MINIX系统软件,等待该系统软件的过程中,他仔细研究了80386的硬件知识,而后为能通过Modem拨号连接到学校主机,使用汇编语言并利用80386 CPU的多任务特性编制出一个终端仿真程序。同时为了将老式电脑的软件复制到新电脑中,还为软驱,键盘等一些硬件设备编制出了驱动程序。学习MINIX系统的过程中,他认识到该系统的诸多限制,1991年4月,他通过修改终端仿真程序和硬件驱动程序,开始编写自己的操作系统,操作系统的主要算法和很多重要函数的名称都参考和取自M.J.Bach的《UNIX操作系统设计》。尤其他在回答一个MINIX问题时,第一句话就是“请阅读源代码”,这说明学习系统软件来说,不仅需要懂得系统的基本工作原理,还需要结合源代码,学习系统的实现方式,还真不是件简单的事情,不过这样的学习过程可以学到很多东西,80386体系结构,汇编语言,操作系统(单这一项就包含相当多的知识可供将来的设计开发提供参考),c语言编程,也许你还能发现更多让你茅舍顿开的闪光点。
     无论你出于什么样的目的要学习Linux内核,请注意操作系统内核程序是对硬件资源的抽象和访问调度,所以你需要结合在学习之前复习一下有关Intel80*86体系结构的硬件及其编程知识,尤其是它工作在保护虚拟地址模式下的工作原理,或是在学习内核的过程中不断查阅相关参考资料不断充电,同时还需要参考《UNIX操作系统设计》建立操作系统概念,了解相关算法。为此我从图书馆找来了B.B.Bary的《Intel微处理器结构、编程与接口》,UNIX那本书没找到,找到一本胡希明的《UNIX结构分析(核心代码的结构与算法)》作为参考。还有一个重要前提就是你已经熟悉了Linux操作系统,对于它的系统管理已经有了一定认识,也就是说你会用Linux,其实我也说不好我是否已经会用Linux了,基于Linux系统的嵌入式设计已经搞了大半年,深深的被其开放性所吸引,一直想深入其中一探究竟,趁着寒假好好了下心愿吧!如果你也有兴趣,请跟贴,一块探讨吧!

第二章    Linux内核体系结构
2.1  Linux内核模式和体系结构
操作系统的结构:
*    用户应用程序:字处理,浏览器etc
*    操作系统服务:X窗口系统,shell命令解释系统etc
*    操作系统内核:我们的研究对象,它是对硬件资源的抽象和访问调度
*    硬件系统
操作系统的内核结构模式:
*    整体的单内核模式(0.11版本所采用的模式):可分为调用服务的主程序层,执行系统调用的服务层,支持系统调用的底层函数
*    层次式的微内核模式
Linux内核的5个模块:
*    进程调度模块:控制进程对CPU的使用
*    内核管理模块:确保所有进程可以安全共享机器主内存区,同时支持虚拟内存管理方式(0.95版实现),在二级存储单元兆中设置交换区的交换机制等
*    文件系统模块:提供各设备的驱动和存储,将各设备作为统一的文件形式进行操作
*    进程间通信模块:用来支持多种进程之间的信息交换
*    网络接口模块:提供网络通信标准的访问并支持多种网络硬件(0.96版提供)
2.2 Linux中断机制
      80*86CPU中采用了两片8259A可编程中断控制器,每片管理8个中断,2片级联,分主从片,共可管理15个中断源(因从片的INT连到了主片的IRQ2上)。CPU用IN或OUT指令对8259A芯片进行初始化编程。而后芯片进入操作状态,随时响应外部设备提出的响应中断。中断的响应须进行判优选择,选定中断服务对象后通过CPU的INT通知CPU外部中断请求到来,返回响应后,芯片从数据线D7~D0将当前服务对象的中断号送出,CPU根据该中断号执行相应中断服务。
Linux内核的中断号有两类:硬件中断和软件中断(用0~255之间的数字标识),其中中断int0~int31为Intel固定设定或保留用,属软件中断,它们在CPU执行指令时探测到异常情况时触发,因此Intel将其称为异常,它又可分为故障和陷阱两类。int32~int255用户自动设定,Linux系统中将int32~int47作为8259A的硬件中断请求IRQ0~IRQ15,程序编程发出的系统调用中断为int128。内核源码head.s程序对中断描述符表(IDT)的256个描述符进行了默认设置。
2.3  Linux系统定时
      在0.11版本的Linux中,PC机通过可编程定时芯片Intel8253每10ms发出一个时钟中断(IRQ0),也就是一个系统滴答。每一个滴答调用一次时钟中断处理程序(timer_interrupt),该程序中首先将jiffies(系统启动以来的总滴答数)变量加1。然后将中断程序中的当前特权级CPL作为参数调用do_timer()函数。它根据CPL对当前进程的运行时间进行累计,从而实现多进程的分时机制,采用时间片概念使多个进程好像在同时执行。同时还根据特权级别判断当前进程工作在用户态(CPL>0)还是内核态,若工作在用户态,时间片用完do_timer()调用调度程序schedule()调用其他进程运行,若工作在内核态时被中断,do_timer()立即退出。也就是说内核态程序是不可抢占的,而用户态可以。
2.4Linux内核进程控制
      上面已经提到内核的进程控制采用分时机制已使所有的进程好像在同时运行,每个进程运行的时间片为15个系统滴答,也就是150ms。系统启动第一个进程“手工”建立,其他进程都是系统调用fork函数并以上一进程为父进程,对它的数据结构进行拷贝而得到。各进程由可执行的指令代码、数据和堆栈区组成,各进程采用进程标识号(process ID,pid)标识。
2.4.1  任务数据结构
     Linux系统的内核程序通过进程表(定义在sched.h中的task_struct任务结构指针)进行管理。表中包含了控制和管理进程的所有信息,如进程当前运行的状态,进程号,父进程号,累计运行时间,正在使用的文件等等。进程的在切换前首先要将当前进程的上下文(CPU所有寄存器的值,进程的状态以及堆栈中的内容)保存在进程表中,以便再次执行该进程的时候恢复现场。
2.4.2 进程运行状态
      由于分时机制的存在,进程在生存期中可能存在以下各种状态:运行(正在被CPU执行)、可中断睡眠(在系统产生一个中断或该进程所需资源得到释放)、不可中断睡眠(需要wake_up()函数明确唤醒)、暂停(收到SIGSTOP、SIGTSTP、SIGTTIN等信号触发,收到SIGCOUT信号恢复执行)、僵死(进程已结束,但还未被父进程询问)五种状态。为防止进程切换时造成的内核数据错误,临界区代码的执行禁止一切中断。
2.4.3 进程初始化
      boot/目录中的引导程序将内核加载到内存,然后执行初始化程序init/main,确定如何使用系统的物理内存并调用系统各部分的初始化程序(内存管理、中断处理、块设备和字符设备、进程管理以及软硬盘等硬件设备)进行初始化处理。这时调度程序的初始化(sched_init())中已对任务0的运行环境进行了初始化(设置它的数据结构各字段,在全局描述符中添入任务0的任务状态段(TSS)描述符和局部描述表(LDT)的段描述符),然后由宏move_to_user_mode(include/asm/system.h)移动到进程0运行。
2.4.4 创建新进程
      所有新进程的创建都是通过复制进程0来得到的,都是进程0的子进程。
子进程的创建流程:在任务数组中找出未被使用的空项(若无空项则出错返回,0.11中设定值为64个最大进程数)——>为新进程申请一页内存区存放任务数据结构信息,同时复制当前进程任务数据结构作为新进程的模板——>设置新进程为不可中断睡眠状态——>设置当前进程为新进程的父进程——>清除信号位图并复位新进程各统计值——>设置初始运行时间片值为15个系统滴答——>根据当前进程设置任务状态段(TSS)中各寄存器的值——>设置新任务的代码和数据段基址、限长并复制当前进程内存分页管理的页表——>在GDT中设置新任务的TSS和LDT——>将新任务设置成可运行状态并返回新进程号
2.4.5 进程调度
Linux0.11中采用了基于优先级排队的调度策略。
1. 调度程序schedule()
      扫描任务数组(比较各running态任务的运行时间递减滴答数counter)——>选中其中值较大的利用任务切换宏函数切换到该进程运行——>所有进程时间片都用完了,重新计算counter(counter=counter/2+priority)——>重复以上选择过程,调用switch_to()执行实际进程切换——>无进程可用时选择进程0运行
2. 进程切换switch_to()
      检查须切换的进程是否为当前进程,若是直接退出——>置current为任务的指针——>跳转到新任务TSS组成的地址处——>CPU将所有寄存器的状态保存到当前任务寄存器TR中TSS所指向的当前任务数据结构tss结构中——>新进程TSS所指向的新任务数据结构中tss结构中寄存器信息恢复到CPU——>新任务开始执行
2.4.6 终止进程
      用户程序调用exit()时,系统执行内核函数do_exit()——>释放进程代码段和数据段占用的内存页面——>关闭进程打开的所有文件——>若进程又子进程,则让init作为该子进程的父进程,若进程为一个会话头并有控制终端,则释放终端,并向属于该终端的所有进程发送SIGHUP——>把进程置位僵死状态——>向父进程发送SIGCHLD信号——>父进程将子进程运行时间累加到自己进程中——>释放该子进程数据结构所占用的内存页面并置空子进程在任务数组中占用的指针项。
原创粉丝点击